Procházet zdrojové kódy

update import bom

reset-do-picking-order
CANCERYS\kw093 před 1 týdnem
rodič
revize
555964ba98
6 změnil soubory, kde provedl 899 přidání a 294 odebrání
  1. +707
    -287
      src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt
  2. +77
    -1
      src/main/java/com/ffii/fpsms/modules/master/service/QcItemAllService.kt
  3. +32
    -3
      src/main/java/com/ffii/fpsms/modules/master/web/BomController.kt
  4. +18
    -1
      src/main/java/com/ffii/fpsms/modules/master/web/QcItemAllController.kt
  5. +52
    -2
      src/main/java/com/ffii/fpsms/modules/master/web/models/ItemUomRequest.kt
  6. +13
    -0
      src/main/java/com/ffii/fpsms/modules/master/web/models/QcCategoryWithItemCountAndType.kt

+ 707
- 287
src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt
Diff nebyl zobrazen, protože je příliš veliký
Zobrazit soubor


+ 77
- 1
src/main/java/com/ffii/fpsms/modules/master/service/QcItemAllService.kt Zobrazit soubor

@@ -7,7 +7,7 @@ import com.ffii.fpsms.modules.qc.entity.projection.QcItemInfo
import jakarta.validation.Valid
import org.springframework.stereotype.Service
import org.springframework.web.bind.annotation.RequestBody
import com.ffii.core.exception.ConflictException
@Service
open class QcItemAllService(
private val qcCategoryRepository: QcCategoryRepository,
@@ -48,6 +48,75 @@ open class QcItemAllService(
)
}
}
// Get all qc categories with item counts and type (type from first mapping if any)
open fun getAllQcCategoriesWithItemCountsAndType(): List<QcCategoryWithItemCountAndType> {
val categories = qcCategoryRepository.findAllByDeletedIsFalse()
val allMappings = itemsQcCategoryMappingRepository.findAll()
val countMap = allMappings
.filter { it.qcCategoryId != null }
.groupBy { it.qcCategoryId!! }
.mapValues { it.value.size.toLong() }
val typeMap = allMappings
.filter { it.qcCategoryId != null && it.type != null }
.groupBy { it.qcCategoryId!! }
.mapValues { it.value.first().type }
return categories.map { category ->
val rawType = typeMap[category.id]
val normalizedType = if (rawType == "[object Object]") null else rawType
QcCategoryWithItemCountAndType(
id = category.id!!,
code = category.code,
name = category.name,
description = category.description,
itemCount = countMap[category.id] ?: 0L,
type = normalizedType
)
}
}
// Get type for one category (from any mapping; null if no mappings). For 映射詳情 dropdown default.
open fun getCategoryType(qcCategoryId: Long): String? {
val list = itemsQcCategoryMappingRepository.findAllByQcCategoryId(qcCategoryId)
val rawType = list.firstOrNull()?.type
return if (rawType == "[object Object]") null else rawType
}
// Batch update type for all mappings of a category. 方案 A: no mappings -> no-op.
open fun updateCategoryType(qcCategoryId: Long, type: String) {
val mappings = itemsQcCategoryMappingRepository.findAllByQcCategoryId(qcCategoryId)
if (mappings.isEmpty()) return // 方案 A:沒有映射就直接結束
// 先取得目前這個 Category,用來組錯誤訊息
val currentCategory = qcCategoryRepository.findById(qcCategoryId).orElse(null)
val currentCategoryName = currentCategory?.name ?: currentCategory?.code ?: qcCategoryId.toString()
// 檢查:這些 itemId + 新 type 在別的 category 有沒有出現
mappings.forEach { mapping ->
val itemId = mapping.itemId ?: return@forEach
// 找出「同一個 item + type」的其他映射
val conflict = itemsQcCategoryMappingRepository.findByItemIdAndType(itemId, type)
if (conflict != null && conflict.qcCategoryId != qcCategoryId) {
// 取出物料與衝突的 category 名稱
val item = itemsRepository.findById(itemId).orElse(null)
val itemCode = item?.code ?: itemId.toString()
val conflictCategoryId = conflict.qcCategoryId
val conflictCategory = conflictCategoryId?.let { qcCategoryRepository.findById(it).orElse(null) }
val conflictCategoryName =
conflictCategory?.name ?: conflictCategory?.code ?: conflictCategoryId.toString()
// 用 ConflictException 讓 ErrorHandler 回 409 + { "error": msg }
val msg = "物料 $itemCode 已經以 $type 映射在模板 $conflictCategoryName," +
"不能再把模板 $currentCategoryName 改為 $type"
throw ConflictException(msg)
}
}
// 檢查通過才真正更新
mappings.forEach { it.type = type }
itemsQcCategoryMappingRepository.saveAll(mappings)
}
// Get all qc categories with qc item counts (batch operation for performance)
open fun getAllQcCategoriesWithQcItemCounts(): List<QcCategoryWithQcItemCount> {
@@ -109,6 +178,13 @@ open class QcItemAllService(
open fun saveItemQcCategoryMapping(itemId: Long, qcCategoryId: Long, type: String): ItemQcCategoryMappingInfo {
// Check if mapping already exists
val existing = itemsQcCategoryMappingRepository.findByItemIdAndQcCategoryIdAndType(itemId, qcCategoryId, type)
val existingByItem = itemsQcCategoryMappingRepository.findAllByItemId(itemId)
val sameTypeOtherCategory = existingByItem.firstOrNull { it.type == type && it.qcCategoryId != qcCategoryId }
if (sameTypeOtherCategory != null) {
val category = qcCategoryRepository.findById(sameTypeOtherCategory.qcCategoryId!!).orElse(null)
val msg = "Item already has type \"$type\" linked to QcCategory: ${category?.name ?: sameTypeOtherCategory.qcCategoryId}. One item can only have each type in one QcCategory."
throw ConflictException(msg)
}
val mapping = existing ?: ItemsQcCategoryMapping()
mapping.itemId = itemId


+ 32
- 3
src/main/java/com/ffii/fpsms/modules/master/web/BomController.kt Zobrazit soubor

@@ -25,7 +25,12 @@ import com.ffii.fpsms.modules.master.web.models.BomFormatCheckResponse
import com.ffii.fpsms.modules.master.web.models.BomUploadResponse
import com.ffii.fpsms.modules.master.web.models.BomFormatCheckRequest
import com.ffii.fpsms.modules.master.web.models.ImportBomRequestPayload
import com.ffii.fpsms.modules.master.web.models.BomDetailResponse
import com.ffii.fpsms.modules.master.web.models.BomExcelCheckProgress
import java.util.logging.Logger
import java.nio.file.Files
import org.springframework.core.io.FileSystemResource

@RestController
@RequestMapping("/bom")
class BomController (
@@ -64,11 +69,31 @@ class BomController (
log.info("import-bom/upload: using getParts(), count={}", parts.size)
return bomService.uploadBomFilesFromParts(parts)
}
@GetMapping("/import-bom/format-issue-log")
fun downloadBomFormatIssueLog(
@RequestParam batchId: String,
@RequestParam issueLogFileId: String
): ResponseEntity<Resource> {
val path = bomService.loadBomFormatIssueLog(batchId, issueLogFileId)
?: return ResponseEntity.notFound().build()

val resource: Resource = FileSystemResource(path.toFile())
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"bom_format_issue.xlsx\"")
.body(resource)
}
@PostMapping("/import-bom/format-check")
fun checkBomFormat(@RequestBody request: BomFormatCheckRequest): BomFormatCheckResponse {
return bomService.checkBomExcelFormat(request.batchId)
}
/*
@GetMapping("/import-bom/format-check/progress")
fun getBomFormatCheckProgress(@RequestParam batchId: String): ResponseEntity<BomExcelCheckProgress> {
val progress = bomService.getBomExcelCheckProgress(batchId)
?: return ResponseEntity.notFound().build()
return ResponseEntity.ok(progress)
}
*/
@GetMapping
fun getBoms(): List<Bom> {
return bomService.findAll()
@@ -82,15 +107,19 @@ class BomController (
@PostMapping("/import-bom")
fun importBom(@RequestBody payload: ImportBomRequestPayload): ResponseEntity<Resource> {
val reportResult = bomService.importBOM(payload.batchId, payload.items)
val filename = "bom_excel_issue_log_${LocalDate.now()}.xlsx"
// val filename = "bom_excel_issue_log_${LocalDate.now()}.xlsx"

return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"$filename\"")
.header(HttpHeaders.CONTENT_TYPE, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
// .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"$filename\"")
//.header(HttpHeaders.CONTENT_TYPE, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
.body(ByteArrayResource(reportResult))
}
// @PostMapping("/export-problematic-bom")
// fun exportProblematicBom() {
// return bomService.importBOM()
// }
@GetMapping("/{id}/detail")
fun getBomDetail(@PathVariable id: Long): BomDetailResponse {
return bomService.getBomDetail(id)
}
}

+ 18
- 1
src/main/java/com/ffii/fpsms/modules/master/web/QcItemAllController.kt Zobrazit soubor

@@ -13,7 +13,24 @@ import org.springframework.web.bind.annotation.*
class QcItemAllController(
private val qcItemAllService: QcItemAllService
) {
@GetMapping("/categoriesWithItemCountsAndType")
fun getAllQcCategoriesWithItemCountsAndType(): List<QcCategoryWithItemCountAndType> {
return qcItemAllService.getAllQcCategoriesWithItemCountsAndType()
}

@GetMapping("/categoryType/{qcCategoryId}")
fun getCategoryType(@PathVariable qcCategoryId: Long): CategoryTypeResponse {
val type = qcItemAllService.getCategoryType(qcCategoryId)
return CategoryTypeResponse(type = type)
}

@PutMapping("/categoryType")
fun updateCategoryType(
@RequestParam qcCategoryId: Long,
@RequestParam type: String
) {
qcItemAllService.updateCategoryType(qcCategoryId, type)
}
// Get item count by qc category
@GetMapping("/itemCount/{qcCategoryId}")
fun getItemCountByQcCategory(@PathVariable qcCategoryId: Long): Long {


+ 52
- 2
src/main/java/com/ffii/fpsms/modules/master/web/models/ItemUomRequest.kt Zobrazit soubor

@@ -55,15 +55,65 @@ data class BomFormatCheckRequest(
/** Format-check 回傳:正確檔名列表 + 失敗列表(檔名 + 問題) */
data class BomFormatCheckResponse(
val correctFileNames: List<String>,
val failList: List<BomFormatFileGroup>
val failList: List<BomFormatFileGroup>,
val issueLogFileId: String? = null // 或直接是 URL
)

data class ImportBomItemRequest(
val fileName: String,
val isAlsoWip: Boolean = false
val isAlsoWip: Boolean = false,
val isDrink: Boolean = false
)

data class ImportBomRequestPayload(
val batchId: String,
val items: List<ImportBomItemRequest>
)

data class BomMaterialDto(
val itemCode: String?,
val itemName: String?,
val baseQty: BigDecimal?,
val baseUom: String?,
val stockQty: BigDecimal?,
val stockUom: String?,
val salesQty: BigDecimal?,
val salesUom: String?
)

data class BomProcessDto(
val seqNo: Long?,
val processName: String?,
val processDescription: String?,
val equipmentName: String?,
val durationInMinute: Int?,
val prepTimeInMinute: Int?,
val postProdTimeInMinute: Int?
)
data class BomExcelCheckProgress(
val batchId: String,
@Volatile var totalFiles: Int,
@Volatile var processedFiles: Int,
@Volatile var currentFileName: String?, // 正在處理哪一個
@Volatile var lastUpdateTime: Long // 最後一次更新時間
)
data class BomDetailResponse(
val id: Long,
val itemCode: String?,
val itemName: String?,
val isDark: Boolean?,
val isFloat: Int?,
val isDense: Int?,
val isDrink: Boolean?,
val scrapRate: Int?,
val allergicSubstances: Int?,
val timeSequence: Int?,
val complexity: Int?,
val baseScore: Int?,
val description: String?,
val outputQty: BigDecimal?,
val outputQtyUom: String?,
val materials: List<BomMaterialDto>,
val processes: List<BomProcessDto>
)

+ 13
- 0
src/main/java/com/ffii/fpsms/modules/master/web/models/QcCategoryWithItemCountAndType.kt Zobrazit soubor

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

data class QcCategoryWithItemCountAndType(
val id: Long,
val code: String?,
val name: String?,
val description: String?,
val itemCount: Long,
val type: String? // items_qc_category_mapping.type from any one mapping of this category
)
data class CategoryTypeResponse(
val type: String?
)

Načítá se…
Zrušit
Uložit