Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-05-15 14:33:35 +03:00
parent 562d397480
commit dacd3aefa9
14 changed files with 2409 additions and 17 deletions

View File

@@ -7,9 +7,269 @@ import (
"fmt"
"strconv"
"strings"
"time"
"unicode"
)
func GetNextOnMLNoFrom100k(ctx context.Context, uretimDB *sql.DB) (int, error) {
// nOnMLNo is NOT identity. Generate next number safely.
// Requirement: always continue from MAX, but never below 100001.
sqlText := `
SELECT
ISNULL(MAX(M.nOnMLNo), 100000) + 1 AS NextNo
FROM dbo.spUrtOnMLMas M WITH (UPDLOCK, HOLDLOCK)
`
var next int
if err := uretimDB.QueryRowContext(ctx, sqlText).Scan(&next); err != nil {
return 0, err
}
if next < 100001 {
next = 100001
}
return next, nil
}
func GetOnMLMamulTuruNoByAciklama(ctx context.Context, tx *sql.Tx, sAciklama string) (int, error) {
sAciklama = strings.TrimSpace(sAciklama)
if sAciklama == "" {
return 1, nil
}
var existing int
err := tx.QueryRowContext(ctx, `
SELECT TOP 1 ISNULL(nMamulTuruNo, 0)
FROM dbo.spUrtOnMLMamulTuru WITH (UPDLOCK, HOLDLOCK)
WHERE LTRIM(RTRIM(ISNULL(sAciklama,''))) = @p1
ORDER BY nMamulTuruNo
`, sAciklama).Scan(&existing)
if err == sql.ErrNoRows {
return 0, nil
}
if err != nil {
return 0, err
}
return existing, nil
}
func LookupFirmaIDByKodu(ctx context.Context, uretimDB *sql.DB, firmaKodu string) (int, error) {
firmaKodu = strings.TrimSpace(firmaKodu)
if firmaKodu == "" {
return 0, nil
}
sqlText := `
SELECT TOP 1 ISNULL(F.nFirmaID, 0) AS nFirmaID
FROM dbo.tbFirma F WITH (NOLOCK)
WHERE LTRIM(RTRIM(ISNULL(F.sKodu, ''))) = @p1
ORDER BY F.nFirmaID
`
var id int
if err := uretimDB.QueryRowContext(ctx, sqlText, firmaKodu).Scan(&id); err != nil {
if err == sql.ErrNoRows {
return 0, nil
}
return 0, err
}
return id, nil
}
type OnMLHeaderUpsertArgs struct {
NOnMLNo int
UrunKodu string
UrunAdi string
Tarihi time.Time
NMamulTuruNo int
NUrtReceteID sql.NullInt64
UretimSekliID sql.NullInt64
SAciklama sql.NullString
NFirmaID int
SUser string
LTutarTL float64
LTutarUSD float64
LTutarEURO float64
SDovizCinsi string
LTutarDoviz float64
}
func UpsertOnMLHeader(tx *sql.Tx, ctx context.Context, args OnMLHeaderUpsertArgs) error {
// Note: Many NOT NULL columns exist. We use dummy/system values as per business rules.
sqlText := `
IF EXISTS (SELECT 1 FROM dbo.spUrtOnMLMas WITH (UPDLOCK, HOLDLOCK) WHERE nOnMLNo = @p1)
BEGIN
UPDATE dbo.spUrtOnMLMas
SET
UrunKodu = @p2,
UrunAdi = @p3,
Tarihi = @p4,
nDonemNo = 1,
nMamulTuruNo = @p16,
nBolgeNo = 2,
nZorlukNo = 1,
bDurum = 1,
lTutarTL = @p5,
lTutarUSD = @p6,
lTutarEURO = @p7,
sDovizCinsi = @p8,
lTutarDoviz = @p9,
bSablon = 0,
dteGuncellemeTarihi = GETDATE(),
sGuncellemeKullaniciAdi = @p10,
nUrtReceteID = @p11,
sAciklama = @p12,
lMasMiktar = 0,
sRenk = NULLIF(LTRIM(RTRIM(@p13)), ''),
nFirmaID = @p14,
uretim_sekli_id = @p15
WHERE nOnMLNo = @p1
END
ELSE
BEGIN
INSERT INTO dbo.spUrtOnMLMas (
nOnMLNo,
UrunKodu,
UrunAdi,
Tarihi,
nDonemNo,
nMamulTuruNo,
nBolgeNo,
nZorlukNo,
dteKayitTarihi,
sKullaniciAdi,
bDurum,
lTutarTL,
lTutarUSD,
lTutarEURO,
sDovizCinsi,
lTutarDoviz,
bSablon,
dteGuncellemeTarihi,
sGuncellemeKullaniciAdi,
nUrtReceteID,
sAciklama,
lMasMiktar,
nFirmaID,
bVarsayilan,
bOnay,
bIptal,
bRParcaTakip,
uretim_sekli_id
)
VALUES (
@p1,
@p2,
@p3,
@p4,
1,
@p16,
2,
1,
GETDATE(),
@p10,
1,
@p5,
@p6,
@p7,
@p8,
@p9,
0,
GETDATE(),
@p10,
@p11,
@p12,
0,
@p14,
0,
0,
0,
0,
@p15
)
END
`
_, err := tx.ExecContext(
ctx,
sqlText,
args.NOnMLNo,
strings.TrimSpace(args.UrunKodu),
strings.TrimSpace(args.UrunAdi),
args.Tarihi,
args.LTutarTL,
args.LTutarUSD,
args.LTutarEURO,
strings.TrimSpace(args.SDovizCinsi),
args.LTutarDoviz,
strings.TrimSpace(args.SUser),
args.NUrtReceteID,
args.SAciklama,
"", // sRenk (header) currently not driven from UI
args.NFirmaID,
args.UretimSekliID,
args.NMamulTuruNo,
)
return err
}
// ============================================================
// V3 (Nebim) base price update
// ============================================================
func UpsertV3ItemBasePriceUSD(
ctx context.Context,
mssqlDB *sql.DB,
itemCode string,
priceDate string, // YYYY-MM-DD
priceUSD float64,
user string,
) error {
itemCode = strings.TrimSpace(itemCode)
priceDate = strings.TrimSpace(priceDate)
user = strings.TrimSpace(user)
if mssqlDB == nil || itemCode == "" || priceDate == "" {
return fmt.Errorf("missing params for base price upsert")
}
// NOTE: In this DB, PRIMARY KEY is on:
// (ItemTypeCode, ItemCode, CountryCode, SeasonCode, BasePriceCode)
// so we cannot insert multiple rows for different dates under the same base price.
// We update the single row's PriceDate/Price to reflect latest costing.
sqlText := `
MERGE dbo.prItemBasePrice AS T
USING (
SELECT
@p1 AS ItemTypeCode,
@p2 AS ItemCode,
'TR' AS CountryCode,
'' AS SeasonCode,
1 AS BasePriceCode,
CONVERT(date, @p3, 23) AS PriceDate,
'USD' AS CurrencyCode
) AS S
ON T.ItemTypeCode = S.ItemTypeCode
AND LTRIM(RTRIM(T.ItemCode)) = LTRIM(RTRIM(S.ItemCode))
AND ISNULL(T.CountryCode,'') = S.CountryCode
AND ISNULL(T.SeasonCode,'') = S.SeasonCode
AND ISNULL(T.BasePriceCode,0) = S.BasePriceCode
WHEN MATCHED THEN
UPDATE SET
PriceDate = S.PriceDate,
CurrencyCode = S.CurrencyCode,
Price = @p4,
LastUpdatedUserName = @p5,
LastUpdatedDate = GETDATE()
WHEN NOT MATCHED THEN
INSERT (
ItemTypeCode, ItemCode, CountryCode, SeasonCode, BasePriceCode,
PriceDate, CurrencyCode, Price, CreatedUserName, CreatedDate, LastUpdatedUserName, LastUpdatedDate
)
VALUES (
S.ItemTypeCode, S.ItemCode, S.CountryCode, S.SeasonCode, S.BasePriceCode,
S.PriceDate, S.CurrencyCode, @p4, @p5, GETDATE(), @p5, GETDATE()
);
`
_, err := mssqlDB.ExecContext(ctx, sqlText, 1, itemCode, priceDate, priceUSD, user)
return err
}
func GetProductAnaAltGrupByUrunKodu(ctx context.Context, mssqlDB *sql.DB, urunKodu string) (urunAnaGrubu string, urunAltGrubu string, err error) {
urunKodu = strings.TrimSpace(urunKodu)
if mssqlDB == nil || urunKodu == "" {
@@ -449,7 +709,7 @@ SELECT
END AS UretimSekli,
RTRIM(CONVERT(VARCHAR(32), ISNULL(SonIsEmri.nUrtSiparisNo, 0))) AS nUrtSiparisNo,
CONVERT(VARCHAR(10), SonIsEmri.dteIslemTarihi, 23) AS dteIslemTarihi,
ISNULL(CONVERT(VARCHAR(10), SonIsEmri.dteIslemTarihi, 23), '') AS dteIslemTarihi,
ISNULL(SonIsEmri.FirmaKodu, '') AS FirmaKodu,
ISNULL(SonIsEmri.FirmaAdi, '') AS FirmaAdi,
ISNULL(SonIsEmri.sVeren, '') AS SonIsEmriVeren,
@@ -679,6 +939,8 @@ func GetProductionHasCostDetailHeaderByOnMLNo(
SELECT TOP 1
ISNULL(UF.UretimiYapanFirma, '') AS UretimiYapanFirma,
ISNULL(UF.SonIsEmriVeren, '') AS SonIsEmriVeren,
ISNULL(UF.FirmaKodu, '') AS FirmaKodu,
ISNULL(UF.nFirmaID, 0) AS nFirmaID,
RTRIM(CONVERT(VARCHAR(32), ISNULL(M.nOnMLNo, 0))) AS nOnMLNo,
LTRIM(RTRIM(ISNULL(M.UrunKodu, ''))) AS UrunKodu,
ISNULL(M.UrunAdi, '') AS UrunAdi,
@@ -701,6 +963,8 @@ LEFT JOIN dbo.mk_uretim_sekli US
OUTER APPLY (
SELECT TOP 1
ISNULL(F.sAciklama, '') AS UretimiYapanFirma,
ISNULL(F.sKodu, '') AS FirmaKodu,
ISNULL(F.nFirmaID, 0) AS nFirmaID,
ISNULL(SM.sVeren, '') AS SonIsEmriVeren
FROM dbo.spUrtSiparisDet SD
INNER JOIN dbo.spUrtSiparis SM
@@ -757,6 +1021,8 @@ WITH RecipeMatch AS (
SELECT TOP 1
ISNULL(SonIsEmri.FirmaAdi, '') AS UretimiYapanFirma,
ISNULL(SonIsEmri.SonIsEmriVeren, '') AS SonIsEmriVeren,
ISNULL(SonIsEmri.FirmaKodu, '') AS FirmaKodu,
ISNULL(SonIsEmri.nFirmaID, 0) AS nFirmaID,
'' AS nOnMLNo,
RM.UrunKodu,
RM.UrunAdi,
@@ -777,6 +1043,8 @@ FROM RecipeMatch RM
OUTER APPLY (
SELECT TOP 1
ISNULL(F.sAciklama, '') AS FirmaAdi,
ISNULL(F.sKodu, '') AS FirmaKodu,
ISNULL(F.nFirmaID, 0) AS nFirmaID,
ISNULL(SM.sVeren, '') AS SonIsEmriVeren,
CONVERT(VARCHAR(16), SD.dteIslemTarihi, 120) AS dteIslemTarihi
FROM dbo.spUrtSiparisDet SD
@@ -960,6 +1228,221 @@ ORDER BY
return uretimDB.QueryContext(ctx, sqlText, search, limit, searchLike)
}
// ============================================================
// Default quantities (URETIM): mk_MaliyetParcaEslestirme_vmiktarlar
// ============================================================
func ListProductionProductCostingDefaultQtyRows(ctx context.Context, uretimDB *sql.DB, search string, limit int) (*sql.Rows, error) {
search = strings.TrimSpace(search)
if limit <= 0 {
limit = 500
}
if limit > 5000 {
limit = 5000
}
searchLike := "%" + search + "%"
sqlText := `
SELECT TOP (@p3)
ISNULL(V.nHammaddeTuruNo, 0) AS nHammaddeTuruNo,
ISNULL(H.sAciklama, '') AS sAciklama,
ISNULL(V.lDefaultMiktar, 0) AS lDefaultMiktar,
CONVERT(VARCHAR(16), V.dteCalcTarihi, 120) AS dteCalcTarihi,
CAST(CASE WHEN ISNULL(V.bAktif, 0) = 1 THEN 1 ELSE 0 END AS bit) AS bAktif
FROM dbo.mk_MaliyetParcaEslestirme_vmiktarlar V WITH (NOLOCK)
LEFT JOIN dbo.spUrtOnMLHammaddeTuru H WITH (NOLOCK)
ON H.nHammaddeTuruNo = V.nHammaddeTuruNo
WHERE
(@p1 = '' OR CONVERT(VARCHAR(32), ISNULL(V.nHammaddeTuruNo, 0)) LIKE @p2)
AND ISNULL(H.bAktif, 0) = 1
ORDER BY
V.nHammaddeTuruNo ASC
`
return uretimDB.QueryContext(ctx, sqlText, search, searchLike, limit)
}
func UpsertProductionProductCostingDefaultQtyRow(ctx context.Context, uretimDB *sql.DB, nHammaddeTuruNo int, lDefaultMiktar float64, bAktif *bool) error {
// NOTE: Legacy helper kept for backward-compat but now behaves as UPDATE-only.
activeVal := -1
if bAktif != nil {
if *bAktif {
activeVal = 1
} else {
activeVal = 0
}
}
sqlText := `
UPDATE dbo.mk_MaliyetParcaEslestirme_vmiktarlar
SET
lDefaultMiktar = @p2,
dteCalcTarihi = GETDATE(),
bAktif = CASE WHEN @p3 < 0 THEN ISNULL(bAktif, 1) ELSE @p3 END
WHERE nHammaddeTuruNo = @p1;
SELECT @@ROWCOUNT;
`
var affected int
if err := uretimDB.QueryRowContext(ctx, sqlText, nHammaddeTuruNo, lDefaultMiktar, activeVal).Scan(&affected); err != nil {
return err
}
if affected == 0 {
return fmt.Errorf("default qty row not found: nHammaddeTuruNo=%d", nHammaddeTuruNo)
}
return nil
}
func RefreshProductionProductCostingDefaultQty(ctx context.Context, uretimDB *sql.DB, topN int) error {
if topN <= 0 {
topN = 10
}
if topN > 50 {
topN = 50
}
sqlText := `
;WITH ranked AS (
SELECT
D.nHammaddeTuruNo,
D.lMiktar,
ROW_NUMBER() OVER (
PARTITION BY D.nHammaddeTuruNo
ORDER BY
ISNULL(M.Tarihi, '19000101') DESC,
ISNULL(M.dteGuncellemeTarihi, '19000101') DESC,
D.nOnMLNo DESC,
D.dteIslemTarihi DESC,
D.nOnMLDetNo DESC
) AS rn
FROM dbo.spUrtOnMLMasDet D WITH (NOLOCK)
INNER JOIN dbo.spUrtOnMLMas M WITH (NOLOCK)
ON M.nOnMLNo = D.nOnMLNo
WHERE ISNULL(D.lMiktar, 0) > 0
AND ISNULL(M.bIptal, 0) = 0
),
agg AS (
SELECT
nHammaddeTuruNo,
CAST(AVG(CAST(lMiktar AS float)) AS numeric(14,4)) AS lDefaultMiktar
FROM ranked
WHERE rn <= @p1
GROUP BY nHammaddeTuruNo
)
MERGE dbo.mk_MaliyetParcaEslestirme_vmiktarlar AS T
USING agg AS S
ON T.nHammaddeTuruNo = S.nHammaddeTuruNo
WHEN MATCHED THEN
UPDATE SET
T.lDefaultMiktar = S.lDefaultMiktar,
T.dteCalcTarihi = GETDATE(),
T.bAktif = 1
WHEN NOT MATCHED THEN
INSERT (nHammaddeTuruNo, lDefaultMiktar, dteCalcTarihi, bAktif)
VALUES (S.nHammaddeTuruNo, S.lDefaultMiktar, GETDATE(), 1);
`
_, err := uretimDB.ExecContext(ctx, sqlText, topN)
return err
}
func CalcProductionProductCostingDefaultQtyFromLastOnML(ctx context.Context, uretimDB *sql.DB, nHammaddeTuruNo int, topN int) (float64, int, error) {
if nHammaddeTuruNo <= 0 {
return 0, 0, fmt.Errorf("nHammaddeTuruNo required")
}
if topN <= 0 {
topN = 10
}
if topN > 50 {
topN = 50
}
sqlText := `
;WITH ranked AS (
SELECT TOP (@p2)
ISNULL(D.lMiktar, 0) AS lMiktar
FROM dbo.spUrtOnMLMasDet D WITH (NOLOCK)
INNER JOIN dbo.spUrtOnMLMas M WITH (NOLOCK)
ON M.nOnMLNo = D.nOnMLNo
WHERE D.nHammaddeTuruNo = @p1
AND ISNULL(D.lMiktar, 0) > 0
ORDER BY ISNULL(M.Tarihi, M.dteKayitTarihi) DESC, D.nOnMLNo DESC, D.nOnMLDetNo DESC
)
SELECT
CAST(ISNULL(AVG(CAST(lMiktar AS DECIMAL(18,4))), 0) AS FLOAT) AS avgQty,
COUNT(1) AS sampleCount
FROM ranked;
`
var avg float64
var cnt int
if err := uretimDB.QueryRowContext(ctx, sqlText, nHammaddeTuruNo, topN).Scan(&avg, &cnt); err != nil {
return 0, 0, err
}
return avg, cnt, nil
}
func LookupProductionProductCostingDefaultQtyByNos(ctx context.Context, uretimDB *sql.DB, nos []int) (map[int]float64, error) {
clean := make([]int, 0, len(nos))
seen := make(map[int]struct{}, len(nos))
for _, n := range nos {
if n <= 0 {
continue
}
if _, ok := seen[n]; ok {
continue
}
seen[n] = struct{}{}
clean = append(clean, n)
}
if len(clean) == 0 {
return map[int]float64{}, nil
}
if len(clean) > 1000 {
return nil, fmt.Errorf("too many hammadde nos")
}
valueRows := make([]string, 0, len(clean))
args := make([]any, 0, len(clean))
for i, n := range clean {
valueRows = append(valueRows, "(@p"+strconv.Itoa(i+1)+")")
args = append(args, n)
}
sqlText := `
WITH req(nHammaddeTuruNo) AS (
SELECT DISTINCT v.nHammaddeTuruNo
FROM (VALUES ` + strings.Join(valueRows, ",") + `) v(nHammaddeTuruNo)
)
SELECT
ISNULL(V.nHammaddeTuruNo, 0) AS nHammaddeTuruNo,
CAST(ISNULL(V.lDefaultMiktar, 0) AS FLOAT) AS lDefaultMiktar
FROM req R
INNER JOIN dbo.mk_MaliyetParcaEslestirme_vmiktarlar V WITH (NOLOCK)
ON V.nHammaddeTuruNo = R.nHammaddeTuruNo
INNER JOIN dbo.spUrtOnMLHammaddeTuru H WITH (NOLOCK)
ON H.nHammaddeTuruNo = V.nHammaddeTuruNo
WHERE ISNULL(H.bAktif, 0) = 1;
`
rows, err := uretimDB.QueryContext(ctx, sqlText, args...)
if err != nil {
return nil, err
}
defer rows.Close()
out := make(map[int]float64, len(clean))
for rows.Next() {
var no int
var qty float64
if err := rows.Scan(&no, &qty); err != nil {
return nil, err
}
if no > 0 && qty > 0 {
out[no] = qty
}
}
if err := rows.Err(); err != nil {
return nil, err
}
return out, nil
}
func buildSQLServerFullTextPrefixQuery(search string) string {
terms := strings.Fields(strings.TrimSpace(search))
parts := make([]string, 0, len(terms))