Przeglądaj źródła

update new stock take

production
CANCERYS\kw093 3 tygodni temu
rodzic
commit
03663cc801
14 zmienionych plików z 321 dodań i 26 usunięć
  1. +1
    -1
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoFloorSupplierSettingsService.kt
  2. +20
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt
  3. +40
    -1
      src/main/java/com/ffii/fpsms/modules/master/service/WarehouseService.kt
  4. +9
    -0
      src/main/java/com/ffii/fpsms/modules/master/web/WarehouseController.kt
  5. +17
    -0
      src/main/java/com/ffii/fpsms/modules/master/web/models/MissingStockTakeSectionIssuesResponse.kt
  6. +3
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockTake.kt
  7. +126
    -14
      src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt
  8. +26
    -4
      src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeService.kt
  9. +21
    -2
      src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeController.kt
  10. +41
    -4
      src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeRecordController.kt
  11. +3
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/model/CreateStockTakeForSectionsRequest.kt
  12. +1
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockTakeRequest.kt
  13. +8
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/model/StockTakeRecordReponse.kt
  14. +5
    -0
      src/main/resources/db/changelog/changes/20260519_01_Enson/03_stock_take_round_name.sql

+ 1
- 1
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoFloorSupplierSettingsService.kt Wyświetl plik

@@ -4,7 +4,7 @@ import com.ffii.fpsms.modules.settings.entity.SettingsRepository
import org.springframework.stereotype.Service
import java.util.Locale

