Sfoglia il codice sorgente

update

stockTakeUpdate
CANCERYS\kw093 1 settimana fa
parent
commit
02c4a15b56
7 ha cambiato i file con 102 aggiunte e 13 eliminazioni
  1. +2
    -2
      src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt
  2. +2
    -2
      src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt
  3. +46
    -7
      src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt
  4. +8
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeRecordController.kt
  5. +1
    -1
      src/main/resources/application-db-local.yml
  6. +1
    -1
      src/main/resources/application-db-toa.yml
  7. +42
    -0
      src/main/resources/db/changelog/changes/20261028_fix_completed_unapproved_stock_take/01_backfill_approver_on_completed_unapproved.sql

+ 2
- 2
src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt Vedi File

@@ -298,8 +298,8 @@ return result
END AS mode,
CONCAT(
CASE
WHEN LOWER(COALESCE(MAX(st.stockTakeRoundMode), 'section')) = 'item' THEN '【依貨品】'
ELSE '【依區域】'
WHEN LOWER(COALESCE(MAX(st.stockTakeRoundMode), 'section')) = 'item' THEN '【貨品盤點】'
ELSE '【區域盤點】'
END,
' ',
CASE


+ 2
- 2
src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt Vedi File

@@ -1074,8 +1074,8 @@ ORDER BY
SELECT
CONCAT(
CASE
WHEN LOWER(COALESCE(MAX(st.stockTakeRoundMode), 'section')) = 'item' THEN '【依貨品】'
ELSE '【依區域】'
WHEN LOWER(COALESCE(MAX(st.stockTakeRoundMode), 'section')) = 'item' THEN '【貨品盤點】'
ELSE '【區域盤點】'
END,
' ',
CASE


+ 46
- 7
src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt Vedi File

@@ -1339,6 +1339,38 @@ lot_root_origin AS (
.sorted()
}

/**
* 建立區域輪前預覽:所選 section 目前沒有可預建明細的可用 lot
*(與 [createPlaceholderStockTakeRecordsForStockTake] 規則一致:availableQty>0 且非 busy)。
* 建立後會變成「空區」單頭。
*/
open fun previewSectionsWithNoAvailableLots(sections: Collection<String>): List<String> {
val wanted = sections.map { it.trim() }.filter { it.isNotBlank() }.distinct()
if (wanted.isEmpty()) return emptyList()
val busyLotKeys = busyOpenLotWarehouseKeys()
val empty = ArrayList<String>()
for (section in wanted) {
val warehouses = warehouseRepository.findAllByStockTakeSectionAndDeletedIsFalse(section)
val warehouseIds = warehouses.mapNotNull { it.id }
if (warehouseIds.isEmpty()) {
empty.add(section)
continue
}
val hasCreatable = inventoryLotLineRepository
.findAllByWarehouseIdInAndDeletedIsFalseAndStatusIsAvailable(warehouseIds)
.any { ill ->
val lotId = ill.inventoryLot?.id ?: return@any false
val warehouseId = ill.warehouse?.id ?: return@any false
if ((lotId to warehouseId) in busyLotKeys) return@any false
val availableQty = (ill.inQty ?: BigDecimal.ZERO)
.subtract(ill.outQty ?: BigDecimal.ZERO)
availableQty.compareTo(BigDecimal.ZERO) > 0
}
if (!hasCreatable) empty.add(section)
}
return empty.sorted()
}

/**
* 建立區域輪前預覽:所選 section 內已被 open「依貨品」輪佔用的貨品
*(Option B 會略過這些 lot,本輪不會盤到)。
@@ -1892,6 +1924,7 @@ ORDER BY it.code, wh.stockTakeSection, il.lotNo
val totalLots = countedRecords.size
val stockTakenCount: Int
val approvedCount: Int
val sectionCountForProgress: Int
if (itemMode) {
stockTakenCount = countedRecords.count {
it.pickerFirstStockTakeQty != null || it.status == "pass" || it.status == "completed"
@@ -1899,8 +1932,13 @@ ORDER BY it.code, wh.stockTakeSection, il.lotNo
approvedCount = countedRecords.count {
it.status == "completed" || it.approverStockTakeQty != null || it.approverTime != null
}
sectionCountForProgress = sections.size
} else {
val bySection = records.groupBy { it.stockTakeSection?.trim()?.lowercase() }
// Empty sections (header with 0 records) do not count toward progress denominator.
val sectionsWithLots = sections.filter { sec ->
bySection[sec.lowercase()].orEmpty().isNotEmpty()
}
fun sectionComplete(section: String, approved: Boolean): Boolean {
val group = bySection[section.lowercase()].orEmpty()
if (group.isEmpty()) return false
@@ -1910,8 +1948,9 @@ ORDER BY it.code, wh.stockTakeSection, il.lotNo
group.all { it.pickerFirstStockTakeQty != null }
}
}
stockTakenCount = sections.count { sectionComplete(it, false) }
approvedCount = sections.count { sectionComplete(it, true) }
stockTakenCount = sectionsWithLots.count { sectionComplete(it, false) }
approvedCount = sectionsWithLots.count { sectionComplete(it, true) }
sectionCountForProgress = sectionsWithLots.size
}
return StockTakeRoundCardResponse(
stockTakeRoundId = roundKey,
@@ -1921,7 +1960,7 @@ ORDER BY it.code, wh.stockTakeSection, il.lotNo
yearMonth = yearMonth,
open = open,
status = statusValue,
sectionCount = sections.size,
sectionCount = sectionCountForProgress,
totalItemNumber = countedRecords.mapNotNull { it.itemId }.distinct().size,
totalInventoryLotNumber = totalLots,
stockTakenCount = stockTakenCount,
@@ -2405,7 +2444,8 @@ ORDER BY it.code, wh.stockTakeSection, il.lotNo
existingRecord
}
existingRecord.pickerFirstStockTakeQty == null -> {
val isCompled = inventoryLotLine.inQty == inventoryLotLine.outQty
// Always pass (await approver). Do not auto-complete when inQty==outQty —
// that left status=completed with null approver and hid rows on approver UI.
val varianceQty = availableQty - request.qty - request.badQty
existingRecord.apply {
this.stockTakeRoundId = this.stockTakeRoundId ?: (stockTake.stockTakeRoundId ?: stockTake.id)
@@ -2413,7 +2453,7 @@ ORDER BY it.code, wh.stockTakeSection, il.lotNo
this.pickerFirstBadQty = request.badQty
this.bookQty = availableQty
this.varianceQty = varianceQty
this.status = if (isCompled) "completed" else "pass"
this.status = "pass"
this.remarks = request.remark ?: this.remarks
this.stockTakerId = stockTakerId
this.stockTakerName = user?.name
@@ -2448,7 +2488,6 @@ ORDER BY it.code, wh.stockTakeSection, il.lotNo
else -> throw IllegalArgumentException("Stock take record already has first and second counts")
}
} else {
val isCompled = inventoryLotLine.inQty == inventoryLotLine.outQty
val varianceQty = availableQty - request.qty - request.badQty
StockTakeRecord().apply {
this.itemId = item.id
@@ -2466,7 +2505,7 @@ ORDER BY it.code, wh.stockTakeSection, il.lotNo
this.varianceQty = varianceQty
this.uom = inventoryLotLine.stockUom?.uom?.udfudesc
this.date = java.time.LocalDate.now()
this.status = if (isCompled) "completed" else "pass"
this.status = "pass"
this.remarks = request.remark
this.itemCode = item.code
this.itemName = item.name


+ 8
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeRecordController.kt Vedi File

@@ -213,6 +213,14 @@ class StockTakeRecordController(
return stockOutRecordService.previewSectionCreateItemOverlaps(sections.orEmpty())
}

/** 建立區域輪預覽:目前沒有可盤 lot、建立後會成空區的 section */
@GetMapping("/sectionsWithNoAvailableLots")
fun sectionsWithNoAvailableLots(
@RequestParam sections: List<String>?,
): List<String> {
return stockOutRecordService.previewSectionsWithNoAvailableLots(sections.orEmpty())
}

/** 建立 item 輪:搜尋有可用 lot 的貨品(可依來源 PP/PF/其他) */
@GetMapping("/searchItemsForCreate")
fun searchItemsForCreate(


+ 1
- 1
src/main/resources/application-db-local.yml Vedi File

@@ -1,6 +1,6 @@
spring:
datasource:
jdbc-url: jdbc:mysql://127.0.0.1:3308/fpsmsdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8
jdbc-url: jdbc:mysql://127.0.0.1:3306/fpsmsdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8
username: root
password: secret



+ 1
- 1
src/main/resources/application-db-toa.yml Vedi File

@@ -1,6 +1,6 @@
spring:
datasource:
jdbc-url: jdbc:mysql://10.100.0.81:3008/fpsmsdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8
jdbc-url: jdbc:mysql://10.100.0.81:3006/fpsmsdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8
username: root
password: secret



+ 42
- 0
src/main/resources/db/changelog/changes/20261028_fix_completed_unapproved_stock_take/01_backfill_approver_on_completed_unapproved.sql Vedi File

@@ -0,0 +1,42 @@
--liquibase formatted sql
--changeset fpsms:20261028_fix_completed_unapproved_stock_take

-- Option B: records that picker auto-marked status=completed when inQty==outQty
-- never got approver fields, so they stayed on "pending approval" API but were
-- invisible in the UI status filter (pending/pass/notMatch only).
-- Treat them as approved using the picker count (first, or second if present).
-- Does not touch notMatch / pass / pending rows.

UPDATE stocktakerecord
SET
approverStockTakeQty = COALESCE(pickerSecondStockTakeQty, pickerFirstStockTakeQty, 0),
approverBadQty = COALESCE(pickerSecondBadQty, pickerFirstBadQty),
approverTime = IFNULL(stockTakeEndTime, NOW()),
lastSelect = IFNULL(
lastSelect,
CASE WHEN pickerSecondStockTakeQty IS NOT NULL THEN 2 ELSE 1 END
),
stockTakeEndTime = IFNULL(stockTakeEndTime, NOW()),
modified = NOW(),
modifiedBy = 'sql-fix-completed-unapproved'
WHERE deleted = 0
AND LOWER(status) = 'completed'
AND approverStockTakeQty IS NULL
AND approverTime IS NULL;

-- Keep stock_take_line in sync (approver no-variance path sets finalQty + completed).
UPDATE stock_take_line stl
INNER JOIN stocktakerecord r
ON r.id = stl.stockTakeRecordId
AND r.deleted = 0
SET
stl.finalQty = COALESCE(stl.finalQty, r.approverStockTakeQty),
stl.status = 'completed',
stl.completeDate = IFNULL(stl.completeDate, r.approverTime),
stl.modified = NOW(),
stl.modifiedBy = 'sql-fix-completed-unapproved'
WHERE stl.deleted = 0
AND stl.finalQty IS NULL
AND r.modifiedBy = 'sql-fix-completed-unapproved'
AND r.approverStockTakeQty IS NOT NULL
AND r.approverTime IS NOT NULL;

Caricamento…
Annulla
Salva