package routes import ( "bytes" "context" "database/sql" "encoding/json" "fmt" "net/http" "strings" "bssapp-backend/internal/mailer" ) type sendOrderMarketMailPayload struct { OrderHeaderID string `json:"orderHeaderID"` } 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 } 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) } subject := fmt.Sprintf("Sipariş %s - %s", number, marketLabel) bodyHTML := fmt.Sprintf( `

Sipariş kaydı oluşturuldu/güncellendi.

Sipariş No: %s
Cari: %s
Piyasa: %s

PDF ektedir.

`, htmlEsc(number), htmlEsc(currAccCode), htmlEsc(marketLabel), ) 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) }