瀏覽代碼

排產 Excel

在「物料用量總結」加入「需採購/生產數量」=下週用量 − 庫存(不扣 PO,可為負)
欄寬加寬;不放在每日分頁
工單
列表顯示 BOM 工序標準工時(預估分鐘)
「BOM材料」→「材料」
耗材「不適用」→「不用提料」
BOM 工藝
新增 bom.details(含 Liquibase)
「工藝明細」改為可儲存的自由文字;「工藝流程」仍用名稱
看板 / 分頁
工單狀態看板經手人:無 handler 時回退顯示 operator
隱藏飲料生產數量分頁
排產規則文案
「一般日」→「一般規則」
「多備日」等「多備」相關 →「特別規則」
「多備星期」→「特別規則適用星期」(前後端同步)
debrand
tommy 2 天之前
父節點
當前提交
81378f6b8a
共有 10 個文件被更改,包括 79 次插入 和 10 次删除
  1. +3
    -1
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt
  2. +16
    -1
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt
  3. +3
    -3
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/PsScheduleRuleService.kt
  4. +4
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/Bom.kt
  5. +18
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/BomProcessRepository.kt
  6. +13
    -0
      src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt
  7. +6
    -4
      src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt
  8. +10
    -0
      src/main/java/com/ffii/fpsms/modules/master/web/BomController.kt
  9. +2
    -0
      src/main/java/com/ffii/fpsms/modules/productProcess/entity/projections/ProductProcessInfo.kt
  10. +4
    -1
      src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt

+ 3
- 1
src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt 查看文件

