354 lines
9.5 KiB
Go
354 lines
9.5 KiB
Go
package routes
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"log"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"bssapp-backend/db"
|
|
"bssapp-backend/internal/mailer"
|
|
"bssapp-backend/models"
|
|
"bssapp-backend/queries"
|
|
|
|
"github.com/lib/pq"
|
|
)
|
|
|
|
type wholesaleCampaignMailRow struct {
|
|
ProductCode string
|
|
UrunIlkGrubu string
|
|
Marka string
|
|
BrandGroupSec string
|
|
Dim1 int64
|
|
Dim3 int64
|
|
Dim1Token string
|
|
Dim3Token string
|
|
CampaignCode string
|
|
CampaignTitle string
|
|
DiscountRate float64
|
|
}
|
|
|
|
func buildWholesaleCampaignChangeMailHTML(firstGroupCode string, rows []wholesaleCampaignMailRow, actor string, at time.Time) string {
|
|
var b strings.Builder
|
|
b.WriteString(`<div style="font-family:Segoe UI, Arial, sans-serif; font-size:18px; line-height:1.35; -webkit-text-size-adjust:100%;">`)
|
|
b.WriteString(`<div style="margin-bottom:10px;">`)
|
|
b.WriteString(`<div style="font-size:22px; margin-bottom:4px;"><b>Kampanya Degisikligi</b></div>`)
|
|
b.WriteString(`<div>Urun Ilk Grubu: <b>` + htmlEscapeMini(firstGroupCode) + `</b></div>`)
|
|
if strings.TrimSpace(actor) != "" {
|
|
b.WriteString(`<div>Islem Yapan: <b>` + htmlEscapeMini(actor) + `</b></div>`)
|
|
}
|
|
b.WriteString(`<div>Tarih: <b>` + htmlEscapeMini(at.Format("02.01.2006 15:04")) + `</b></div>`)
|
|
b.WriteString(`<div>Varyant Sayisi: <b>` + fmt.Sprintf("%d", len(rows)) + `</b></div>`)
|
|
b.WriteString(`</div>`)
|
|
|
|
b.WriteString(`<div style="max-width:100%; overflow-x:auto;">`)
|
|
b.WriteString(`<table style="border-collapse:collapse; font-size:16px; white-space:nowrap;">`)
|
|
b.WriteString(`<thead><tr>`)
|
|
heads := []string{
|
|
"MARKA GRUBU",
|
|
"MARKA",
|
|
"URUN KODU",
|
|
"DIM1",
|
|
"DIM3",
|
|
"KAMPANYA",
|
|
"IND %",
|
|
}
|
|
for _, h := range heads {
|
|
b.WriteString(`<th style="border:1px solid #d0d0d0; background:#f3f3f3; padding:8px 10px; text-align:left; font-size:16px;">` + htmlEscapeMini(h) + `</th>`)
|
|
}
|
|
b.WriteString(`</tr></thead><tbody>`)
|
|
|
|
for _, r := range rows {
|
|
b.WriteString(`<tr>`)
|
|
campaignLabel := strings.TrimSpace(r.CampaignCode)
|
|
if t := strings.TrimSpace(r.CampaignTitle); t != "" {
|
|
if campaignLabel != "" {
|
|
campaignLabel = campaignLabel + " - " + t
|
|
} else {
|
|
campaignLabel = t
|
|
}
|
|
}
|
|
cells := []string{
|
|
r.BrandGroupSec,
|
|
r.Marka,
|
|
r.ProductCode,
|
|
strings.TrimSpace(r.Dim1Token),
|
|
func() string {
|
|
if strings.TrimSpace(r.Dim3Token) != "" {
|
|
return strings.TrimSpace(r.Dim3Token)
|
|
}
|
|
return ""
|
|
}(),
|
|
campaignLabel,
|
|
fmt.Sprintf("%.2f", r.DiscountRate),
|
|
}
|
|
for i, c := range cells {
|
|
align := "left"
|
|
if i == 3 || i == 4 || i == 6 {
|
|
align = "right"
|
|
}
|
|
b.WriteString(`<td style="border:1px solid #e0e0e0; padding:8px 10px; text-align:` + align + `;">` + htmlEscapeMini(strings.TrimSpace(c)) + `</td>`)
|
|
}
|
|
b.WriteString(`</tr>`)
|
|
}
|
|
|
|
b.WriteString(`</tbody></table></div>`)
|
|
b.WriteString(`<div style="margin-top:12px; font-size:14px; color:#666;">Bu e-posta BSSApp sistemi tarafindan otomatik olusturulmustur.</div>`)
|
|
b.WriteString(`</div>`)
|
|
return b.String()
|
|
}
|
|
|
|
// sendWholesaleCampaignChangeMails sends one mail per UrunIlkGrubu using existing pricing mail mapping tables.
|
|
// It lists only variants that currently have a campaign assigned.
|
|
func sendWholesaleCampaignChangeMails(bg context.Context, ml *mailer.GraphMailer, productCodes []string, actor string) {
|
|
if ml == nil {
|
|
return
|
|
}
|
|
pg := db.PgDB
|
|
if pg == nil {
|
|
log.Printf("[campaign-mail] skipped: pg not ready")
|
|
return
|
|
}
|
|
// Ensure mapping tables exist (reuse pricing mapping).
|
|
if err := ensureFirstGroupMailMappingTables(pg); err != nil {
|
|
log.Printf("[campaign-mail] mapping bootstrap error: %v", err)
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(bg, 90*time.Second)
|
|
defer cancel()
|
|
|
|
codes := make([]string, 0, len(productCodes))
|
|
seen := map[string]struct{}{}
|
|
for _, c := range productCodes {
|
|
c = strings.TrimSpace(c)
|
|
if c == "" {
|
|
continue
|
|
}
|
|
if _, ok := seen[c]; ok {
|
|
continue
|
|
}
|
|
seen[c] = struct{}{}
|
|
codes = append(codes, c)
|
|
}
|
|
if len(codes) == 0 {
|
|
return
|
|
}
|
|
|
|
// Product info for grouping (UrunIlkGrubu, Marka, BrandGroupSec) comes from Nebim query.
|
|
// This is best-effort: if MSSQL is down, we still send a single mail under group "UNKNOWN".
|
|
productInfo := map[string]models.ProductPricing{}
|
|
{
|
|
rows, err := queries.GetAllProductPricingRows(ctx, 500, queries.ProductPricingFilters{ProductCode: codes}, "productCode", false)
|
|
if err == nil {
|
|
for _, r := range rows {
|
|
code := strings.TrimSpace(r.ProductCode)
|
|
if code != "" {
|
|
productInfo[code] = r
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type dbRow struct {
|
|
ProductCode string
|
|
Dim1 int64
|
|
Dim3 sql.NullInt64
|
|
CampaignCode string
|
|
CampaignTitle string
|
|
DiscountRate float64
|
|
}
|
|
|
|
rows, err := pg.QueryContext(ctx, `
|
|
WITH mm AS (
|
|
SELECT id AS mmitem_id, code
|
|
FROM mmitem
|
|
WHERE code = ANY($1::text[])
|
|
),
|
|
latest AS (
|
|
SELECT DISTINCT ON (z.mmitem_id, z.dim1, COALESCE(z.dim3, 0))
|
|
z.mmitem_id,
|
|
z.dim1,
|
|
z.dim3,
|
|
z.sdcampaign_id
|
|
FROM zbggcampaign z
|
|
JOIN mm ON mm.mmitem_id = z.mmitem_id
|
|
ORDER BY z.mmitem_id, z.dim1, COALESCE(z.dim3, 0), z.id DESC
|
|
)
|
|
SELECT
|
|
mm.code AS product_code,
|
|
l.dim1,
|
|
l.dim3,
|
|
COALESCE(sc.code,'') AS campaign_code,
|
|
COALESCE(sc.title,'') AS campaign_title,
|
|
COALESCE(sc.discount_rate, 0)::float8 AS discount_rate
|
|
FROM mm
|
|
JOIN latest l
|
|
ON l.mmitem_id = mm.mmitem_id
|
|
JOIN sdcampaign sc
|
|
ON sc.id = l.sdcampaign_id
|
|
WHERE COALESCE(sc.is_active, TRUE) = TRUE
|
|
ORDER BY mm.code, l.dim1, COALESCE(l.dim3, 0), sc.discount_rate DESC;
|
|
`, pq.Array(codes))
|
|
if err != nil {
|
|
log.Printf("[campaign-mail] campaign rows query error: %v", err)
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
// Resolve dim ids -> human tokens so mails show Nebim-style variant codes (not numeric ids).
|
|
resolveDimTokens := func(ctx context.Context, dimIDs []int64) map[int64]string {
|
|
out := make(map[int64]string, len(dimIDs))
|
|
uniq := make([]int64, 0, len(dimIDs))
|
|
seen := make(map[int64]struct{}, len(dimIDs))
|
|
for _, id := range dimIDs {
|
|
if id <= 0 {
|
|
continue
|
|
}
|
|
if _, ok := seen[id]; ok {
|
|
continue
|
|
}
|
|
seen[id] = struct{}{}
|
|
uniq = append(uniq, id)
|
|
}
|
|
if len(uniq) == 0 {
|
|
return out
|
|
}
|
|
tRows, err := pg.QueryContext(ctx, `
|
|
SELECT DISTINCT ON (dim_id)
|
|
dim_id,
|
|
token
|
|
FROM mk_dim_token_map
|
|
WHERE dim_column = 'dimval1'
|
|
AND dim_id = ANY($1::bigint[])
|
|
ORDER BY dim_id, updated_at DESC;
|
|
`, pq.Array(uniq))
|
|
if err != nil {
|
|
return out
|
|
}
|
|
defer tRows.Close()
|
|
for tRows.Next() {
|
|
var id int64
|
|
var tok string
|
|
if err := tRows.Scan(&id, &tok); err == nil {
|
|
tok = strings.TrimSpace(tok)
|
|
if tok != "" {
|
|
out[id] = tok
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
mailRows := make([]wholesaleCampaignMailRow, 0, 1024)
|
|
dimIDs := make([]int64, 0, 2048)
|
|
for rows.Next() {
|
|
var r dbRow
|
|
if err := rows.Scan(&r.ProductCode, &r.Dim1, &r.Dim3, &r.CampaignCode, &r.CampaignTitle, &r.DiscountRate); err != nil {
|
|
log.Printf("[campaign-mail] scan error: %v", err)
|
|
return
|
|
}
|
|
code := strings.TrimSpace(r.ProductCode)
|
|
info := productInfo[code]
|
|
group := strings.TrimSpace(info.UrunIlkGrubu)
|
|
if group == "" {
|
|
group = "UNKNOWN"
|
|
}
|
|
d3 := int64(0)
|
|
if r.Dim3.Valid {
|
|
d3 = r.Dim3.Int64
|
|
}
|
|
if r.Dim1 > 0 {
|
|
dimIDs = append(dimIDs, r.Dim1)
|
|
}
|
|
if d3 > 0 {
|
|
dimIDs = append(dimIDs, d3)
|
|
}
|
|
mailRows = append(mailRows, wholesaleCampaignMailRow{
|
|
ProductCode: code,
|
|
UrunIlkGrubu: group,
|
|
Marka: strings.TrimSpace(info.Marka),
|
|
BrandGroupSec: strings.TrimSpace(info.BrandGroupSec),
|
|
Dim1: r.Dim1,
|
|
Dim3: d3,
|
|
CampaignCode: strings.TrimSpace(r.CampaignCode),
|
|
CampaignTitle: strings.TrimSpace(r.CampaignTitle),
|
|
DiscountRate: r.DiscountRate,
|
|
})
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
log.Printf("[campaign-mail] rows error: %v", err)
|
|
return
|
|
}
|
|
|
|
if len(mailRows) == 0 {
|
|
// Nothing assigned => no mail.
|
|
return
|
|
}
|
|
|
|
tokens := resolveDimTokens(ctx, dimIDs)
|
|
for i := range mailRows {
|
|
mailRows[i].Dim1Token = tokens[mailRows[i].Dim1]
|
|
if mailRows[i].Dim1Token == "" && mailRows[i].Dim1 > 0 {
|
|
mailRows[i].Dim1Token = fmt.Sprintf("%d", mailRows[i].Dim1)
|
|
}
|
|
if mailRows[i].Dim3 > 0 {
|
|
mailRows[i].Dim3Token = tokens[mailRows[i].Dim3]
|
|
if mailRows[i].Dim3Token == "" {
|
|
mailRows[i].Dim3Token = fmt.Sprintf("%d", mailRows[i].Dim3)
|
|
}
|
|
}
|
|
}
|
|
|
|
byGroup := map[string][]wholesaleCampaignMailRow{}
|
|
for _, r := range mailRows {
|
|
g := strings.TrimSpace(r.UrunIlkGrubu)
|
|
if g == "" {
|
|
g = "UNKNOWN"
|
|
}
|
|
byGroup[g] = append(byGroup[g], r)
|
|
}
|
|
|
|
now := time.Now()
|
|
for group, list := range byGroup {
|
|
recipients, err := loadPricingRecipients(pg, group)
|
|
if err != nil {
|
|
log.Printf("[campaign-mail] recipient query error group=%s err=%v", group, err)
|
|
continue
|
|
}
|
|
if len(recipients) == 0 {
|
|
log.Printf("[campaign-mail] no recipients mapped group=%s", group)
|
|
continue
|
|
}
|
|
|
|
sort.Slice(list, func(i, j int) bool {
|
|
if list[i].ProductCode != list[j].ProductCode {
|
|
return list[i].ProductCode < list[j].ProductCode
|
|
}
|
|
if list[i].Dim1 != list[j].Dim1 {
|
|
return list[i].Dim1 < list[j].Dim1
|
|
}
|
|
return list[i].Dim3 < list[j].Dim3
|
|
})
|
|
|
|
subject := fmt.Sprintf("Kampanya Degisikligi | %s | %s | %d varyant", group, now.Format("02.01.2006 15:04"), len(list))
|
|
html := buildWholesaleCampaignChangeMailHTML(group, list, actor, now)
|
|
|
|
stepCtx, stepCancel := context.WithTimeout(bg, 25*time.Second)
|
|
err = ml.Send(stepCtx, mailer.Message{
|
|
To: recipients,
|
|
Subject: subject,
|
|
BodyHTML: html,
|
|
})
|
|
stepCancel()
|
|
if err != nil {
|
|
log.Printf("[campaign-mail] send failed group=%s err=%v", group, err)
|
|
} else {
|
|
log.Printf("[campaign-mail] sent group=%s to=%d variants=%d", group, len(recipients), len(list))
|
|
}
|
|
}
|
|
}
|