diff --git a/agent/history/sseoyeon-ssonia/dev.md b/agent/history/sseoyeon-ssonia/dev.md new file mode 100644 index 0000000..eeea40c --- /dev/null +++ b/agent/history/sseoyeon-ssonia/dev.md @@ -0,0 +1,15 @@ +### 작업지시 마감 테스트 수정 (Codex) +- **User Intent**: `./gradlew clean test --refresh-dependencies --no-configuration-cache` 실행 시 `WorkOrderServiceTest.closeWorkOrder_underTargetRequiresRequestFlag`에서 `expected: 8 but was: 0` assertion 실패가 발생하여, 테스트를 서비스 규칙에 맞추는 방식으로 수정 요청. +- **Agent Context**: `data.sql`의 `창원제1공장/A라인`은 5개 공정을 가지지만 테스트가 3번째 공정을 마지막 공정으로 가정해, 서비스의 실제 마지막 공정 기준 집계 결과가 0으로 반환되는 원인을 확인하고 마지막 라우팅까지 실적을 등록하도록 테스트를 수정. +- **Key Decisions**: + - 테스트가 seed 데이터의 고정 공정 인덱스에 의존하지 않고 `lineRoutings.size() - 1`을 마지막 공정으로 사용하도록 변경 — `agent/project/backend.md`의 실제 코드 우선 및 기존 서비스 규칙 준수. + - 앞 공정은 양품 9개, 마지막 공정은 양품 8개와 불량 1개로 등록하도록 반복 처리 — `ProductionExecutionServiceImpl`의 이전 공정 양품 수량 기반 공정 진행 검증 규칙과 일치. +- **Affected Files**:
1개 파일 + + - **Created**: + - `agent/history/sseoyeon-ssonia/dev.md` — 작업 히스토리 신규 기록 + - **Modified**: + - `backend/src/test/java/com/ssafy/demo_app/domain/production/service/WorkOrderServiceTest.java` (+8/-10) — 목표 미달 마감 테스트가 실제 마지막 공정까지 생산 실적을 등록하도록 수정 + - **Deleted**: + +
diff --git a/backend/build.gradle b/backend/build.gradle index a16eef0..e940f74 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -35,11 +35,18 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' - + testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - testRuntimeOnly 'com.h2database:h2' + + testImplementation platform('org.testcontainers:testcontainers-bom:1.21.3') + testImplementation 'org.springframework.boot:spring-boot-testcontainers' + testImplementation 'org.testcontainers:junit-jupiter' + testImplementation 'org.testcontainers:mysql' + testImplementation 'org.testcontainers:jdbc' + testRuntimeOnly 'com.mysql:mysql-connector-j' + implementation project(':lib:web-starter') implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.5' diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 0a3ab07..afe7a4e 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -27,7 +27,7 @@ spring: sql: init: - mode: always + mode: never jwt: secret: ${JWT_SECRET} diff --git a/backend/src/main/resources/data.sql b/backend/src/main/resources/data.sql index 4950851..5f3c982 100644 --- a/backend/src/main/resources/data.sql +++ b/backend/src/main/resources/data.sql @@ -53,12 +53,12 @@ SELECT WHEN n <= 20 THEN CONCAT('RM-', LPAD(n, 3, '0')) WHEN n <= 35 THEN CONCAT('SM-', LPAD(n - 20, 3, '0')) ELSE CONCAT('FG-', LPAD(n - 35, 3, '0')) - END, + END, CASE WHEN n <= 20 THEN CONCAT('원자재 ', LPAD(n, 3, '0')) WHEN n <= 35 THEN CONCAT('반제품 ', LPAD(n - 20, 3, '0')) ELSE CONCAT('완제품 ', LPAD(n - 35, 3, '0')) - END, + END, CONCAT(ELT(1 + MOD(n - 1, 5), 'STD', 'PRO', 'ECO', 'HEAVY', 'LIGHT'), '-', LPAD(n, 3, '0')), ELT(1 + MOD(n - 1, 4), 'ea', 'kg', 'box', 'L'), CASE WHEN n <= 20 THEN 'RAW' WHEN n <= 35 THEN 'HALF' ELSE 'FG' END, @@ -138,16 +138,16 @@ SELECT CASE WHEN MOD(n, 29) = 0 THEN 'INACTIVE' ELSE 'ACTIVE' END, TIMESTAMP('2026-01-06 08:00:00') + INTERVAL n DAY FROM seq -JOIN item_master parent_item - ON parent_item.item_code = CASE - WHEN n <= 30 THEN CONCAT('SM-', LPAD(1 + MOD(n - 1, 15), 3, '0')) - ELSE CONCAT('FG-', LPAD(1 + MOD(n - 31, 15), 3, '0')) - END + JOIN item_master parent_item +ON parent_item.item_code = CASE + WHEN n <= 30 THEN CONCAT('SM-', LPAD(1 + MOD(n - 1, 15), 3, '0')) + ELSE CONCAT('FG-', LPAD(1 + MOD(n - 31, 15), 3, '0')) +END JOIN item_master child_item ON child_item.item_code = CASE WHEN n <= 30 THEN CONCAT('RM-', LPAD(1 + MOD((n * 7) - 1, 20), 3, '0')) ELSE CONCAT('SM-', LPAD(1 + MOD((n - 31) + (5 * FLOOR((n - 31) / 15)), 15), 3, '0')) - END; +END; -- =================================================================== -- 4. 입고 및 재고 도메인 (WMS / INVENTORY) @@ -166,12 +166,12 @@ SELECT DATE('2026-01-01') + INTERVAL n DAY, TIMESTAMP('2026-02-01 10:00:00') + INTERVAL n DAY FROM seq -JOIN item_master item - ON item.item_code = CASE - WHEN n <= 20 THEN CONCAT('RM-', LPAD(n, 3, '0')) - WHEN n <= 35 THEN CONCAT('SM-', LPAD(n - 20, 3, '0')) - ELSE CONCAT('FG-', LPAD(n - 35, 3, '0')) - END + JOIN item_master item +ON item.item_code = CASE + WHEN n <= 20 THEN CONCAT('RM-', LPAD(n, 3, '0')) + WHEN n <= 35 THEN CONCAT('SM-', LPAD(n - 20, 3, '0')) + ELSE CONCAT('FG-', LPAD(n - 35, 3, '0')) +END JOIN warehouse_location location ON location.location_code = CONCAT('WH', LPAD(1 + MOD(n - 1, 5), 2, '0'), '-R', CHAR(65 + MOD(n - 1, 10)), '-C', LPAD(1 + FLOOR((n - 1) / 10), 2, '0')); @@ -191,12 +191,12 @@ SELECT ELT(1 + MOD(n - 1, 3), 'READY', 'COMPLETED', 'STACKED'), TIMESTAMP('2026-01-10 09:30:00') + INTERVAL n DAY FROM seq -JOIN item_master item - ON item.item_code = CASE - WHEN n <= 20 THEN CONCAT('RM-', LPAD(n, 3, '0')) - WHEN n <= 35 THEN CONCAT('SM-', LPAD(n - 20, 3, '0')) - ELSE CONCAT('FG-', LPAD(n - 35, 3, '0')) - END + JOIN item_master item +ON item.item_code = CASE + WHEN n <= 20 THEN CONCAT('RM-', LPAD(n, 3, '0')) + WHEN n <= 35 THEN CONCAT('SM-', LPAD(n - 20, 3, '0')) + ELSE CONCAT('FG-', LPAD(n - 35, 3, '0')) +END JOIN partner_master partner ON partner.partner_code = CONCAT('SUP-', LPAD(1 + MOD(n - 1, 25), 3, '0')) JOIN warehouse_location location @@ -224,8 +224,8 @@ SELECT TIMESTAMP('2026-01-20 08:00:00') + INTERVAL n DAY, TIMESTAMP('2026-01-20 08:00:00') + INTERVAL n DAY FROM seq -JOIN item_master item - ON item.item_code = CASE WHEN n <= 25 THEN CONCAT('SM-', LPAD(1 + MOD(n - 1, 15), 3, '0')) ELSE CONCAT('FG-', LPAD(1 + MOD(n - 26, 15), 3, '0')) END + JOIN item_master item +ON item.item_code = CASE WHEN n <= 25 THEN CONCAT('SM-', LPAD(1 + MOD(n - 1, 15), 3, '0')) ELSE CONCAT('FG-', LPAD(1 + MOD(n - 26, 15), 3, '0')) END JOIN factory_routing routing ON routing.factory_name = CONCAT('창원제', 1 + FLOOR((n - 1) / 25), '공장') AND routing.line_name = CONCAT(CHAR(65 + MOD(FLOOR((n - 1) / 5), 5)), '라인') @@ -263,12 +263,12 @@ SELECT 60 + (n * 7), TIMESTAMP('2026-02-01 13:00:00') + INTERVAL n DAY FROM seq -JOIN work_order ON work_order.order_no = CONCAT('WO-2026-', LPAD(n, 4, '0')) -JOIN factory_routing routing + JOIN work_order ON work_order.order_no = CONCAT('WO-2026-', LPAD(n, 4, '0')) + JOIN factory_routing routing ON routing.factory_name = CONCAT('창원제', 1 + FLOOR((n - 1) / 25), '공장') AND routing.line_name = CONCAT(CHAR(65 + MOD(FLOOR((n - 1) / 5), 5)), '라인') AND routing.operation_seq = 1 + MOD(n - 1, 5) -JOIN users worker + JOIN users worker ON worker.employee_no = CONCAT('user', LPAD(1 + MOD(n + 1, 50), 3, '0')); INSERT IGNORE INTO inventory_transaction_history ( @@ -292,12 +292,12 @@ SELECT worker.user_id, TIMESTAMP('2026-02-10 11:00:00') + INTERVAL n DAY FROM seq -JOIN item_master item - ON item.item_code = CASE - WHEN n <= 20 THEN CONCAT('RM-', LPAD(n, 3, '0')) - WHEN n <= 35 THEN CONCAT('SM-', LPAD(n - 20, 3, '0')) - ELSE CONCAT('FG-', LPAD(n - 35, 3, '0')) - END + JOIN item_master item +ON item.item_code = CASE + WHEN n <= 20 THEN CONCAT('RM-', LPAD(n, 3, '0')) + WHEN n <= 35 THEN CONCAT('SM-', LPAD(n - 20, 3, '0')) + ELSE CONCAT('FG-', LPAD(n - 35, 3, '0')) +END JOIN warehouse_location location ON location.location_code = CONCAT('WH', LPAD(1 + MOD(n - 1, 5), 2, '0'), '-R', CHAR(65 + MOD(n - 1, 10)), '-C', LPAD(1 + FLOOR((n - 1) / 10), 2, '0')) LEFT JOIN work_order ON work_order.order_no = CONCAT('WO-2026-', LPAD(n, 4, '0')) @@ -375,8 +375,8 @@ SELECT CASE WHEN MOD(n, 6) IN (3, 4, 5) THEN CONCAT('더미 오류 로그 ', LPAD(n, 3, '0')) ELSE NULL END, TIMESTAMP('2026-03-10 10:00:00') + INTERVAL n DAY FROM seq -JOIN users worker - ON worker.employee_no = CONCAT('user', LPAD(1 + MOD(n + 4, 50), 3, '0')); + JOIN users worker +ON worker.employee_no = CONCAT('user', LPAD(1 + MOD(n + 4, 50), 3, '0')); INSERT IGNORE INTO dynamic_batch_schedule ( schedule_name, cron_expression, query_id, is_active, last_run_at, @@ -397,8 +397,8 @@ SELECT worker.user_id, TIMESTAMP('2026-03-20 08:00:00') + INTERVAL n DAY FROM seq -JOIN ai_query_history query_history - ON query_history.conversation_id = CONCAT('conv-2026-', LPAD(1 + FLOOR((n - 1) / 5), 3, '0')) + JOIN ai_query_history query_history +ON query_history.conversation_id = CONCAT('conv-2026-', LPAD(1 + FLOOR((n - 1) / 5), 3, '0')) AND query_history.natural_question = CONCAT('2026년 재고와 생산 현황을 조회해줘 ', LPAD(n, 3, '0')) -JOIN users worker - ON worker.employee_no = CONCAT('user', LPAD(1 + MOD(n + 5, 50), 3, '0')); + JOIN users worker + ON worker.employee_no = CONCAT('user', LPAD(1 + MOD(n + 5, 50), 3, '0')); \ No newline at end of file diff --git a/backend/src/test/java/com/ssafy/demo_app/domain/production/service/WorkOrderServiceTest.java b/backend/src/test/java/com/ssafy/demo_app/domain/production/service/WorkOrderServiceTest.java index 0c44a6e..b07098f 100644 --- a/backend/src/test/java/com/ssafy/demo_app/domain/production/service/WorkOrderServiceTest.java +++ b/backend/src/test/java/com/ssafy/demo_app/domain/production/service/WorkOrderServiceTest.java @@ -208,8 +208,7 @@ void closeWorkOrder_underTargetRequiresRequestFlag() { .setParameter("lineName", "A라인") .getResultList(); FactoryRouting firstRouting = lineRoutings.get(0); - FactoryRouting secondRouting = lineRoutings.get(1); - FactoryRouting lastRouting = lineRoutings.get(2); + FactoryRouting lastRouting = lineRoutings.get(lineRoutings.size() - 1); WarehouseLocation location = em.createQuery("select l from WarehouseLocation l", WarehouseLocation.class).getResultList().get(0); location.setProductionReceiptDefault(true); ItemMaster item = createItem("WO-FG-CLOSE", "미달마감 완제품", ItemMaster.ItemType.FG); @@ -222,14 +221,13 @@ void closeWorkOrder_underTargetRequiresRequestFlag() { workOrderService.issueMaterials(workOrder.getOrderNo(), worker.getUserId()); - productionExecutionService.createExecution( - worker.getUserId(), - new ProductionExecutionCreateRequest(workOrder.getOrderNo(), firstRouting.getRoutingId(), 9, 0, 60) - ); - productionExecutionService.createExecution( - worker.getUserId(), - new ProductionExecutionCreateRequest(workOrder.getOrderNo(), secondRouting.getRoutingId(), 9, 0, 60) - ); + for (int index = 0; index < lineRoutings.size() - 1; index++) { + FactoryRouting routing = lineRoutings.get(index); + productionExecutionService.createExecution( + worker.getUserId(), + new ProductionExecutionCreateRequest(workOrder.getOrderNo(), routing.getRoutingId(), 9, 0, 60) + ); + } ProductionExecutionCreateRequest request = new ProductionExecutionCreateRequest(workOrder.getOrderNo(), lastRouting.getRoutingId(), 8, 1, 120); request.setDefectReason("테스트 불량"); ProductionExecutionResponse execution = productionExecutionService.createExecution(worker.getUserId(), request); diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml index 4f66ee6..bc83191 100644 --- a/backend/src/test/resources/application.yml +++ b/backend/src/test/resources/application.yml @@ -1,9 +1,10 @@ spring: datasource: - url: jdbc:h2:mem:ssafy_pjt;MODE=MySQL;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=FALSE - username: sa - password: - driver-class-name: org.h2.Driver + driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver + url: jdbc:tc:mysql:8.0:///mallo_test + username: test + password: test + jpa: hibernate: ddl-auto: create-drop @@ -12,10 +13,11 @@ spring: properties: hibernate: format_sql: true - dialect: org.hibernate.dialect.H2Dialect + sql: init: mode: always + data-locations: classpath:data.sql jwt: secret: testsecretkeyfordemoapplocaltesting1234567890 @@ -29,4 +31,4 @@ langchain4j: base-url: https://gms.ssafy.io/gmsapi/api.openai.com/v1 api-key: test-gms-key model-name: gpt-4o-mini - temperature: 0 + temperature: 0 \ No newline at end of file