Files
bssapp/svc/queries/customer_balance_list.go
2026-03-03 10:16:19 +03:00

791 lines
20 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
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)
switch strings.TrimSpace(ln.PislemTipi) {
case "1_2":
row.Bakiye12 += ln.Bakiye
row.TLBakiye12 += ln.YerelBakiye
row.USDBakiye12 += usd
case "1_3":
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) {
query := `
SELECT
CurrAccTypeCode,
CariKodu,
CariDoviz,
SirketKodu,
PislemTipi,
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 + '%')
`
rows, err := db.MssqlDB.QueryContext(ctx, query,
sql.Named("SonTarih", selectedDate),
sql.Named("CariSearch", strings.TrimSpace(cariSearch)),
)
if err != nil {
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.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: parseCSVSet(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 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 ""
}