@@ -68,7 +68,9 @@ data class JobOrderInfoWithTypeName(
val status: String,
val jobTypeId: Long?,
val jobTypeName: String?,
val lotNo: String?
val lotNo: String?,
/** Standard BOM process minutes (prep + duration + post). Null if unknown. */
val estimatedMinutes: Int? = null,
)
// Job Order
interface JobOrderDetailWithJsonString {


+ 16
- 1
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt 查看文件

@@ -69,6 +69,7 @@ import java.math.RoundingMode
import java.time.LocalDate
import java.time.LocalDateTime
import com.ffii.fpsms.modules.master.entity.BomMaterialRepository
import com.ffii.fpsms.modules.master.entity.BomProcessRepository
import com.ffii.fpsms.modules.master.service.ItemUomService
import com.ffii.fpsms.modules.master.web.models.ConvertUomByItemRequest
import com.ffii.fpsms.modules.stock.service.StockInLineService
@@ -96,6 +97,7 @@ open class JobOrderService(
val productProcessRepository: ProductProcessRepository,
val jobOrderBomMaterialRepository: JobOrderBomMaterialRepository,
val bomMaterialRepository: BomMaterialRepository,
val bomProcessRepository: BomProcessRepository,
val itemUomService: ItemUomService

) {
@@ -171,6 +173,15 @@ open class JobOrderService(
} else {
emptyMap()
}

val bomIds = jobOrdersMap.values.mapNotNull { it.bom?.id }.distinct()
val estimatedMinutesByBomId: Map<Long, Int> = if (bomIds.isNotEmpty()) {
bomProcessRepository.sumStandardMinutesByBomIds(bomIds).associate { agg ->
agg.bomId to agg.totalMinutes.toInt().coerceAtLeast(0)
}
} else {
emptyMap()
}
// 移除内存中的日期过滤,因为已经在数据库层面过滤了
val records = response.content
@@ -184,6 +195,9 @@ open class JobOrderService(

Pair(null, null)
}

val bomId = jobOrder?.bom?.id
val estimatedMinutes = bomId?.let { estimatedMinutesByBomId[it] }
JobOrderInfoWithTypeName(
id = info.id,
@@ -210,7 +224,8 @@ open class JobOrderService(
},
jobTypeId = info.jobTypeId,
jobTypeName = info.jobTypeId?.let { jobTypes[it]?.name },
lotNo = info.lotNo
lotNo = info.lotNo,
estimatedMinutes = estimatedMinutes,
)
}
.filter { info ->


+ 3
- 3
src/main/java/com/ffii/fpsms/modules/jobOrder/service/PsScheduleRuleService.kt 查看文件

@@ -134,12 +134,12 @@ open class PsScheduleRuleService(
}

fun savePlantRules(request: SavePsPlantRulesRequest) {
requireDays(request.redLineDays, request.safetyStockDays, UiText.t("一般日", "Normal days"))
requireDays(request.prepareMoreRedLineDays, request.prepareMoreSafetyStockDays, UiText.t("多備日", "Extra-prep days"))
requireDays(request.redLineDays, request.safetyStockDays, UiText.t("一般規則", "Normal rules"))
requireDays(request.prepareMoreRedLineDays, request.prepareMoreSafetyStockDays, UiText.t("特別規則", "Special rules"))
val coffee = requireProduceDays(request.coffeeDays, UiText.t("類別 A", "Category A"))
val tea = requireProduceDays(request.teaDays, UiText.t("類別 B", "Category B"))
val lemon = requireProduceDays(request.lemonDays, UiText.t("類別 C", "Category C"))
val prepareMore = parseDaysStrict(request.prepareMoreDays, UiText.t("多備星期", "Extra-prep weekdays"))
val prepareMore = parseDaysStrict(request.prepareMoreDays, UiText.t("特別規則適用星期", "Special-rule weekdays"))
val noProduction = parseDaysStrict(request.noProductionDays, UiText.t("不生產星期", "Non-production weekdays"))

upsert(RED_LINE, formatNumber(request.redLineDays), "float")


+ 4
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/Bom.kt 查看文件

@@ -56,6 +56,10 @@ open class Bom : BaseEntity<Long>() {
@Column(name = "description", nullable = false, length = 100)
open var description: String? = null

/** Free-text process / craft details shown on job order 工藝明細. */
@Column(name = "details", columnDefinition = "TEXT")
open var details: String? = null

@Column(name = "outputQty", precision = 14, scale = 2)
open var outputQty: BigDecimal? = null



+ 18
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/BomProcessRepository.kt 查看文件

@@ -30,4 +30,22 @@ interface BomProcessRepository : AbstractRepository<BomProcess, Long> {
"""
)
fun sumDurationByBomIds(@Param("bomIds") bomIds: Collection<Long>): List<BomDurationAggregate>

/** prep + duration + post for JO list estimated time */
@Query(
"""
SELECT
bp.bom.id AS bomId,
COALESCE(SUM(
COALESCE(bp.prepTimeInMinute, 0) +
COALESCE(bp.durationInMinute, 0) +
COALESCE(bp.postProdTimeInMinute, 0)
), 0) AS totalMinutes
FROM BomProcess bp
WHERE bp.deleted = false
AND bp.bom.id IN :bomIds
GROUP BY bp.bom.id
"""
)
fun sumStandardMinutesByBomIds(@Param("bomIds") bomIds: Collection<Long>): List<BomDurationAggregate>
}

+ 13
- 0
src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt 查看文件

@@ -212,6 +212,19 @@ open class BomService(
return response
}

/** Update free-text craft details (工藝明細) on BOM. */
@Transactional
open fun updateBomDetails(id: Long, details: String?): Map<String, Any?> {
val bom = bomRepository.findByIdAndDeletedIsFalse(id)
?: throw NotFoundException()
bom.details = details
bomRepository.saveAndFlush(bom)
return mapOf(
"id" to bom.id,
"details" to bom.details,
)
}

/**
* Edit BOM (basic fields + BOM materials + BOM process lines).
*


+ 6
- 4
src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt 查看文件

@@ -1564,7 +1564,8 @@ open class ProductionScheduleService(
val matSheet = workbook.createSheet(UiText.t("物料用量總結", "Material usage summary"))

val matHeaders = listOf(
UiText.t("物料編號", "Material code"), UiText.t("物料名稱", "Material name"), UiText.t("需求量", "Required qty"), UiText.t("總用量", "Total usage"),
UiText.t("物料編號", "Material code"), UiText.t("物料名稱", "Material name"),
UiText.t("需採購/生產數量", "Qty to purchase/produce"), UiText.t("總用量", "Total usage"),
UiText.t("單位", "UOM"), UiText.t("數量(採購單位)", "Qty (purchase UOM)"), UiText.t("採購單位", "Purchase UOM"),
UiText.t("已採購數量", "Purchased qty"), UiText.t("庫存數", "On hand"), UiText.t("庫存單位", "Stock UOM"),
UiText.t("相關成品編號", "Related finished-good code"), UiText.t("相關成品名稱", "Related finished-good name")
@@ -1586,12 +1587,13 @@ open class ProductionScheduleService(
val purchaseQtyNeed = asDouble(rowData["purchaseQtyNeed"])
val purchased = asDouble(rowData["purchasedQty"])
val onHand = asDouble(rowData["onHandQty"])
val requiredQty = (totalNeed - purchased - onHand).coerceAtLeast(0.0)
// 總用量 − 庫存;不扣在途採購。負數代表有餘量。
val qtyToPurchaseOrProduce = totalNeed - onHand

val values = listOf<Any>(
rowData["matCode"]?.toString() ?: "",
rowData["matName"]?.toString() ?: "",
requiredQty,
qtyToPurchaseOrProduce,
totalNeed,
rowData["uomName"]?.toString() ?: "",
purchaseQtyNeed,
@@ -1628,7 +1630,7 @@ open class ProductionScheduleService(
// Column widths for material summary
matSheet.setColumnWidth(0, 20 * 256)
matSheet.setColumnWidth(1, 50 * 256) // 物料名稱
matSheet.setColumnWidth(2, 16 * 256)
matSheet.setColumnWidth(2, 22 * 256) // 需採購/生產數量
matSheet.setColumnWidth(3, 16 * 256)
matSheet.setColumnWidth(4, 12 * 256)
matSheet.setColumnWidth(5, 16 * 256)


+ 10
- 0
src/main/java/com/ffii/fpsms/modules/master/web/BomController.kt 查看文件

@@ -29,6 +29,7 @@ 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.EditBomRequest
import com.ffii.fpsms.modules.master.web.models.UpdateBomDetailsRequest
import com.ffii.fpsms.modules.master.web.models.BomExcelCheckProgress
import com.ffii.fpsms.modules.master.web.models.BomIdByItemCodeResponse
import com.ffii.fpsms.modules.master.web.models.MasterDataIssueResponse
@@ -169,4 +170,13 @@ fun getBomDetail(@PathVariable id: Long): BomDetailResponse {
): BomDetailResponse {
return bomService.editBom(id, request)
}

/** Update free-text craft details (工藝明細) only. */
@PutMapping("/{id}/details")
fun updateBomDetails(
@PathVariable id: Long,
@RequestBody request: UpdateBomDetailsRequest,
): Map<String, Any?> {
return bomService.updateBomDetails(id, request.details)
}
}

+ 2
- 0
src/main/java/com/ffii/fpsms/modules/productProcess/entity/projections/ProductProcessInfo.kt 查看文件

@@ -24,6 +24,8 @@ data class ProductProcessInfo(
val jobOrderStatus: String?,
val jobType: String?,
val bomDescription: String?,
/** Free-text craft details from bom.details */
val bomDetails: String? = null,
val isDark: String?,
val isDense: Int?,
val isFloat: String?,


+ 4
- 1
src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt 查看文件

@@ -674,6 +674,7 @@ open class ProductProcessService(
jobOrderStatus = jobOrder?.status?.value ?: "",
jobType = jobType?.name ?: "",
bomDescription = bom?.description ?: "",
bomDetails = bom?.details,
itemId = bom?.item?.id ?: 0,
itemCode = bom?.item?.code ?: "",
itemName = bom?.item?.name ?: "",
@@ -2602,7 +2603,9 @@ open class ProductProcessService(
processName = line.name,
equipmentName = equipmentName,
equipmentDetailName = equipmentDetailName,
handlerName = line.handler?.name,
// Scan staff writes operator; startLine writes handler. Prefer handler, fall back to operator.
handlerName = line.handler?.name?.takeIf { it.isNotBlank() }
?: line.operator?.name,
startTime = line.startTime,
endTime = line.endTime,
processingTime = line.processingTime,


Loading…
取消
儲存