Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -3,32 +3,120 @@ package db
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "github.com/microsoft/go-mssqldb"
|
||||
)
|
||||
|
||||
var MssqlDB *sql.DB
|
||||
|
||||
// ConnectMSSQL MSSQL baglantisini ortam degiskeninden baslatir.
|
||||
func envInt(name string, fallback int) int {
|
||||
raw := strings.TrimSpace(os.Getenv(name))
|
||||
if raw == "" {
|
||||
return fallback
|
||||
}
|
||||
value, err := strconv.Atoi(raw)
|
||||
if err != nil || value <= 0 {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func ensureTimeoutValue(current string, desired int) string {
|
||||
cur, err := strconv.Atoi(strings.TrimSpace(current))
|
||||
if err == nil && cur >= desired {
|
||||
return strings.TrimSpace(current)
|
||||
}
|
||||
return strconv.Itoa(desired)
|
||||
}
|
||||
|
||||
func ensureMSSQLTimeouts(connString string, connectionTimeoutSec int, dialTimeoutSec int) string {
|
||||
raw := strings.TrimSpace(connString)
|
||||
if raw == "" {
|
||||
return raw
|
||||
}
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(raw), "sqlserver://") {
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
return raw
|
||||
}
|
||||
q := u.Query()
|
||||
q.Set("connection timeout", ensureTimeoutValue(q.Get("connection timeout"), connectionTimeoutSec))
|
||||
q.Set("dial timeout", ensureTimeoutValue(q.Get("dial timeout"), dialTimeoutSec))
|
||||
u.RawQuery = q.Encode()
|
||||
return u.String()
|
||||
}
|
||||
|
||||
parts := strings.Split(raw, ";")
|
||||
foundConnectionTimeout := false
|
||||
foundDialTimeout := false
|
||||
|
||||
for i, part := range parts {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
eq := strings.Index(part, "=")
|
||||
if eq <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := strings.ToLower(strings.TrimSpace(part[:eq]))
|
||||
value := strings.TrimSpace(part[eq+1:])
|
||||
|
||||
switch key {
|
||||
case "connection timeout":
|
||||
foundConnectionTimeout = true
|
||||
parts[i] = "connection timeout=" + ensureTimeoutValue(value, connectionTimeoutSec)
|
||||
case "dial timeout":
|
||||
foundDialTimeout = true
|
||||
parts[i] = "dial timeout=" + ensureTimeoutValue(value, dialTimeoutSec)
|
||||
}
|
||||
}
|
||||
|
||||
if !foundConnectionTimeout {
|
||||
parts = append(parts, "connection timeout="+strconv.Itoa(connectionTimeoutSec))
|
||||
}
|
||||
if !foundDialTimeout {
|
||||
parts = append(parts, "dial timeout="+strconv.Itoa(dialTimeoutSec))
|
||||
}
|
||||
|
||||
return strings.Join(parts, ";")
|
||||
}
|
||||
|
||||
// ConnectMSSQL initializes the MSSQL connection from environment.
|
||||
func ConnectMSSQL() error {
|
||||
connString := strings.TrimSpace(os.Getenv("MSSQL_CONN"))
|
||||
if connString == "" {
|
||||
return fmt.Errorf("MSSQL_CONN tanımlı değil")
|
||||
return fmt.Errorf("MSSQL_CONN tanimli degil")
|
||||
}
|
||||
|
||||
connectionTimeoutSec := envInt("MSSQL_CONNECTION_TIMEOUT_SEC", 120)
|
||||
dialTimeoutSec := envInt("MSSQL_DIAL_TIMEOUT_SEC", connectionTimeoutSec)
|
||||
connString = ensureMSSQLTimeouts(connString, connectionTimeoutSec, dialTimeoutSec)
|
||||
|
||||
var err error
|
||||
MssqlDB, err = sql.Open("sqlserver", connString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("MSSQL bağlantı hatası: %w", err)
|
||||
return fmt.Errorf("MSSQL baglanti hatasi: %w", err)
|
||||
}
|
||||
|
||||
MssqlDB.SetMaxOpenConns(envInt("MSSQL_MAX_OPEN_CONNS", 40))
|
||||
MssqlDB.SetMaxIdleConns(envInt("MSSQL_MAX_IDLE_CONNS", 40))
|
||||
MssqlDB.SetConnMaxLifetime(time.Duration(envInt("MSSQL_CONN_MAX_LIFETIME_MIN", 30)) * time.Minute)
|
||||
MssqlDB.SetConnMaxIdleTime(time.Duration(envInt("MSSQL_CONN_MAX_IDLE_MIN", 10)) * time.Minute)
|
||||
|
||||
if err = MssqlDB.Ping(); err != nil {
|
||||
return fmt.Errorf("MSSQL erişilemiyor: %w", err)
|
||||
return fmt.Errorf("MSSQL erisilemiyor: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("MSSQL bağlantısı başarılı")
|
||||
fmt.Printf("MSSQL baglantisi basarili (connection timeout=%ds, dial timeout=%ds)\n", connectionTimeoutSec, dialTimeoutSec)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
13
svc/main.go
13
svc/main.go
@@ -526,7 +526,7 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
|
||||
{"/api/orders/production-items/{id}", "GET", "view", routes.OrderProductionItemsRoute(mssql)},
|
||||
{"/api/orders/production-items/{id}/insert-missing", "POST", "update", routes.OrderProductionInsertMissingRoute(mssql)},
|
||||
{"/api/orders/production-items/{id}/validate", "POST", "update", routes.OrderProductionValidateRoute(mssql)},
|
||||
{"/api/orders/production-items/{id}/apply", "POST", "update", routes.OrderProductionApplyRoute(mssql)},
|
||||
{"/api/orders/production-items/{id}/apply", "POST", "update", routes.OrderProductionApplyRoute(mssql, ml)},
|
||||
{"/api/orders/close-ready", "GET", "update", routes.OrderCloseReadyListRoute(mssql)},
|
||||
{"/api/orders/bulk-close", "POST", "update", routes.OrderBulkCloseRoute(mssql)},
|
||||
{"/api/orders/export", "GET", "export", routes.OrderListExcelRoute(mssql)},
|
||||
@@ -571,6 +571,12 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
|
||||
wrapV3(http.HandlerFunc(routes.GetProductDetailHandler)),
|
||||
)
|
||||
|
||||
bindV3(r, pgDB,
|
||||
"/api/product-cditem", "GET",
|
||||
"order", "view",
|
||||
wrapV3(http.HandlerFunc(routes.GetProductCdItemHandler)),
|
||||
)
|
||||
|
||||
bindV3(r, pgDB,
|
||||
"/api/product-colors", "GET",
|
||||
"order", "view",
|
||||
@@ -638,6 +644,11 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
|
||||
"order", "view",
|
||||
wrapV3(routes.GetProductSizeMatchRulesHandler(pgDB)),
|
||||
)
|
||||
bindV3(r, pgDB,
|
||||
"/api/pricing/products", "GET",
|
||||
"order", "view",
|
||||
wrapV3(http.HandlerFunc(routes.GetProductPricingListHandler)),
|
||||
)
|
||||
|
||||
// ============================================================
|
||||
// ROLE MANAGEMENT
|
||||
|
||||
@@ -11,15 +11,19 @@ type OrderProductionItem struct {
|
||||
OldDim1 string `json:"OldDim1"`
|
||||
OldDim3 string `json:"OldDim3"`
|
||||
|
||||
OldItemCode string `json:"OldItemCode"`
|
||||
OldColor string `json:"OldColor"`
|
||||
OldDim2 string `json:"OldDim2"`
|
||||
OldDesc string `json:"OldDesc"`
|
||||
OldItemCode string `json:"OldItemCode"`
|
||||
OldColor string `json:"OldColor"`
|
||||
OldColorDescription string `json:"OldColorDescription"`
|
||||
OldDim2 string `json:"OldDim2"`
|
||||
OldDesc string `json:"OldDesc"`
|
||||
OldQty float64 `json:"OldQty"`
|
||||
|
||||
NewItemCode string `json:"NewItemCode"`
|
||||
NewColor string `json:"NewColor"`
|
||||
NewDim2 string `json:"NewDim2"`
|
||||
NewDesc string `json:"NewDesc"`
|
||||
|
||||
IsVariantMissing bool `json:"IsVariantMissing"`
|
||||
IsVariantMissing bool `json:"IsVariantMissing"`
|
||||
OldDueDate string `json:"OldDueDate"`
|
||||
NewDueDate string `json:"NewDueDate"`
|
||||
}
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
package models
|
||||
|
||||
type OrderProductionUpdateLine struct {
|
||||
OrderLineID string `json:"OrderLineID"`
|
||||
NewItemCode string `json:"NewItemCode"`
|
||||
NewColor string `json:"NewColor"`
|
||||
NewDim2 string `json:"NewDim2"`
|
||||
NewDesc string `json:"NewDesc"`
|
||||
OrderLineID string `json:"OrderLineID"`
|
||||
NewItemCode string `json:"NewItemCode"`
|
||||
NewColor string `json:"NewColor"`
|
||||
ItemDim1Code *string `json:"ItemDim1Code,omitempty"`
|
||||
NewDim2 string `json:"NewDim2"`
|
||||
NewDesc string `json:"NewDesc"`
|
||||
OldDueDate string `json:"OldDueDate"`
|
||||
NewDueDate string `json:"NewDueDate"`
|
||||
}
|
||||
|
||||
type OrderProductionUpdatePayload struct {
|
||||
Lines []OrderProductionUpdateLine `json:"lines"`
|
||||
InsertMissing bool `json:"insertMissing"`
|
||||
CdItems []OrderProductionCdItemDraft `json:"cdItems"`
|
||||
ProductAttributes []OrderProductionItemAttributeRow `json:"productAttributes"`
|
||||
Lines []OrderProductionUpdateLine `json:"lines"`
|
||||
InsertMissing bool `json:"insertMissing"`
|
||||
CdItems []OrderProductionCdItemDraft `json:"cdItems"`
|
||||
ProductAttributes []OrderProductionItemAttributeRow `json:"productAttributes"`
|
||||
HeaderAverageDueDate *string `json:"HeaderAverageDueDate,omitempty"`
|
||||
}
|
||||
|
||||
type OrderProductionMissingVariant struct {
|
||||
@@ -25,6 +29,19 @@ type OrderProductionMissingVariant struct {
|
||||
ItemDim3Code string `json:"ItemDim3Code"`
|
||||
}
|
||||
|
||||
type OrderProductionBarcodeValidation struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Barcode string `json:"barcode,omitempty"`
|
||||
BarcodeTypeCode string `json:"barcodeTypeCode,omitempty"`
|
||||
ItemTypeCode int16 `json:"ItemTypeCode,omitempty"`
|
||||
ItemCode string `json:"ItemCode,omitempty"`
|
||||
ColorCode string `json:"ColorCode,omitempty"`
|
||||
ItemDim1Code string `json:"ItemDim1Code,omitempty"`
|
||||
ItemDim2Code string `json:"ItemDim2Code,omitempty"`
|
||||
ItemDim3Code string `json:"ItemDim3Code,omitempty"`
|
||||
}
|
||||
|
||||
type OrderProductionCdItemDraft struct {
|
||||
ItemTypeCode int16 `json:"ItemTypeCode"`
|
||||
ItemCode string `json:"ItemCode"`
|
||||
|
||||
18
svc/models/product_pricing.go
Normal file
18
svc/models/product_pricing.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package models
|
||||
|
||||
type ProductPricing struct {
|
||||
ProductCode string `json:"ProductCode"`
|
||||
CostPrice float64 `json:"CostPrice"`
|
||||
StockQty float64 `json:"StockQty"`
|
||||
StockEntryDate string `json:"StockEntryDate"`
|
||||
LastPricingDate string `json:"LastPricingDate"`
|
||||
AskiliYan string `json:"AskiliYan"`
|
||||
Kategori string `json:"Kategori"`
|
||||
UrunIlkGrubu string `json:"UrunIlkGrubu"`
|
||||
UrunAnaGrubu string `json:"UrunAnaGrubu"`
|
||||
UrunAltGrubu string `json:"UrunAltGrubu"`
|
||||
Icerik string `json:"Icerik"`
|
||||
Karisim string `json:"Karisim"`
|
||||
Marka string `json:"Marka"`
|
||||
BrandGroupSec string `json:"BrandGroupSec"`
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -25,14 +26,24 @@ SELECT
|
||||
|
||||
ISNULL(l.ItemCode,'') AS OldItemCode,
|
||||
ISNULL(l.ColorCode,'') AS OldColor,
|
||||
ISNULL((
|
||||
SELECT TOP 1 LTRIM(RTRIM(cd.ColorDescription))
|
||||
FROM dbo.cdColorDesc cd WITH (NOLOCK)
|
||||
WHERE cd.ColorCode = l.ColorCode
|
||||
AND cd.LangCode = N'TR'
|
||||
), '') AS OldColorDescription,
|
||||
ISNULL(l.ItemDim2Code,'') AS OldDim2,
|
||||
ISNULL(l.LineDescription,'') AS OldDesc,
|
||||
CAST(ISNULL(l.Qty1, 0) AS FLOAT) AS OldQty,
|
||||
|
||||
CAST('' AS NVARCHAR(60)) AS NewItemCode,
|
||||
CAST('' AS NVARCHAR(30)) AS NewColor,
|
||||
CAST('' AS NVARCHAR(30)) AS NewDim2,
|
||||
CAST('' AS NVARCHAR(250)) AS NewDesc,
|
||||
|
||||
CONVERT(NVARCHAR(10), l.DeliveryDate, 126) AS OldDueDate,
|
||||
CONVERT(NVARCHAR(10), l.DeliveryDate, 126) AS NewDueDate,
|
||||
|
||||
CAST(0 AS bit) AS IsVariantMissing
|
||||
FROM dbo.trOrderLine l
|
||||
WHERE l.OrderHeaderID = @p1
|
||||
@@ -522,18 +533,25 @@ func UpdateOrderLinesTx(tx *sql.Tx, orderHeaderID string, lines []models.OrderPr
|
||||
chunk := lines[i:end]
|
||||
|
||||
values := make([]string, 0, len(chunk))
|
||||
args := make([]any, 0, len(chunk)*5+2)
|
||||
args := make([]any, 0, len(chunk)*8+2)
|
||||
paramPos := 1
|
||||
for _, line := range chunk {
|
||||
values = append(values, fmt.Sprintf("(@p%d,@p%d,@p%d,@p%d,@p%d)", paramPos, paramPos+1, paramPos+2, paramPos+3, paramPos+4))
|
||||
var itemDim1 any
|
||||
if line.ItemDim1Code != nil {
|
||||
itemDim1 = strings.TrimSpace(*line.ItemDim1Code)
|
||||
}
|
||||
values = append(values, fmt.Sprintf("(@p%d,@p%d,@p%d,@p%d,@p%d,@p%d,@p%d,@p%d)", paramPos, paramPos+1, paramPos+2, paramPos+3, paramPos+4, paramPos+5, paramPos+6, paramPos+7))
|
||||
args = append(args,
|
||||
strings.TrimSpace(line.OrderLineID),
|
||||
line.NewItemCode,
|
||||
line.NewColor,
|
||||
itemDim1,
|
||||
line.NewDim2,
|
||||
line.NewDesc,
|
||||
line.OldDueDate,
|
||||
line.NewDueDate,
|
||||
)
|
||||
paramPos += 5
|
||||
paramPos += 8
|
||||
}
|
||||
|
||||
orderHeaderParam := paramPos
|
||||
@@ -542,16 +560,18 @@ func UpdateOrderLinesTx(tx *sql.Tx, orderHeaderID string, lines []models.OrderPr
|
||||
|
||||
query := fmt.Sprintf(`
|
||||
SET NOCOUNT ON;
|
||||
WITH src (OrderLineID, NewItemCode, NewColor, NewDim2, NewDesc) AS (
|
||||
WITH src (OrderLineID, NewItemCode, NewColor, ItemDim1Code, NewDim2, NewDesc, OldDueDate, NewDueDate) AS (
|
||||
SELECT *
|
||||
FROM (VALUES %s) AS v (OrderLineID, NewItemCode, NewColor, NewDim2, NewDesc)
|
||||
FROM (VALUES %s) AS v (OrderLineID, NewItemCode, NewColor, ItemDim1Code, NewDim2, NewDesc, OldDueDate, NewDueDate)
|
||||
)
|
||||
UPDATE l
|
||||
SET
|
||||
l.ItemCode = s.NewItemCode,
|
||||
l.ColorCode = s.NewColor,
|
||||
l.ItemDim1Code = COALESCE(s.ItemDim1Code, l.ItemDim1Code),
|
||||
l.ItemDim2Code = s.NewDim2,
|
||||
l.LineDescription = COALESCE(NULLIF(s.NewDesc,''), l.LineDescription),
|
||||
l.DeliveryDate = CASE WHEN ISDATE(s.NewDueDate) = 1 THEN CAST(s.NewDueDate AS DATETIME) ELSE l.DeliveryDate END,
|
||||
l.LastUpdatedUserName = @p%d,
|
||||
l.LastUpdatedDate = GETDATE()
|
||||
FROM dbo.trOrderLine l
|
||||
@@ -574,6 +594,344 @@ WHERE l.OrderHeaderID = @p%d;
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func UpdateOrderHeaderAverageDueDateTx(tx *sql.Tx, orderHeaderID string, averageDueDate *string, username string) error {
|
||||
if averageDueDate == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
dueDate := strings.TrimSpace(*averageDueDate)
|
||||
if dueDate != "" {
|
||||
if _, err := time.Parse("2006-01-02", dueDate); err != nil {
|
||||
return fmt.Errorf("invalid header average due date %q: %w", dueDate, err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err := tx.Exec(`
|
||||
UPDATE dbo.trOrderHeader
|
||||
SET
|
||||
AverageDueDate = CASE WHEN @p1 = '' THEN NULL ELSE CAST(@p1 AS DATETIME) END,
|
||||
LastUpdatedUserName = @p2,
|
||||
LastUpdatedDate = GETDATE()
|
||||
WHERE OrderHeaderID = @p3;
|
||||
`, dueDate, username, orderHeaderID)
|
||||
return err
|
||||
}
|
||||
|
||||
type sqlQueryRower interface {
|
||||
QueryRow(query string, args ...any) *sql.Row
|
||||
}
|
||||
|
||||
type plannedProductionBarcode struct {
|
||||
Barcode string
|
||||
BarcodeTypeCode string
|
||||
ItemTypeCode int16
|
||||
ItemCode string
|
||||
ColorCode string
|
||||
ItemDim1Code string
|
||||
ItemDim2Code string
|
||||
ItemDim3Code string
|
||||
}
|
||||
|
||||
func barcodeTypeExists(q sqlQueryRower, barcodeTypeCode string) (bool, error) {
|
||||
var exists int
|
||||
err := q.QueryRow(`
|
||||
SELECT TOP 1 1
|
||||
FROM dbo.cdBarcodeType
|
||||
WHERE BarcodeTypeCode = @p1
|
||||
`, strings.TrimSpace(barcodeTypeCode)).Scan(&exists)
|
||||
if err == sql.ErrNoRows {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func barcodeExists(q sqlQueryRower, barcode string) (bool, error) {
|
||||
var exists int
|
||||
err := q.QueryRow(`
|
||||
SELECT TOP 1 1
|
||||
FROM dbo.prItemBarcode WITH (UPDLOCK, HOLDLOCK)
|
||||
WHERE Barcode = @p1
|
||||
`, strings.TrimSpace(barcode)).Scan(&exists)
|
||||
if err == sql.ErrNoRows {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func existingVariantBarcode(q sqlQueryRower, barcodeTypeCode string, itemTypeCode int16, itemCode string, colorCode string, dim1 string, dim2 string, dim3 string) (string, bool, error) {
|
||||
var barcode string
|
||||
err := q.QueryRow(`
|
||||
SELECT TOP 1 LTRIM(RTRIM(ISNULL(Barcode, '')))
|
||||
FROM dbo.prItemBarcode WITH (UPDLOCK, HOLDLOCK)
|
||||
WHERE BarcodeTypeCode = @p1
|
||||
AND ItemTypeCode = @p2
|
||||
AND ISNULL(LTRIM(RTRIM(ItemCode)), '') = @p3
|
||||
AND ISNULL(LTRIM(RTRIM(ColorCode)), '') = @p4
|
||||
AND ISNULL(LTRIM(RTRIM(ItemDim1Code)), '') = @p5
|
||||
AND ISNULL(LTRIM(RTRIM(ItemDim2Code)), '') = @p6
|
||||
AND ISNULL(LTRIM(RTRIM(ItemDim3Code)), '') = @p7
|
||||
AND ISNULL(LTRIM(RTRIM(UnitOfMeasureCode)), '') = 'AD'
|
||||
ORDER BY TRY_CONVERT(BIGINT, NULLIF(LTRIM(RTRIM(Barcode)), '')) DESC, Barcode DESC
|
||||
`, strings.TrimSpace(barcodeTypeCode), itemTypeCode, strings.TrimSpace(itemCode), strings.TrimSpace(colorCode), strings.TrimSpace(dim1), strings.TrimSpace(dim2), strings.TrimSpace(dim3)).Scan(&barcode)
|
||||
if err == sql.ErrNoRows {
|
||||
return "", false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
return strings.TrimSpace(barcode), true, nil
|
||||
}
|
||||
|
||||
func maxNumericBarcode(q sqlQueryRower) (int64, error) {
|
||||
var maxBarcode int64
|
||||
err := q.QueryRow(`
|
||||
SELECT ISNULL(MAX(TRY_CONVERT(BIGINT, NULLIF(LTRIM(RTRIM(Barcode)), ''))), 0)
|
||||
FROM dbo.prItemBarcode WITH (UPDLOCK, HOLDLOCK)
|
||||
`).Scan(&maxBarcode)
|
||||
return maxBarcode, err
|
||||
}
|
||||
|
||||
func ValidateProductionBarcodePlan(q sqlQueryRower, variants []models.OrderProductionMissingVariant, barcodeTypeCode string) ([]models.OrderProductionBarcodeValidation, error) {
|
||||
typeCode := strings.ToUpper(strings.TrimSpace(barcodeTypeCode))
|
||||
if len(variants) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
validations := make([]models.OrderProductionBarcodeValidation, 0)
|
||||
typeExists, err := barcodeTypeExists(q, typeCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !typeExists {
|
||||
validations = append(validations, models.OrderProductionBarcodeValidation{
|
||||
Code: "invalid_barcode_type",
|
||||
Message: fmt.Sprintf("Barkod tipi bulunamadi: %s", typeCode),
|
||||
BarcodeTypeCode: typeCode,
|
||||
})
|
||||
return validations, nil
|
||||
}
|
||||
|
||||
sorted := append([]models.OrderProductionMissingVariant(nil), variants...)
|
||||
sort.Slice(sorted, func(i, j int) bool {
|
||||
left := sorted[i]
|
||||
right := sorted[j]
|
||||
leftKey := fmt.Sprintf("%05d|%s|%s|%s|%s|%s", left.ItemTypeCode, left.ItemCode, left.ColorCode, left.ItemDim1Code, left.ItemDim2Code, left.ItemDim3Code)
|
||||
rightKey := fmt.Sprintf("%05d|%s|%s|%s|%s|%s", right.ItemTypeCode, right.ItemCode, right.ColorCode, right.ItemDim1Code, right.ItemDim2Code, right.ItemDim3Code)
|
||||
return leftKey < rightKey
|
||||
})
|
||||
|
||||
maxBarcode, err := maxNumericBarcode(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nextOffset := int64(0)
|
||||
planned := make(map[string]struct{}, len(sorted))
|
||||
for _, variant := range sorted {
|
||||
existingBarcode, exists, err := existingVariantBarcode(q, typeCode, variant.ItemTypeCode, variant.ItemCode, variant.ColorCode, variant.ItemDim1Code, variant.ItemDim2Code, variant.ItemDim3Code)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exists && existingBarcode != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
nextOffset++
|
||||
barcode := strconv.FormatInt(maxBarcode+nextOffset, 10)
|
||||
if _, duplicated := planned[barcode]; duplicated {
|
||||
validations = append(validations, models.OrderProductionBarcodeValidation{
|
||||
Code: "barcode_duplicate_in_plan",
|
||||
Message: fmt.Sprintf("Planlanan barkod ayni istekte birden fazla kez olusuyor: %s", barcode),
|
||||
Barcode: barcode,
|
||||
BarcodeTypeCode: typeCode,
|
||||
ItemTypeCode: variant.ItemTypeCode,
|
||||
ItemCode: strings.TrimSpace(variant.ItemCode),
|
||||
ColorCode: strings.TrimSpace(variant.ColorCode),
|
||||
ItemDim1Code: strings.TrimSpace(variant.ItemDim1Code),
|
||||
ItemDim2Code: strings.TrimSpace(variant.ItemDim2Code),
|
||||
ItemDim3Code: strings.TrimSpace(variant.ItemDim3Code),
|
||||
})
|
||||
continue
|
||||
}
|
||||
planned[barcode] = struct{}{}
|
||||
|
||||
inUse, err := barcodeExists(q, barcode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if inUse {
|
||||
validations = append(validations, models.OrderProductionBarcodeValidation{
|
||||
Code: "barcode_in_use",
|
||||
Message: fmt.Sprintf("Barkod daha once kullanilmis: %s (%s / %s / %s / %s)", barcode, strings.TrimSpace(variant.ItemCode), strings.TrimSpace(variant.ColorCode), strings.TrimSpace(variant.ItemDim1Code), strings.TrimSpace(variant.ItemDim2Code)),
|
||||
Barcode: barcode,
|
||||
BarcodeTypeCode: typeCode,
|
||||
ItemTypeCode: variant.ItemTypeCode,
|
||||
ItemCode: strings.TrimSpace(variant.ItemCode),
|
||||
ColorCode: strings.TrimSpace(variant.ColorCode),
|
||||
ItemDim1Code: strings.TrimSpace(variant.ItemDim1Code),
|
||||
ItemDim2Code: strings.TrimSpace(variant.ItemDim2Code),
|
||||
ItemDim3Code: strings.TrimSpace(variant.ItemDim3Code),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return validations, nil
|
||||
}
|
||||
|
||||
func UpsertItemBarcodesTx(tx *sql.Tx, orderHeaderID string, lines []models.OrderProductionUpdateLine, username string) (int64, error) {
|
||||
start := time.Now()
|
||||
if len(lines) == 0 {
|
||||
log.Printf("[UpsertItemBarcodesTx] lines=0 inserted=0 duration_ms=0")
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
lineIDs := make([]string, 0, len(lines))
|
||||
seen := make(map[string]struct{}, len(lines))
|
||||
for _, line := range lines {
|
||||
lineID := strings.TrimSpace(line.OrderLineID)
|
||||
if lineID == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[lineID]; ok {
|
||||
continue
|
||||
}
|
||||
seen[lineID] = struct{}{}
|
||||
lineIDs = append(lineIDs, lineID)
|
||||
}
|
||||
if len(lineIDs) == 0 {
|
||||
log.Printf("[UpsertItemBarcodesTx] lines=%d uniqueLineIDs=0 inserted=0 duration_ms=%d", len(lines), time.Since(start).Milliseconds())
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
const chunkSize = 900
|
||||
var inserted int64
|
||||
|
||||
for i := 0; i < len(lineIDs); i += chunkSize {
|
||||
end := i + chunkSize
|
||||
if end > len(lineIDs) {
|
||||
end = len(lineIDs)
|
||||
}
|
||||
chunk := lineIDs[i:end]
|
||||
|
||||
values := make([]string, 0, len(chunk))
|
||||
args := make([]any, 0, len(chunk)+2)
|
||||
paramPos := 1
|
||||
for _, lineID := range chunk {
|
||||
values = append(values, fmt.Sprintf("(@p%d)", paramPos))
|
||||
args = append(args, lineID)
|
||||
paramPos++
|
||||
}
|
||||
|
||||
orderHeaderParam := paramPos
|
||||
usernameParam := paramPos + 1
|
||||
args = append(args, orderHeaderID, username)
|
||||
|
||||
query := fmt.Sprintf(`
|
||||
SET NOCOUNT ON;
|
||||
WITH srcLine (OrderLineID) AS (
|
||||
SELECT *
|
||||
FROM (VALUES %s) AS v (OrderLineID)
|
||||
),
|
||||
src AS (
|
||||
SELECT DISTINCT
|
||||
l.ItemTypeCode,
|
||||
UPPER(LTRIM(RTRIM(ISNULL(l.ItemCode, '')))) AS ItemCode,
|
||||
UPPER(LTRIM(RTRIM(ISNULL(l.ColorCode, '')))) AS ColorCode,
|
||||
UPPER(LTRIM(RTRIM(ISNULL(l.ItemDim1Code, '')))) AS ItemDim1Code,
|
||||
UPPER(LTRIM(RTRIM(ISNULL(l.ItemDim2Code, '')))) AS ItemDim2Code,
|
||||
CAST('' AS NVARCHAR(50)) AS ItemDim3Code
|
||||
FROM dbo.trOrderLine l WITH (UPDLOCK, HOLDLOCK)
|
||||
JOIN srcLine s
|
||||
ON CAST(l.OrderLineID AS NVARCHAR(50)) = s.OrderLineID
|
||||
WHERE l.OrderHeaderID = @p%d
|
||||
AND NULLIF(LTRIM(RTRIM(ISNULL(l.ItemCode, ''))), '') IS NOT NULL
|
||||
),
|
||||
missing AS (
|
||||
SELECT
|
||||
s.ItemTypeCode,
|
||||
s.ItemCode,
|
||||
s.ColorCode,
|
||||
s.ItemDim1Code,
|
||||
s.ItemDim2Code,
|
||||
s.ItemDim3Code,
|
||||
ROW_NUMBER() OVER (
|
||||
ORDER BY s.ItemCode, s.ColorCode, s.ItemDim1Code, s.ItemDim2Code, s.ItemDim3Code
|
||||
) AS RowNo
|
||||
FROM src s
|
||||
LEFT JOIN dbo.prItemBarcode b WITH (UPDLOCK, HOLDLOCK)
|
||||
ON UPPER(LTRIM(RTRIM(ISNULL(b.BarcodeTypeCode, '')))) = 'BAGGI3'
|
||||
AND b.ItemTypeCode = s.ItemTypeCode
|
||||
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemCode, '')))) = s.ItemCode
|
||||
AND UPPER(LTRIM(RTRIM(ISNULL(b.ColorCode, '')))) = s.ColorCode
|
||||
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemDim1Code, '')))) = s.ItemDim1Code
|
||||
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemDim2Code, '')))) = s.ItemDim2Code
|
||||
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemDim3Code, '')))) = s.ItemDim3Code
|
||||
AND UPPER(LTRIM(RTRIM(ISNULL(b.UnitOfMeasureCode, '')))) = 'AD'
|
||||
WHERE b.Barcode IS NULL
|
||||
)
|
||||
INSERT INTO dbo.prItemBarcode (
|
||||
Barcode,
|
||||
BarcodeTypeCode,
|
||||
ItemTypeCode,
|
||||
ItemCode,
|
||||
ColorCode,
|
||||
ItemDim1Code,
|
||||
ItemDim2Code,
|
||||
ItemDim3Code,
|
||||
UnitOfMeasureCode,
|
||||
Qty,
|
||||
CreatedUserName,
|
||||
CreatedDate,
|
||||
LastUpdatedUserName,
|
||||
LastUpdatedDate,
|
||||
RowGuid
|
||||
)
|
||||
SELECT
|
||||
CAST(seed.MaxBarcode + m.RowNo AS NVARCHAR(50)) AS Barcode,
|
||||
'BAGGI3',
|
||||
m.ItemTypeCode,
|
||||
m.ItemCode,
|
||||
m.ColorCode,
|
||||
m.ItemDim1Code,
|
||||
m.ItemDim2Code,
|
||||
m.ItemDim3Code,
|
||||
'AD',
|
||||
1,
|
||||
@p%d,
|
||||
GETDATE(),
|
||||
@p%d,
|
||||
GETDATE(),
|
||||
NEWID()
|
||||
FROM missing m
|
||||
CROSS JOIN (
|
||||
SELECT ISNULL(MAX(TRY_CONVERT(BIGINT, NULLIF(LTRIM(RTRIM(Barcode)), ''))), 0) AS MaxBarcode
|
||||
FROM dbo.prItemBarcode WITH (UPDLOCK, HOLDLOCK)
|
||||
) seed;
|
||||
|
||||
SELECT @@ROWCOUNT AS Inserted;
|
||||
`, strings.Join(values, ","), orderHeaderParam, usernameParam, usernameParam)
|
||||
|
||||
chunkStart := time.Now()
|
||||
var chunkInserted int64
|
||||
if err := tx.QueryRow(query, args...).Scan(&chunkInserted); err != nil {
|
||||
return inserted, fmt.Errorf("upsert item barcodes chunk failed chunkStart=%d chunkEnd=%d duration_ms=%d: %w", i, end, time.Since(chunkStart).Milliseconds(), err)
|
||||
}
|
||||
inserted += chunkInserted
|
||||
log.Printf("[UpsertItemBarcodesTx] orderHeaderID=%s chunk=%d-%d chunkInserted=%d cumulative=%d duration_ms=%d",
|
||||
orderHeaderID, i, end, chunkInserted, inserted, time.Since(chunkStart).Milliseconds())
|
||||
}
|
||||
|
||||
log.Printf("[UpsertItemBarcodesTx] orderHeaderID=%s lines=%d uniqueLineIDs=%d inserted=%d duration_ms=%d",
|
||||
orderHeaderID, len(lines), len(lineIDs), inserted, time.Since(start).Milliseconds())
|
||||
return inserted, nil
|
||||
}
|
||||
|
||||
func UpsertItemAttributesTx(tx *sql.Tx, attrs []models.OrderProductionItemAttributeRow, username string) (int64, error) {
|
||||
start := time.Now()
|
||||
if len(attrs) == 0 {
|
||||
|
||||
193
svc/queries/product_pricing.go
Normal file
193
svc/queries/product_pricing.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"bssapp-backend/db"
|
||||
"bssapp-backend/models"
|
||||
)
|
||||
|
||||
func GetProductPricingList() ([]models.ProductPricing, error) {
|
||||
rows, err := db.MssqlDB.Query(`
|
||||
WITH base_products AS (
|
||||
SELECT
|
||||
LTRIM(RTRIM(ProductCode)) AS ProductCode,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '') AS AskiliYan,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '') AS Kategori,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '') AS UrunIlkGrubu,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '') AS UrunAnaGrubu,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '') AS UrunAltGrubu,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '') AS Icerik,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '') AS Karisim,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '') AS Marka
|
||||
FROM ProductFilterWithDescription('TR')
|
||||
WHERE ProductAtt42 IN ('SERI', 'AKSESUAR')
|
||||
AND IsBlocked = 0
|
||||
AND LEN(LTRIM(RTRIM(ProductCode))) = 13
|
||||
),
|
||||
latest_base_price AS (
|
||||
SELECT
|
||||
LTRIM(RTRIM(b.ItemCode)) AS ItemCode,
|
||||
CAST(b.Price AS DECIMAL(18, 2)) AS CostPrice,
|
||||
CONVERT(VARCHAR(10), b.PriceDate, 23) AS LastPricingDate,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY LTRIM(RTRIM(b.ItemCode))
|
||||
ORDER BY b.PriceDate DESC, b.LastUpdatedDate DESC
|
||||
) AS rn
|
||||
FROM prItemBasePrice b
|
||||
WHERE b.ItemTypeCode = 1
|
||||
AND b.BasePriceCode = 1
|
||||
AND LTRIM(RTRIM(b.CurrencyCode)) = 'USD'
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM base_products bp
|
||||
WHERE bp.ProductCode = LTRIM(RTRIM(b.ItemCode))
|
||||
)
|
||||
),
|
||||
stock_entry_dates AS (
|
||||
SELECT
|
||||
LTRIM(RTRIM(s.ItemCode)) AS ItemCode,
|
||||
CONVERT(VARCHAR(10), MAX(s.OperationDate), 23) AS StockEntryDate
|
||||
FROM trStock s WITH(NOLOCK)
|
||||
WHERE s.ItemTypeCode = 1
|
||||
AND LEN(LTRIM(RTRIM(s.ItemCode))) = 13
|
||||
AND s.In_Qty1 > 0
|
||||
AND LTRIM(RTRIM(s.WarehouseCode)) IN (
|
||||
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
||||
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
||||
'1-0-33','101','1-014','1-0-49','1-0-36'
|
||||
)
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM base_products bp
|
||||
WHERE bp.ProductCode = LTRIM(RTRIM(s.ItemCode))
|
||||
)
|
||||
GROUP BY LTRIM(RTRIM(s.ItemCode))
|
||||
),
|
||||
stock_base AS (
|
||||
SELECT
|
||||
LTRIM(RTRIM(s.ItemCode)) AS ItemCode,
|
||||
SUM(s.In_Qty1 - s.Out_Qty1) AS InventoryQty1
|
||||
FROM trStock s WITH(NOLOCK)
|
||||
WHERE s.ItemTypeCode = 1
|
||||
AND LEN(LTRIM(RTRIM(s.ItemCode))) = 13
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM base_products bp
|
||||
WHERE bp.ProductCode = LTRIM(RTRIM(s.ItemCode))
|
||||
)
|
||||
GROUP BY LTRIM(RTRIM(s.ItemCode))
|
||||
),
|
||||
pick_base AS (
|
||||
SELECT
|
||||
LTRIM(RTRIM(p.ItemCode)) AS ItemCode,
|
||||
SUM(p.Qty1) AS PickingQty1
|
||||
FROM PickingStates p
|
||||
WHERE p.ItemTypeCode = 1
|
||||
AND LEN(LTRIM(RTRIM(p.ItemCode))) = 13
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM base_products bp
|
||||
WHERE bp.ProductCode = LTRIM(RTRIM(p.ItemCode))
|
||||
)
|
||||
GROUP BY LTRIM(RTRIM(p.ItemCode))
|
||||
),
|
||||
reserve_base AS (
|
||||
SELECT
|
||||
LTRIM(RTRIM(r.ItemCode)) AS ItemCode,
|
||||
SUM(r.Qty1) AS ReserveQty1
|
||||
FROM ReserveStates r
|
||||
WHERE r.ItemTypeCode = 1
|
||||
AND LEN(LTRIM(RTRIM(r.ItemCode))) = 13
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM base_products bp
|
||||
WHERE bp.ProductCode = LTRIM(RTRIM(r.ItemCode))
|
||||
)
|
||||
GROUP BY LTRIM(RTRIM(r.ItemCode))
|
||||
),
|
||||
disp_base AS (
|
||||
SELECT
|
||||
LTRIM(RTRIM(d.ItemCode)) AS ItemCode,
|
||||
SUM(d.Qty1) AS DispOrderQty1
|
||||
FROM DispOrderStates d
|
||||
WHERE d.ItemTypeCode = 1
|
||||
AND LEN(LTRIM(RTRIM(d.ItemCode))) = 13
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM base_products bp
|
||||
WHERE bp.ProductCode = LTRIM(RTRIM(d.ItemCode))
|
||||
)
|
||||
GROUP BY LTRIM(RTRIM(d.ItemCode))
|
||||
),
|
||||
stock_totals AS (
|
||||
SELECT
|
||||
bp.ProductCode AS ItemCode,
|
||||
CAST(ROUND(
|
||||
ISNULL(sb.InventoryQty1, 0)
|
||||
- ISNULL(pb.PickingQty1, 0)
|
||||
- ISNULL(rb.ReserveQty1, 0)
|
||||
- ISNULL(db.DispOrderQty1, 0)
|
||||
, 2) AS DECIMAL(18, 2)) AS StockQty
|
||||
FROM base_products bp
|
||||
LEFT JOIN stock_base sb
|
||||
ON sb.ItemCode = bp.ProductCode
|
||||
LEFT JOIN pick_base pb
|
||||
ON pb.ItemCode = bp.ProductCode
|
||||
LEFT JOIN reserve_base rb
|
||||
ON rb.ItemCode = bp.ProductCode
|
||||
LEFT JOIN disp_base db
|
||||
ON db.ItemCode = bp.ProductCode
|
||||
)
|
||||
SELECT
|
||||
bp.ProductCode AS ProductCode,
|
||||
COALESCE(lp.CostPrice, 0) AS CostPrice,
|
||||
COALESCE(st.StockQty, 0) AS StockQty,
|
||||
COALESCE(se.StockEntryDate, '') AS StockEntryDate,
|
||||
COALESCE(lp.LastPricingDate, '') AS LastPricingDate,
|
||||
bp.AskiliYan,
|
||||
bp.Kategori,
|
||||
bp.UrunIlkGrubu,
|
||||
bp.UrunAnaGrubu,
|
||||
bp.UrunAltGrubu,
|
||||
bp.Icerik,
|
||||
bp.Karisim,
|
||||
bp.Marka
|
||||
FROM base_products bp
|
||||
LEFT JOIN latest_base_price lp
|
||||
ON lp.ItemCode = bp.ProductCode
|
||||
AND lp.rn = 1
|
||||
LEFT JOIN stock_entry_dates se
|
||||
ON se.ItemCode = bp.ProductCode
|
||||
LEFT JOIN stock_totals st
|
||||
ON st.ItemCode = bp.ProductCode
|
||||
ORDER BY bp.ProductCode;
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var out []models.ProductPricing
|
||||
for rows.Next() {
|
||||
var item models.ProductPricing
|
||||
if err := rows.Scan(
|
||||
&item.ProductCode,
|
||||
&item.CostPrice,
|
||||
&item.StockQty,
|
||||
&item.StockEntryDate,
|
||||
&item.LastPricingDate,
|
||||
&item.AskiliYan,
|
||||
&item.Kategori,
|
||||
&item.UrunIlkGrubu,
|
||||
&item.UrunAnaGrubu,
|
||||
&item.UrunAltGrubu,
|
||||
&item.Icerik,
|
||||
&item.Karisim,
|
||||
&item.Marka,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, item)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
@@ -464,6 +464,7 @@ func UserCreateRoute(db *sql.DB) http.HandlerFunc {
|
||||
defer tx.Rollback()
|
||||
|
||||
var newID int64
|
||||
log.Printf("DEBUG: UserCreateRoute payload=%+v", payload)
|
||||
err = tx.QueryRow(`
|
||||
INSERT INTO mk_dfusr (
|
||||
username,
|
||||
@@ -472,11 +473,12 @@ func UserCreateRoute(db *sql.DB) http.HandlerFunc {
|
||||
email,
|
||||
mobile,
|
||||
address,
|
||||
password_hash,
|
||||
force_password_change,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,true,NOW(),NOW())
|
||||
VALUES ($1,$2,$3,$4,$5,$6,'',true,NOW(),NOW())
|
||||
RETURNING id
|
||||
`,
|
||||
payload.Code,
|
||||
@@ -489,7 +491,7 @@ func UserCreateRoute(db *sql.DB) http.HandlerFunc {
|
||||
|
||||
if err != nil {
|
||||
log.Printf("USER INSERT ERROR code=%q email=%q err=%v", payload.Code, payload.Email, err)
|
||||
http.Error(w, "Kullanıcı oluşturulamadı", http.StatusInternalServerError)
|
||||
http.Error(w, fmt.Sprintf("Kullanıcı oluşturulamadı: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -15,12 +15,23 @@ import (
|
||||
)
|
||||
|
||||
type sendOrderMarketMailPayload struct {
|
||||
OrderHeaderID string `json:"orderHeaderID"`
|
||||
Operation string `json:"operation"`
|
||||
DeletedItems []string `json:"deletedItems"`
|
||||
UpdatedItems []string `json:"updatedItems"`
|
||||
AddedItems []string `json:"addedItems"`
|
||||
ExtraRecipients []string `json:"extraRecipients"`
|
||||
OrderHeaderID string `json:"orderHeaderID"`
|
||||
Operation string `json:"operation"`
|
||||
DeletedItems []string `json:"deletedItems"`
|
||||
UpdatedItems []string `json:"updatedItems"`
|
||||
AddedItems []string `json:"addedItems"`
|
||||
OldDueDate string `json:"oldDueDate"`
|
||||
NewDueDate string `json:"newDueDate"`
|
||||
ExtraRecipients []string `json:"extraRecipients"`
|
||||
DueDateChanges []sendOrderMailDueDateChange `json:"dueDateChanges"`
|
||||
}
|
||||
|
||||
type sendOrderMailDueDateChange struct {
|
||||
ItemCode string `json:"itemCode"`
|
||||
ColorCode string `json:"colorCode"`
|
||||
ItemDim2Code string `json:"itemDim2Code"`
|
||||
OldDueDate string `json:"oldDueDate"`
|
||||
NewDueDate string `json:"newDueDate"`
|
||||
}
|
||||
|
||||
func SendOrderMarketMailHandler(pg *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) http.HandlerFunc {
|
||||
@@ -108,6 +119,18 @@ func SendOrderMarketMailHandler(pg *sql.DB, mssql *sql.DB, ml *mailer.GraphMaile
|
||||
if isUpdate {
|
||||
subjectAction = "SİPARİŞ GÜNCELLENDİ."
|
||||
}
|
||||
if payload.NewDueDate != "" && payload.OldDueDate != payload.NewDueDate {
|
||||
subjectAction = "SİPARİŞ TERMİNİ GÜNCELLENDİ."
|
||||
}
|
||||
if isUpdate && subjectAction == "SİPARİŞ GÜNCELLENDİ." {
|
||||
// Satır bazlı termin kontrolü
|
||||
for _, item := range payload.UpdatedItems {
|
||||
if strings.Contains(item, "Termin:") {
|
||||
subjectAction = "SİPARİŞ TERMİNİ GÜNCELLENDİ."
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
subject := fmt.Sprintf("%s kullanıcısı tarafından %s %s", actor, number, subjectAction)
|
||||
|
||||
cariDetail := ""
|
||||
@@ -127,6 +150,13 @@ func SendOrderMarketMailHandler(pg *sql.DB, mssql *sql.DB, ml *mailer.GraphMaile
|
||||
`</p>`,
|
||||
)
|
||||
|
||||
if payload.NewDueDate != "" && payload.OldDueDate != payload.NewDueDate {
|
||||
body = append(body,
|
||||
fmt.Sprintf(`<p><b>Termin Değişikliği:</b> %s → <b style="color:red">%s</b></p>`,
|
||||
htmlEsc(payload.OldDueDate), htmlEsc(payload.NewDueDate)),
|
||||
)
|
||||
}
|
||||
|
||||
if isUpdate {
|
||||
body = append(body,
|
||||
renderItemListHTML("Silinen Ürün Kodları", payload.DeletedItems),
|
||||
@@ -137,6 +167,10 @@ func SendOrderMarketMailHandler(pg *sql.DB, mssql *sql.DB, ml *mailer.GraphMaile
|
||||
|
||||
body = append(body, `<p><i>Bu sipariş BaggiSS App Uygulamasından oluşturulmuştur.</i></p>`)
|
||||
body = append(body, `<p>PDF ektedir.</p>`)
|
||||
if dueDateTableHTML := renderDueDateChangesTableHTML("Termin DeÄŸiÅŸiklikleri", payload.DueDateChanges); dueDateTableHTML != "" {
|
||||
body = append(body, dueDateTableHTML)
|
||||
}
|
||||
|
||||
bodyHTML := strings.Join(body, "\n")
|
||||
|
||||
fileNo := sanitizeFileName(number)
|
||||
@@ -393,3 +427,54 @@ func renderItemListHTML(title string, items []string) string {
|
||||
b = append(b, `</p>`)
|
||||
return strings.Join(b, "\n")
|
||||
}
|
||||
|
||||
func renderDueDateChangesTableHTML(title string, rows []sendOrderMailDueDateChange) string {
|
||||
if len(rows) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
seen := make(map[string]struct{}, len(rows))
|
||||
clean := make([]sendOrderMailDueDateChange, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
itemCode := strings.TrimSpace(row.ItemCode)
|
||||
colorCode := strings.TrimSpace(row.ColorCode)
|
||||
itemDim2Code := strings.TrimSpace(row.ItemDim2Code)
|
||||
oldDueDate := strings.TrimSpace(row.OldDueDate)
|
||||
newDueDate := strings.TrimSpace(row.NewDueDate)
|
||||
if itemCode == "" || newDueDate == "" || oldDueDate == newDueDate {
|
||||
continue
|
||||
}
|
||||
key := strings.ToUpper(strings.Join([]string{itemCode, colorCode, itemDim2Code, oldDueDate, newDueDate}, "|"))
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
clean = append(clean, sendOrderMailDueDateChange{
|
||||
ItemCode: itemCode,
|
||||
ColorCode: colorCode,
|
||||
ItemDim2Code: itemDim2Code,
|
||||
OldDueDate: oldDueDate,
|
||||
NewDueDate: newDueDate,
|
||||
})
|
||||
}
|
||||
|
||||
if len(clean) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString(fmt.Sprintf(`<p><b>%s:</b></p>`, htmlEsc(title)))
|
||||
b.WriteString(`<table border="1" cellpadding="5" style="border-collapse: collapse; width: 100%;">`)
|
||||
b.WriteString(`<tr style="background-color: #f2f2f2;"><th>Ürün Kodu</th><th>Renk</th><th>2. Renk</th><th>Eski Termin</th><th>Yeni Termin</th></tr>`)
|
||||
for _, row := range clean {
|
||||
b.WriteString("<tr>")
|
||||
b.WriteString(fmt.Sprintf("<td>%s</td>", htmlEsc(row.ItemCode)))
|
||||
b.WriteString(fmt.Sprintf("<td>%s</td>", htmlEsc(row.ColorCode)))
|
||||
b.WriteString(fmt.Sprintf("<td>%s</td>", htmlEsc(row.ItemDim2Code)))
|
||||
b.WriteString(fmt.Sprintf("<td>%s</td>", htmlEsc(row.OldDueDate)))
|
||||
b.WriteString(fmt.Sprintf(`<td style="color:red;font-weight:bold;">%s</td>`, htmlEsc(row.NewDueDate)))
|
||||
b.WriteString("</tr>")
|
||||
}
|
||||
b.WriteString(`</table>`)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package routes
|
||||
|
||||
import (
|
||||
"bssapp-backend/auth"
|
||||
"bssapp-backend/internal/mailer"
|
||||
"bssapp-backend/models"
|
||||
"bssapp-backend/queries"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -20,6 +22,8 @@ import (
|
||||
|
||||
var baggiModelCodeRegex = regexp.MustCompile(`^[A-Z][0-9]{3}-[A-Z]{3}[0-9]{5}$`)
|
||||
|
||||
const productionBarcodeTypeCode = "BAGGI3"
|
||||
|
||||
// ======================================================
|
||||
// 📌 OrderProductionItemsRoute — U ürün satırları
|
||||
// ======================================================
|
||||
@@ -54,12 +58,16 @@ func OrderProductionItemsRoute(mssql *sql.DB) http.Handler {
|
||||
&o.OldDim3,
|
||||
&o.OldItemCode,
|
||||
&o.OldColor,
|
||||
&o.OldColorDescription,
|
||||
&o.OldDim2,
|
||||
&o.OldDesc,
|
||||
&o.OldQty,
|
||||
&o.NewItemCode,
|
||||
&o.NewColor,
|
||||
&o.NewDim2,
|
||||
&o.NewDesc,
|
||||
&o.OldDueDate,
|
||||
&o.NewDueDate,
|
||||
&o.IsVariantMissing,
|
||||
); err != nil {
|
||||
log.Printf("⚠️ SCAN HATASI: %v", err)
|
||||
@@ -183,9 +191,22 @@ func OrderProductionValidateRoute(mssql *sql.DB) http.Handler {
|
||||
log.Printf("[OrderProductionValidateRoute] rid=%s orderHeaderID=%s lineCount=%d missingCount=%d build_missing_ms=%d total_ms=%d",
|
||||
rid, id, len(payload.Lines), len(missing), time.Since(stepStart).Milliseconds(), time.Since(start).Milliseconds())
|
||||
|
||||
targetVariants, err := buildTargetVariants(mssql, id, payload.Lines)
|
||||
if err != nil {
|
||||
writeDBError(w, http.StatusInternalServerError, "validate_barcode_targets", id, "", len(payload.Lines), err)
|
||||
return
|
||||
}
|
||||
barcodeValidations, err := queries.ValidateProductionBarcodePlan(mssql, targetVariants, productionBarcodeTypeCode)
|
||||
if err != nil {
|
||||
writeDBError(w, http.StatusInternalServerError, "validate_barcodes", id, "", len(payload.Lines), err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := map[string]any{
|
||||
"missingCount": len(missing),
|
||||
"missing": missing,
|
||||
"missingCount": len(missing),
|
||||
"missing": missing,
|
||||
"barcodeValidationCount": len(barcodeValidations),
|
||||
"barcodeValidations": barcodeValidations,
|
||||
}
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("❌ encode error: %v", err)
|
||||
@@ -196,7 +217,7 @@ func OrderProductionValidateRoute(mssql *sql.DB) http.Handler {
|
||||
// ======================================================
|
||||
// OrderProductionApplyRoute - yeni model varyant guncelleme
|
||||
// ======================================================
|
||||
func OrderProductionApplyRoute(mssql *sql.DB) http.Handler {
|
||||
func OrderProductionApplyRoute(mssql *sql.DB, ml *mailer.GraphMailer) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
rid := fmt.Sprintf("opa-%d", time.Now().UnixNano())
|
||||
@@ -232,6 +253,12 @@ func OrderProductionApplyRoute(mssql *sql.DB) http.Handler {
|
||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s lineCount=%d missingCount=%d build_missing_ms=%d",
|
||||
rid, id, len(payload.Lines), len(missing), time.Since(stepMissingStart).Milliseconds())
|
||||
|
||||
targetVariants, err := buildTargetVariants(mssql, id, payload.Lines)
|
||||
if err != nil {
|
||||
writeDBError(w, http.StatusInternalServerError, "apply_barcode_targets", id, "", len(payload.Lines), err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(missing) > 0 && !payload.InsertMissing {
|
||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s early_exit=missing_variants total_ms=%d",
|
||||
rid, id, time.Since(start).Milliseconds())
|
||||
@@ -282,6 +309,24 @@ func OrderProductionApplyRoute(mssql *sql.DB) http.Handler {
|
||||
rid, id, inserted, time.Since(stepInsertMissingStart).Milliseconds())
|
||||
}
|
||||
|
||||
stepValidateBarcodeStart := time.Now()
|
||||
barcodeValidations, err := queries.ValidateProductionBarcodePlan(tx, targetVariants, productionBarcodeTypeCode)
|
||||
if err != nil {
|
||||
writeDBError(w, http.StatusInternalServerError, "validate_barcodes_before_apply", id, username, len(payload.Lines), err)
|
||||
return
|
||||
}
|
||||
if len(barcodeValidations) > 0 {
|
||||
w.WriteHeader(http.StatusConflict)
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"message": "Barkod validasyonu basarisiz",
|
||||
"barcodeValidationCount": len(barcodeValidations),
|
||||
"barcodeValidations": barcodeValidations,
|
||||
})
|
||||
return
|
||||
}
|
||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=validate_barcodes count=%d duration_ms=%d",
|
||||
rid, id, len(barcodeValidations), time.Since(stepValidateBarcodeStart).Milliseconds())
|
||||
|
||||
stepValidateAttrStart := time.Now()
|
||||
if err := validateProductAttributes(payload.ProductAttributes); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
@@ -290,6 +335,14 @@ func OrderProductionApplyRoute(mssql *sql.DB) http.Handler {
|
||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=validate_attributes count=%d duration_ms=%d",
|
||||
rid, id, len(payload.ProductAttributes), time.Since(stepValidateAttrStart).Milliseconds())
|
||||
|
||||
stepUpdateHeaderStart := time.Now()
|
||||
if err := queries.UpdateOrderHeaderAverageDueDateTx(tx, id, payload.HeaderAverageDueDate, username); err != nil {
|
||||
writeDBError(w, http.StatusInternalServerError, "update_order_header_average_due_date", id, username, 0, err)
|
||||
return
|
||||
}
|
||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=update_header_average_due_date changed=%t duration_ms=%d",
|
||||
rid, id, payload.HeaderAverageDueDate != nil, time.Since(stepUpdateHeaderStart).Milliseconds())
|
||||
|
||||
stepUpdateLinesStart := time.Now()
|
||||
updated, err := queries.UpdateOrderLinesTx(tx, id, payload.Lines, username)
|
||||
if err != nil {
|
||||
@@ -299,6 +352,15 @@ func OrderProductionApplyRoute(mssql *sql.DB) http.Handler {
|
||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=update_lines updated=%d duration_ms=%d",
|
||||
rid, id, updated, time.Since(stepUpdateLinesStart).Milliseconds())
|
||||
|
||||
stepUpsertBarcodeStart := time.Now()
|
||||
barcodeInserted, err := queries.UpsertItemBarcodesTx(tx, id, payload.Lines, username)
|
||||
if err != nil {
|
||||
writeDBError(w, http.StatusInternalServerError, "upsert_item_barcodes", id, username, len(payload.Lines), err)
|
||||
return
|
||||
}
|
||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=upsert_barcodes inserted=%d duration_ms=%d",
|
||||
rid, id, barcodeInserted, time.Since(stepUpsertBarcodeStart).Milliseconds())
|
||||
|
||||
stepUpsertAttrStart := time.Now()
|
||||
attributeAffected, err := queries.UpsertItemAttributesTx(tx, payload.ProductAttributes, username)
|
||||
if err != nil {
|
||||
@@ -316,13 +378,27 @@ func OrderProductionApplyRoute(mssql *sql.DB) http.Handler {
|
||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=commit duration_ms=%d total_ms=%d",
|
||||
rid, id, time.Since(stepCommitStart).Milliseconds(), time.Since(start).Milliseconds())
|
||||
|
||||
// Mail gönderim mantığı
|
||||
if false && ml != nil {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("[OrderProductionApplyRoute] mail panic recover: %v", r)
|
||||
}
|
||||
}()
|
||||
sendProductionUpdateMails(mssql, ml, id, username, payload.Lines)
|
||||
}()
|
||||
}
|
||||
|
||||
resp := map[string]any{
|
||||
"updated": updated,
|
||||
"inserted": inserted,
|
||||
"barcodeInserted": barcodeInserted,
|
||||
"attributeUpserted": attributeAffected,
|
||||
"headerUpdated": payload.HeaderAverageDueDate != nil,
|
||||
}
|
||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s result updated=%d inserted=%d attributeUpserted=%d",
|
||||
rid, id, updated, inserted, attributeAffected)
|
||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s result updated=%d inserted=%d barcodeInserted=%d attributeUpserted=%d",
|
||||
rid, id, updated, inserted, barcodeInserted, attributeAffected)
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("❌ encode error: %v", err)
|
||||
}
|
||||
@@ -367,21 +443,20 @@ func buildCdItemDraftMap(list []models.OrderProductionCdItemDraft) map[string]mo
|
||||
return out
|
||||
}
|
||||
|
||||
func buildMissingVariants(mssql *sql.DB, orderHeaderID string, lines []models.OrderProductionUpdateLine) ([]models.OrderProductionMissingVariant, error) {
|
||||
func buildTargetVariants(mssql *sql.DB, orderHeaderID string, lines []models.OrderProductionUpdateLine) ([]models.OrderProductionMissingVariant, error) {
|
||||
start := time.Now()
|
||||
missing := make([]models.OrderProductionMissingVariant, 0)
|
||||
lineDimsMap, err := queries.GetOrderLineDimsMap(mssql, orderHeaderID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existsCache := make(map[string]bool, len(lines))
|
||||
|
||||
out := make([]models.OrderProductionMissingVariant, 0, len(lines))
|
||||
seen := make(map[string]struct{}, len(lines))
|
||||
for _, line := range lines {
|
||||
lineID := strings.TrimSpace(line.OrderLineID)
|
||||
newItem := strings.TrimSpace(line.NewItemCode)
|
||||
newColor := strings.TrimSpace(line.NewColor)
|
||||
newDim2 := strings.TrimSpace(line.NewDim2)
|
||||
|
||||
newItem := strings.ToUpper(strings.TrimSpace(line.NewItemCode))
|
||||
newColor := strings.ToUpper(strings.TrimSpace(line.NewColor))
|
||||
newDim2 := strings.ToUpper(strings.TrimSpace(line.NewDim2))
|
||||
if lineID == "" || newItem == "" {
|
||||
continue
|
||||
}
|
||||
@@ -391,38 +466,68 @@ func buildMissingVariants(mssql *sql.DB, orderHeaderID string, lines []models.Or
|
||||
continue
|
||||
}
|
||||
|
||||
dim1 := strings.ToUpper(strings.TrimSpace(dims.ItemDim1Code))
|
||||
if line.ItemDim1Code != nil {
|
||||
dim1 = strings.ToUpper(strings.TrimSpace(*line.ItemDim1Code))
|
||||
}
|
||||
dim3 := strings.ToUpper(strings.TrimSpace(dims.ItemDim3Code))
|
||||
|
||||
key := fmt.Sprintf("%d|%s|%s|%s|%s|%s", dims.ItemTypeCode, newItem, newColor, dim1, newDim2, dim3)
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
|
||||
out = append(out, models.OrderProductionMissingVariant{
|
||||
OrderLineID: lineID,
|
||||
ItemTypeCode: dims.ItemTypeCode,
|
||||
ItemCode: newItem,
|
||||
ColorCode: newColor,
|
||||
ItemDim1Code: dim1,
|
||||
ItemDim2Code: newDim2,
|
||||
ItemDim3Code: dim3,
|
||||
})
|
||||
}
|
||||
|
||||
log.Printf("[buildTargetVariants] orderHeaderID=%s lineCount=%d dimMapCount=%d targetCount=%d total_ms=%d",
|
||||
orderHeaderID, len(lines), len(lineDimsMap), len(out), time.Since(start).Milliseconds())
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func buildMissingVariants(mssql *sql.DB, orderHeaderID string, lines []models.OrderProductionUpdateLine) ([]models.OrderProductionMissingVariant, error) {
|
||||
start := time.Now()
|
||||
targets, err := buildTargetVariants(mssql, orderHeaderID, lines)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
missing := make([]models.OrderProductionMissingVariant, 0, len(targets))
|
||||
existsCache := make(map[string]bool, len(targets))
|
||||
|
||||
for _, target := range targets {
|
||||
cacheKey := fmt.Sprintf("%d|%s|%s|%s|%s|%s",
|
||||
dims.ItemTypeCode,
|
||||
strings.ToUpper(strings.TrimSpace(newItem)),
|
||||
strings.ToUpper(strings.TrimSpace(newColor)),
|
||||
strings.ToUpper(strings.TrimSpace(dims.ItemDim1Code)),
|
||||
strings.ToUpper(strings.TrimSpace(newDim2)),
|
||||
strings.ToUpper(strings.TrimSpace(dims.ItemDim3Code)),
|
||||
target.ItemTypeCode,
|
||||
target.ItemCode,
|
||||
target.ColorCode,
|
||||
target.ItemDim1Code,
|
||||
target.ItemDim2Code,
|
||||
target.ItemDim3Code,
|
||||
)
|
||||
exists, cached := existsCache[cacheKey]
|
||||
if !cached {
|
||||
var checkErr error
|
||||
exists, checkErr = queries.VariantExists(mssql, dims.ItemTypeCode, newItem, newColor, dims.ItemDim1Code, newDim2, dims.ItemDim3Code)
|
||||
exists, checkErr = queries.VariantExists(mssql, target.ItemTypeCode, target.ItemCode, target.ColorCode, target.ItemDim1Code, target.ItemDim2Code, target.ItemDim3Code)
|
||||
if checkErr != nil {
|
||||
return nil, checkErr
|
||||
}
|
||||
existsCache[cacheKey] = exists
|
||||
}
|
||||
if !exists {
|
||||
missing = append(missing, models.OrderProductionMissingVariant{
|
||||
OrderLineID: lineID,
|
||||
ItemTypeCode: dims.ItemTypeCode,
|
||||
ItemCode: newItem,
|
||||
ColorCode: newColor,
|
||||
ItemDim1Code: dims.ItemDim1Code,
|
||||
ItemDim2Code: newDim2,
|
||||
ItemDim3Code: dims.ItemDim3Code,
|
||||
})
|
||||
missing = append(missing, target)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[buildMissingVariants] orderHeaderID=%s lineCount=%d dimMapCount=%d missingCount=%d total_ms=%d",
|
||||
orderHeaderID, len(lines), len(lineDimsMap), len(missing), time.Since(start).Milliseconds())
|
||||
orderHeaderID, len(lines), len(targets), len(missing), time.Since(start).Milliseconds())
|
||||
return missing, nil
|
||||
}
|
||||
|
||||
@@ -464,3 +569,69 @@ func writeDBError(w http.ResponseWriter, status int, step string, orderHeaderID
|
||||
"detail": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
func sendProductionUpdateMails(db *sql.DB, ml *mailer.GraphMailer, orderHeaderID string, actor string, lines []models.OrderProductionUpdateLine) {
|
||||
if len(lines) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Sipariş bağlamını çöz
|
||||
orderNo, currAccCode, marketCode, marketTitle, err := resolveOrderMailContext(db, orderHeaderID)
|
||||
if err != nil {
|
||||
log.Printf("[sendProductionUpdateMails] context error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Piyasa alıcılarını yükle (PG db lazım ama burada mssql üzerinden sadece log atalım veya graphmailer üzerinden gönderelim)
|
||||
// Not: PG bağlantısı Route içinde yok, ancak mailer.go içindeki alıcı listesini payload'dan veya sabit bir adresten alabiliriz.
|
||||
// Kullanıcı "ürün kodu-renk-renk2 eski termin tarihi yeni termin tarihi" bilgisini mailde istiyor.
|
||||
|
||||
subject := fmt.Sprintf("%s tarafından %s Nolu Sipariş Güncellendi (Üretim)", actor, orderNo)
|
||||
|
||||
var body strings.Builder
|
||||
body.WriteString("<html><body>")
|
||||
body.WriteString(fmt.Sprintf("<p><b>Sipariş No:</b> %s</p>", orderNo))
|
||||
body.WriteString(fmt.Sprintf("<p><b>Cari:</b> %s</p>", currAccCode))
|
||||
body.WriteString(fmt.Sprintf("<p><b>Piyasa:</b> %s (%s)</p>", marketTitle, marketCode))
|
||||
body.WriteString("<p>Aşağıdaki satırlarda termin tarihi güncellenmiştir:</p>")
|
||||
body.WriteString("<table border='1' cellpadding='5' style='border-collapse: collapse;'>")
|
||||
body.WriteString("<tr style='background-color: #f2f2f2;'><th>Ürün Kodu</th><th>Renk</th><th>2. Renk</th><th>Eski Termin</th><th>Yeni Termin</th></tr>")
|
||||
|
||||
hasTerminChange := false
|
||||
for _, l := range lines {
|
||||
if l.OldDueDate != l.NewDueDate && l.NewDueDate != "" {
|
||||
hasTerminChange = true
|
||||
body.WriteString("<tr>")
|
||||
body.WriteString(fmt.Sprintf("<td>%s</td>", l.NewItemCode))
|
||||
body.WriteString(fmt.Sprintf("<td>%s</td>", l.NewColor))
|
||||
body.WriteString(fmt.Sprintf("<td>%s</td>", l.NewDim2))
|
||||
body.WriteString(fmt.Sprintf("<td>%s</td>", l.OldDueDate))
|
||||
body.WriteString(fmt.Sprintf("<td style='color: red; font-weight: bold;'>%s</td>", l.NewDueDate))
|
||||
body.WriteString("</tr>")
|
||||
}
|
||||
}
|
||||
body.WriteString("</table>")
|
||||
body.WriteString("<p><i>Bu mail sistem tarafından otomatik oluşturulmuştur.</i></p>")
|
||||
body.WriteString("</body></html>")
|
||||
|
||||
if !hasTerminChange {
|
||||
return
|
||||
}
|
||||
|
||||
// Alıcı listesi için OrderMarketMail'deki mantığı taklit edelim veya sabit bir gruba atalım
|
||||
// Şimdilik sadece loglayalım veya GraphMailer üzerinden test amaçlı bir yere atalım
|
||||
// Gerçek uygulamada pgDB üzerinden alıcılar çekilmeli.
|
||||
recipients := []string{"urun@baggi.com.tr"} // Varsayılan alıcı
|
||||
|
||||
msg := mailer.Message{
|
||||
To: recipients,
|
||||
Subject: subject,
|
||||
BodyHTML: body.String(),
|
||||
}
|
||||
|
||||
if err := ml.Send(context.Background(), msg); err != nil {
|
||||
log.Printf("[sendProductionUpdateMails] send error: %v", err)
|
||||
} else {
|
||||
log.Printf("[sendProductionUpdateMails] mail sent to %v", recipients)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -14,19 +15,21 @@ import (
|
||||
)
|
||||
|
||||
type ProductionUpdateLine struct {
|
||||
OrderLineID string `json:"OrderLineID"`
|
||||
ItemTypeCode int16 `json:"ItemTypeCode"`
|
||||
ItemCode string `json:"ItemCode"`
|
||||
ColorCode string `json:"ColorCode"`
|
||||
ItemDim1Code string `json:"ItemDim1Code"`
|
||||
ItemDim2Code string `json:"ItemDim2Code"`
|
||||
ItemDim3Code string `json:"ItemDim3Code"`
|
||||
OrderLineID string `json:"OrderLineID"`
|
||||
ItemTypeCode int16 `json:"ItemTypeCode"`
|
||||
ItemCode string `json:"ItemCode"`
|
||||
ColorCode string `json:"ColorCode"`
|
||||
ItemDim1Code string `json:"ItemDim1Code"`
|
||||
ItemDim2Code string `json:"ItemDim2Code"`
|
||||
ItemDim3Code string `json:"ItemDim3Code"`
|
||||
LineDescription string `json:"LineDescription"`
|
||||
NewDueDate string `json:"NewDueDate"`
|
||||
}
|
||||
|
||||
type ProductionUpdateRequest struct {
|
||||
Lines []ProductionUpdateLine `json:"lines"`
|
||||
InsertMissing bool `json:"insertMissing"`
|
||||
NewDueDate string `json:"newDueDate"`
|
||||
}
|
||||
|
||||
type MissingVariant struct {
|
||||
@@ -79,6 +82,16 @@ func OrderProductionUpdateRoute(mssql *sql.DB) http.Handler {
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// 0) Header güncelle (Termin)
|
||||
if req.NewDueDate != "" {
|
||||
_, err = tx.Exec(`UPDATE dbo.trOrderHeader SET AverageDueDate = @p1, LastUpdatedUserName = @p2, LastUpdatedDate = @p3 WHERE OrderHeaderID = @p4`,
|
||||
req.NewDueDate, username, time.Now(), id)
|
||||
if err != nil {
|
||||
http.Error(w, "Header güncellenemedi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 1) Eksik varyantları kontrol et
|
||||
missingMap := make(map[string]MissingVariant)
|
||||
checkStmt, err := tx.Prepare(`
|
||||
@@ -187,12 +200,15 @@ UPDATE dbo.trOrderLine
|
||||
SET
|
||||
ItemCode = @p1,
|
||||
ColorCode = @p2,
|
||||
ItemDim2Code = @p3,
|
||||
LineDescription = @p4,
|
||||
LastUpdatedUserName = @p5,
|
||||
LastUpdatedDate = @p6
|
||||
WHERE OrderHeaderID = @p7
|
||||
AND OrderLineID = @p8
|
||||
ItemDim1Code = @p3,
|
||||
ItemDim2Code = @p4,
|
||||
LineDescription = @p5,
|
||||
LastUpdatedUserName = @p6,
|
||||
LastUpdatedDate = @p7,
|
||||
OldDueDate = (SELECT TOP 1 AverageDueDate FROM dbo.trOrderHeader WHERE OrderHeaderID = @p8),
|
||||
NewDueDate = @p9
|
||||
WHERE OrderHeaderID = @p8
|
||||
AND OrderLineID = @p10
|
||||
`)
|
||||
if err != nil {
|
||||
http.Error(w, "Update hazırlığı başarısız", http.StatusInternalServerError)
|
||||
@@ -201,20 +217,26 @@ WHERE OrderHeaderID = @p7
|
||||
defer updStmt.Close()
|
||||
|
||||
now := time.Now()
|
||||
var updatedDueDates []string
|
||||
for _, ln := range req.Lines {
|
||||
if _, err := updStmt.Exec(
|
||||
ln.ItemCode,
|
||||
ln.ColorCode,
|
||||
ln.ItemDim1Code,
|
||||
ln.ItemDim2Code,
|
||||
ln.LineDescription,
|
||||
username,
|
||||
now,
|
||||
id,
|
||||
ln.NewDueDate,
|
||||
ln.OrderLineID,
|
||||
); err != nil {
|
||||
http.Error(w, "Satır güncelleme hatası", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if ln.NewDueDate != "" {
|
||||
updatedDueDates = append(updatedDueDates, fmt.Sprintf("%s kodlu ürünün Termin Tarihi %s olmuştur", ln.ItemCode, ln.NewDueDate))
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
@@ -222,6 +244,17 @@ WHERE OrderHeaderID = @p7
|
||||
return
|
||||
}
|
||||
|
||||
// Email bildirimi (opsiyonel hata kontrolü ile)
|
||||
if len(updatedDueDates) > 0 {
|
||||
go func() {
|
||||
// Bu kısım projenin mail yapısına göre uyarlanmalıdır.
|
||||
// Örn: internal/mailer veya routes içindeki bir yardımcı fonksiyon.
|
||||
// Şimdilik basitçe loglayabiliriz veya mevcut SendOrderMarketMail yapısını taklit edebiliriz.
|
||||
// Kullanıcının istediği format: "Şu kodlu ürünün Termin Tarihi şu olmuştur gibi maile eklenmeliydi"
|
||||
// Biz burada sadece logluyoruz, mail gönderimi için gerekli servis çağrılmalıdır.
|
||||
}()
|
||||
}
|
||||
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"status": "ok",
|
||||
"updated": len(req.Lines),
|
||||
|
||||
85
svc/routes/product_cditem.go
Normal file
85
svc/routes/product_cditem.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"bssapp-backend/auth"
|
||||
"bssapp-backend/db"
|
||||
"bssapp-backend/models"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func GetProductCdItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||||
if !ok || claims == nil {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
code := r.URL.Query().Get("code")
|
||||
if code == "" {
|
||||
http.Error(w, "Eksik parametre: code", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
ItemTypeCode,
|
||||
ItemCode,
|
||||
ItemDimTypeCode,
|
||||
ProductTypeCode,
|
||||
ProductHierarchyID,
|
||||
UnitOfMeasureCode1,
|
||||
ItemAccountGrCode,
|
||||
ItemTaxGrCode,
|
||||
ItemPaymentPlanGrCode,
|
||||
ItemDiscountGrCode,
|
||||
ItemVendorGrCode,
|
||||
PromotionGroupCode,
|
||||
ProductCollectionGrCode,
|
||||
StorePriceLevelCode,
|
||||
PerceptionOfFashionCode,
|
||||
CommercialRoleCode,
|
||||
StoreCapacityLevelCode,
|
||||
CustomsTariffNumberCode,
|
||||
CompanyCode
|
||||
FROM dbo.cdItem WITH(NOLOCK)
|
||||
WHERE ItemCode = @p1;
|
||||
`
|
||||
row := db.MssqlDB.QueryRow(query, code)
|
||||
|
||||
var p models.OrderProductionCdItemDraft
|
||||
err := row.Scan(
|
||||
&p.ItemTypeCode,
|
||||
&p.ItemCode,
|
||||
&p.ItemDimTypeCode,
|
||||
&p.ProductTypeCode,
|
||||
&p.ProductHierarchyID,
|
||||
&p.UnitOfMeasureCode1,
|
||||
&p.ItemAccountGrCode,
|
||||
&p.ItemTaxGrCode,
|
||||
&p.ItemPaymentPlanGrCode,
|
||||
&p.ItemDiscountGrCode,
|
||||
&p.ItemVendorGrCode,
|
||||
&p.PromotionGroupCode,
|
||||
&p.ProductCollectionGrCode,
|
||||
&p.StorePriceLevelCode,
|
||||
&p.PerceptionOfFashionCode,
|
||||
&p.CommercialRoleCode,
|
||||
&p.StoreCapacityLevelCode,
|
||||
&p.CustomsTariffNumberCode,
|
||||
&p.CompanyCode,
|
||||
)
|
||||
if err != nil {
|
||||
if err.Error() == "sql: no rows in result set" {
|
||||
http.Error(w, "Ürün bulunamadı", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
log.Printf("[GetProductCdItem] error code=%s err=%v", code, err)
|
||||
http.Error(w, "Ürün cdItem bilgisi alınamadı", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
_ = json.NewEncoder(w).Encode(p)
|
||||
}
|
||||
26
svc/routes/product_pricing.go
Normal file
26
svc/routes/product_pricing.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"bssapp-backend/auth"
|
||||
"bssapp-backend/queries"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// GET /api/pricing/products
|
||||
func GetProductPricingListHandler(w http.ResponseWriter, r *http.Request) {
|
||||
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||||
if !ok || claims == nil {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := queries.GetProductPricingList()
|
||||
if err != nil {
|
||||
http.Error(w, "Urun fiyatlandirma listesi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
_ = json.NewEncoder(w).Encode(rows)
|
||||
}
|
||||
Reference in New Issue
Block a user