259 lines
6.4 KiB
Go
259 lines
6.4 KiB
Go
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(
|
||
`<p>Sipariş kaydı oluşturuldu/güncellendi.</p><p><b>Sipariş No:</b> %s<br/><b>Cari:</b> %s<br/><b>Piyasa:</b> %s</p><p>PDF ektedir.</p>`,
|
||
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)
|
||
}
|