Files
wace_plm/CLAUDE.md
2026-03-26 15:32:37 +09:00

14 KiB

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

# 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

@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

@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

<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:

<%@ 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:

$.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:

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.