[RAPID] gitignore 업데이트, CLAUDE.md 추가
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -35,7 +35,8 @@ Thumbs.db
|
||||
.cursor/
|
||||
|
||||
# Claude Code
|
||||
CLAUDE.md
|
||||
|
||||
.claude/
|
||||
.playwright-mcp/
|
||||
.omc/
|
||||
.mcp.json
|
||||
|
||||
343
CLAUDE.md
Normal file
343
CLAUDE.md
Normal 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.
|
||||
Reference in New Issue
Block a user