[RAPID] gitignore 업데이트, CLAUDE.md 추가

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 15:32:37 +09:00
parent 2cd097e127
commit 0772fb8642
2 changed files with 345 additions and 1 deletions

3
.gitignore vendored
View File

@@ -35,7 +35,8 @@ Thumbs.db
.cursor/
# Claude Code
CLAUDE.md
.claude/
.playwright-mcp/
.omc/
.mcp.json

343
CLAUDE.md Normal file
View File

@@ -0,0 +1,343 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
WACE-PLM (웨이스 PLM) — a Java-based enterprise Resource Planning System (RPS) for managing business processes, resources, projects, and documents. Korean-language UI and documentation throughout.
## Tech Stack
- **Backend:** Java 7, Spring Framework 3.2.4, MyBatis 3.2.3
- **Frontend:** JSP (1,100+ views), jQuery 2.1.4, Select2, SweetAlert2, jqGrid 4.7.1, Tabulator/TUI-Grid, Highcharts, dhtmlxGantt, SmartEditor 2 (HuskyEZCreator)
- **Database:** PostgreSQL (JNDI datasource `java:/comp/env/plm`)
- **Server:** Apache Tomcat 7.0.94
- **Build:** Eclipse IDE-based (no Maven/Gradle), Docker for dev/prod
- **CSS:** Custom `basic.css`, Bootstrap, jQuery UI, Select2 CSS, Tabulator CSS
## Build & Run Commands
```bash
# Development environment (Docker)
docker-compose -f docker-compose.dev.yml up --build -d
docker-compose -f docker-compose.dev.yml down
# Compile only
./compile_only.sh
# Full rebuild + restart
./rebuild-and-restart.sh
# Production
./start-prod-full.sh
```
Dev port: 9090 (maps to 8080 in container). JVM: Xms=512m, Xmx=1024m.
## Architecture
### MVC Pattern (Spring + MyBatis)
All source code lives under `src/com/pms/`. The request flow is:
```
HTTP Request (*.do) → Controller (@RequestMapping) → Service (@Service) → SqlSession → Mapper XML → PostgreSQL
```
- View resolution: prefix `/WEB-INF/view`, suffix `.jsp`
- JSON responses: `@ResponseBody` with Jackson converter
- Component scan: base package `com.pms`
- Session timeout: 1440 minutes (24 hours)
### Module Structure (`src/com/pms/`)
| Package | Domain | Scale |
|---------|--------|-------|
| `salesmgmt/` | Sales management (contracts, orders, estimates, delivery, dealers, goods, inspections) | 25+ controllers, 20+ mappers |
| `ions/itemmgmt/` | Item/BOM management, purchasing, material warehousing | 9 controllers, 8 mappers |
| `ions/productioninventory/` | Production & inventory | controllers + mappers |
| `controller/`, `service/`, `mapper/` | Core modules (admin, login, specs, distribution, project, quality, dashboard) | root-level |
| `api/` | External API integrations (ERP sync, Amaranth approval/user) | 11 client classes |
| `common/` | Shared utilities, beans, base services | CommonUtils, Constants, SessionManager |
### View Directory Structure (`WebContent/WEB-INF/view/`)
Major directories: `admin/` (40+ sub-modules), `salesmgmt/` (20+ sub-modules), `ions/` (itemmgmt, productioninventory), `project/` (gate, partMaster, wbs), `board/`, `bom/`, `contractMgmt/`, `costMgmt/`, `dashboard/`, `documentMng/`, `fundMgmt/`, `inventoryMng/`, `orderMgmt/`, `part/`, `quality/`, `productionplanning/`, `purchaseOrder/`, and more.
### Key Configuration Files
| File | Purpose |
|------|---------|
| `WebContent/WEB-INF/web.xml` | Deployment descriptor (UTF-8 filter, `*.do` mapping, 1440min session, error pages) |
| `WebContent/WEB-INF/dispatcher-servlet.xml` | Spring MVC config (component scan, view resolver, Jackson JSON, scheduler pool=10) |
| `src/com/pms/mapper/mybatisConf.xml` | MyBatis config with 52 registered mapper XML files |
| `WebContent/WEB-INF/log4j.xml` | Logging config (DEBUG level, daily rolling) |
| `tomcat-conf/context.xml` | JNDI DataSource (max 200 connections, 50 idle, 10s wait) |
| `.env.development` | Dev DB: `jdbc:postgresql://211.115.91.141:11133/waceplm` |
| `.env.production` | Prod DB: Docker internal `wace-plm-db:5432/waceplm` |
### Database
PostgreSQL with MyBatis XML mappers (52 registered). Automated backup via `db/backup.py` (2x daily, 7-day retention). Database dump: `db/dbexport.pgsql`.
**Table naming:** UPPERCASE_SNAKE_CASE (primary: `USER_INFO`, `PART_MGMT`, `ORDER_MGMT`). Some legacy tables use ERP-style naming (`SWSB110A_TBL`).
**Column naming:** UPPERCASE_SNAKE_CASE (`USER_ID`, `PART_NO`, `DEL_YN`).
**Boolean columns:** `DEL_YN` with Y/N values.
---
## Coding Conventions
### Controller Pattern
```java
@Controller
public class {Feature}Controller {
@Autowired
private {Feature}Service service;
// List page
@RequestMapping(value = "/{module}/{feature}List.do", method = RequestMethod.GET)
public String get{Feature}All(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
// ... returns JSP path string
}
// Form popup
@RequestMapping(value = "/{module}/{feature}FormPopup.do", method = RequestMethod.GET)
public String {feature}FormPopup(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) { ... }
// Save (AJAX)
@ResponseBody
@RequestMapping(value = "/{module}/save{Feature}.do", method = RequestMethod.POST)
public Map<String, Object> save{Feature}(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) { ... }
// Delete (AJAX)
@ResponseBody
@RequestMapping(value = "/{module}/delete{Feature}.do", method = RequestMethod.POST)
public Map<String, Object> delete{Feature}(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) { ... }
}
```
**Naming:** `{Feature}Controller.java` — CamelCase. Methods: `get{Feature}All`, `save{Feature}`, `delete{Feature}`.
### Service Pattern
```java
@Service
public class {Feature}Service {
@Autowired
CommonService commonService;
public List<Map<String,Object>> get{Feature}All(HttpServletRequest request, Map<String, Object> paramMap) {
SqlSession sqlSession = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
// Pagination
String countPerPage = CommonUtils.checkNull(request.getParameter("countPerPage"), Constants.ADMIN_COUNT_PER_PAGE+"");
paramMap.put("COUNT_PER_PAGE", Integer.parseInt(countPerPage));
Map pageMap = (HashMap) sqlSession.selectOne("namespace.get{Feature}ListCnt", paramMap);
pageMap = (HashMap) CommonUtils.setPagingInfo(request, pageMap, countPerPage);
paramMap.put("PAGE_END", CommonUtils.checkNull(pageMap.get("PAGE_END")));
paramMap.put("PAGE_START", CommonUtils.checkNull(pageMap.get("PAGE_START")));
resultList = (ArrayList) sqlSession.selectList("namespace.get{Feature}List", paramMap);
} catch(Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
return CommonUtils.toUpperCaseMapKey(resultList);
}
}
```
**Key points:**
- SqlSession obtained via `SqlMapConfig.getInstance().getSqlSession()` — must close in finally block.
- Result maps converted to uppercase keys via `CommonUtils.toUpperCaseMapKey()`.
- Pagination uses `PAGE_START`/`PAGE_END` parameters with count query + list query.
### Mapper XML Pattern
```xml
<mapper namespace="{module}.{feature}">
<!-- Count for pagination -->
<select id="get{Feature}ListCnt" parameterType="map" resultType="map">
SELECT COUNT(*) AS TOTAL_COUNT FROM TABLE_NAME WHERE 1=1
<if test='condField != null and condField != ""'>AND FIELD = #{condField}</if>
</select>
<!-- List with pagination -->
<select id="get{Feature}List" parameterType="map" resultType="map">
SELECT * FROM (
SELECT ROW_NUMBER() OVER (ORDER BY CRET_DATE) AS RNUM, ...
FROM TABLE_NAME WHERE 1=1
<if test='condField != null and condField != ""'>AND FIELD = #{condField}</if>
) T WHERE 1=1
<if test="PAGE_END != null and PAGE_END != ''">
<![CDATA[ AND RNUM <= #{PAGE_END}::integer ]]>
</if>
<if test="PAGE_START != null and PAGE_START != ''">
<![CDATA[ AND RNUM >= #{PAGE_START}::integer ]]>
</if>
</select>
<!-- Single record -->
<select id="get{Feature}" parameterType="map" resultType="map"> ... </select>
<!-- Insert/Update -->
<insert id="insert{Feature}" parameterType="map"> ... </insert>
<!-- Delete -->
<delete id="delete{Feature}" parameterType="map"> ... </delete>
</mapper>
```
**Naming conventions:**
- File: `{feature}.xml` (camelCase)
- Namespace: `{module}.{feature}` or just `{feature}` for root-level
- SQL IDs: `get{Feature}List`, `get{Feature}ListCnt`, `get{Feature}`, `insert{Feature}`, `delete{Feature}`
- Parameter type casting: `#{param}::numeric`, `#{param}::integer` for PostgreSQL
- All `parameterType="map"`, `resultType="map"` (no typed beans for queries)
### JSP Pattern
**Every JSP starts with:**
```jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page import="java.util.*" %>
<%@include file="/init.jsp" %>
```
**`init.jsp`** (common include) provides:
- Session check and PersonBean extraction (`connectUserId`, `connectUserDeptCode`, `connectUserName`, etc.)
- All CSS/JS includes (jQuery, Select2, SweetAlert2, Tabulator, common.js, datepicker)
- Loading overlay markup
- Page authorization AJAX call
**List page structure:**
```
Search form (#plmSearchZon) → Button group (.plm_btn_wrap) → Data table (.plm_table) → Pagination (.pdm_page)
```
**Form popup structure:**
```
Hidden fields → Form table (#adminPopupForm) → Button group (저장/닫기)
```
**No Tiles framework** — layout is managed through `init.jsp` include and consistent HTML structure.
### JavaScript/AJAX Patterns
**Standard AJAX call:**
```javascript
$.ajax({
url: "/module/action.do",
type: "POST",
data: {"param1": value1, "param2": value2},
dataType: "json",
success: function(data) {
if (data == "SUCCESS") {
Swal.fire("완료되었습니다.");
fn_search();
}
},
error: function(jqxhr, status, error) {
Swal.fire(jqxhr.statusText + ", " + status + ", " + error);
}
});
```
**Popup pattern:**
```javascript
window.open("", "formPopup", "width=1150, height=676");
document.form1.action = "/module/formPopup.do";
document.form1.target = "formPopup";
document.form1.submit();
// From popup back to parent:
opener.fn_search();
self.close(0);
```
**Key functions in `common.js` (WebContent/js/common.js, 3500+ lines):**
| Category | Functions |
|----------|-----------|
| Validation | `fnc_valitate(formName)`, `fnc_validate(formName)`, `fnc_checkDataType(dataType, value)` |
| Null check | `fnc_isEmpty(val)`, `fnc_isNotEmpty(val)`, `fnc_checkNull(val)`, `fnc_checkNullDefaultValue(val, default)` |
| Date | `fnc_datepick()` (auto-init on ID containing "Date"), `fnc_monthpick()`, `fnc_getDate(delimiter)`, `fnc_dateCheck(from, to)` |
| Pagination | `fnc_goPrev(page)`, `fnc_goNext(page)`, `fnc_goPage(page)`, `fnc_goStart()`, `fnc_goEnd(maxPage)` |
| Code lookup | `fnc_getCodeList(codeId)`, `fnc_getCodeJsonStr(codeId)` |
| File ops | `fnc_deleteFile(fileObjId, callback)`, `fnc_downloadFile(fileObjId)` |
| Grid | `fnc_createGridAnchorTag(cell, params, onRendered)` |
| Form | `serializeObject()` (jQuery extension), custom `Map` class |
**Common AJAX endpoints:** `/common/getCodeList.do`, `/common/getSupplyCodeList.do`, `/common/getClientMngList.do`, `/common/getProjectNoList.do`, `/common/getDeptList.do`, `/common/searchUserList.do`, `/common/getCarTypeList.do`, `/common/getOEMList.do`, `/common/getPartMngList.do`, `/common/getProductMgmtList.do`
---
## Key Utility Classes
### CommonUtils (`src/com/pms/common/utils/CommonUtils.java`)
| Method | Purpose |
|--------|---------|
| `checkNull(Object obj)` | Returns empty string if null |
| `isBlank(Object)` / `isNotBlank(Object)` | Null/empty check |
| `isEmpty(Object)` / `isNotEmpty(Object)` | Null/empty check |
| `setPagingInfo(request, pageMap, countPerPage)` | Calculate pagination |
| `toUpperCaseMapKey(List/Map)` | Convert map keys to uppercase |
| `createObjId()` | Generate unique object ID |
| Date format constants | `DATE_FORMAT1="yyyy-MM-dd"`, `DATE_FORMAT2="yyyyMMdd"`, etc. |
### Constants (`src/com/pms/common/utils/Constants.java`)
| Constant | Value | Purpose |
|----------|-------|---------|
| `PERSON_BEAN` | `"PERSON_BEAN"` | Session key for logged-in user |
| `COUNT_PER_PAGE` | `25` | Default pagination size |
| `ADMIN_COUNT_PER_PAGE` | `25` | Admin pagination size |
| `FILE_STORAGE` | `"/data_storage"` | File storage root |
| `AJAX_RESPONSOR` | `"/common/ajaxResponsor"` | AJAX response view |
| `SYSTEM_CHARSET` | `"UTF-8"` | System encoding |
| `SUPER_ADMIN` | `"plm_admin"` | Super admin user ID |
### SessionManager (`src/com/pms/common/utils/SessionManager.java`)
- `setSessionManage(request, paramMap)` — Creates PersonBean from login query, stores in session
- `hasSession(session)` — Checks if PERSON_BEAN exists
- `destroy(session)` — Removes session attributes and invalidates
### SqlMapConfig (`src/com/pms/common/utils/SqlMapConfig.java`)
- Singleton pattern: `SqlMapConfig.getInstance().getSqlSession()`
- Reads `mybatisConf.xml` for DataSource and mapper configuration
---
## Adding New Features
To add a new screen/feature, follow the existing pattern:
1. **Controller** — Create `{Feature}Controller.java` with `@Controller`, `@RequestMapping` for `*.do` URLs
2. **Service** — Create `{Feature}Service.java` with `@Service`, use `SqlMapConfig.getInstance().getSqlSession()`
3. **Mapper XML** — Create `{feature}.xml` with namespace matching, register in `mybatisConf.xml`
4. **JSP View** — Create in `WebContent/WEB-INF/view/{module}/`, start with `<%@include file="/init.jsp" %>`
5. **Wire**`@Autowired` service into controller
**Pagination:** Use count query (`get{Feature}ListCnt`) + list query (`get{Feature}List`) with `PAGE_START`/`PAGE_END` pattern.
**Session:** Get user via `request.getSession().getAttribute(Constants.PERSON_BEAN)``PersonBean`.
**Grids:** Use jqGrid or Tabulator depending on module. Sales modules increasingly use Tabulator.
**Alerts:** Use `Swal.fire()` (SweetAlert2) for user notifications.
**Popups:** Use `window.open()` with form target submission.
## External Integrations
11 API client classes in `src/com/pms/api/` handle ERP synchronization (parts, sales slips, warehouses), Amaranth system (approvals, users), and master data sync (accounts, customers, departments, employees).
## No Automated Tests
There is no test framework configured. Testing is manual via browser.