Files
bssapp/svc/routes/order_mail.go
2026-03-23 10:16:30 +03:00

335 lines
8.5 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 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,
`<p>`,
fmt.Sprintf(`<b>Cari Kodu:</b> %s<br/>`, htmlEsc(currAccCode)),
fmt.Sprintf(`<b>Cari Detay:</b> %s<br/>`, htmlEsc(cariDetail)),
fmt.Sprintf(`<b>Müşteri Temsilcisi:</b> %s<br/>`, htmlEsc(customerRep)),
fmt.Sprintf(`<b>Piyasa:</b> %s`, htmlEsc(marketLabel)),
`</p>`,
)
if isUpdate {
body = append(body,
renderItemListHTML("Silinen Ürün Kodları", payload.DeletedItems),
renderItemListHTML("Güncellenen Ürün Kodları", payload.UpdatedItems),
renderItemListHTML("Eklenen Ürün Kodları", payload.AddedItems),
)
}
body = append(body, `<p><i>Bu sipariş BaggiSS App Uygulamasından oluşturulmuştur.</i></p>`)
body = append(body, `<p>PDF ektedir.</p>`)
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(
"&", "&amp;",
"<", "&lt;",
">", "&gt;",
`"`, "&quot;",
"'", "&#39;",
)
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(`<p><b>%s:</b> Yok</p>`, htmlEsc(title))
}
b := make([]string, 0, len(clean)+3)
b = append(b, fmt.Sprintf(`<p><b>%s:</b><br/>`, htmlEsc(title)))
for _, item := range clean {
b = append(b, "- "+htmlEsc(item)+"<br/>")
}
b = append(b, `</p>`)
return strings.Join(b, "\n")
}