package routes import ( "bytes" "context" "database/sql" "encoding/json" "fmt" "net/http" "strings" "bssapp-backend/auth" "bssapp-backend/internal/mailer" ) type sendOrderMarketMailPayload struct { OrderHeaderID string `json:"orderHeaderID"` Operation string `json:"operation"` DeletedItems []string `json:"deletedItems"` UpdatedItems []string `json:"updatedItems"` AddedItems []string `json:"addedItems"` } func SendOrderMarketMailHandler(pg *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") if ml == nil { http.Error(w, "mailer not initialized", http.StatusServiceUnavailable) return } if pg == nil || mssql == nil { http.Error(w, "database not initialized", http.StatusInternalServerError) return } claims, ok := auth.GetClaimsFromContext(r.Context()) if !ok || claims == nil { http.Error(w, "unauthorized", http.StatusUnauthorized) return } var payload sendOrderMarketMailPayload if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { http.Error(w, "invalid payload", http.StatusBadRequest) return } orderID := strings.TrimSpace(payload.OrderHeaderID) if orderID == "" { http.Error(w, "orderHeaderID is required", http.StatusBadRequest) return } orderNo, currAccCode, marketCode, marketTitle, err := resolveOrderMailContext(mssql, orderID) if err != nil { http.Error(w, "order context error: "+err.Error(), http.StatusInternalServerError) return } if strings.TrimSpace(marketCode) == "" && strings.TrimSpace(marketTitle) == "" { http.Error(w, "market not found for order/cari", http.StatusBadRequest) return } recipients, err := loadMarketRecipients(pg, marketCode, marketTitle) if err != nil { http.Error(w, "recipient query error: "+err.Error(), http.StatusInternalServerError) return } if len(recipients) == 0 { http.Error(w, "no active email mapping for market", http.StatusBadRequest) return } pdfBytes, header, err := buildOrderPDFBytesForMail(mssql, orderID) if err != nil { http.Error(w, "pdf build error: "+err.Error(), http.StatusInternalServerError) return } number := strings.TrimSpace(orderNo) if number == "" && header != nil { number = strings.TrimSpace(header.OrderNumber) } if number == "" { number = orderID } marketLabel := strings.TrimSpace(marketTitle) if marketLabel == "" { marketLabel = strings.TrimSpace(marketCode) } actor := strings.TrimSpace(claims.Username) if actor == "" { actor = strings.TrimSpace(claims.V3Username) } if actor == "" { actor = "Bilinmeyen Kullanici" } op := strings.ToLower(strings.TrimSpace(payload.Operation)) isUpdate := op == "update" subjectAction := "SİPARİŞ KAYDI OLUŞTURULDU" if isUpdate { subjectAction = "SİPARİŞ GÜNCELLENDİ." } subject := fmt.Sprintf("%s kullanıcısı tarafından %s %s", actor, number, subjectAction) cariDetail := "" customerRep := "" if header != nil { cariDetail = strings.TrimSpace(header.CurrAccName) customerRep = strings.TrimSpace(header.CustomerRep) } body := make([]string, 0, 12) body = append(body, `
`,
fmt.Sprintf(`Cari Kodu: %s
`, htmlEsc(currAccCode)),
fmt.Sprintf(`Cari Detay: %s
`, htmlEsc(cariDetail)),
fmt.Sprintf(`Müşteri Temsilcisi: %s
`, htmlEsc(customerRep)),
fmt.Sprintf(`Piyasa: %s`, htmlEsc(marketLabel)),
`
Bu sipariş BaggiSS App Uygulamasından oluşturulmuştur.
`) body = append(body, `PDF ektedir.
`) bodyHTML := strings.Join(body, "\n") fileNo := sanitizeFileName(number) if fileNo == "" { fileNo = orderID } msg := mailer.Message{ To: recipients, Subject: subject, BodyHTML: bodyHTML, Attachments: []mailer.Attachment{ { FileName: "ORDER_" + fileNo + ".pdf", ContentType: "application/pdf", Data: pdfBytes, }, }, } if err := ml.Send(context.Background(), msg); err != nil { http.Error(w, "mail send error: "+err.Error(), http.StatusInternalServerError) return } _ = json.NewEncoder(w).Encode(map[string]any{ "success": true, "orderHeaderID": orderID, "orderNumber": number, "marketCode": marketCode, "marketTitle": marketTitle, "recipients": recipients, "sentCount": len(recipients), }) } } func resolveOrderMailContext(db *sql.DB, orderID string) (orderNo, currAccCode, marketCode, marketTitle string, err error) { row := db.QueryRow(` SELECT TOP (1) ISNULL(h.OrderNumber, ''), ISNULL(h.CurrAccCode, ''), ISNULL(LTRIM(RTRIM(f.CustomerAtt01)), '') AS MarketCode, ISNULL(py.AttributeDescription, '') AS MarketTitle FROM BAGGI_V3.dbo.trOrderHeader h WITH (NOLOCK) LEFT JOIN BAGGI_V3.dbo.CustomerAttributesFilter f WITH (NOLOCK) ON f.CurrAccCode = h.CurrAccCode LEFT JOIN BAGGI_V3.dbo.cdCurrAccAttributeDesc py WITH (NOLOCK) ON py.CurrAccTypeCode = h.CurrAccTypeCode AND py.AttributeTypeCode = 1 AND py.AttributeCode = f.CustomerAtt01 AND py.LangCode = 'TR' WHERE CAST(h.OrderHeaderID AS varchar(36)) = @p1 `, orderID) var no, cari, pCode, pTitle string if err = row.Scan(&no, &cari, &pCode, &pTitle); err != nil { return "", "", "", "", err } no = strings.TrimSpace(no) cari = strings.TrimSpace(cari) pCode = strings.TrimSpace(pCode) pTitle = strings.TrimSpace(pTitle) return no, cari, pCode, pTitle, nil } func loadMarketRecipients(pg *sql.DB, marketCode, marketTitle string) ([]string, error) { rows, err := pg.Query(` SELECT DISTINCT TRIM(m.email) AS email FROM mk_sales_piy p JOIN mk_market_mail mm ON mm.market_id = p.id JOIN mk_mail m ON m.id = mm.mail_id WHERE p.is_active = true AND m.is_active = true AND ( UPPER(TRIM(p.code)) = UPPER(TRIM($1)) OR ($2 <> '' AND UPPER(TRIM(p.title)) = UPPER(TRIM($2))) ) AND COALESCE(TRIM(m.email), '') <> '' ORDER BY email `, strings.TrimSpace(marketCode), strings.TrimSpace(marketTitle)) if err != nil { return nil, err } defer rows.Close() out := make([]string, 0, 8) for rows.Next() { var email string if err := rows.Scan(&email); err != nil { return nil, err } email = strings.TrimSpace(email) if email != "" { out = append(out, email) } } if err := rows.Err(); err != nil { return nil, err } return out, nil } func buildOrderPDFBytesForMail(db *sql.DB, orderID string) ([]byte, *OrderHeader, error) { header, err := getOrderHeaderFromDB(db, orderID) if err != nil { return nil, nil, err } lines, err := getOrderLinesFromDB(db, orderID) if err != nil { return nil, nil, err } hasVat := false var vatRate float64 for _, l := range lines { if l.VatRate.Valid && l.VatRate.Float64 > 0 { hasVat = true vatRate = l.VatRate.Float64 break } } rows := normalizeOrderLinesForPdf(lines) pdf, err := newOrderPdf() if err != nil { return nil, nil, err } renderOrderGrid(pdf, header, rows, hasVat, vatRate) if err := pdf.Error(); err != nil { return nil, nil, err } var buf bytes.Buffer if err := pdf.Output(&buf); err != nil { return nil, nil, err } return buf.Bytes(), header, nil } func sanitizeFileName(v string) string { s := strings.TrimSpace(v) if s == "" { return "" } invalid := []string{`\\`, `/`, `:`, `*`, `?`, `"`, `<`, `>`, `|`} for _, bad := range invalid { s = strings.ReplaceAll(s, bad, "_") } return strings.TrimSpace(s) } func htmlEsc(s string) string { r := strings.NewReplacer( "&", "&", "<", "<", ">", ">", `"`, """, "'", "'", ) return r.Replace(s) } func renderItemListHTML(title string, items []string) string { clean := make([]string, 0, len(items)) seen := make(map[string]struct{}, len(items)) for _, raw := range items { v := strings.TrimSpace(raw) if v == "" { continue } if _, ok := seen[v]; ok { continue } seen[v] = struct{}{} clean = append(clean, v) } if len(clean) == 0 { return fmt.Sprintf(`%s: Yok
`, htmlEsc(title)) } b := make([]string, 0, len(clean)+3) b = append(b, fmt.Sprintf(`%s:
`, htmlEsc(title)))
for _, item := range clean {
b = append(b, "- "+htmlEsc(item)+"
")
}
b = append(b, `