diff --git a/svc/routes/production_product_costing.go b/svc/routes/production_product_costing.go index 1626b87..349d682 100644 --- a/svc/routes/production_product_costing.go +++ b/svc/routes/production_product_costing.go @@ -1430,7 +1430,9 @@ func postProductionProductCostingOnMLSaveHandler(w http.ResponseWriter, r *http. // The top header in the detail screen only includes rows marked // "Maliyete Dahil". Persist that exact business total to the master // record as well; excluded detail rows must not inflate the cost. - if r.MaliyeteDahil != 1 { + groupName := strings.ToUpper(strings.TrimSpace(r.SAciklama3)) + isCMLaborGroup := strings.Contains(groupName, "CM1") || strings.Contains(groupName, "CM2") + if r.MaliyeteDahil != 1 && !isCMLaborGroup { continue } qty := r.LMiktar diff --git a/ui/src/pages/ProductionProductCostingHasCostDetail.vue b/ui/src/pages/ProductionProductCostingHasCostDetail.vue index 957db70..9a94b23 100644 --- a/ui/src/pages/ProductionProductCostingHasCostDetail.vue +++ b/ui/src/pages/ProductionProductCostingHasCostDetail.vue @@ -1557,8 +1557,15 @@ function ensureBeforeUnloadGuard (enabled) { return '' } } +function shouldIncludeRowInCostingTotal (row) { + // CM1/CM2 are labor-cost groups and always contribute to costing. For all + // other groups the explicit "Maliyete Dahil" selection is authoritative. + return isCMGroupName(row?.sAciklama3) || + normalizeBooleanFlag(row?.maliyeteDahil ?? row?.maliyete_dahil ?? row?.Maliyete_dahil) +} + const toolbarSummary = computed(() => flatDetailRows.value.reduce((acc, row) => { - if (!normalizeBooleanFlag(row?.maliyeteDahil ?? row?.maliyete_dahil ?? row?.Maliyete_dahil)) return acc + if (!shouldIncludeRowInCostingTotal(row)) return acc acc.tryTotal += resolveRowTRYTutar(row) acc.usdTotal += resolveRowUSDTutar(row) acc.eurTotal += resolveRowEURTutar(row) @@ -2849,9 +2856,7 @@ function resolveRowUSDTutar (row) { } function shouldIncludeRowInGroupTotal (_grp, row) { - // Keep sub-header totals aligned with the top header and the value persisted - // to spUrtOnMLMas: only rows explicitly included in costing contribute. - return normalizeBooleanFlag(row?.maliyeteDahil ?? row?.maliyete_dahil ?? row?.Maliyete_dahil) + return shouldIncludeRowInCostingTotal(row) } function resolveGroupTRYTutar (grp) { @@ -3926,10 +3931,14 @@ async function ensureNoCostRequiredRowsFromMappings (mappings) { const groupName = normalizeGroupName(meta?.groupName || '') const hammaddeAdi = String(meta?.hammaddeAdi || '').trim() const effectiveGroupName = groupName || 'TANIMSIZ' - const mtBolumID = (meta?.mtBolumID > 0 ? meta.mtBolumID : mappingMtBolumID) || 0 - const metaParcaCandidate = normalizeGroupName((meta?.parcaAdi || '').trim()) - // Defensive: if backend/lookup accidentally returns group label (DT/TP/...) as part name, ignore it. - const desiredParcaAdi = normalizeGroupName((metaParcaCandidate && !isKnownGroupName(metaParcaCandidate)) ? metaParcaCandidate : mappingParcaAdi) + // The product-type mapping is authoritative for the required part. A raw + // material type can be required for multiple parts, while its master + // record only carries one default MT section. Using that default here + // incorrectly made an existing recipe row satisfy every mapped part. + const mtBolumID = mappingMtBolumID > 0 ? mappingMtBolumID : (meta?.mtBolumID || 0) + const desiredParcaAdi = mappingMtBolumID > 0 + ? mappingParcaAdi + : normalizeGroupName(meta?.parcaAdi || mappingParcaAdi) const anyMatch = flatDetailRows.value.find(r => { if (normalizeHammaddeNo(r?.nHammaddeTuruNo) !== hNo) return false