diff --git a/svc/routes/production_product_costing.go b/svc/routes/production_product_costing.go index 1128dbf..c850533 100644 --- a/svc/routes/production_product_costing.go +++ b/svc/routes/production_product_costing.go @@ -1963,9 +1963,13 @@ DELETE FROM dbo.spUrtOnMLMas WHERE nOnMLNo = @p1 return } - // V3: Delete base price ONLY if row was created by this app (CreatedUserName starts with BSSAPP). + // V3: Delete the base price row we created for this costing date (PriceDate = maliyetTarihi). + // We intentionally do NOT delete older base prices for the same item. deletedBasePrice := false if mssqlDB != nil && urunKodu != "" { + priceDate := maliyetTarihi.Format("2006-01-02") + // Primary rule: delete only the row for this exact date and USD currency. + // Safety: require that either CreatedUserName/LastUpdatedUserName matches current user, or one of them starts with BSSAPP. var createdBy sql.NullString var lastBy sql.NullString _ = mssqlDB.QueryRowContext(ctx, ` @@ -1978,11 +1982,23 @@ WHERE ItemTypeCode = 1 AND ISNULL(CountryCode,'') = 'TR' AND ISNULL(SeasonCode,'') = '' AND ISNULL(BasePriceCode,0) = 1 -`, urunKodu).Scan(&createdBy, &lastBy) + AND CONVERT(date, PriceDate) = CONVERT(date, @p2, 23) + AND LTRIM(RTRIM(ISNULL(CurrencyCode,''))) = 'USD' +`, urunKodu, priceDate).Scan(&createdBy, &lastBy) created := strings.ToUpper(strings.TrimSpace(createdBy.String)) last := strings.ToUpper(strings.TrimSpace(lastBy.String)) - if strings.HasPrefix(created, "BSSAPP") && strings.HasPrefix(last, "BSSAPP") { + u := strings.ToUpper(strings.TrimSpace(user)) + + allowed := false + if u != "" && (strings.ToUpper(strings.TrimSpace(createdBy.String)) == u || strings.ToUpper(strings.TrimSpace(lastBy.String)) == u) { + allowed = true + } + if strings.HasPrefix(created, "BSSAPP") || strings.HasPrefix(last, "BSSAPP") { + allowed = true + } + + if allowed { if _, err := mssqlDB.ExecContext(ctx, ` DELETE FROM dbo.prItemBasePrice WHERE ItemTypeCode = 1 @@ -1990,9 +2006,15 @@ WHERE ItemTypeCode = 1 AND ISNULL(CountryCode,'') = 'TR' AND ISNULL(SeasonCode,'') = '' AND ISNULL(BasePriceCode,0) = 1 -`, urunKodu); err == nil { + AND CONVERT(date, PriceDate) = CONVERT(date, @p2, 23) + AND LTRIM(RTRIM(ISNULL(CurrencyCode,''))) = 'USD' +`, urunKodu, priceDate); err == nil { deletedBasePrice = true + } else { + logger.Warn("v3 base price delete failed", "err", err, "urun_kodu", urunKodu, "price_date", priceDate) } + } else { + logger.Info("v3 base price delete skipped (not owned)", "urun_kodu", urunKodu, "price_date", priceDate, "created_by", createdBy.String, "last_by", lastBy.String, "user", user) } } diff --git a/ui/src/pages/ProductionProductCostingHasCostDetail.vue b/ui/src/pages/ProductionProductCostingHasCostDetail.vue index e7d1ff6..092367c 100644 --- a/ui/src/pages/ProductionProductCostingHasCostDetail.vue +++ b/ui/src/pages/ProductionProductCostingHasCostDetail.vue @@ -85,7 +85,6 @@ @click="saveChanges" /> { }) const canDeleteCosting = computed(() => { - // Only allow delete for records created from no-cost flow. - // UX expectation: delete button becomes available after a costing record exists. + // Allow delete only for records created by this app's no-cost flow (we generate >= 100000). + // This avoids accidental deletion of legacy OnML records. const n = parseInt(String(onMLNo.value || detailHeader.value?.nOnMLNo || detailHeader.value?.NOnMLNo || '0'), 10) || 0 - return n > 0 && String(detailSource.value || '').trim().toLowerCase() === 'no-cost' + return n >= 100000 }) function persistLocalDraftNow () {