/** 供 DO 搜/車線/報表 SQL 等共用的 2F/4F 供應商代碼(來自 `settings` CSV)。 */
/** 供 DO 搜/車線/報表 SQL 等共用的 2F/4F 供應商代碼(來自 `settings` CSV)。 */
@Service
open class DoFloorSupplierSettingsService(
private val settingsRepository: SettingsRepository,


+ 20
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt Wyświetl plik

@@ -2,6 +2,8 @@ package com.ffii.fpsms.modules.master.entity

import com.ffii.core.support.AbstractRepository
import com.ffii.fpsms.modules.master.entity.projections.WarehouseCombo
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.Query
import org.springframework.stereotype.Repository
import java.io.Serializable
@@ -19,4 +21,22 @@ interface WarehouseRepository : AbstractRepository<Warehouse, Long> {
fun findDistinctStockTakeSectionsByDeletedIsFalse(): List<String>;
fun findAllByIdIn(ids: List<Long>): List<Warehouse>;
fun findAllByCodeAndDeletedIsFalse(code: String): List<Warehouse>

@Query(
"""
SELECT COUNT(w) FROM Warehouse w
WHERE w.deleted = false
AND (w.stockTakeSection IS NULL OR TRIM(w.stockTakeSection) = '')
"""
)
fun countMissingStockTakeSection(): Long

@Query(
"""
SELECT w FROM Warehouse w
WHERE w.deleted = false
AND (w.stockTakeSection IS NULL OR TRIM(w.stockTakeSection) = '')
"""
)
fun findMissingStockTakeSection(pageable: Pageable): Page<Warehouse>
}

+ 40
- 1
src/main/java/com/ffii/fpsms/modules/master/service/WarehouseService.kt Wyświetl plik

@@ -26,8 +26,12 @@ import java.math.BigDecimal
import kotlin.jvm.optionals.getOrNull
import com.ffii.fpsms.modules.master.web.models.ExcelWarehouseData
import com.ffii.core.exception.BadRequestException
import com.ffii.fpsms.modules.master.web.models.MissingStockTakeSectionIssueItem
import com.ffii.fpsms.modules.master.web.models.MissingStockTakeSectionIssuesResponse
import com.ffii.fpsms.modules.master.web.models.StockTakeSectionInfo
import com.ffii.fpsms.modules.master.web.models.UpdateSectionDescriptionRequest
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort
@Service
open class WarehouseService(
private val jdbcDao: JdbcDao,
@@ -406,13 +410,48 @@ open class WarehouseService(
return floorOrder * 10000L + letterOrder * 100L + slot
}

open fun getMissingStockTakeSectionIssues(limit: Int): MissingStockTakeSectionIssuesResponse {
val capped = limit.coerceIn(1, 200)
val count = warehouseRepository.countMissingStockTakeSection()
val sort = Sort.by(Sort.Order.asc("store_id"), Sort.Order.asc("code"))
val page = warehouseRepository.findMissingStockTakeSection(PageRequest.of(0, capped, sort))
val items = page.content.map { w ->
MissingStockTakeSectionIssueItem(
id = w.id!!,
code = w.code,
storeId = w.store_id,
warehouse = w.warehouse,
area = w.area,
slot = w.slot,
order = w.order,
)
}
return MissingStockTakeSectionIssuesResponse(
count = count,
limit = capped,
items = items,
)
}

open fun getStockTakeSections(): List<StockTakeSectionInfo> {
val all = warehouseRepository.findAllByDeletedIsFalse()
.filter { it.stockTakeSection != null && it.stockTakeSection!!.isNotBlank() }
val grouped = all.groupBy { it.stockTakeSection!! }
return grouped.map { (section, list) ->
val desc = list.mapNotNull { it.stockTakeSectionDescription }.firstOrNull()?.trim()
StockTakeSectionInfo(section, desc, list.size.toLong())
val storeId = list.asSequence()
.mapNotNull { it.store_id?.trim() }
.firstOrNull { it.isNotBlank() }
val warehouseArea = list.asSequence()
.mapNotNull { it.area?.trim() }
.firstOrNull { it.isNotBlank() }
StockTakeSectionInfo(
stockTakeSection = section,
stockTakeSectionDescription = desc,
warehouseCount = list.size.toLong(),
storeId = storeId,
warehouseArea = warehouseArea,
)
}.sortedBy { it.stockTakeSection }
}


+ 9
- 0
src/main/java/com/ffii/fpsms/modules/master/web/WarehouseController.kt Wyświetl plik

@@ -25,8 +25,10 @@ import org.springframework.web.multipart.MultipartHttpServletRequest
import net.sf.jasperreports.engine.JasperExportManager
import net.sf.jasperreports.engine.JasperPrint
import java.io.OutputStream
import com.ffii.fpsms.modules.master.web.models.MissingStockTakeSectionIssuesResponse
import com.ffii.fpsms.modules.master.web.models.StockTakeSectionInfo
import com.ffii.fpsms.modules.master.web.models.UpdateSectionDescriptionRequest
import org.springframework.web.bind.annotation.RequestParam
@RestController
@RequestMapping("/warehouse")
class WarehouseController(
@@ -102,6 +104,13 @@ class WarehouseController(
fun printQrCode(@Valid @RequestBody request: PrintWarehouseQrCodeRequest) {
warehouseQrCodeService.printWarehouseQrCode(request)
}
@GetMapping("/missingStockTakeSectionIssues")
fun getMissingStockTakeSectionIssues(
@RequestParam(defaultValue = "50") limit: Int,
): MissingStockTakeSectionIssuesResponse {
return warehouseService.getMissingStockTakeSectionIssues(limit)
}

@GetMapping("/stockTakeSections")
fun getStockTakeSections(): List<StockTakeSectionInfo> {
return warehouseService.getStockTakeSections()


+ 17
- 0
src/main/java/com/ffii/fpsms/modules/master/web/models/MissingStockTakeSectionIssuesResponse.kt Wyświetl plik

@@ -0,0 +1,17 @@
package com.ffii.fpsms.modules.master.web.models

data class MissingStockTakeSectionIssueItem(
val id: Long,
val code: String?,
val storeId: String?,
val warehouse: String?,
val area: String?,
val slot: String?,
val order: String?,
)

data class MissingStockTakeSectionIssuesResponse(
val count: Long,
val limit: Int,
val items: List<MissingStockTakeSectionIssueItem>,
)

+ 3
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/StockTake.kt Wyświetl plik

@@ -46,4 +46,7 @@ open class StockTake: BaseEntity<Long>() {
/** 同一輪盤點(多 section 多筆 stock_take)共用此 id;由批次建立時依 MAX+1 遞增,不必等於任一筆主鍵 */
@Column(name = "stockTakeRoundId")
open var stockTakeRoundId: Long? = null
@Size(max = 255)
@Column(name = "stockTakeRoundName", length = 255)
open var stockTakeRoundName: String? = null
}

+ 126
- 14
src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt Wyświetl plik

@@ -316,6 +316,72 @@ open class StockTakeRecordService(
}
}

/** 與 Approver 列表前端一致:優先第二次盤點數,否則第一次 */
private fun resolvePickerQtyForVarianceFilter(response: InventoryLotDetailResponse): BigDecimal {
val second = response.secondStockTakeQty
val first = response.firstStockTakeQty
return when {
second != null && second >= BigDecimal.ZERO -> second
first != null -> first
else -> BigDecimal.ZERO
}
}

private fun computeInventoryLotDetailVariancePercentage(response: InventoryLotDetailResponse): BigDecimal {
val selectedQty = resolvePickerQtyForVarianceFilter(response)
val bookQty = response.bookQty ?: response.availableQty ?: BigDecimal.ZERO
val difference = selectedQty.subtract(bookQty)
if (bookQty.compareTo(BigDecimal.ZERO) != 0) {
return difference
.divide(bookQty, 6, RoundingMode.HALF_UP)
.multiply(BigDecimal("100"))
}
return when {
difference.compareTo(BigDecimal.ZERO) == 0 -> BigDecimal.ZERO
difference.compareTo(BigDecimal.ZERO) > 0 -> BigDecimal("100")
else -> BigDecimal("-100")
}
}

/**
* @param varianceFilterInclusive false:只保留區間外(vp ≤ -t 或 vp ≥ t);
* true:只保留區間內(-t ≤ vp ≤ t)
* @param varianceFilterStrict true:邊界用 &gt; &lt;;false:用 ≥ ≤
*/
private fun filterInventoryLotDetailsByVariancePercent(
responses: List<InventoryLotDetailResponse>,
variancePercentTolerance: BigDecimal?,
varianceFilterInclusive: Boolean?,
varianceFilterStrict: Boolean?,
): List<InventoryLotDetailResponse> {
if (variancePercentTolerance == null) {
return responses
}
val tolerance = variancePercentTolerance.abs()
if (tolerance.compareTo(BigDecimal.ZERO) <= 0) {
return responses
}
val inclusive = varianceFilterInclusive == true
val strict = varianceFilterStrict == true
val negTolerance = tolerance.negate()
return responses.filter { response ->
val vp = computeInventoryLotDetailVariancePercentage(response)
if (inclusive) {
if (strict) {
vp > negTolerance && vp < tolerance
} else {
vp >= negTolerance && vp <= tolerance
}
} else {
if (strict) {
vp < negTolerance || vp > tolerance
} else {
vp <= negTolerance || vp >= tolerance
}
}
}
}

private fun filterInventoryLotDetailsByItemName(
responses: List<InventoryLotDetailResponse>,
itemName: String?
@@ -510,10 +576,13 @@ open class StockTakeRecordService(
itemKeyword: String? = null,
sectionDescription: String? = null,
stockTakeSections: String? = null,
warehouseKeyword: String? = null
warehouseKeyword: String? = null,
variancePercentTolerance: BigDecimal? = null,
varianceFilterInclusive: Boolean? = null,
varianceFilterStrict: Boolean? = null,
): RecordsRes<InventoryLotDetailResponse> {
println("getApproverInventoryLotDetailsAll called with stockTakeId: $stockTakeId, pageNum: $pageNum, pageSize: $pageSize, itemKeyword: $itemKeyword, sectionDescription: $sectionDescription, stockTakeSections: $stockTakeSections, warehouseKeyword: $warehouseKeyword")
println("getApproverInventoryLotDetailsAll called with stockTakeId: $stockTakeId, pageNum: $pageNum, pageSize: $pageSize, itemKeyword: $itemKeyword, sectionDescription: $sectionDescription, stockTakeSections: $stockTakeSections, warehouseKeyword: $warehouseKeyword, variancePercentTolerance: $variancePercentTolerance, varianceFilterInclusive: $varianceFilterInclusive, varianceFilterStrict: $varianceFilterStrict")

// 3. 如果传了 stockTakeId,就把「同一轮」的所有 stockTake 找出来(stockTakeRoundId,舊資料則 planStart)
// stockTakeId != null 时,优先用 stocktakerecord.stockTakeRoundId 取整轮记录(更快)
@@ -720,19 +789,26 @@ open class StockTakeRecordService(
} else {
approvalFilteredResults
}

val varianceFilteredFinal = filterInventoryLotDetailsByVariancePercent(
approvalFilteredFinal,
variancePercentTolerance,
varianceFilterInclusive,
varianceFilterStrict,
)
// 7. 分页(和 section 版一模一样)
val pageable = PageRequest.of(pageNum, pageSize)
val startIndex = pageable.offset.toInt()
val endIndex = minOf(startIndex + pageSize, approvalFilteredFinal.size)
val endIndex = minOf(startIndex + pageSize, varianceFilteredFinal.size)
val paginatedResult = if (startIndex < approvalFilteredFinal.size) {
approvalFilteredFinal.subList(startIndex, endIndex)
val paginatedResult = if (startIndex < varianceFilteredFinal.size) {
varianceFilteredFinal.subList(startIndex, endIndex)
} else {
emptyList()
}
return RecordsRes(paginatedResult, approvalFilteredFinal.size)
return RecordsRes(paginatedResult, varianceFilteredFinal.size)
}
open fun AllApproverStockTakeList(): List<AllPickedStockTakeListReponse> {
// Overall 卡:只取“最新一轮”,并且总数口径与
@@ -1268,6 +1344,48 @@ open class StockTakeRecordService(
return toSaveStockTakeRecordResponse(savedRecord)
}

/**
* 拣货员一次提交多行已输入数量;每行复用 [saveStockTakeRecord],与单行保存一致。
*/
@Transactional
open fun batchSavePickerStockTakeInputs(
request: BatchSavePickerStockTakeInputRequest
): BatchSaveStockTakeRecordResponse {
if (request.records.isEmpty()) {
return BatchSaveStockTakeRecordResponse(
0,
0,
listOf("No records provided")
)
}

var successCount = 0
var errorCount = 0
val errors = mutableListOf<String>()

request.records.forEach { item ->
try {
saveStockTakeRecord(item, request.stockTakeId, request.stockTakerId)
successCount++
} catch (e: Exception) {
errorCount++
val msg = "inventoryLotLineId ${item.inventoryLotLineId}: ${e.message ?: e.javaClass.simpleName}"
errors.add(msg)
logger.warn("batchSavePickerStockTakeInputs failed: $msg", e)
}
}

if (successCount > 0 && request.stockTakeSection.isNotBlank()) {
checkAndUpdateStockTakeStatus(request.stockTakeId, request.stockTakeSection)
}

return BatchSaveStockTakeRecordResponse(
successCount = successCount,
errorCount = errorCount,
errors = errors
)
}

private fun toSaveStockTakeRecordResponse(r: StockTakeRecord): SaveStockTakeRecordResponse {
return SaveStockTakeRecordResponse(
id = r.id,
@@ -1366,13 +1484,8 @@ open class StockTakeRecordService(
// 使用 availableQty 作为 qty,badQty 为 0
val qty = availableQty
val badQty = BigDecimal.ZERO

// 判断是否匹配
val totalInputQty = qty.add(badQty)
val isMatched = totalInputQty.compareTo(availableQty) == 0
val varianceQty = availableQty - qty - badQty
val isCompleted = (ill.inQty ?: BigDecimal.ZERO) == (ill.outQty ?: BigDecimal.ZERO)
// 创建新记录
// 拣货员第一次批量盘点:与单笔第一次保存一致,一律 pass;不在此写 stockTakeEndTime(仅第二次盘点结束)
val stockTakeRecord = StockTakeRecord().apply {
this.itemId = itemEntity.id
this.lotId = inventoryLot.id
@@ -1389,14 +1502,13 @@ open class StockTakeRecordService(
this.varianceQty = varianceQty
this.uom = ill.stockUom?.uom?.udfudesc
this.date = java.time.LocalDate.now()
this.status = if (isCompleted) "completed" else "pass"
this.status = "pass"
this.remarks = null
this.itemCode = itemEntity.code
this.itemName = itemEntity.name
this.lotNo = inventoryLot.lotNo
this.expiredDate = inventoryLot.expiryDate
this.stockTakeStartTime = java.time.LocalDateTime.now()
this.stockTakeEndTime = java.time.LocalDateTime.now()
}

stockTakeRecordRepository.save(stockTakeRecord)


+ 26
- 4
src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeService.kt Wyświetl plik

@@ -64,6 +64,7 @@ open class StockTakeService(
request.remarks?.let { stockTake.remarks = it }
request.stockTakeSection?.let { stockTake.stockTakeSection = it } // 添加此行
request.stockTakeRoundId?.let { stockTake.stockTakeRoundId = it }
request.stockTakeRoundName?.let { stockTake.stockTakeRoundName = it }

return stockTakeRepository.save(stockTake)
}
@@ -239,23 +240,43 @@ open class StockTakeService(



fun createStockTakeForSections(): Map<String, String> {
fun createStockTakeForSections(
sectionsFilter: List<String>? = null,
stockTakeRoundName: String? = null,
planStart: LocalDateTime? = null,
): Map<String, String> {
log.info("--------- Start - Create Stock Take for Sections -------")
val result = mutableMapOf<String, String>()
// 1. 获取所有不同的 stockTakeSection(从 warehouse 表)
val allWarehouses = warehouseRepository.findAllByDeletedIsFalse()
val distinctSections = allWarehouses
var distinctSections = allWarehouses
.mapNotNull { it.stockTakeSection }
.distinct()
.filter { !it.isBlank() }

if (sectionsFilter != null) {
if (sectionsFilter.isEmpty()) {
log.warn("Create stock take: empty section list in request")
return mapOf("_" to "Error: No stock take sections selected")
}
val wanted = sectionsFilter.map { it.trim() }.filter { it.isNotBlank() }.toSet()
distinctSections = distinctSections.filter { section ->
wanted.any { w -> section.equals(w, ignoreCase = true) }
}
if (distinctSections.isEmpty()) {
log.warn("Create stock take: no warehouse sections matched filter")
return mapOf("_" to "Error: No matching warehouse stock take sections")
}
}

// 移除 null section 处理逻辑,因为 warehouse 表中没有 null 的 stockTakeSection
val batchPlanStart = LocalDateTime.now()
val batchPlanStart = planStart ?: LocalDateTime.now()
val batchPlanEnd = batchPlanStart.plusDays(1)
// 輪次序號:每批次共用同一個遞增 roundId,與各筆 stock_take 主鍵脫鉤(避免第二輪變成 4,4,4)
val roundId = stockTakeRepository.findMaxStockTakeRoundId() + 1
val roundName = stockTakeRoundName?.trim()?.takeIf { it.isNotBlank() }
var placeholderStockTakerId: Long? = null
distinctSections.forEach { section ->
try {
@@ -269,7 +290,8 @@ open class StockTakeService(
status = StockTakeStatus.PENDING.value,
remarks = null,
stockTakeSection = section,
stockTakeRoundId = roundId
stockTakeRoundId = roundId,
stockTakeRoundName = roundName,
)
val savedStockTake = saveStockTake(saveStockTakeReq)
if (placeholderStockTakerId == null) {


+ 21
- 2
src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeController.kt Wyświetl plik

@@ -1,6 +1,7 @@
package com.ffii.fpsms.modules.stock.web

import com.ffii.fpsms.modules.stock.service.StockTakeService
import com.ffii.fpsms.modules.stock.web.model.CreateStockTakeForSectionsRequest
import jakarta.servlet.http.HttpServletRequest
import org.apache.poi.ss.usermodel.Workbook
import org.apache.poi.util.IOUtils
@@ -9,6 +10,7 @@ import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.ServletRequestBindingException
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartHttpServletRequest
@@ -41,8 +43,25 @@ class StockTakeController(
return ResponseEntity.ok(stockTakeService.importExcel(workbook))
}
@PostMapping("/createForSections")
fun createStockTakeForSections(): ResponseEntity<Map<String, String>> {
val result = stockTakeService.createStockTakeForSections()
fun createStockTakeForSections(
@RequestBody(required = false) body: CreateStockTakeForSectionsRequest?,
): ResponseEntity<Map<String, String>> {
val trimmed =
body?.sections
?.map { it.trim() }
?.filter { it.isNotEmpty() }
val sectionsFilter: List<String>? =
when {
body == null -> null
trimmed == null -> null
trimmed.isEmpty() -> emptyList()
else -> trimmed
}
val result = stockTakeService.createStockTakeForSections(
sectionsFilter = sectionsFilter,
stockTakeRoundName = body?.stockTakeRoundName,
planStart = body?.planStart,
)
return ResponseEntity.ok(result)
}


+ 41
- 4
src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeRecordController.kt Wyświetl plik

@@ -117,7 +117,10 @@ class StockTakeRecordController(
@RequestParam(required = false) itemKeyword: String?,
@RequestParam(required = false) sectionDescription: String?,
@RequestParam(required = false) stockTakeSections: String?,
@RequestParam(required = false) warehouseKeyword: String?
@RequestParam(required = false) warehouseKeyword: String?,
@RequestParam(required = false) variancePercentTolerance: java.math.BigDecimal?,
@RequestParam(required = false) varianceFilterInclusive: Boolean?,
@RequestParam(required = false) varianceFilterStrict: Boolean?,
): RecordsRes<InventoryLotDetailResponse> {
return stockOutRecordService.getApproverInventoryLotDetailsAll(
stockTakeId = stockTakeId,
@@ -127,7 +130,10 @@ class StockTakeRecordController(
itemKeyword = itemKeyword,
sectionDescription = sectionDescription,
stockTakeSections = stockTakeSections,
warehouseKeyword = warehouseKeyword
warehouseKeyword = warehouseKeyword,
variancePercentTolerance = variancePercentTolerance,
varianceFilterInclusive = varianceFilterInclusive,
varianceFilterStrict = varianceFilterStrict,
)
}
@GetMapping("/approverInventoryLotDetailsAllApproved")
@@ -138,7 +144,10 @@ class StockTakeRecordController(
@RequestParam(required = false) itemKeyword: String?,
@RequestParam(required = false) sectionDescription: String?,
@RequestParam(required = false) stockTakeSections: String?,
@RequestParam(required = false) warehouseKeyword: String?
@RequestParam(required = false) warehouseKeyword: String?,
@RequestParam(required = false) variancePercentTolerance: java.math.BigDecimal?,
@RequestParam(required = false) varianceFilterInclusive: Boolean?,
@RequestParam(required = false) varianceFilterStrict: Boolean?,
): RecordsRes<InventoryLotDetailResponse> {
return stockOutRecordService.getApproverInventoryLotDetailsAll(
stockTakeId = stockTakeId,
@@ -148,7 +157,10 @@ class StockTakeRecordController(
itemKeyword = itemKeyword,
sectionDescription = sectionDescription,
stockTakeSections = stockTakeSections,
warehouseKeyword = warehouseKeyword
warehouseKeyword = warehouseKeyword,
variancePercentTolerance = variancePercentTolerance,
varianceFilterInclusive = varianceFilterInclusive,
varianceFilterStrict = varianceFilterStrict,
)
}
@GetMapping("/AllApproverStockTakeList")
@@ -250,6 +262,31 @@ class StockTakeRecordController(
))
}
}

@PostMapping("/batchSavePickerStockTakeInputs")
fun batchSavePickerStockTakeInputs(
@RequestBody request: BatchSavePickerStockTakeInputRequest
): ResponseEntity<Any> {
return try {
val result = stockOutRecordService.batchSavePickerStockTakeInputs(request)
logger.info(
"Batch save picker inputs: success=${result.successCount}, errors=${result.errorCount}"
)
ResponseEntity.ok(result)
} catch (e: IllegalArgumentException) {
logger.warn("Validation error: ${e.message}")
ResponseEntity.badRequest().body(mapOf(
"error" to "VALIDATION_ERROR",
"message" to (e.message ?: "Validation failed")
))
} catch (e: Exception) {
logger.error("Error batch saving picker stock take inputs", e)
ResponseEntity.status(500).body(mapOf(
"error" to "INTERNAL_ERROR",
"message" to (e.message ?: "Failed to batch save picker stock take inputs")
))
}
}
@GetMapping("/checkAndUpdateStockTakeStatus")
fun checkAndUpdateStockTakeStatus(


+ 3
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/model/CreateStockTakeForSectionsRequest.kt Wyświetl plik

@@ -1,8 +1,11 @@
package com.ffii.fpsms.modules.stock.web.model

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import java.time.LocalDateTime

@JsonIgnoreProperties(ignoreUnknown = true)
data class CreateStockTakeForSectionsRequest(
val sections: List<String>? = null,
val stockTakeRoundName: String? = null,
val planStart: LocalDateTime? = null,
)

+ 1
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockTakeRequest.kt Wyświetl plik

@@ -13,4 +13,5 @@ data class SaveStockTakeRequest(
val remarks: String?,
val stockTakeSection: String?=null,
val stockTakeRoundId: Long? = null,
val stockTakeRoundName: String? = null,
)

+ 8
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/model/StockTakeRecordReponse.kt Wyświetl plik

@@ -123,6 +123,14 @@ data class BatchSaveStockTakeRecordRequest(
//val stockTakerName: String
)

/** 拣货员批量提交已输入行(与 [saveStockTakeRecord] 相同业务,逐条保存用户 qty/badQty)。 */
data class BatchSavePickerStockTakeInputRequest(
val stockTakeId: Long,
val stockTakeSection: String,
val stockTakerId: Long,
val records: List<SaveStockTakeRecordRequest>,
)

data class BatchSaveStockTakeRecordResponse(
val successCount: Int,
val errorCount: Int,


+ 5
- 0
src/main/resources/db/changelog/changes/20260519_01_Enson/03_stock_take_round_name.sql Wyświetl plik

@@ -0,0 +1,5 @@
--liquibase formatted sql

--changeset Enson:20260519-05-stock-take-round-name
ALTER TABLE stock_take
ADD COLUMN stockTakeRoundName VARCHAR(255);

Ładowanie…
Anuluj
Zapisz