package queries import ( "bssapp-backend/db" "bssapp-backend/models" "context" "database/sql" "fmt" "strings" ) // Ana tabloyu getiren fonksiyon (Vue header tablosu için) func GetStatements(ctx context.Context, params models.StatementParams) ([]models.StatementHeader, error) { // AccountCode normalize: "ZLA0127" → "ZLA 0127" params.AccountCode = normalizeMasterAccountCode(params.AccountCode) if strings.TrimSpace(params.LangCode) == "" { params.LangCode = "TR" } // Parislemler []string → '1','2','3' parislemFilter := "''" if len(params.Parislemler) > 0 { quoted := make([]string, 0, len(params.Parislemler)) for _, v := range params.Parislemler { v = strings.TrimSpace(v) if v == "" { continue } quoted = append(quoted, fmt.Sprintf("'%s'", strings.ReplaceAll(v, "'", "''"))) } if len(quoted) > 0 { parislemFilter = strings.Join(quoted, ",") } } piyasaScope, err := buildPiyasaExistsForCariCode(ctx, "b.CurrAccCode") if err != nil { return nil, err } query := fmt.Sprintf(` ;WITH CurrDesc AS ( SELECT CurrAccCode, MAX(CurrAccDescription) AS CurrAccDescription FROM cdCurrAccDesc WHERE LangCode = @LangCode GROUP BY CurrAccCode ), /* ========================================================= ✅ Bu aralıkta hareket var mı? Varsa : Devir = startdate öncesi Yoksa : Devir = enddate dahil (enddate itibariyle bakiye) ========================================================= */ HasMovement AS ( SELECT CASE WHEN EXISTS ( SELECT 1 FROM trCurrAccBook b INNER JOIN CurrAccBookATAttributesFilter f ON f.CurrAccBookID = b.CurrAccBookID AND f.ATAtt01 IN (%s) WHERE LEFT(REPLACE(b.CurrAccCode, ' ', ''), 7) = REPLACE(@Carikod, ' ', '') AND b.DocumentDate BETWEEN @startdate AND @enddate AND %s ) THEN 1 ELSE 0 END AS HasMov ), /* ========================================================= ✅ Opening (Devir) — TEK CARİ KOD ALTINDA KONSOLİDE Cari_Kod = @Carikod (sabit) ========================================================= */ Opening AS ( SELECT @Carikod AS Cari_Kod, b.DocCurrencyCode AS Para_Birimi, SUM(ISNULL(c.Debit,0) - ISNULL(c.Credit,0)) AS Devir_Bakiyesi FROM trCurrAccBook b CROSS JOIN HasMovement hm INNER JOIN CurrAccBookATAttributesFilter f2 ON f2.CurrAccBookID = b.CurrAccBookID AND f2.ATAtt01 IN (%s) LEFT JOIN trCurrAccBookCurrency c ON c.CurrAccBookID = b.CurrAccBookID AND c.CurrencyCode = b.DocCurrencyCode WHERE LEFT(REPLACE(b.CurrAccCode, ' ', ''), 7) = REPLACE(@Carikod, ' ', '') AND %s AND ( (hm.HasMov = 1 AND b.DocumentDate < @startdate) -- hareket varsa: klasik devir OR (hm.HasMov = 0 AND b.DocumentDate <= @enddate) -- hareket yoksa: enddate itibariyle bakiye ) GROUP BY b.DocCurrencyCode ), /* ========================================================= ✅ Hareketler (Movements) — TEK CARİ KOD ALTINDA KONSOLİDE Cari_Kod = @Carikod (sabit) Running sadece aralıktaki hareketlerden gelir. ========================================================= */ Movements AS ( SELECT @Carikod AS Ana_Cari_Kod, b.CurrAccCode AS Cari_Kod, COALESCE( (SELECT TOP 1 cd.CurrAccDescription FROM CurrDesc cd WHERE REPLACE(cd.CurrAccCode, ' ', '') = REPLACE(b.CurrAccCode, ' ', '')), (SELECT TOP 1 cd.CurrAccDescription FROM CurrDesc cd WHERE LEFT(REPLACE(cd.CurrAccCode, ' ', ''), 7) = REPLACE(@Carikod, ' ', '') ORDER BY cd.CurrAccCode) ) AS Cari_Isim, CONVERT(varchar(10), b.DocumentDate, 23) AS Belge_Tarihi, CONVERT(varchar(10), b.DueDate, 23) AS Vade_Tarihi, b.RefNumber AS Belge_No, b.BaseApplicationCode AS Islem_Tipi, b.LineDescription AS Aciklama, b.DocCurrencyCode AS Para_Birimi, ISNULL(c.Debit,0) AS Borc, ISNULL(c.Credit,0) AS Alacak, SUM(ISNULL(c.Debit,0) - ISNULL(c.Credit,0)) OVER ( PARTITION BY b.DocCurrencyCode ORDER BY b.DocumentDate, b.CurrAccBookID ) AS Hareket_Bakiyesi, f.ATAtt01 AS Parislemtipi FROM trCurrAccBook b INNER JOIN CurrAccBookATAttributesFilter f ON f.CurrAccBookID = b.CurrAccBookID AND f.ATAtt01 IN (%s) LEFT JOIN trCurrAccBookCurrency c ON c.CurrAccBookID = b.CurrAccBookID AND c.CurrencyCode = b.DocCurrencyCode WHERE LEFT(REPLACE(b.CurrAccCode, ' ', ''), 7) = REPLACE(@Carikod, ' ', '') AND %s AND b.DocumentDate BETWEEN @startdate AND @enddate ) SELECT m.Cari_Kod, m.Cari_Isim, m.Belge_Tarihi, m.Vade_Tarihi, m.Belge_No, m.Islem_Tipi, m.Aciklama, m.Para_Birimi, m.Borc, m.Alacak, /* ✅ Bakiye = Devir + Aralıktaki Running */ ISNULL(o.Devir_Bakiyesi,0) + m.Hareket_Bakiyesi AS Bakiye, m.Parislemtipi AS Parislemler FROM Movements m LEFT JOIN Opening o ON o.Cari_Kod = m.Ana_Cari_Kod AND o.Para_Birimi = m.Para_Birimi UNION ALL /* ========================================================= ✅ Devir Satırı (kur bazında) — Opening'den gelir Hareket varsa: startdate öncesi Hareket yoksa: enddate itibariyle bakiye ========================================================= */ SELECT o.Cari_Kod, COALESCE( (SELECT TOP 1 cd.CurrAccDescription FROM CurrDesc cd WHERE REPLACE(cd.CurrAccCode, ' ', '') = REPLACE(o.Cari_Kod, ' ', '')), (SELECT TOP 1 cd.CurrAccDescription FROM CurrDesc cd WHERE LEFT(REPLACE(cd.CurrAccCode, ' ', ''), 7) = REPLACE(@Carikod, ' ', '') ORDER BY cd.CurrAccCode) ) AS Cari_Isim, CONVERT(varchar(10), @startdate, 23) AS Belge_Tarihi, CONVERT(varchar(10), @startdate, 23) AS Vade_Tarihi, 'Baslangic_devir' AS Belge_No, 'Devir' AS Islem_Tipi, 'Devir Bakiyesi' AS Aciklama, o.Para_Birimi, CASE WHEN o.Devir_Bakiyesi >= 0 THEN o.Devir_Bakiyesi ELSE 0 END AS Borc, CASE WHEN o.Devir_Bakiyesi < 0 THEN ABS(o.Devir_Bakiyesi) ELSE 0 END AS Alacak, o.Devir_Bakiyesi AS Bakiye, CAST(NULL AS varchar(32)) AS Parislemler FROM Opening o ORDER BY Para_Birimi, Belge_Tarihi; `, parislemFilter, // HasMovement ATAtt01 piyasaScope, // HasMovement piyasa scope parislemFilter, // Opening ATAtt01 piyasaScope, // Opening piyasa scope parislemFilter, // Movements ATAtt01 piyasaScope, // Movements piyasa scope ) rows, err := db.MssqlDB.QueryContext(ctx, query, sql.Named("startdate", params.StartDate), sql.Named("enddate", params.EndDate), sql.Named("Carikod", params.AccountCode), sql.Named("LangCode", params.LangCode), ) if err != nil { return nil, fmt.Errorf("MSSQL query error: %v", err) } defer rows.Close() var results []models.StatementHeader for rows.Next() { var r models.StatementHeader if err := rows.Scan( &r.CariKod, &r.CariIsim, &r.BelgeTarihi, &r.VadeTarihi, &r.BelgeNo, &r.IslemTipi, &r.Aciklama, &r.ParaBirimi, &r.Borc, &r.Alacak, &r.Bakiye, &r.Parislemler, ); err != nil { return nil, err } results = append(results, r) } return results, nil } func normalizeMasterAccountCode(code string) string { code = strings.ToUpper(strings.TrimSpace(code)) if code == "" { return code } noSpace := strings.ReplaceAll(code, " ", "") if len(noSpace) < 7 { return code } main := noSpace[:7] return main[:3] + " " + main[3:] }