Просмотр исходного кода

po round up/down backend part

debrand
CANCERYS\kw093 2 недель назад
Родитель
Сommit
e7889a0e59
3 измененных файлов: 147 добавлений и 1 удалений
  1. +26
    -0
      src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt
  2. +114
    -0
      src/main/java/com/ffii/fpsms/modules/stock/service/StockQtyRoundSupport.kt
  3. +7
    -1
      src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockInRequest.kt

+ 26
- 0
src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt Просмотреть файл

@@ -332,6 +332,29 @@ open class StockInLineService(
latestCode = latestCode,
)
}
private fun applyRequestedStockQtyRound(
stockInLine: StockInLine,
request: SaveStockInLineRequest,
isCreate: Boolean,
) {
if (request.stockQtyRoundMode.isNullOrBlank()) return
if (stockInLine.purchaseOrderLine == null) return
if (!isCreate) {
val status = stockInLine.status?.lowercase()?.trim()
if (status != StockInLineStatus.PENDING.status && status != StockInLineStatus.ESCALATED.status) {
return
}
}
val currentQty = stockInLine.acceptedQty ?: return
val (roundedQty, remarks) = StockQtyRoundSupport.applyIfRequested(
currentQty = currentQty,
currentRemarks = stockInLine.remarks ?: request.remarks,
request = request,
defaultSource = if (isCreate) "CREATE" else "QC",
)
stockInLine.acceptedQty = roundedQty
stockInLine.remarks = remarks
}
@Throws(IOException::class)
@Transactional
open fun create(request: SaveStockInLineRequest): MessageResponse {
@@ -453,6 +476,7 @@ open class StockInLineService(
this.type = "NOR"
status = StockInLineStatus.PENDING.status
}
applyRequestedStockQtyRound(stockInLine, request, isCreate = true)
if (jo != null) {
val planStartDate = jo.planStart?.toLocalDate()
stockInLine.lotNo = assignLotNoForJo(planStartDate)
@@ -472,6 +496,7 @@ open class StockInLineService(
errorPosition = null,
entity = lineInfo
)
}

@Throws(IOException::class)
@@ -1034,6 +1059,7 @@ open class StockInLineService(
this.expiryDate = stockInLine.expiryDate ?: request.expiryDate
this.remarks = stockInLine.remarks ?: request.remarks
}
applyRequestedStockQtyRound(stockInLine, request, isCreate = false)
_logStep("apply_fields", _tApplyFields)
if (request.status == StockInLineStatus.RECEIVED.status) {
val _tPutAwayTotal = System.nanoTime()


+ 114
- 0
src/main/java/com/ffii/fpsms/modules/stock/service/StockQtyRoundSupport.kt Просмотреть файл

@@ -0,0 +1,114 @@
package com.ffii.fpsms.modules.stock.service

import com.ffii.core.exception.BadRequestException
import com.ffii.fpsms.modules.common.SecurityUtils
import com.ffii.fpsms.modules.stock.web.model.SaveStockInLineRequest
import java.math.BigDecimal
import java.math.RoundingMode
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

object StockQtyRoundSupport {
private val TAG_REGEX = Regex("""\[QTY_ROUND[^\]]*\]""")
private val AT_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm")
private val REMARKS_MAX = 500

enum class Mode {
HALF_UP,
CEILING,
FLOOR,
CUSTOM,
}

fun isNotInteger(qty: BigDecimal): Boolean {
return qty.stripTrailingZeros().scale() > 0
}

/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.6 | 2026-10-14 */
fun applyIfRequested(
currentQty: BigDecimal,
currentRemarks: String?,
request: SaveStockInLineRequest,
defaultSource: String,
): Pair<BigDecimal, String?> {
val mode = parseMode(request.stockQtyRoundMode) ?: return currentQty to currentRemarks
if (!isNotInteger(currentQty)) {
return currentQty to currentRemarks
}
val after = roundQty(currentQty, mode, request.stockQtyCustomQty)
val source = request.stockQtyRoundSource?.trim()?.ifBlank { null } ?: defaultSource
val tag = buildTag(
mode = mode,
before = currentQty,
after = after,
source = source,
)
val withReason = if (mode == Mode.CUSTOM) {
val reason = request.stockQtyCustomReason?.trim().orEmpty()
if (reason.isEmpty()) tag else "$tag 原因:$reason"
} else {
tag
}
return after to mergeRemark(currentRemarks, withReason)
}

/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.6 | 2026-10-14 */
fun parseMode(raw: String?): Mode? {
val value = raw?.trim()?.uppercase() ?: return null
if (value.isEmpty()) return null
return try {
Mode.valueOf(value)
} catch (_: IllegalArgumentException) {
throw BadRequestException("Invalid stockQtyRoundMode: $raw")
}
}

/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.6 | 2026-10-14 */
fun roundQty(before: BigDecimal, mode: Mode, customQty: BigDecimal?): BigDecimal {
return when (mode) {
Mode.HALF_UP -> before.setScale(0, RoundingMode.HALF_UP)
Mode.CEILING -> before.setScale(0, RoundingMode.CEILING)
Mode.FLOOR -> before.setScale(0, RoundingMode.FLOOR)
Mode.CUSTOM -> {
val custom = customQty ?: throw BadRequestException("stockQtyCustomQty is required for CUSTOM")
if (custom <= BigDecimal.ZERO) {
throw BadRequestException("stockQtyCustomQty must be greater than 0")
}
if (isNotInteger(custom)) {
throw BadRequestException("stockQtyCustomQty must be an integer")
}
custom.setScale(0, RoundingMode.DOWN)
}
}
}

/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.6 | 2026-10-14 */
fun buildTag(
mode: Mode,
before: BigDecimal,
after: BigDecimal,
source: String,
at: LocalDateTime = LocalDateTime.now(),
by: String = currentUsername(),
): String {
return "[QTY_ROUND mode=${mode.name} before=${plain(before)} after=${plain(after)} source=$source by=${sanitize(by)} at=${at.format(AT_FORMAT)}]"
}

/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.6 | 2026-10-14 */
fun mergeRemark(existing: String?, tag: String): String {
val stripped = existing?.replace(TAG_REGEX, "")?.trim().orEmpty()
val merged = if (stripped.isEmpty()) tag else "$tag $stripped"
return if (merged.length <= REMARKS_MAX) merged else merged.take(REMARKS_MAX)
}

private fun currentUsername(): String {
val user = SecurityUtils.getUser().orElse(null)
return user?.username ?: user?.name ?: "unknown"
}

private fun plain(qty: BigDecimal): String = qty.stripTrailingZeros().toPlainString()

private fun sanitize(value: String): String {
return value.replace(Regex("""[\s\[\]]+"""), "_").ifBlank { "unknown" }
}
}

+ 7
- 1
src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockInRequest.kt Просмотреть файл

@@ -63,7 +63,13 @@ data class SaveStockInLineRequest(
var escalationLog: SaveEscalationLogRequest? = null,
var warehouseId: Long? = null,
var rejectQty: BigDecimal? = null,
var inventoryLotLines: List<SaveInventoryLotLineForSil>? = null
var inventoryLotLines: List<SaveInventoryLotLineForSil>? = null,
/** HALF_UP / CEILING / FLOOR / CUSTOM. Null keeps current decimal stock qty. */
var stockQtyRoundMode: String? = null,
var stockQtyCustomQty: BigDecimal? = null,
/** CREATE (PO submit) or QC (old pending line). */
var stockQtyRoundSource: String? = null,
var stockQtyCustomReason: String? = null,
)

data class SaveInventoryLotLineForSil (


Загрузка…
Отмена
Сохранить