From c1e4e7ca1b9c0d6cae7353e678e5370980676276 Mon Sep 17 00:00:00 2001 From: "vluk@2fi-solutions.com.hk" Date: Wed, 4 Mar 2026 18:28:02 +0800 Subject: [PATCH] 1st time create GRN success --- .../M18GoodsReceiptNoteLogRepository.kt | 3 ++ .../m18/model/GoodsReceiptNoteRequest.kt | 21 +++++++++-- .../m18/service/M18GoodsReceiptNoteService.kt | 2 +- .../stock/service/StockInLineService.kt | 37 ++++++++++++++----- 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/ffii/fpsms/m18/entity/M18GoodsReceiptNoteLogRepository.kt b/src/main/java/com/ffii/fpsms/m18/entity/M18GoodsReceiptNoteLogRepository.kt index 776edc0..079e8c6 100644 --- a/src/main/java/com/ffii/fpsms/m18/entity/M18GoodsReceiptNoteLogRepository.kt +++ b/src/main/java/com/ffii/fpsms/m18/entity/M18GoodsReceiptNoteLogRepository.kt @@ -3,4 +3,7 @@ package com.ffii.fpsms.m18.entity import com.ffii.core.support.AbstractRepository interface M18GoodsReceiptNoteLogRepository : AbstractRepository { + + /** Returns true if a successful GRN was already created for this PO (avoids core_201 duplicate). */ + fun existsByPurchaseOrderIdAndStatusTrue(purchaseOrderId: Long): Boolean } diff --git a/src/main/java/com/ffii/fpsms/m18/model/GoodsReceiptNoteRequest.kt b/src/main/java/com/ffii/fpsms/m18/model/GoodsReceiptNoteRequest.kt index d285a4e..f329d26 100644 --- a/src/main/java/com/ffii/fpsms/m18/model/GoodsReceiptNoteRequest.kt +++ b/src/main/java/com/ffii/fpsms/m18/model/GoodsReceiptNoteRequest.kt @@ -1,33 +1,46 @@ package com.ffii.fpsms.m18.model +import com.fasterxml.jackson.annotation.JsonInclude + /** * Request body for M18 Goods Receipt Note (AN) save API. * PUT /root/api/save/an?menuCode=an */ +@JsonInclude(JsonInclude.Include.NON_NULL) data class GoodsReceiptNoteRequest( val mainan: GoodsReceiptNoteMainan, val ant: GoodsReceiptNoteAnt, ) +@JsonInclude(JsonInclude.Include.NON_NULL) data class GoodsReceiptNoteMainan( val values: List, ) +@JsonInclude(JsonInclude.Include.NON_NULL) data class GoodsReceiptNoteMainanValue( + val id: String? = null, // "" for create new val beId: Int, - val code: String, + val code: String? = null, // omit; M18 auto-generates GRN code val venId: Int, - val curId: Int, + val curId: Int, // required by M18 (core_101905) val rate: Number, + val status: String? = "Y", + val docDate: String? = null, + val tDate: String? = null, // PO delivery date (estimatedArrivalDate) + val locId: Int? = null, val flowTypeId: Int, - val staffId: Int, + val staffId: Int, // required by M18 (core_101905) + val cnDeptId: Int? = null, val virDeptId: Int? = null, ) +@JsonInclude(JsonInclude.Include.NON_NULL) data class GoodsReceiptNoteAnt( val values: List, ) +@JsonInclude(JsonInclude.Include.NON_NULL) data class GoodsReceiptNoteAntValue( val sourceType: String, val sourceId: Long, @@ -38,4 +51,6 @@ data class GoodsReceiptNoteAntValue( val qty: Number, val up: Number, val amt: Number, + val beId: Int? = null, + val flowTypeId: Int? = null, ) diff --git a/src/main/java/com/ffii/fpsms/m18/service/M18GoodsReceiptNoteService.kt b/src/main/java/com/ffii/fpsms/m18/service/M18GoodsReceiptNoteService.kt index 3341eb3..4cf2a15 100644 --- a/src/main/java/com/ffii/fpsms/m18/service/M18GoodsReceiptNoteService.kt +++ b/src/main/java/com/ffii/fpsms/m18/service/M18GoodsReceiptNoteService.kt @@ -56,7 +56,7 @@ open class M18GoodsReceiptNoteService( add("menuCode", MENU_CODE_AN) param?.let { add("param", it) } } - val queryString = queryParams.entries.joinToString("&") { (k, v) -> "$k=$v" } + val queryString = queryParams.entries.flatMap { (k, v) -> v.map { value -> "$k=$value" } }.joinToString("&") val fullUrl = "${m18Config.BASE_URL}$M18_SAVE_GOODS_RECEIPT_NOTE_API?$queryString" val requestJson = Gson().toJson(request) logger.info("[M18 GRN API] call=PUT url=$fullUrl queryParams=$queryParams body=$requestJson") diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt index 46d0716..b29de1a 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt @@ -414,37 +414,52 @@ open class StockInLineService( * Builds M18 Goods Receipt Note (AN) request from completed PO and its completed stock-in lines. */ private fun buildGoodsReceiptNoteRequest(po: PurchaseOrder, stockInLines: List): GoodsReceiptNoteRequest { + val poCode = po.code ?: "" + val beId = po.m18BeId?.toInt() ?: 1 + val flowTypeId = when { + poCode.startsWith("TOA") -> 1 + poCode.startsWith("PF") -> 2 + poCode.startsWith("PP") -> 3 + else -> 1 + } val mainan = GoodsReceiptNoteMainan( values = listOf( GoodsReceiptNoteMainanValue( - beId = po.m18BeId!!.toInt(), - code = po.code!!, + id = null, // omit; "0" and "" didn't fix core_201 + beId = beId, + code = null, // omit; empty string may cause 400 venId = (po.supplier?.m18Id ?: 0L).toInt(), curId = (po.currency?.m18Id ?: 0L).toInt(), rate = 1, - flowTypeId = 1, // TODO temp for M18 API - staffId = 232, // TODO temp for M18 API; revert to config/default when done - virDeptId = 117, // TODO temp for M18 API + flowTypeId = flowTypeId, + staffId = 194, // Steve + virDeptId = po.shop?.m18Id?.toInt(), + tDate = (po.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now()).format(DateTimeFormatter.ofPattern("MM/dd/yyyy")), + status = null, // commented out - omit + // docDate = ..., locId = ..., cnDeptId = ... ) ) ) val sourceId = po.m18DataLog?.m18Id ?: 0L val antValues = stockInLines.map { sil -> val pol = sil.purchaseOrderLine!! + val unitIdFromDataLog = (pol.m18DataLog?.dataLog?.get("unitId") as? Number)?.toLong()?.toInt() GoodsReceiptNoteAntValue( sourceType = "po", sourceId = sourceId, - sourceLot = sil.lotNo ?: "", + sourceLot = pol.m18Lot ?: "", proId = (sil.item?.m18Id ?: 0L).toInt(), - locId = 39, // TODO temp for M18 API - unitId = (pol.uom?.m18Id ?: 0L).toInt(), + locId = 155, + unitId = unitIdFromDataLog ?: (pol.uom?.m18Id ?: 0L).toInt(), qty = sil.acceptedQty?.toDouble() ?: 0.0, up = pol.up?.toDouble() ?: 0.0, amt = CommonUtils.getAmt( up = pol.up ?: BigDecimal.ZERO, discount = pol.m18Discount ?: BigDecimal.ZERO, qty = sil.acceptedQty ?: BigDecimal.ZERO - ) + ), + beId = beId, // same as header + flowTypeId = flowTypeId // same as header: TOA->1, PF->2, PP->3 ) } val ant = GoodsReceiptNoteAnt(values = antValues) @@ -506,6 +521,10 @@ open class StockInLineService( logger.info("[tryUpdatePurchaseOrderAndCreateGrnIfCompleted] DEBUG: Skipping M18 GRN - missing M18 ids for PO id=${savedPo.id} code=${savedPo.code}. m18BeId=${savedPo.m18BeId}, supplier.m18Id=${savedPo.supplier?.m18Id}, currency.m18Id=${savedPo.currency?.m18Id}") return } + if (m18GoodsReceiptNoteLogRepository.existsByPurchaseOrderIdAndStatusTrue(savedPo.id!!)) { + logger.info("[tryUpdatePurchaseOrderAndCreateGrnIfCompleted] Skipping M18 GRN - already created for PO id=${savedPo.id} code=${savedPo.code} (avoids core_201 duplicate)") + return + } val grnRequest = buildGoodsReceiptNoteRequest(savedPo, linesForGrn) val grnRequestJson = Gson().toJson(grnRequest) logger.info("[tryUpdatePurchaseOrderAndCreateGrnIfCompleted] M18 GRN API request (for discussion with M18) PO id=${savedPo.id} code=${savedPo.code}: $grnRequestJson")