1020 lines
25 KiB
Go
1020 lines
25 KiB
Go
package queries
|
||
|
||
import (
|
||
"bssapp-backend/auth"
|
||
"bssapp-backend/db"
|
||
"bssapp-backend/internal/authz"
|
||
"bssapp-backend/models"
|
||
"context"
|
||
"database/sql"
|
||
"fmt"
|
||
"log"
|
||
"sort"
|
||
"strconv"
|
||
"strings"
|
||
)
|
||
|
||
type mkCariBakiyeLine struct {
|
||
CurrAccTypeCode int
|
||
CariKodu string
|
||
CariDoviz string
|
||
SirketKodu int
|
||
PislemTipi string
|
||
ParasalIslemTipi string
|
||
YerelBakiye float64
|
||
Bakiye float64
|
||
VadeGun float64
|
||
VadeBelgeGun float64
|
||
}
|
||
|
||
type cariMeta struct {
|
||
CariDetay string
|
||
CariTip string
|
||
Kanal1 string
|
||
Piyasa string
|
||
Temsilci string
|
||
Ulke string
|
||
Il string
|
||
Ilce string
|
||
TC string
|
||
RiskDurumu string
|
||
MuhasebeKodu string
|
||
SirketDetay string
|
||
}
|
||
|
||
type masterCariMeta struct {
|
||
CariDetay string
|
||
Kanal1 string
|
||
Piyasa string
|
||
Temsilci string
|
||
Ulke string
|
||
Il string
|
||
Ilce string
|
||
RiskDurumu string
|
||
}
|
||
|
||
type balanceFilters struct {
|
||
cariIlkGrup map[string]struct{}
|
||
piyasa map[string]struct{}
|
||
temsilci map[string]struct{}
|
||
riskDurumu map[string]struct{}
|
||
islemTipi map[string]struct{}
|
||
ulke map[string]struct{}
|
||
il map[string]struct{}
|
||
ilce map[string]struct{}
|
||
}
|
||
|
||
func GetCustomerBalanceList(ctx context.Context, params models.CustomerBalanceListParams) ([]models.CustomerBalanceListRow, error) {
|
||
if strings.TrimSpace(params.SelectedDate) == "" {
|
||
return nil, fmt.Errorf("selected_date is required")
|
||
}
|
||
|
||
lines, err := loadBalanceLines(ctx, params.SelectedDate, params.CariSearch)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
metaMap, err := loadCariMetaMap(ctx, lines)
|
||
if err != nil {
|
||
log.Printf("customer_balance_list: cari meta query failed, fallback without meta: %v", err)
|
||
metaMap = map[string]cariMeta{}
|
||
}
|
||
|
||
masterMetaMap, err := loadMasterCariMetaMap(ctx, lines)
|
||
if err != nil {
|
||
log.Printf("customer_balance_list: master cari meta query failed, fallback without master meta: %v", err)
|
||
masterMetaMap = map[string]masterCariMeta{}
|
||
}
|
||
|
||
companyMap, err := loadCompanyMap(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
glMap, err := loadGLAccountMap(ctx, lines)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
rateMap, err := loadNearestTryRates(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
usdTry := rateMap["USD"]
|
||
if usdTry <= 0 {
|
||
usdTry = 1
|
||
}
|
||
|
||
filters := buildFilters(params)
|
||
agg := make(map[string]*models.CustomerBalanceListRow, len(lines))
|
||
|
||
for _, ln := range lines {
|
||
cari := strings.TrimSpace(ln.CariKodu)
|
||
if cari == "" {
|
||
continue
|
||
}
|
||
|
||
curr := strings.ToUpper(strings.TrimSpace(ln.CariDoviz))
|
||
if curr == "" {
|
||
curr = "TRY"
|
||
}
|
||
|
||
meta := metaMap[metaKey(ln.CurrAccTypeCode, cari)]
|
||
meta.MuhasebeKodu = glMap[glKey(ln.CurrAccTypeCode, cari, ln.SirketKodu)]
|
||
meta.SirketDetay = companyMap[ln.SirketKodu]
|
||
master := deriveMasterCari(cari)
|
||
mm := masterMetaMap[master]
|
||
|
||
if strings.TrimSpace(mm.Kanal1) != "" {
|
||
meta.Kanal1 = mm.Kanal1
|
||
}
|
||
if strings.TrimSpace(mm.Piyasa) != "" {
|
||
meta.Piyasa = mm.Piyasa
|
||
}
|
||
if strings.TrimSpace(mm.Temsilci) != "" {
|
||
meta.Temsilci = mm.Temsilci
|
||
}
|
||
if strings.TrimSpace(mm.Ulke) != "" {
|
||
meta.Ulke = mm.Ulke
|
||
}
|
||
if strings.TrimSpace(mm.Il) != "" {
|
||
meta.Il = mm.Il
|
||
}
|
||
if strings.TrimSpace(mm.Ilce) != "" {
|
||
meta.Ilce = mm.Ilce
|
||
}
|
||
if strings.TrimSpace(mm.RiskDurumu) != "" {
|
||
meta.RiskDurumu = mm.RiskDurumu
|
||
}
|
||
|
||
if !filters.matchLine(ln.PislemTipi, meta) {
|
||
continue
|
||
}
|
||
|
||
key := strconv.Itoa(ln.CurrAccTypeCode) + "|" + cari + "|" + curr + "|" + strconv.Itoa(ln.SirketKodu)
|
||
row, ok := agg[key]
|
||
if !ok {
|
||
row = &models.CustomerBalanceListRow{
|
||
CariIlkGrup: meta.Kanal1,
|
||
Piyasa: meta.Piyasa,
|
||
Temsilci: meta.Temsilci,
|
||
Sirket: strconv.Itoa(ln.SirketKodu),
|
||
AnaCariKodu: master,
|
||
AnaCariAdi: firstNonEmpty(mm.CariDetay, meta.CariDetay),
|
||
CariKodu: cari,
|
||
CariDetay: meta.CariDetay,
|
||
CariTip: meta.CariTip,
|
||
Kanal1: meta.Kanal1,
|
||
Ozellik03: meta.RiskDurumu,
|
||
Ozellik05: meta.Ulke,
|
||
Ozellik06: meta.Il,
|
||
Ozellik07: meta.Ilce,
|
||
Il: meta.Il,
|
||
Ilce: meta.Ilce,
|
||
MuhasebeKodu: meta.MuhasebeKodu,
|
||
TC: meta.TC,
|
||
RiskDurumu: meta.RiskDurumu,
|
||
SirketDetay: meta.SirketDetay,
|
||
CariDoviz: curr,
|
||
}
|
||
agg[key] = row
|
||
}
|
||
|
||
usd := toUSD(ln.Bakiye, curr, usdTry, rateMap)
|
||
add12, add13 := resolveBalanceBuckets(ln)
|
||
if add12 {
|
||
row.Bakiye12 += ln.Bakiye
|
||
row.TLBakiye12 += ln.YerelBakiye
|
||
row.USDBakiye12 += usd
|
||
}
|
||
if add13 {
|
||
row.Bakiye13 += ln.Bakiye
|
||
row.TLBakiye13 += ln.YerelBakiye
|
||
row.USDBakiye13 += usd
|
||
}
|
||
}
|
||
|
||
out := make([]models.CustomerBalanceListRow, 0, len(agg))
|
||
for _, v := range agg {
|
||
out = append(out, *v)
|
||
}
|
||
|
||
sort.Slice(out, func(i, j int) bool {
|
||
if out[i].AnaCariKodu == out[j].AnaCariKodu {
|
||
if out[i].CariKodu == out[j].CariKodu {
|
||
return out[i].CariDoviz < out[j].CariDoviz
|
||
}
|
||
return out[i].CariKodu < out[j].CariKodu
|
||
}
|
||
return out[i].AnaCariKodu < out[j].AnaCariKodu
|
||
})
|
||
|
||
return out, nil
|
||
}
|
||
|
||
func loadMasterCariMetaMap(ctx context.Context, lines []mkCariBakiyeLine) (map[string]masterCariMeta, error) {
|
||
masters := make(map[string]struct{})
|
||
for _, ln := range lines {
|
||
m := strings.TrimSpace(deriveMasterCari(ln.CariKodu))
|
||
if m != "" {
|
||
masters[m] = struct{}{}
|
||
}
|
||
}
|
||
if len(masters) == 0 {
|
||
return map[string]masterCariMeta{}, nil
|
||
}
|
||
|
||
query := fmt.Sprintf(`
|
||
WITH BaseCari AS
|
||
(
|
||
SELECT
|
||
CB.CurrAccCode,
|
||
CB.CurrAccTypeCode,
|
||
MasterCari = LEFT(CB.CurrAccCode, 8),
|
||
rn = ROW_NUMBER() OVER
|
||
(
|
||
PARTITION BY LEFT(CB.CurrAccCode, 8)
|
||
ORDER BY CB.CurrAccCode
|
||
)
|
||
FROM cdCurrAcc CB WITH (NOLOCK)
|
||
WHERE CB.CurrAccTypeCode IN (1,3)
|
||
AND LEFT(CB.CurrAccCode, 8) IN (%s)
|
||
),
|
||
FirstCari AS
|
||
(
|
||
SELECT *
|
||
FROM BaseCari
|
||
WHERE rn = 1
|
||
)
|
||
SELECT
|
||
CariKodu = F.MasterCari,
|
||
CariDetay = ISNULL(cd.CurrAccDescription, ''),
|
||
KANAL_1 = ISNULL(CASE WHEN F.CurrAccTypeCode=1 THEN VDesc.VendorAtt08Desc ELSE CDesc.CustomerAtt08Desc END, ''),
|
||
PIYASA = ISNULL(CASE WHEN F.CurrAccTypeCode=1 THEN VDesc.VendorAtt01Desc ELSE CDesc.CustomerAtt01Desc END, ''),
|
||
CARI_TEMSILCI = ISNULL(
|
||
CASE
|
||
WHEN ISNULL(CASE WHEN F.CurrAccTypeCode = 1 THEN VDesc.VendorAtt02Desc ELSE CDesc.CustomerAtt02Desc END,'') = ''
|
||
THEN ISNULL(CASE WHEN F.CurrAccTypeCode = 1 THEN VAttr.VendorAtt09 ELSE CAttr.CustomerAtt09 END,'')
|
||
ELSE CASE WHEN F.CurrAccTypeCode = 1 THEN VDesc.VendorAtt02Desc ELSE CDesc.CustomerAtt02Desc END
|
||
END,''
|
||
),
|
||
ULKE = ISNULL(CASE WHEN F.CurrAccTypeCode=1 THEN VDesc.VendorAtt05Desc ELSE CDesc.CustomerAtt05Desc END, ''),
|
||
IL = ISNULL(CASE WHEN F.CurrAccTypeCode=1 THEN VDesc.VendorAtt06Desc ELSE CDesc.CustomerAtt06Desc END, ''),
|
||
ILCE = ISNULL(CASE WHEN F.CurrAccTypeCode=1 THEN VDesc.VendorAtt07Desc ELSE CDesc.CustomerAtt07Desc END, ''),
|
||
Risk_Durumu = ISNULL(CASE WHEN F.CurrAccTypeCode=1 THEN VDesc.VendorAtt03Desc ELSE CDesc.CustomerAtt03Desc END, '')
|
||
FROM FirstCari F
|
||
LEFT JOIN cdCurrAccDesc cd WITH (NOLOCK)
|
||
ON cd.CurrAccTypeCode = F.CurrAccTypeCode
|
||
AND cd.CurrAccCode = F.CurrAccCode
|
||
AND cd.LangCode = 'TR'
|
||
LEFT JOIN VendorAttributeDescriptions('TR') VDesc
|
||
ON VDesc.CurrAccCode = F.CurrAccCode
|
||
AND VDesc.CurrAccTypeCode = F.CurrAccTypeCode
|
||
LEFT JOIN CustomerAttributeDescriptions('TR') CDesc
|
||
ON CDesc.CurrAccCode = F.CurrAccCode
|
||
AND CDesc.CurrAccTypeCode = F.CurrAccTypeCode
|
||
LEFT JOIN VendorAttributes VAttr
|
||
ON VAttr.CurrAccCode = F.CurrAccCode
|
||
AND VAttr.CurrAccTypeCode = F.CurrAccTypeCode
|
||
LEFT JOIN CustomerAttributes CAttr
|
||
ON CAttr.CurrAccCode = F.CurrAccCode
|
||
AND CAttr.CurrAccTypeCode = F.CurrAccTypeCode
|
||
ORDER BY F.MasterCari;
|
||
`, quotedInList(masters))
|
||
|
||
rows, err := db.MssqlDB.QueryContext(ctx, query)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("master cari meta query error: %w", err)
|
||
}
|
||
defer rows.Close()
|
||
|
||
out := make(map[string]masterCariMeta, len(masters))
|
||
for rows.Next() {
|
||
var master string
|
||
var m masterCariMeta
|
||
if err := rows.Scan(
|
||
&master,
|
||
&m.CariDetay,
|
||
&m.Kanal1,
|
||
&m.Piyasa,
|
||
&m.Temsilci,
|
||
&m.Ulke,
|
||
&m.Il,
|
||
&m.Ilce,
|
||
&m.RiskDurumu,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
out[strings.TrimSpace(master)] = m
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
func loadBalanceLines(ctx context.Context, selectedDate, cariSearch string) ([]mkCariBakiyeLine, error) {
|
||
piyasaScope, err := buildPiyasaExistsForCariCode(ctx, "CariKodu")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
queryTemplate := `
|
||
SELECT
|
||
CurrAccTypeCode,
|
||
CariKodu,
|
||
CariDoviz,
|
||
SirketKodu,
|
||
PislemTipi,
|
||
%s
|
||
YerelBakiye,
|
||
Bakiye,
|
||
CAST(0 AS DECIMAL(18,4)) AS Vade_Gun,
|
||
CAST(0 AS DECIMAL(18,4)) AS Vade_BelgeTarihi_Gun
|
||
FROM dbo.MK_CARI_BAKIYE_LIST(@SonTarih)
|
||
WHERE (@CariSearch = '' OR CariKodu LIKE '%%' + @CariSearch + '%%')
|
||
AND %s
|
||
`
|
||
|
||
selectParasalCandidates := make([]string, 0, 7)
|
||
if expr := strings.TrimSpace(resolveParasalIslemSelectExpr(ctx, "SELECT * FROM dbo.MK_CARI_BAKIYE_LIST('2000-01-01')")); expr != "" {
|
||
selectParasalCandidates = append(selectParasalCandidates, expr)
|
||
}
|
||
selectParasalCandidates = append(selectParasalCandidates,
|
||
"CAST(ATAtt01 AS varchar(16)) AS ParasalIslemTipi,",
|
||
"CAST(ParasalIslemTipi AS varchar(16)) AS ParasalIslemTipi,",
|
||
"CAST(ParislemTipi AS varchar(16)) AS ParasalIslemTipi,",
|
||
"CAST(ParIslemTipi AS varchar(16)) AS ParasalIslemTipi,",
|
||
"CAST('' AS varchar(16)) AS ParasalIslemTipi,",
|
||
)
|
||
|
||
var rows *sql.Rows
|
||
for i, sel := range selectParasalCandidates {
|
||
query := fmt.Sprintf(queryTemplate, sel, piyasaScope)
|
||
rows, err = db.MssqlDB.QueryContext(ctx, query,
|
||
sql.Named("SonTarih", selectedDate),
|
||
sql.Named("CariSearch", strings.TrimSpace(cariSearch)),
|
||
)
|
||
if err == nil {
|
||
break
|
||
}
|
||
if i < len(selectParasalCandidates)-1 && isInvalidColumnError(err) {
|
||
continue
|
||
}
|
||
return nil, fmt.Errorf("MK_CARI_BAKIYE_LIST query error: %w", err)
|
||
}
|
||
defer rows.Close()
|
||
|
||
out := make([]mkCariBakiyeLine, 0, 4096)
|
||
for rows.Next() {
|
||
var r mkCariBakiyeLine
|
||
if err := rows.Scan(
|
||
&r.CurrAccTypeCode,
|
||
&r.CariKodu,
|
||
&r.CariDoviz,
|
||
&r.SirketKodu,
|
||
&r.PislemTipi,
|
||
&r.ParasalIslemTipi,
|
||
&r.YerelBakiye,
|
||
&r.Bakiye,
|
||
&r.VadeGun,
|
||
&r.VadeBelgeGun,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
out = append(out, r)
|
||
}
|
||
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return out, nil
|
||
}
|
||
|
||
func loadCariMetaMap(ctx context.Context, lines []mkCariBakiyeLine) (map[string]cariMeta, error) {
|
||
vendorCodes := make(map[string]struct{})
|
||
customerCodes := make(map[string]struct{})
|
||
|
||
for _, ln := range lines {
|
||
code := strings.TrimSpace(ln.CariKodu)
|
||
if code == "" {
|
||
continue
|
||
}
|
||
if ln.CurrAccTypeCode == 1 {
|
||
vendorCodes[code] = struct{}{}
|
||
} else if ln.CurrAccTypeCode == 3 {
|
||
customerCodes[code] = struct{}{}
|
||
}
|
||
}
|
||
|
||
if len(vendorCodes) == 0 && len(customerCodes) == 0 {
|
||
return map[string]cariMeta{}, nil
|
||
}
|
||
|
||
whereParts := make([]string, 0, 2)
|
||
if len(vendorCodes) > 0 {
|
||
whereParts = append(whereParts, fmt.Sprintf("(c.CurrAccTypeCode=1 AND c.CurrAccCode IN (%s))", quotedInList(vendorCodes)))
|
||
}
|
||
if len(customerCodes) > 0 {
|
||
whereParts = append(whereParts, fmt.Sprintf("(c.CurrAccTypeCode=3 AND c.CurrAccCode IN (%s))", quotedInList(customerCodes)))
|
||
}
|
||
|
||
query := fmt.Sprintf(`
|
||
SELECT
|
||
c.CurrAccTypeCode,
|
||
c.CurrAccCode,
|
||
CariDetay = ISNULL(d.CurrAccDescription, ''),
|
||
CariTip = CASE WHEN c.CurrAccTypeCode = 1 THEN N'Satıcı' ELSE N'Müşteri' END,
|
||
KANAL_1 = ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt08Desc ELSE cad.CustomerAtt08Desc END, ''),
|
||
PIYASA = ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt01Desc ELSE cad.CustomerAtt01Desc END, ''),
|
||
CARI_TEMSILCI = ISNULL(
|
||
CASE
|
||
WHEN ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt02Desc ELSE cad.CustomerAtt02Desc END, '') = ''
|
||
THEN ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN va.VendorAtt09 ELSE ca.CustomerAtt09 END, '')
|
||
ELSE CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt02Desc ELSE cad.CustomerAtt02Desc END
|
||
END,
|
||
''),
|
||
ULKE = ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt05Desc ELSE cad.CustomerAtt05Desc END, ''),
|
||
IL = ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt06Desc ELSE cad.CustomerAtt06Desc END, ''),
|
||
ILCE = ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt07Desc ELSE cad.CustomerAtt07Desc END, ''),
|
||
TC = ISNULL(c.IdentityNum, ''),
|
||
Risk_Durumu = ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt03Desc ELSE cad.CustomerAtt03Desc END, '')
|
||
FROM cdCurrAcc c WITH(NOLOCK)
|
||
LEFT JOIN cdCurrAccDesc d WITH(NOLOCK)
|
||
ON d.CurrAccTypeCode = c.CurrAccTypeCode
|
||
AND d.CurrAccCode = c.CurrAccCode
|
||
AND d.LangCode = 'TR'
|
||
LEFT JOIN VendorAttributes va WITH(NOLOCK)
|
||
ON va.CurrAccTypeCode = c.CurrAccTypeCode
|
||
AND va.CurrAccCode = c.CurrAccCode
|
||
LEFT JOIN VendorAttributeDescriptions('TR') vad
|
||
ON vad.CurrAccTypeCode = c.CurrAccTypeCode
|
||
AND vad.CurrAccCode = c.CurrAccCode
|
||
LEFT JOIN CustomerAttributes ca WITH(NOLOCK)
|
||
ON ca.CurrAccTypeCode = c.CurrAccTypeCode
|
||
AND ca.CurrAccCode = c.CurrAccCode
|
||
LEFT JOIN CustomerAttributeDescriptions('TR') cad
|
||
ON cad.CurrAccTypeCode = c.CurrAccTypeCode
|
||
AND cad.CurrAccCode = c.CurrAccCode
|
||
WHERE c.CurrAccTypeCode IN (1,3)
|
||
AND (%s)
|
||
`, strings.Join(whereParts, " OR "))
|
||
|
||
rows, err := db.MssqlDB.QueryContext(ctx, query)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("cari meta query error: %w", err)
|
||
}
|
||
defer rows.Close()
|
||
|
||
out := make(map[string]cariMeta, len(lines))
|
||
for rows.Next() {
|
||
var t int
|
||
var code string
|
||
var m cariMeta
|
||
if err := rows.Scan(
|
||
&t,
|
||
&code,
|
||
&m.CariDetay,
|
||
&m.CariTip,
|
||
&m.Kanal1,
|
||
&m.Piyasa,
|
||
&m.Temsilci,
|
||
&m.Ulke,
|
||
&m.Il,
|
||
&m.Ilce,
|
||
&m.TC,
|
||
&m.RiskDurumu,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
out[metaKey(t, code)] = m
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return out, nil
|
||
}
|
||
|
||
func loadGLAccountMap(ctx context.Context, lines []mkCariBakiyeLine) (map[string]string, error) {
|
||
vendorCodes := make(map[string]struct{})
|
||
customerCodes := make(map[string]struct{})
|
||
companyCodes := make(map[int]struct{})
|
||
|
||
for _, ln := range lines {
|
||
code := strings.TrimSpace(ln.CariKodu)
|
||
if code == "" {
|
||
continue
|
||
}
|
||
companyCodes[ln.SirketKodu] = struct{}{}
|
||
if ln.CurrAccTypeCode == 1 {
|
||
vendorCodes[code] = struct{}{}
|
||
} else if ln.CurrAccTypeCode == 3 {
|
||
customerCodes[code] = struct{}{}
|
||
}
|
||
}
|
||
|
||
if len(companyCodes) == 0 || (len(vendorCodes) == 0 && len(customerCodes) == 0) {
|
||
return map[string]string{}, nil
|
||
}
|
||
|
||
whereParts := make([]string, 0, 2)
|
||
if len(vendorCodes) > 0 {
|
||
whereParts = append(whereParts, fmt.Sprintf("(CurrAccTypeCode=1 AND CurrAccCode IN (%s))", quotedInList(vendorCodes)))
|
||
}
|
||
if len(customerCodes) > 0 {
|
||
whereParts = append(whereParts, fmt.Sprintf("(CurrAccTypeCode=3 AND CurrAccCode IN (%s))", quotedInList(customerCodes)))
|
||
}
|
||
|
||
query := fmt.Sprintf(`
|
||
SELECT CurrAccTypeCode, CurrAccCode, CompanyCode, GLAccCode
|
||
FROM prCurrAccGLAccount WITH(NOLOCK)
|
||
WHERE PostAccTypeCode = 100
|
||
AND CompanyCode IN (%s)
|
||
AND (%s)
|
||
`, intInList(companyCodes), strings.Join(whereParts, " OR "))
|
||
|
||
rows, err := db.MssqlDB.QueryContext(ctx, query)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("gl account query error: %w", err)
|
||
}
|
||
defer rows.Close()
|
||
|
||
out := make(map[string]string)
|
||
for rows.Next() {
|
||
var t int
|
||
var code string
|
||
var company int
|
||
var gl sql.NullString
|
||
if err := rows.Scan(&t, &code, &company, &gl); err != nil {
|
||
return nil, err
|
||
}
|
||
out[glKey(t, code, company)] = strings.TrimSpace(gl.String)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return out, nil
|
||
}
|
||
|
||
func loadCompanyMap(ctx context.Context) (map[int]string, error) {
|
||
rows, err := db.MssqlDB.QueryContext(ctx, `SELECT CompanyCode, CompanyName FROM cdCompany WITH(NOLOCK)`)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("company map query error: %w", err)
|
||
}
|
||
defer rows.Close()
|
||
|
||
out := make(map[int]string)
|
||
for rows.Next() {
|
||
var code int
|
||
var name sql.NullString
|
||
if err := rows.Scan(&code, &name); err != nil {
|
||
return nil, err
|
||
}
|
||
out[code] = strings.TrimSpace(name.String)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
func loadNearestTryRates(ctx context.Context) (map[string]float64, error) {
|
||
query := `
|
||
WITH Ranked AS (
|
||
SELECT
|
||
CurrencyCode,
|
||
Rate,
|
||
rn = ROW_NUMBER() OVER (
|
||
PARTITION BY CurrencyCode
|
||
ORDER BY ABS(DATEDIFF(DAY, Date, GETDATE())), Date DESC
|
||
)
|
||
FROM AllExchangeRates
|
||
WHERE RelationCurrencyCode = 'TRY'
|
||
AND ExchangeTypeCode = 6
|
||
AND Rate > 0
|
||
)
|
||
SELECT CurrencyCode, Rate
|
||
FROM Ranked
|
||
WHERE rn = 1
|
||
`
|
||
|
||
rows, err := db.MssqlDB.QueryContext(ctx, query)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("exchange rates query error: %w", err)
|
||
}
|
||
defer rows.Close()
|
||
|
||
out := map[string]float64{"TRY": 1}
|
||
for rows.Next() {
|
||
var code string
|
||
var rate float64
|
||
if err := rows.Scan(&code, &rate); err != nil {
|
||
return nil, err
|
||
}
|
||
code = strings.ToUpper(strings.TrimSpace(code))
|
||
if code != "" && rate > 0 {
|
||
out[code] = rate
|
||
}
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return out, nil
|
||
}
|
||
|
||
func toUSD(amount float64, currency string, usdTry float64, rateMap map[string]float64) float64 {
|
||
if usdTry <= 0 {
|
||
return 0
|
||
}
|
||
|
||
switch currency {
|
||
case "USD":
|
||
return amount
|
||
case "TRY":
|
||
return amount / usdTry
|
||
default:
|
||
currTry := rateMap[currency]
|
||
if currTry <= 0 {
|
||
return 0
|
||
}
|
||
return (amount * currTry) / usdTry
|
||
}
|
||
}
|
||
|
||
func deriveMasterCari(cari string) string {
|
||
cari = strings.TrimSpace(cari)
|
||
if cari == "" {
|
||
return ""
|
||
}
|
||
|
||
base := cari
|
||
if idx := strings.Index(base, "/"); idx > 0 {
|
||
base = base[:idx]
|
||
}
|
||
|
||
base = strings.TrimSpace(base)
|
||
if len(base) >= 8 {
|
||
return strings.TrimSpace(base[:8])
|
||
}
|
||
|
||
return base
|
||
}
|
||
|
||
func buildFilters(params models.CustomerBalanceListParams) balanceFilters {
|
||
return balanceFilters{
|
||
cariIlkGrup: parseCSVSet(params.CariIlkGrup),
|
||
piyasa: parseCSVSet(params.Piyasa),
|
||
temsilci: parseCSVSet(params.Temsilci),
|
||
riskDurumu: parseCSVSet(params.RiskDurumu),
|
||
islemTipi: parseIslemTipiSet(params.IslemTipi),
|
||
ulke: parseCSVSet(params.Ulke),
|
||
il: parseCSVSet(params.Il),
|
||
ilce: parseCSVSet(params.Ilce),
|
||
}
|
||
}
|
||
|
||
func (f balanceFilters) matchLine(islemTipi string, m cariMeta) bool {
|
||
if !matchSet(f.islemTipi, islemTipi) {
|
||
return false
|
||
}
|
||
if !matchSet(f.cariIlkGrup, m.Kanal1) {
|
||
return false
|
||
}
|
||
if !matchSet(f.piyasa, m.Piyasa) {
|
||
return false
|
||
}
|
||
if !matchSet(f.temsilci, m.Temsilci) {
|
||
return false
|
||
}
|
||
if !matchSet(f.riskDurumu, m.RiskDurumu) {
|
||
return false
|
||
}
|
||
if !matchSet(f.ulke, m.Ulke) {
|
||
return false
|
||
}
|
||
if !matchSet(f.il, m.Il) {
|
||
return false
|
||
}
|
||
if !matchSet(f.ilce, m.Ilce) {
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
func matchSet(set map[string]struct{}, value string) bool {
|
||
if len(set) == 0 {
|
||
return true
|
||
}
|
||
trimmed := strings.TrimSpace(value)
|
||
if trimmed == "" {
|
||
return true
|
||
}
|
||
_, ok := set[trimmed]
|
||
return ok
|
||
}
|
||
|
||
func parseCSVSet(v string) map[string]struct{} {
|
||
out := make(map[string]struct{})
|
||
for _, p := range strings.Split(v, ",") {
|
||
t := strings.TrimSpace(p)
|
||
if t == "" {
|
||
continue
|
||
}
|
||
out[t] = struct{}{}
|
||
}
|
||
return out
|
||
}
|
||
|
||
func parseIslemTipiSet(v string) map[string]struct{} {
|
||
raw := parseCSVSet(v)
|
||
if len(raw) == 0 {
|
||
return raw
|
||
}
|
||
|
||
out := make(map[string]struct{}, 2)
|
||
for token := range raw {
|
||
switch strings.ToLower(strings.TrimSpace(token)) {
|
||
case "1_2", "prbr_1_2", "usd_1_2", "try_1_2", "tl_1_2", "usd_bakiye_1_2", "tl_bakiye_1_2":
|
||
out["1_2"] = struct{}{}
|
||
case "1_3", "prbr_1_3", "usd_1_3", "try_1_3", "tl_1_3", "usd_bakiye_1_3", "tl_bakiye_1_3":
|
||
out["1_3"] = struct{}{}
|
||
}
|
||
}
|
||
|
||
if len(out) == 0 {
|
||
return raw
|
||
}
|
||
return out
|
||
}
|
||
|
||
func getAuthorizedPiyasaCodes(ctx context.Context) ([]string, error) {
|
||
claims, ok := auth.GetClaimsFromContext(ctx)
|
||
if !ok || claims == nil {
|
||
return nil, fmt.Errorf("unauthorized: claims not found")
|
||
}
|
||
if claims.IsAdmin() {
|
||
return nil, nil
|
||
}
|
||
|
||
rawCodes := authz.GetPiyasaCodesFromCtx(ctx)
|
||
if len(rawCodes) == 0 {
|
||
return []string{}, nil
|
||
}
|
||
|
||
unique := make(map[string]struct{}, len(rawCodes))
|
||
out := make([]string, 0, len(rawCodes))
|
||
for _, code := range rawCodes {
|
||
norm := strings.ToUpper(strings.TrimSpace(code))
|
||
if norm == "" {
|
||
continue
|
||
}
|
||
if _, exists := unique[norm]; exists {
|
||
continue
|
||
}
|
||
unique[norm] = struct{}{}
|
||
out = append(out, norm)
|
||
}
|
||
if len(out) == 0 {
|
||
return []string{}, nil
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
func buildPiyasaWhereClause(codes []string, column string) string {
|
||
if len(codes) == 0 {
|
||
return "1=1"
|
||
}
|
||
return authz.BuildINClause(column, codes)
|
||
}
|
||
|
||
func metaKey(currType int, code string) string {
|
||
return strconv.Itoa(currType) + "|" + strings.TrimSpace(code)
|
||
}
|
||
|
||
func glKey(currType int, code string, company int) string {
|
||
return strconv.Itoa(currType) + "|" + strings.TrimSpace(code) + "|" + strconv.Itoa(company)
|
||
}
|
||
|
||
func quotedInList(set map[string]struct{}) string {
|
||
vals := make([]string, 0, len(set))
|
||
for v := range set {
|
||
esc := strings.ReplaceAll(strings.TrimSpace(v), "'", "''")
|
||
if esc != "" {
|
||
vals = append(vals, "'"+esc+"'")
|
||
}
|
||
}
|
||
if len(vals) == 0 {
|
||
return "''"
|
||
}
|
||
sort.Strings(vals)
|
||
return strings.Join(vals, ",")
|
||
}
|
||
|
||
func intInList(set map[int]struct{}) string {
|
||
vals := make([]int, 0, len(set))
|
||
for v := range set {
|
||
vals = append(vals, v)
|
||
}
|
||
if len(vals) == 0 {
|
||
return "0"
|
||
}
|
||
sort.Ints(vals)
|
||
parts := make([]string, 0, len(vals))
|
||
for _, v := range vals {
|
||
parts = append(parts, strconv.Itoa(v))
|
||
}
|
||
return strings.Join(parts, ",")
|
||
}
|
||
|
||
func firstNonEmpty(v ...string) string {
|
||
for _, s := range v {
|
||
if strings.TrimSpace(s) != "" {
|
||
return s
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func isInvalidColumnError(err error) bool {
|
||
if err == nil {
|
||
return false
|
||
}
|
||
msg := strings.ToLower(err.Error())
|
||
return strings.Contains(msg, "invalid column name")
|
||
}
|
||
|
||
func shouldSkipBalanceLine(ln mkCariBakiyeLine) bool {
|
||
add12, add13 := resolveBalanceBuckets(ln)
|
||
p := strings.TrimSpace(ln.PislemTipi)
|
||
if p == "1_2" {
|
||
return !add12
|
||
}
|
||
if p == "1_3" {
|
||
return !add13
|
||
}
|
||
return false
|
||
}
|
||
|
||
func resolveBalanceBuckets(ln mkCariBakiyeLine) (add12 bool, add13 bool) {
|
||
p := strings.TrimSpace(ln.PislemTipi)
|
||
t := normalizeParasalIslemTipi(ln.ParasalIslemTipi)
|
||
switch t {
|
||
case "1":
|
||
return true, true
|
||
case "2", "1_2":
|
||
return true, false
|
||
case "3", "1_3":
|
||
return false, true
|
||
}
|
||
|
||
// Parasal tip yoksa eski davranis: PislemTipi'ne gore ayir.
|
||
if p == "1_2" {
|
||
return true, false
|
||
}
|
||
if p == "1_3" {
|
||
return false, true
|
||
}
|
||
return false, false
|
||
}
|
||
|
||
func normalizeParasalIslemTipi(v string) string {
|
||
s := strings.TrimSpace(v)
|
||
if s == "" {
|
||
return ""
|
||
}
|
||
|
||
lower := strings.ToLower(s)
|
||
compact := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(lower, " ", ""), "-", "_"), "/", "_")
|
||
if strings.Contains(compact, "1_2") {
|
||
return "1_2"
|
||
}
|
||
if strings.Contains(compact, "1_3") {
|
||
return "1_3"
|
||
}
|
||
|
||
// "1,2" / "1,3" gibi liste formatlarini dogrudan yakala.
|
||
tokenized := strings.NewReplacer(" ", "", ";", ",", "|", ",", "/", ",", "-", ",", "_", ",").Replace(lower)
|
||
parts := strings.Split(tokenized, ",")
|
||
has1 := false
|
||
has2 := false
|
||
has3 := false
|
||
for _, p := range parts {
|
||
t := strings.TrimSpace(p)
|
||
switch t {
|
||
case "1":
|
||
has1 = true
|
||
case "2":
|
||
has2 = true
|
||
case "3":
|
||
has3 = true
|
||
}
|
||
}
|
||
if has1 && has2 {
|
||
return "1_2"
|
||
}
|
||
if has1 && has3 {
|
||
return "1_3"
|
||
}
|
||
if has2 && !has1 && !has3 {
|
||
return "2"
|
||
}
|
||
if has3 && !has1 && !has2 {
|
||
return "3"
|
||
}
|
||
if has1 && !has2 && !has3 {
|
||
return "1"
|
||
}
|
||
|
||
// "2.00", "2,00", " 2 " gibi varyasyonlari tek tipe indir.
|
||
s = strings.ReplaceAll(s, ",", ".")
|
||
if n, err := strconv.ParseFloat(s, 64); err == nil {
|
||
return strconv.Itoa(int(n))
|
||
}
|
||
|
||
// Metinsel geldiyse ilk rakam bloğunu al.
|
||
start := -1
|
||
end := -1
|
||
for i, r := range s {
|
||
if r >= '0' && r <= '9' {
|
||
if start == -1 {
|
||
start = i
|
||
}
|
||
end = i
|
||
continue
|
||
}
|
||
if start != -1 {
|
||
break
|
||
}
|
||
}
|
||
if start == -1 || end < start {
|
||
return s
|
||
}
|
||
return s[start : end+1]
|
||
}
|
||
|
||
func resolveParasalIslemSelectExpr(ctx context.Context, sampleQuery string) string {
|
||
sampleQuery = strings.TrimSpace(sampleQuery)
|
||
if sampleQuery == "" {
|
||
return ""
|
||
}
|
||
|
||
metaQuery := `
|
||
SELECT name
|
||
FROM sys.dm_exec_describe_first_result_set(@tsql, NULL, 0)
|
||
WHERE error_number IS NULL
|
||
AND name IS NOT NULL
|
||
`
|
||
rows, err := db.MssqlDB.QueryContext(ctx, metaQuery, sql.Named("tsql", sampleQuery))
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
defer rows.Close()
|
||
|
||
type candidate struct {
|
||
key string
|
||
expr string
|
||
}
|
||
priority := []candidate{
|
||
{key: "ata tt01", expr: "CAST(%s AS varchar(16)) AS ParasalIslemTipi,"},
|
||
{key: "atatt01", expr: "CAST(%s AS varchar(16)) AS ParasalIslemTipi,"},
|
||
{key: "parasalislemtipi", expr: "CAST(%s AS varchar(16)) AS ParasalIslemTipi,"},
|
||
{key: "parislemtipi", expr: "CAST(%s AS varchar(16)) AS ParasalIslemTipi,"},
|
||
{key: "parislemtur", expr: "CAST(%s AS varchar(16)) AS ParasalIslemTipi,"},
|
||
}
|
||
|
||
available := make(map[string]string)
|
||
for rows.Next() {
|
||
var col sql.NullString
|
||
if err := rows.Scan(&col); err != nil {
|
||
return ""
|
||
}
|
||
name := strings.TrimSpace(col.String)
|
||
if name == "" {
|
||
continue
|
||
}
|
||
normalized := strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(name, "_", ""), " ", ""))
|
||
available[normalized] = name
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return ""
|
||
}
|
||
|
||
for _, c := range priority {
|
||
key := strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(c.key, "_", ""), " ", ""))
|
||
if col, ok := available[key]; ok {
|
||
return fmt.Sprintf(c.expr, quoteSQLIdent(col))
|
||
}
|
||
}
|
||
|
||
return ""
|
||
}
|
||
|
||
func quoteSQLIdent(ident string) string {
|
||
return "[" + strings.ReplaceAll(strings.TrimSpace(ident), "]", "]]") + "]"
|
||
}
|