Merge remote-tracking branch 'origin/master'
This commit is contained in:
27
scripts/sql/add_indexes_order_validate.sql
Normal file
27
scripts/sql/add_indexes_order_validate.sql
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/* Indexes for order validate performance */
|
||||||
|
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM sys.indexes
|
||||||
|
WHERE name = 'IX_trOrderLine_OrderHeader_ItemCode'
|
||||||
|
AND object_id = OBJECT_ID('dbo.trOrderLine')
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
CREATE NONCLUSTERED INDEX IX_trOrderLine_OrderHeader_ItemCode
|
||||||
|
ON dbo.trOrderLine (OrderHeaderID, ItemCode)
|
||||||
|
INCLUDE (ItemTypeCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, LineDescription, SortOrder, OrderLineID);
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM sys.indexes
|
||||||
|
WHERE name = 'IX_prItemVariant_Combo'
|
||||||
|
AND object_id = OBJECT_ID('dbo.prItemVariant')
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
CREATE NONCLUSTERED INDEX IX_prItemVariant_Combo
|
||||||
|
ON dbo.prItemVariant (ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code)
|
||||||
|
INCLUDE (PLU);
|
||||||
|
END
|
||||||
|
GO
|
||||||
@@ -441,6 +441,9 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
|
|||||||
{"/api/orders/list", "GET", "view", routes.OrderListRoute(mssql)},
|
{"/api/orders/list", "GET", "view", routes.OrderListRoute(mssql)},
|
||||||
{"/api/orders/production-list", "GET", "update", routes.OrderProductionListRoute(mssql)},
|
{"/api/orders/production-list", "GET", "update", routes.OrderProductionListRoute(mssql)},
|
||||||
{"/api/orders/production-items/{id}", "GET", "view", routes.OrderProductionItemsRoute(mssql)},
|
{"/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/close-ready", "GET", "update", routes.OrderCloseReadyListRoute(mssql)},
|
{"/api/orders/close-ready", "GET", "update", routes.OrderCloseReadyListRoute(mssql)},
|
||||||
{"/api/orders/bulk-close", "POST", "update", routes.OrderBulkCloseRoute(mssql)},
|
{"/api/orders/bulk-close", "POST", "update", routes.OrderBulkCloseRoute(mssql)},
|
||||||
{"/api/orders/export", "GET", "export", routes.OrderListExcelRoute(mssql)},
|
{"/api/orders/export", "GET", "export", routes.OrderListExcelRoute(mssql)},
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ type OrderProductionItem struct {
|
|||||||
OrderHeaderID string `json:"OrderHeaderID"`
|
OrderHeaderID string `json:"OrderHeaderID"`
|
||||||
OrderLineID string `json:"OrderLineID"`
|
OrderLineID string `json:"OrderLineID"`
|
||||||
|
|
||||||
|
ItemTypeCode int16 `json:"ItemTypeCode"`
|
||||||
|
OldDim1 string `json:"OldDim1"`
|
||||||
|
OldDim3 string `json:"OldDim3"`
|
||||||
|
|
||||||
OldItemCode string `json:"OldItemCode"`
|
OldItemCode string `json:"OldItemCode"`
|
||||||
OldColor string `json:"OldColor"`
|
OldColor string `json:"OldColor"`
|
||||||
OldDim2 string `json:"OldDim2"`
|
OldDim2 string `json:"OldDim2"`
|
||||||
@@ -16,4 +20,6 @@ type OrderProductionItem struct {
|
|||||||
NewColor string `json:"NewColor"`
|
NewColor string `json:"NewColor"`
|
||||||
NewDim2 string `json:"NewDim2"`
|
NewDim2 string `json:"NewDim2"`
|
||||||
NewDesc string `json:"NewDesc"`
|
NewDesc string `json:"NewDesc"`
|
||||||
|
|
||||||
|
IsVariantMissing bool `json:"IsVariantMissing"`
|
||||||
}
|
}
|
||||||
|
|||||||
24
svc/models/orderproductionupdate.go
Normal file
24
svc/models/orderproductionupdate.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderProductionUpdatePayload struct {
|
||||||
|
Lines []OrderProductionUpdateLine `json:"lines"`
|
||||||
|
InsertMissing bool `json:"insertMissing"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderProductionMissingVariant 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"`
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ package queries
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
|
"bssapp-backend/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ========================================================
|
// ========================================================
|
||||||
@@ -12,20 +14,209 @@ func GetOrderProductionItems(mssql *sql.DB, orderHeaderID string) (*sql.Rows, er
|
|||||||
SELECT
|
SELECT
|
||||||
CAST(l.OrderHeaderID AS NVARCHAR(50)) AS OrderHeaderID,
|
CAST(l.OrderHeaderID AS NVARCHAR(50)) AS OrderHeaderID,
|
||||||
CAST(l.OrderLineID AS NVARCHAR(50)) AS OrderLineID,
|
CAST(l.OrderLineID AS NVARCHAR(50)) AS OrderLineID,
|
||||||
|
l.ItemTypeCode AS ItemTypeCode,
|
||||||
|
ISNULL(l.ItemDim1Code,'') AS OldDim1,
|
||||||
|
ISNULL(l.ItemDim3Code,'') AS OldDim3,
|
||||||
|
|
||||||
-- Şimdilik eski alanlar boş döndürülür
|
ISNULL(l.ItemCode,'') AS OldItemCode,
|
||||||
CAST('' AS NVARCHAR(50)) AS OldItemCode,
|
ISNULL(l.ColorCode,'') AS OldColor,
|
||||||
CAST('' AS NVARCHAR(50)) AS OldColor,
|
ISNULL(l.ItemDim2Code,'') AS OldDim2,
|
||||||
CAST('' AS NVARCHAR(50)) AS OldDim2,
|
ISNULL(l.LineDescription,'') AS OldDesc,
|
||||||
CAST('' AS NVARCHAR(250)) AS OldDesc,
|
|
||||||
|
|
||||||
ISNULL(l.ItemCode,'') AS NewItemCode,
|
CAST('' AS NVARCHAR(60)) AS NewItemCode,
|
||||||
ISNULL(l.ColorCode,'') AS NewColor,
|
CAST('' AS NVARCHAR(30)) AS NewColor,
|
||||||
ISNULL(l.ItemDim2Code,'') AS NewDim2,
|
CAST('' AS NVARCHAR(30)) AS NewDim2,
|
||||||
ISNULL(l.LineDescription,'') AS NewDesc
|
CAST('' AS NVARCHAR(250)) AS NewDesc,
|
||||||
|
|
||||||
|
CAST(0 AS bit) AS IsVariantMissing
|
||||||
FROM dbo.trOrderLine l
|
FROM dbo.trOrderLine l
|
||||||
WHERE l.OrderHeaderID = @p1
|
WHERE l.OrderHeaderID = @p1
|
||||||
AND ISNULL(l.ItemCode,'') LIKE 'U%'
|
AND ISNULL(l.ItemCode,'') LIKE 'U%'
|
||||||
ORDER BY l.SortOrder, l.OrderLineID
|
ORDER BY l.SortOrder, l.OrderLineID
|
||||||
`, orderHeaderID)
|
`, orderHeaderID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================================
|
||||||
|
// 📌 InsertMissingProductionVariants — eksik prItemVariant ekler
|
||||||
|
// ========================================================
|
||||||
|
func InsertMissingProductionVariants(mssql *sql.DB, orderHeaderID string, username string) (int64, error) {
|
||||||
|
query := `
|
||||||
|
;WITH Missing AS (
|
||||||
|
SELECT DISTINCT
|
||||||
|
l.ItemTypeCode,
|
||||||
|
l.ItemCode,
|
||||||
|
l.ColorCode,
|
||||||
|
l.ItemDim1Code,
|
||||||
|
l.ItemDim2Code,
|
||||||
|
l.ItemDim3Code
|
||||||
|
FROM dbo.trOrderLine l
|
||||||
|
LEFT JOIN dbo.prItemVariant pv
|
||||||
|
ON pv.ItemTypeCode = l.ItemTypeCode
|
||||||
|
AND pv.ItemCode = l.ItemCode
|
||||||
|
AND pv.ColorCode = l.ColorCode
|
||||||
|
AND ISNULL(pv.ItemDim1Code,'') = ISNULL(l.ItemDim1Code,'')
|
||||||
|
AND ISNULL(pv.ItemDim2Code,'') = ISNULL(l.ItemDim2Code,'')
|
||||||
|
AND ISNULL(pv.ItemDim3Code,'') = ISNULL(l.ItemDim3Code,'')
|
||||||
|
WHERE l.OrderHeaderID = @p1
|
||||||
|
AND ISNULL(l.ItemCode,'') LIKE 'U%'
|
||||||
|
AND pv.ItemCode IS NULL
|
||||||
|
),
|
||||||
|
MaxPlu AS (
|
||||||
|
SELECT ISNULL(MAX(PLU),0) AS BasePlu
|
||||||
|
FROM dbo.prItemVariant WITH (UPDLOCK, HOLDLOCK)
|
||||||
|
)
|
||||||
|
INSERT INTO dbo.prItemVariant (
|
||||||
|
ItemTypeCode,
|
||||||
|
ItemCode,
|
||||||
|
ColorCode,
|
||||||
|
ItemDim1Code,
|
||||||
|
ItemDim2Code,
|
||||||
|
ItemDim3Code,
|
||||||
|
PLU,
|
||||||
|
CreatedUserName,
|
||||||
|
CreatedDate,
|
||||||
|
LastUpdatedUserName,
|
||||||
|
LastUpdatedDate
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
m.ItemTypeCode,
|
||||||
|
m.ItemCode,
|
||||||
|
m.ColorCode,
|
||||||
|
m.ItemDim1Code,
|
||||||
|
m.ItemDim2Code,
|
||||||
|
m.ItemDim3Code,
|
||||||
|
mp.BasePlu + ROW_NUMBER() OVER (ORDER BY m.ItemCode, m.ColorCode, m.ItemDim1Code, m.ItemDim2Code, m.ItemDim3Code),
|
||||||
|
@p2,
|
||||||
|
GETDATE(),
|
||||||
|
@p2,
|
||||||
|
GETDATE()
|
||||||
|
FROM Missing m
|
||||||
|
CROSS JOIN MaxPlu mp;
|
||||||
|
`
|
||||||
|
|
||||||
|
res, err := mssql.Exec(query, orderHeaderID, username)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return res.RowsAffected()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================
|
||||||
|
// OrderProductionUpdate - variant kontrolu ve guncelleme
|
||||||
|
// ========================================================
|
||||||
|
func GetOrderLineDims(mssql *sql.DB, orderHeaderID string, orderLineID string) (int16, string, string, string, error) {
|
||||||
|
var itemTypeCode int16
|
||||||
|
var dim1 string
|
||||||
|
var dim2 string
|
||||||
|
var dim3 string
|
||||||
|
err := mssql.QueryRow(`
|
||||||
|
SELECT
|
||||||
|
ItemTypeCode,
|
||||||
|
ISNULL(ItemDim1Code,'') AS ItemDim1Code,
|
||||||
|
ISNULL(ItemDim2Code,'') AS ItemDim2Code,
|
||||||
|
ISNULL(ItemDim3Code,'') AS ItemDim3Code
|
||||||
|
FROM dbo.trOrderLine
|
||||||
|
WHERE OrderHeaderID = @p1 AND OrderLineID = @p2
|
||||||
|
`, orderHeaderID, orderLineID).Scan(&itemTypeCode, &dim1, &dim2, &dim3)
|
||||||
|
return itemTypeCode, dim1, dim2, dim3, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func VariantExists(mssql *sql.DB, itemTypeCode int16, itemCode string, colorCode string, dim1 string, dim2 string, dim3 string) (bool, error) {
|
||||||
|
var exists int
|
||||||
|
err := mssql.QueryRow(`
|
||||||
|
SELECT TOP 1 1
|
||||||
|
FROM dbo.prItemVariant
|
||||||
|
WHERE ItemTypeCode = @p1
|
||||||
|
AND ItemCode = @p2
|
||||||
|
AND ColorCode = @p3
|
||||||
|
AND ISNULL(ItemDim1Code,'') = ISNULL(@p4,'')
|
||||||
|
AND ISNULL(ItemDim2Code,'') = ISNULL(@p5,'')
|
||||||
|
AND ISNULL(ItemDim3Code,'') = ISNULL(@p6,'')
|
||||||
|
`, itemTypeCode, itemCode, colorCode, dim1, dim2, dim3).Scan(&exists)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InsertMissingVariantsTx(tx *sql.Tx, missing []models.OrderProductionMissingVariant, username string) (int64, error) {
|
||||||
|
if len(missing) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var basePlu int64
|
||||||
|
if err := tx.QueryRow(`
|
||||||
|
SELECT ISNULL(MAX(PLU),0) AS BasePlu
|
||||||
|
FROM dbo.prItemVariant WITH (UPDLOCK, HOLDLOCK)
|
||||||
|
`).Scan(&basePlu); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var inserted int64
|
||||||
|
for i, v := range missing {
|
||||||
|
plu := basePlu + int64(i) + 1
|
||||||
|
res, err := tx.Exec(`
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM dbo.prItemVariant
|
||||||
|
WHERE ItemTypeCode = @p1
|
||||||
|
AND ItemCode = @p2
|
||||||
|
AND ColorCode = @p3
|
||||||
|
AND ISNULL(ItemDim1Code,'') = ISNULL(@p4,'')
|
||||||
|
AND ISNULL(ItemDim2Code,'') = ISNULL(@p5,'')
|
||||||
|
AND ISNULL(ItemDim3Code,'') = ISNULL(@p6,'')
|
||||||
|
)
|
||||||
|
INSERT INTO dbo.prItemVariant (
|
||||||
|
ItemTypeCode,
|
||||||
|
ItemCode,
|
||||||
|
ColorCode,
|
||||||
|
ItemDim1Code,
|
||||||
|
ItemDim2Code,
|
||||||
|
ItemDim3Code,
|
||||||
|
PLU,
|
||||||
|
CreatedUserName,
|
||||||
|
CreatedDate,
|
||||||
|
LastUpdatedUserName,
|
||||||
|
LastUpdatedDate
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
@p1, @p2, @p3, @p4, @p5, @p6,
|
||||||
|
@p7, @p8, GETDATE(), @p8, GETDATE()
|
||||||
|
);
|
||||||
|
`, v.ItemTypeCode, v.ItemCode, v.ColorCode, v.ItemDim1Code, v.ItemDim2Code, v.ItemDim3Code, plu, username)
|
||||||
|
if err != nil {
|
||||||
|
return inserted, err
|
||||||
|
}
|
||||||
|
if rows, err := res.RowsAffected(); err == nil {
|
||||||
|
inserted += rows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inserted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateOrderLinesTx(tx *sql.Tx, orderHeaderID string, lines []models.OrderProductionUpdateLine, username string) (int64, error) {
|
||||||
|
var updated int64
|
||||||
|
for _, line := range lines {
|
||||||
|
res, err := tx.Exec(`
|
||||||
|
UPDATE dbo.trOrderLine
|
||||||
|
SET
|
||||||
|
ItemCode = @p1,
|
||||||
|
ColorCode = @p2,
|
||||||
|
ItemDim2Code = @p3,
|
||||||
|
LineDescription = COALESCE(NULLIF(@p4,''), LineDescription),
|
||||||
|
LastUpdatedUserName = @p5,
|
||||||
|
LastUpdatedDate = GETDATE()
|
||||||
|
WHERE OrderHeaderID = @p6 AND OrderLineID = @p7
|
||||||
|
`, line.NewItemCode, line.NewColor, line.NewDim2, line.NewDesc, username, orderHeaderID, line.OrderLineID)
|
||||||
|
if err != nil {
|
||||||
|
return updated, err
|
||||||
|
}
|
||||||
|
if rows, err := res.RowsAffected(); err == nil {
|
||||||
|
updated += rows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updated, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bssapp-backend/auth"
|
||||||
"bssapp-backend/models"
|
"bssapp-backend/models"
|
||||||
"bssapp-backend/queries"
|
"bssapp-backend/queries"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
@@ -40,6 +43,9 @@ func OrderProductionItemsRoute(mssql *sql.DB) http.Handler {
|
|||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&o.OrderHeaderID,
|
&o.OrderHeaderID,
|
||||||
&o.OrderLineID,
|
&o.OrderLineID,
|
||||||
|
&o.ItemTypeCode,
|
||||||
|
&o.OldDim1,
|
||||||
|
&o.OldDim3,
|
||||||
&o.OldItemCode,
|
&o.OldItemCode,
|
||||||
&o.OldColor,
|
&o.OldColor,
|
||||||
&o.OldDim2,
|
&o.OldDim2,
|
||||||
@@ -48,6 +54,7 @@ func OrderProductionItemsRoute(mssql *sql.DB) http.Handler {
|
|||||||
&o.NewColor,
|
&o.NewColor,
|
||||||
&o.NewDim2,
|
&o.NewDim2,
|
||||||
&o.NewDesc,
|
&o.NewDesc,
|
||||||
|
&o.IsVariantMissing,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
log.Printf("⚠️ SCAN HATASI: %v", err)
|
log.Printf("⚠️ SCAN HATASI: %v", err)
|
||||||
continue
|
continue
|
||||||
@@ -64,3 +71,224 @@ func OrderProductionItemsRoute(mssql *sql.DB) http.Handler {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ======================================================
|
||||||
|
// 📌 OrderProductionInsertMissingRoute — eksik varyantları ekler
|
||||||
|
// ======================================================
|
||||||
|
func OrderProductionInsertMissingRoute(mssql *sql.DB) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
|
||||||
|
id := mux.Vars(r)["id"]
|
||||||
|
if id == "" {
|
||||||
|
http.Error(w, "OrderHeaderID bulunamadı", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, _ := auth.GetClaimsFromContext(r.Context())
|
||||||
|
username := ""
|
||||||
|
if claims != nil {
|
||||||
|
username = claims.Username
|
||||||
|
}
|
||||||
|
if username == "" {
|
||||||
|
username = "system"
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := queries.InsertMissingProductionVariants(mssql, id, username)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("❌ INSERT varyant hatası: %v", err)
|
||||||
|
http.Error(w, "Veritabanı hatası", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := map[string]any{
|
||||||
|
"inserted": affected,
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||||
|
log.Printf("❌ encode error: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================================================
|
||||||
|
// OrderProductionValidateRoute - yeni model varyant kontrolu
|
||||||
|
// ======================================================
|
||||||
|
func OrderProductionValidateRoute(mssql *sql.DB) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
|
||||||
|
id := mux.Vars(r)["id"]
|
||||||
|
if id == "" {
|
||||||
|
http.Error(w, "OrderHeaderID bulunamadi", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload models.OrderProductionUpdatePayload
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||||
|
http.Error(w, "Gecersiz istek", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := validateUpdateLines(payload.Lines); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
missing, err := buildMissingVariants(mssql, id, payload.Lines)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("❌ validate error: %v", err)
|
||||||
|
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := map[string]any{
|
||||||
|
"missingCount": len(missing),
|
||||||
|
"missing": missing,
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||||
|
log.Printf("❌ encode error: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================================================
|
||||||
|
// OrderProductionApplyRoute - yeni model varyant guncelleme
|
||||||
|
// ======================================================
|
||||||
|
func OrderProductionApplyRoute(mssql *sql.DB) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
|
||||||
|
id := mux.Vars(r)["id"]
|
||||||
|
if id == "" {
|
||||||
|
http.Error(w, "OrderHeaderID bulunamadi", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload models.OrderProductionUpdatePayload
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||||
|
http.Error(w, "Gecersiz istek", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := validateUpdateLines(payload.Lines); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
missing, err := buildMissingVariants(mssql, id, payload.Lines)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("❌ apply validate error: %v", err)
|
||||||
|
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(missing) > 0 && !payload.InsertMissing {
|
||||||
|
w.WriteHeader(http.StatusConflict)
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
"missingCount": len(missing),
|
||||||
|
"missing": missing,
|
||||||
|
"message": "Eksik varyantlar var",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, _ := auth.GetClaimsFromContext(r.Context())
|
||||||
|
username := ""
|
||||||
|
if claims != nil {
|
||||||
|
username = claims.Username
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(username) == "" {
|
||||||
|
username = "system"
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := mssql.Begin()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
var inserted int64
|
||||||
|
if payload.InsertMissing {
|
||||||
|
inserted, err = queries.InsertMissingVariantsTx(tx, missing, username)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("❌ insert missing error: %v", err)
|
||||||
|
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := queries.UpdateOrderLinesTx(tx, id, payload.Lines, username)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("❌ update order lines error: %v", err)
|
||||||
|
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
log.Printf("❌ commit error: %v", err)
|
||||||
|
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := map[string]any{
|
||||||
|
"updated": updated,
|
||||||
|
"inserted": inserted,
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||||
|
log.Printf("❌ encode error: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildMissingVariants(mssql *sql.DB, orderHeaderID string, lines []models.OrderProductionUpdateLine) ([]models.OrderProductionMissingVariant, error) {
|
||||||
|
missing := make([]models.OrderProductionMissingVariant, 0)
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
lineID := strings.TrimSpace(line.OrderLineID)
|
||||||
|
newItem := strings.TrimSpace(line.NewItemCode)
|
||||||
|
newColor := strings.TrimSpace(line.NewColor)
|
||||||
|
newDim2 := strings.TrimSpace(line.NewDim2)
|
||||||
|
|
||||||
|
if lineID == "" || newItem == "" || newColor == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
itemTypeCode, dim1, _, dim3, err := queries.GetOrderLineDims(mssql, orderHeaderID, lineID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err := queries.VariantExists(mssql, itemTypeCode, newItem, newColor, dim1, newDim2, dim3)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
missing = append(missing, models.OrderProductionMissingVariant{
|
||||||
|
OrderLineID: lineID,
|
||||||
|
ItemTypeCode: itemTypeCode,
|
||||||
|
ItemCode: newItem,
|
||||||
|
ColorCode: newColor,
|
||||||
|
ItemDim1Code: dim1,
|
||||||
|
ItemDim2Code: newDim2,
|
||||||
|
ItemDim3Code: dim3,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return missing, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateUpdateLines(lines []models.OrderProductionUpdateLine) error {
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.TrimSpace(line.OrderLineID) == "" {
|
||||||
|
return errors.New("OrderLineID zorunlu")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(line.NewItemCode) == "" {
|
||||||
|
return errors.New("Yeni urun kodu zorunlu")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(line.NewColor) == "" {
|
||||||
|
return errors.New("Yeni renk kodu zorunlu")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
230
svc/routes/orderproductionupdate.go
Normal file
230
svc/routes/orderproductionupdate.go
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bssapp-backend/auth"
|
||||||
|
"bssapp-backend/db"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
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"`
|
||||||
|
LineDescription string `json:"LineDescription"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductionUpdateRequest struct {
|
||||||
|
Lines []ProductionUpdateLine `json:"lines"`
|
||||||
|
InsertMissing bool `json:"insertMissing"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MissingVariant struct {
|
||||||
|
ItemTypeCode int16 `json:"ItemTypeCode"`
|
||||||
|
ItemCode string `json:"ItemCode"`
|
||||||
|
ColorCode string `json:"ColorCode"`
|
||||||
|
ItemDim1Code string `json:"ItemDim1Code"`
|
||||||
|
ItemDim2Code string `json:"ItemDim2Code"`
|
||||||
|
ItemDim3Code string `json:"ItemDim3Code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================================================
|
||||||
|
// 📌 OrderProductionUpdateRoute — U ürün satırlarını güncelle
|
||||||
|
// ======================================================
|
||||||
|
func OrderProductionUpdateRoute(mssql *sql.DB) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
|
||||||
|
id := mux.Vars(r)["id"]
|
||||||
|
if id == "" {
|
||||||
|
http.Error(w, "OrderHeaderID bulunamadı", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req ProductionUpdateRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, "Geçersiz JSON", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Lines) == 0 {
|
||||||
|
http.Error(w, "Satır bulunamadı", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, _ := auth.GetClaimsFromContext(r.Context())
|
||||||
|
username := ""
|
||||||
|
if claims != nil {
|
||||||
|
username = claims.Username
|
||||||
|
}
|
||||||
|
if username == "" {
|
||||||
|
username = "system"
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := db.MssqlDB.Begin()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "İşlem başlatılamadı", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// 1) Eksik varyantları kontrol et
|
||||||
|
missingMap := make(map[string]MissingVariant)
|
||||||
|
checkStmt, err := tx.Prepare(`
|
||||||
|
SELECT TOP 1 1
|
||||||
|
FROM dbo.prItemVariant
|
||||||
|
WHERE ItemTypeCode = @p1
|
||||||
|
AND ItemCode = @p2
|
||||||
|
AND ColorCode = @p3
|
||||||
|
AND ISNULL(ItemDim1Code,'') = ISNULL(@p4,'')
|
||||||
|
AND ISNULL(ItemDim2Code,'') = ISNULL(@p5,'')
|
||||||
|
AND ISNULL(ItemDim3Code,'') = ISNULL(@p6,'')
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Varyant kontrolü hazırlanamadı", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer checkStmt.Close()
|
||||||
|
|
||||||
|
for _, ln := range req.Lines {
|
||||||
|
if strings.TrimSpace(ln.ItemCode) == "" {
|
||||||
|
http.Error(w, "Yeni model kodu boş", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
row := checkStmt.QueryRow(
|
||||||
|
ln.ItemTypeCode,
|
||||||
|
ln.ItemCode,
|
||||||
|
ln.ColorCode,
|
||||||
|
ln.ItemDim1Code,
|
||||||
|
ln.ItemDim2Code,
|
||||||
|
ln.ItemDim3Code,
|
||||||
|
)
|
||||||
|
var ok int
|
||||||
|
if err := row.Scan(&ok); err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
key := strings.Join([]string{
|
||||||
|
ln.ItemCode, ln.ColorCode, ln.ItemDim1Code, ln.ItemDim2Code, ln.ItemDim3Code,
|
||||||
|
}, "|")
|
||||||
|
missingMap[key] = MissingVariant{
|
||||||
|
ItemTypeCode: ln.ItemTypeCode,
|
||||||
|
ItemCode: ln.ItemCode,
|
||||||
|
ColorCode: ln.ColorCode,
|
||||||
|
ItemDim1Code: ln.ItemDim1Code,
|
||||||
|
ItemDim2Code: ln.ItemDim2Code,
|
||||||
|
ItemDim3Code: ln.ItemDim3Code,
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
http.Error(w, "Varyant kontrolü hatası", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(missingMap) > 0 && !req.InsertMissing {
|
||||||
|
missing := make([]MissingVariant, 0, len(missingMap))
|
||||||
|
for _, v := range missingMap {
|
||||||
|
missing = append(missing, v)
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusConflict)
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
"missing": missing,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Eksikleri ekle (gerekirse)
|
||||||
|
if len(missingMap) > 0 {
|
||||||
|
// PLU üretimi (max + row_number)
|
||||||
|
var basePlu int64
|
||||||
|
if err := tx.QueryRow(`SELECT ISNULL(MAX(PLU),0) FROM dbo.prItemVariant WITH (UPDLOCK, HOLDLOCK)`).Scan(&basePlu); err != nil {
|
||||||
|
http.Error(w, "PLU alınamadı", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
i := int64(0)
|
||||||
|
for _, v := range missingMap {
|
||||||
|
i++
|
||||||
|
if _, err := tx.Exec(`
|
||||||
|
INSERT INTO dbo.prItemVariant
|
||||||
|
(
|
||||||
|
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
|
||||||
|
PLU, CreatedUserName, CreatedDate, LastUpdatedUserName, LastUpdatedDate
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(@p1,@p2,@p3,@p4,@p5,@p6,@p7,@p8,@p9,@p8,@p9)
|
||||||
|
`,
|
||||||
|
v.ItemTypeCode,
|
||||||
|
v.ItemCode,
|
||||||
|
v.ColorCode,
|
||||||
|
v.ItemDim1Code,
|
||||||
|
v.ItemDim2Code,
|
||||||
|
v.ItemDim3Code,
|
||||||
|
basePlu+i,
|
||||||
|
username,
|
||||||
|
now,
|
||||||
|
); err != nil {
|
||||||
|
http.Error(w, "Varyant insert hatası", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) trOrderLine güncelle
|
||||||
|
updStmt, err := tx.Prepare(`
|
||||||
|
UPDATE dbo.trOrderLine
|
||||||
|
SET
|
||||||
|
ItemCode = @p1,
|
||||||
|
ColorCode = @p2,
|
||||||
|
ItemDim2Code = @p3,
|
||||||
|
LineDescription = @p4,
|
||||||
|
LastUpdatedUserName = @p5,
|
||||||
|
LastUpdatedDate = @p6
|
||||||
|
WHERE OrderHeaderID = @p7
|
||||||
|
AND OrderLineID = @p8
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Update hazırlığı başarısız", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer updStmt.Close()
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
for _, ln := range req.Lines {
|
||||||
|
if _, err := updStmt.Exec(
|
||||||
|
ln.ItemCode,
|
||||||
|
ln.ColorCode,
|
||||||
|
ln.ItemDim2Code,
|
||||||
|
ln.LineDescription,
|
||||||
|
username,
|
||||||
|
now,
|
||||||
|
id,
|
||||||
|
ln.OrderLineID,
|
||||||
|
); err != nil {
|
||||||
|
http.Error(w, "Satır güncelleme hatası", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
http.Error(w, "Commit hatası", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
"status": "ok",
|
||||||
|
"updated": len(req.Lines),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,9 +1,59 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-page class="q-pa-md">
|
<q-page class="q-pa-md">
|
||||||
<div class="text-h6 text-weight-bold">Üretime Verilen Ürünler</div>
|
<div class="row items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<div class="text-h6 text-weight-bold">Uretime Verilen Urunleri Guncelle</div>
|
||||||
<div class="text-caption text-grey-7 q-mt-xs">
|
<div class="text-caption text-grey-7 q-mt-xs">
|
||||||
OrderHeaderID: {{ orderHeaderID || '-' }}
|
OrderHeaderID: {{ orderHeaderID || '-' }}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-btn
|
||||||
|
color="primary"
|
||||||
|
icon="refresh"
|
||||||
|
label="Yenile"
|
||||||
|
:loading="store.loading"
|
||||||
|
@click="refreshAll"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-bar row q-col-gutter-md q-mt-md">
|
||||||
|
<div class="col-5">
|
||||||
|
<q-input
|
||||||
|
:model-value="cariLabel"
|
||||||
|
label="Cari Secimi"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<q-input
|
||||||
|
:model-value="header?.OrderNumber || ''"
|
||||||
|
label="Siparis No"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<q-input
|
||||||
|
:model-value="formatDate(header?.OrderDate)"
|
||||||
|
label="Olusturulma Tarihi"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<q-input
|
||||||
|
:model-value="formatDate(header?.AverageDueDate)"
|
||||||
|
label="Tahmini Termin Tarihi"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<q-table
|
<q-table
|
||||||
class="q-mt-md"
|
class="q-mt-md"
|
||||||
@@ -12,13 +62,116 @@
|
|||||||
dense
|
dense
|
||||||
separator="cell"
|
separator="cell"
|
||||||
row-key="OrderLineID"
|
row-key="OrderLineID"
|
||||||
:rows="store.items"
|
:rows="rows"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:loading="store.loading"
|
:loading="store.loading"
|
||||||
no-data-label="Üretime verilecek ürün bulunamadı"
|
no-data-label="Uretime verilecek urun bulunamadi"
|
||||||
:rows-per-page-options="[0]"
|
:rows-per-page-options="[0]"
|
||||||
hide-bottom
|
hide-bottom
|
||||||
|
>
|
||||||
|
<template #body-cell-actions="props">
|
||||||
|
<q-td :props="props" class="text-center">
|
||||||
|
<q-btn
|
||||||
|
color="primary"
|
||||||
|
icon="save"
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
dense
|
||||||
|
:loading="rowSavingId === props.row.OrderLineID"
|
||||||
|
@click="onRowSubmit(props.row)"
|
||||||
|
>
|
||||||
|
<q-tooltip>Satiri Guncelle</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body-cell-NewItemCode="props">
|
||||||
|
<q-td :props="props">
|
||||||
|
<q-input
|
||||||
|
v-model="props.row.NewItemCode"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
label="Yeni Urun"
|
||||||
|
@update:model-value="val => onNewItemChange(props.row, val)"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<q-icon name="arrow_drop_down" class="cursor-pointer" />
|
||||||
|
</template>
|
||||||
|
<q-menu
|
||||||
|
anchor="bottom left"
|
||||||
|
self="top left"
|
||||||
|
fit
|
||||||
|
>
|
||||||
|
<div class="q-pa-sm" style="min-width:260px">
|
||||||
|
<q-input
|
||||||
|
v-model="productSearch"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
debounce="200"
|
||||||
|
placeholder="Urun ara..."
|
||||||
/>
|
/>
|
||||||
|
<q-list class="q-mt-xs" bordered separator>
|
||||||
|
<q-item
|
||||||
|
v-for="opt in filteredProducts"
|
||||||
|
:key="opt.ProductCode"
|
||||||
|
clickable
|
||||||
|
@click="onSelectProduct(props.row, opt.ProductCode)"
|
||||||
|
>
|
||||||
|
<q-item-section>{{ opt.ProductCode }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</div>
|
||||||
|
</q-menu>
|
||||||
|
</q-input>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body-cell-NewColor="props">
|
||||||
|
<q-td :props="props">
|
||||||
|
<q-select
|
||||||
|
v-model="props.row.NewColor"
|
||||||
|
:options="getColorOptions(props.row)"
|
||||||
|
option-label="colorLabel"
|
||||||
|
option-value="color_code"
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
use-input
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
label="Yeni Renk"
|
||||||
|
@update:model-value="() => onNewColorChange(props.row)"
|
||||||
|
/>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body-cell-NewDim2="props">
|
||||||
|
<q-td :props="props">
|
||||||
|
<q-select
|
||||||
|
v-model="props.row.NewDim2"
|
||||||
|
:options="getSecondColorOptions(props.row)"
|
||||||
|
option-label="item_dim2_code"
|
||||||
|
option-value="item_dim2_code"
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
use-input
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
label="Yeni 2. Renk"
|
||||||
|
/>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body-cell-NewDesc="props">
|
||||||
|
<q-td :props="props">
|
||||||
|
<q-input
|
||||||
|
v-model="props.row.NewDesc"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
label="Yeni Aciklama"
|
||||||
|
/>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
|
||||||
<q-banner v-if="store.error" class="bg-red text-white q-mt-sm">
|
<q-banner v-if="store.error" class="bg-red text-white q-mt-sm">
|
||||||
Hata: {{ store.error }}
|
Hata: {{ store.error }}
|
||||||
@@ -27,31 +180,193 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, watch } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
import { useQuasar } from 'quasar'
|
||||||
import { useOrderProductionItemStore } from 'src/stores/OrderProductionItemStore'
|
import { useOrderProductionItemStore } from 'src/stores/OrderProductionItemStore'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const $q = useQuasar()
|
||||||
const store = useOrderProductionItemStore()
|
const store = useOrderProductionItemStore()
|
||||||
|
|
||||||
const orderHeaderID = computed(() => String(route.params.orderHeaderID || '').trim())
|
const orderHeaderID = computed(() => String(route.params.orderHeaderID || '').trim())
|
||||||
|
const header = computed(() => store.header || {})
|
||||||
|
const cariLabel = computed(() => {
|
||||||
|
const code = header.value?.CurrAccCode || ''
|
||||||
|
const name = header.value?.CurrAccDescription || ''
|
||||||
|
if (!code && !name) return ''
|
||||||
|
if (!name) return code
|
||||||
|
return `${code} - ${name}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const rows = ref([])
|
||||||
|
const productOptions = ref([])
|
||||||
|
const productSearch = ref('')
|
||||||
|
const rowSavingId = ref('')
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'OldItemCode', label: 'Eski Ürün Kodu', field: 'OldItemCode', align: 'left', sortable: true, style: 'min-width:140px;white-space:nowrap', headerStyle: 'min-width:140px;white-space:nowrap' },
|
{ name: 'OldItemCode', label: 'Eski Urun Kodu', field: 'OldItemCode', align: 'left', sortable: true, style: 'min-width:140px;white-space:nowrap', headerStyle: 'min-width:140px;white-space:nowrap' },
|
||||||
{ name: 'OldColor', label: 'Eski Ürün Rengi', field: 'OldColor', align: 'left', sortable: true, style: 'min-width:120px;white-space:nowrap', headerStyle: 'min-width:120px;white-space:nowrap' },
|
{ name: 'OldColor', label: 'Eski Urun Rengi', field: 'OldColor', align: 'left', sortable: true, style: 'min-width:120px;white-space:nowrap', headerStyle: 'min-width:120px;white-space:nowrap' },
|
||||||
{ name: 'OldDim2', label: 'Eski 2. Renk', field: 'OldDim2', align: 'left', sortable: true, style: 'min-width:110px;white-space:nowrap', headerStyle: 'min-width:110px;white-space:nowrap' },
|
{ name: 'OldDim2', label: 'Eski 2. Renk', field: 'OldDim2', align: 'left', sortable: true, style: 'min-width:110px;white-space:nowrap', headerStyle: 'min-width:110px;white-space:nowrap' },
|
||||||
{ name: 'OldDesc', label: 'Eski Açıklama', field: 'OldDesc', align: 'left', sortable: false, style: 'min-width:180px;white-space:nowrap', headerStyle: 'min-width:180px;white-space:nowrap' },
|
{ name: 'OldDesc', label: 'Eski Aciklama', field: 'OldDesc', align: 'left', sortable: false, style: 'min-width:180px;white-space:nowrap', headerStyle: 'min-width:180px;white-space:nowrap' },
|
||||||
{ name: 'NewItemCode', label: 'Yeni Ürün Kodu', field: 'NewItemCode', align: 'left', sortable: true, style: 'min-width:140px;white-space:nowrap', headerStyle: 'min-width:140px;white-space:nowrap' },
|
{ name: 'NewItemCode', label: 'Yeni Urun Kodu', field: 'NewItemCode', align: 'left', sortable: false, style: 'min-width:190px;', headerStyle: 'min-width:190px;' },
|
||||||
{ name: 'NewColor', label: 'Yeni Ürün Rengi', field: 'NewColor', align: 'left', sortable: true, style: 'min-width:120px;white-space:nowrap', headerStyle: 'min-width:120px;white-space:nowrap' },
|
{ name: 'NewColor', label: 'Yeni Urun Rengi', field: 'NewColor', align: 'left', sortable: false, style: 'min-width:160px;', headerStyle: 'min-width:160px;' },
|
||||||
{ name: 'NewDim2', label: 'Yeni 2. Renk', field: 'NewDim2', align: 'left', sortable: true, style: 'min-width:110px;white-space:nowrap', headerStyle: 'min-width:110px;white-space:nowrap' },
|
{ name: 'NewDim2', label: 'Yeni 2. Renk', field: 'NewDim2', align: 'left', sortable: false, style: 'min-width:160px;', headerStyle: 'min-width:160px;' },
|
||||||
{ name: 'NewDesc', label: 'Yeni Açıklama', field: 'NewDesc', align: 'left', sortable: false, style: 'min-width:180px;white-space:nowrap', headerStyle: 'min-width:180px;white-space:nowrap' }
|
{ name: 'NewDesc', label: 'Yeni Aciklama', field: 'NewDesc', align: 'left', sortable: false, style: 'min-width:220px;', headerStyle: 'min-width:220px;' },
|
||||||
|
{ name: 'actions', label: '', field: 'actions', align: 'center', sortable: false, style: 'width:60px;', headerStyle: 'width:60px;' }
|
||||||
]
|
]
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
store.fetchItems(orderHeaderID.value)
|
await refreshAll()
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(orderHeaderID, (id) => {
|
watch(orderHeaderID, async (id) => {
|
||||||
store.fetchItems(id)
|
await refreshAll()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => store.items,
|
||||||
|
(items) => {
|
||||||
|
rows.value = (items || []).map(item => ({
|
||||||
|
...item,
|
||||||
|
NewItemCode: '',
|
||||||
|
NewColor: '',
|
||||||
|
NewDim2: '',
|
||||||
|
NewDesc: ''
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => store.products,
|
||||||
|
(products) => {
|
||||||
|
productOptions.value = products || []
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
function formatDate (val) {
|
||||||
|
if (!val) return ''
|
||||||
|
const text = String(val)
|
||||||
|
return text.length >= 10 ? text.slice(0, 10) : text
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredProducts = computed(() => {
|
||||||
|
const needle = String(productSearch.value || '').toLowerCase()
|
||||||
|
if (!needle) return productOptions.value.slice(0, 50)
|
||||||
|
return productOptions.value.filter(p =>
|
||||||
|
String(p?.ProductCode || '').toLowerCase().includes(needle)
|
||||||
|
).slice(0, 50)
|
||||||
|
})
|
||||||
|
|
||||||
|
function onSelectProduct (row, code) {
|
||||||
|
productSearch.value = ''
|
||||||
|
onNewItemChange(row, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNewItemChange (row, val) {
|
||||||
|
const next = String(val || '').trim()
|
||||||
|
if (next && !isValidModelCode(next)) {
|
||||||
|
$q.notify({ type: 'negative', message: 'Model kodu formati gecersiz. Ornek: S000-DMY00001' })
|
||||||
|
row.NewItemCode = ''
|
||||||
|
row.NewColor = ''
|
||||||
|
row.NewDim2 = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
row.NewItemCode = next ? next.toUpperCase() : ''
|
||||||
|
row.NewColor = ''
|
||||||
|
row.NewDim2 = ''
|
||||||
|
if (row.NewItemCode) {
|
||||||
|
store.fetchColors(row.NewItemCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNewColorChange (row) {
|
||||||
|
row.NewDim2 = ''
|
||||||
|
if (row.NewItemCode && row.NewColor) {
|
||||||
|
store.fetchSecondColors(row.NewItemCode, row.NewColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColorOptions (row) {
|
||||||
|
const code = row?.NewItemCode || ''
|
||||||
|
const list = store.colorOptionsByCode[code] || []
|
||||||
|
return list.map(c => ({
|
||||||
|
...c,
|
||||||
|
colorLabel: `${c.color_code} - ${c.color_description || ''}`.trim()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSecondColorOptions (row) {
|
||||||
|
const code = row?.NewItemCode || ''
|
||||||
|
const color = row?.NewColor || ''
|
||||||
|
const key = `${code}::${color}`
|
||||||
|
return store.secondColorOptionsByKey[key] || []
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidModelCode (value) {
|
||||||
|
const text = String(value || '').trim().toUpperCase()
|
||||||
|
return /^[A-Z][0-9]{3}-[A-Z]{3}[0-9]{5}$/.test(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPayloadLines () {
|
||||||
|
return rows.value.map(r => ({
|
||||||
|
OrderLineID: r.OrderLineID,
|
||||||
|
NewItemCode: String(r.NewItemCode || '').trim(),
|
||||||
|
NewColor: String(r.NewColor || '').trim(),
|
||||||
|
NewDim2: String(r.NewDim2 || '').trim(),
|
||||||
|
NewDesc: String(r.NewDesc || '').trim()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshAll () {
|
||||||
|
await store.fetchHeader(orderHeaderID.value)
|
||||||
|
await store.fetchItems(orderHeaderID.value)
|
||||||
|
await store.fetchProducts()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onRowSubmit (row) {
|
||||||
|
const line = {
|
||||||
|
OrderLineID: row.OrderLineID,
|
||||||
|
NewItemCode: String(row.NewItemCode || '').trim(),
|
||||||
|
NewColor: String(row.NewColor || '').trim(),
|
||||||
|
NewDim2: String(row.NewDim2 || '').trim(),
|
||||||
|
NewDesc: String(row.NewDesc || '').trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!line.NewItemCode || !line.NewColor) {
|
||||||
|
$q.notify({ type: 'negative', message: 'Yeni urun ve renk zorunludur.' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rowSavingId.value = row.OrderLineID
|
||||||
|
try {
|
||||||
|
const validate = await store.validateUpdates(orderHeaderID.value, [line])
|
||||||
|
const missingCount = validate?.missingCount || 0
|
||||||
|
if (missingCount > 0) {
|
||||||
|
const missingList = (validate?.missing || []).map(v => (
|
||||||
|
`${v.ItemCode} / ${v.ColorCode} / ${v.ItemDim1Code} / ${v.ItemDim2Code}`
|
||||||
|
))
|
||||||
|
$q.dialog({
|
||||||
|
title: 'Eksik Varyantlar',
|
||||||
|
message: `Eksik varyant bulundu: ${missingCount}<br><br>${missingList.join('<br>')}`,
|
||||||
|
html: true,
|
||||||
|
ok: { label: 'Ekle ve Guncelle', color: 'primary' },
|
||||||
|
cancel: { label: 'Vazgec', flat: true }
|
||||||
|
}).onOk(async () => {
|
||||||
|
await store.applyUpdates(orderHeaderID.value, [line], true)
|
||||||
|
await store.fetchItems(orderHeaderID.value)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await store.applyUpdates(orderHeaderID.value, [line], false)
|
||||||
|
await store.fetchItems(orderHeaderID.value)
|
||||||
|
} catch (err) {
|
||||||
|
$q.notify({ type: 'negative', message: 'Islem basarisiz.' })
|
||||||
|
} finally {
|
||||||
|
rowSavingId.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -5,11 +5,35 @@ import api from 'src/services/api'
|
|||||||
export const useOrderProductionItemStore = defineStore('orderproductionitems', {
|
export const useOrderProductionItemStore = defineStore('orderproductionitems', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
items: [],
|
items: [],
|
||||||
|
header: null,
|
||||||
|
products: [],
|
||||||
|
colorOptionsByCode: {},
|
||||||
|
secondColorOptionsByKey: {},
|
||||||
loading: false,
|
loading: false,
|
||||||
|
saving: false,
|
||||||
error: null
|
error: null
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
async fetchHeader (orderHeaderID) {
|
||||||
|
if (!orderHeaderID) {
|
||||||
|
this.header = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true
|
||||||
|
this.error = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await api.get(`/order/get/${encodeURIComponent(orderHeaderID)}`)
|
||||||
|
this.header = res?.data?.header || null
|
||||||
|
} catch (err) {
|
||||||
|
this.header = null
|
||||||
|
this.error = err?.response?.data || err?.message || 'Siparis bilgisi alinamadi'
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
async fetchItems (orderHeaderID) {
|
async fetchItems (orderHeaderID) {
|
||||||
if (!orderHeaderID) {
|
if (!orderHeaderID) {
|
||||||
this.items = []
|
this.items = []
|
||||||
@@ -25,10 +49,99 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
|
|||||||
this.items = Array.isArray(data) ? data : []
|
this.items = Array.isArray(data) ? data : []
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.items = []
|
this.items = []
|
||||||
this.error = err?.response?.data || err?.message || 'Liste alınamadı'
|
this.error = err?.response?.data || err?.message || 'Liste alinamadi'
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
async fetchProducts () {
|
||||||
|
this.error = null
|
||||||
|
try {
|
||||||
|
const res = await api.get('/products')
|
||||||
|
const data = res?.data
|
||||||
|
this.products = Array.isArray(data) ? data : []
|
||||||
|
} catch (err) {
|
||||||
|
this.products = []
|
||||||
|
this.error = err?.response?.data || err?.message || 'Urun listesi alinamadi'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetchColors (productCode) {
|
||||||
|
const code = String(productCode || '').trim()
|
||||||
|
if (!code) return []
|
||||||
|
|
||||||
|
if (this.colorOptionsByCode[code]) {
|
||||||
|
return this.colorOptionsByCode[code]
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await api.get('/product-colors', { params: { code } })
|
||||||
|
const data = res?.data
|
||||||
|
const list = Array.isArray(data) ? data : []
|
||||||
|
this.colorOptionsByCode[code] = list
|
||||||
|
return list
|
||||||
|
} catch (err) {
|
||||||
|
this.error = err?.response?.data || err?.message || 'Renk listesi alinamadi'
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetchSecondColors (productCode, colorCode) {
|
||||||
|
const code = String(productCode || '').trim()
|
||||||
|
const color = String(colorCode || '').trim()
|
||||||
|
if (!code || !color) return []
|
||||||
|
|
||||||
|
const key = `${code}::${color}`
|
||||||
|
if (this.secondColorOptionsByKey[key]) {
|
||||||
|
return this.secondColorOptionsByKey[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await api.get('/product-secondcolor', { params: { code, color } })
|
||||||
|
const data = res?.data
|
||||||
|
const list = Array.isArray(data) ? data : []
|
||||||
|
this.secondColorOptionsByKey[key] = list
|
||||||
|
return list
|
||||||
|
} catch (err) {
|
||||||
|
this.error = err?.response?.data || err?.message || '2. renk listesi alinamadi'
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async validateUpdates (orderHeaderID, lines) {
|
||||||
|
if (!orderHeaderID) return { missingCount: 0, missing: [] }
|
||||||
|
|
||||||
|
this.saving = true
|
||||||
|
this.error = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await api.post(
|
||||||
|
`/orders/production-items/${encodeURIComponent(orderHeaderID)}/validate`,
|
||||||
|
{ lines }
|
||||||
|
)
|
||||||
|
return res?.data || { missingCount: 0, missing: [] }
|
||||||
|
} catch (err) {
|
||||||
|
this.error = err?.response?.data || err?.message || 'Kontrol basarisiz'
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
this.saving = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async applyUpdates (orderHeaderID, lines, insertMissing) {
|
||||||
|
if (!orderHeaderID) return { updated: 0, inserted: 0 }
|
||||||
|
|
||||||
|
this.saving = true
|
||||||
|
this.error = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await api.post(
|
||||||
|
`/orders/production-items/${encodeURIComponent(orderHeaderID)}/apply`,
|
||||||
|
{ lines, insertMissing }
|
||||||
|
)
|
||||||
|
return res?.data || { updated: 0, inserted: 0 }
|
||||||
|
} catch (err) {
|
||||||
|
this.error = err?.response?.data || err?.message || 'Guncelleme basarisiz'
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
this.saving = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1705,6 +1705,7 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
|||||||
rows[idx] = {
|
rows[idx] = {
|
||||||
...prev,
|
...prev,
|
||||||
...newRow,
|
...newRow,
|
||||||
|
_dirty: true,
|
||||||
id: prev.id,
|
id: prev.id,
|
||||||
OrderLineID: prev.OrderLineID || null,
|
OrderLineID: prev.OrderLineID || null,
|
||||||
lineIdMap: preservedLineIdMap
|
lineIdMap: preservedLineIdMap
|
||||||
@@ -1763,6 +1764,7 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
|||||||
|
|
||||||
const insertedRow = {
|
const insertedRow = {
|
||||||
...newRow,
|
...newRow,
|
||||||
|
_dirty: true,
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
OrderLineID: null,
|
OrderLineID: null,
|
||||||
lineIdMap: {}
|
lineIdMap: {}
|
||||||
@@ -1838,6 +1840,7 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
|||||||
rows[dupIdx] = {
|
rows[dupIdx] = {
|
||||||
...prev,
|
...prev,
|
||||||
...newRow,
|
...newRow,
|
||||||
|
_dirty: true,
|
||||||
|
|
||||||
// kritik korumalar
|
// kritik korumalar
|
||||||
id: prev.id,
|
id: prev.id,
|
||||||
@@ -1871,6 +1874,7 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
|||||||
// dup yoksa (veya dup delete satırıydı) → yeni satır
|
// dup yoksa (veya dup delete satırıydı) → yeni satır
|
||||||
rows.push({
|
rows.push({
|
||||||
...newRow,
|
...newRow,
|
||||||
|
_dirty: true,
|
||||||
id: newRow.id || crypto.randomUUID(),
|
id: newRow.id || crypto.randomUUID(),
|
||||||
OrderLineID: null,
|
OrderLineID: null,
|
||||||
lineIdMap: { ...(newRow.lineIdMap || {}) }
|
lineIdMap: { ...(newRow.lineIdMap || {}) }
|
||||||
@@ -2633,7 +2637,12 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
|||||||
// 🧪 PRE-VALIDATE — prItemVariant ön kontrol
|
// 🧪 PRE-VALIDATE — prItemVariant ön kontrol
|
||||||
// - invalid varsa CREATE/UPDATE ÇALIŞMAZ
|
// - invalid varsa CREATE/UPDATE ÇALIŞMAZ
|
||||||
// =======================================================
|
// =======================================================
|
||||||
const v = await api.post('/order/validate', { header, lines })
|
const linesToValidate =
|
||||||
|
isNew
|
||||||
|
? lines
|
||||||
|
: lines.filter(l => l._deleteSignal === true || l._dirty === true || !l.OrderLineID)
|
||||||
|
|
||||||
|
const v = await api.post('/order/validate', { header, lines: linesToValidate })
|
||||||
const invalid = v?.data?.invalid || []
|
const invalid = v?.data?.invalid || []
|
||||||
|
|
||||||
if (invalid.length > 0) {
|
if (invalid.length > 0) {
|
||||||
@@ -2973,6 +2982,8 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
|||||||
OrderLineID: orderLineId || '',
|
OrderLineID: orderLineId || '',
|
||||||
ClientKey: makeLineClientKey(row, grpKey, bedenKey),
|
ClientKey: makeLineClientKey(row, grpKey, bedenKey),
|
||||||
ComboKey: comboKey,
|
ComboKey: comboKey,
|
||||||
|
_dirty: row?._dirty === true,
|
||||||
|
_deleteSignal: isDeleteSignal === true,
|
||||||
|
|
||||||
SortOrder: 0,
|
SortOrder: 0,
|
||||||
ItemTypeCode: 1,
|
ItemTypeCode: 1,
|
||||||
@@ -3054,12 +3065,16 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
|||||||
if (orderLineId && !existing.OrderLineID) {
|
if (orderLineId && !existing.OrderLineID) {
|
||||||
existing.OrderLineID = orderLineId
|
existing.OrderLineID = orderLineId
|
||||||
}
|
}
|
||||||
|
existing._deleteSignal = true
|
||||||
existing.Qty1 = 0
|
existing.Qty1 = 0
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/* MERGE */
|
/* MERGE */
|
||||||
existing.Qty1 += qty
|
existing.Qty1 += qty
|
||||||
|
if (row?._dirty === true) {
|
||||||
|
existing._dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
if (this.mode === 'edit' && orderLineId && !existing.OrderLineID) {
|
if (this.mode === 'edit' && orderLineId && !existing.OrderLineID) {
|
||||||
existing.OrderLineID = orderLineId
|
existing.OrderLineID = orderLineId
|
||||||
@@ -3346,66 +3361,56 @@ export function normalizeBeden(v) {
|
|||||||
- Keeps frontend aksbir bucket for accessory lines
|
- Keeps frontend aksbir bucket for accessory lines
|
||||||
=========================================================== */
|
=========================================================== */
|
||||||
export function detectBedenGroup(bedenList, urunAnaGrubu = '', urunKategori = '') {
|
export function detectBedenGroup(bedenList, urunAnaGrubu = '', urunKategori = '') {
|
||||||
const list = Array.isArray(bedenList) ? bedenList : []
|
const list = Array.isArray(bedenList) && bedenList.length > 0
|
||||||
const ana = normalizeTextForMatch(urunAnaGrubu)
|
? bedenList.map(v => (v || '').toString().trim().toUpperCase())
|
||||||
const alt = normalizeTextForMatch(urunKategori)
|
: [' ']
|
||||||
|
|
||||||
// Frontend compatibility: accessory-only products should stay in aksbir.
|
const ana = (urunAnaGrubu || '')
|
||||||
const accessoryGroups = [
|
.toUpperCase()
|
||||||
'AKSESUAR', 'KRAVAT', 'PAPYON', 'KEMER', 'CORAP',
|
.trim()
|
||||||
'FULAR', 'MENDIL', 'KASKOL', 'ASKI', 'YAKA', 'KOL DUGMESI'
|
.replace(/\(.*?\)/g, '')
|
||||||
|
.replace(/[^A-ZÇĞİÖŞÜ0-9\s]/g, '')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
|
||||||
|
const kat = (urunKategori || '').toUpperCase().trim()
|
||||||
|
// 🔸 Aksesuar ise "aksbir"
|
||||||
|
const aksesuarGruplari = [
|
||||||
|
'AKSESUAR','KRAVAT','PAPYON','KEMER','CORAP','ÇORAP',
|
||||||
|
'FULAR','MENDIL','MENDİL','KASKOL','ASKI',
|
||||||
|
'YAKA','KOL DUGMESI','KOL DÜĞMESİ'
|
||||||
]
|
]
|
||||||
const clothingGroups = ['GOMLEK', 'CEKET', 'PANTOLON', 'MONT', 'YELEK', 'TAKIM', 'TSHIRT']
|
const giyimGruplari = ['GÖMLEK','CEKET','PANTOLON','MONT','YELEK','TAKIM','TSHIRT','TİŞÖRT']
|
||||||
|
// 🔸 Pantolon özel durumu
|
||||||
if (
|
if (
|
||||||
accessoryGroups.some(g => ana.includes(g) || alt.includes(g)) &&
|
aksesuarGruplari.some(g => ana.includes(g) || kat.includes(g)) &&
|
||||||
!clothingGroups.some(g => ana.includes(g))
|
!giyimGruplari.some(g => ana.includes(g))
|
||||||
) {
|
) return 'aksbir'
|
||||||
return 'aksbir'
|
|
||||||
|
if (ana.includes('PANTOLON') && kat.includes('YETİŞKİN')) return 'pan'
|
||||||
|
// 🔸 Tamamen numerik (örneğin 39-44 arası) → ayakkabı
|
||||||
|
const allNumeric = list.every(v => /^\d+$/.test(v))
|
||||||
|
if (allNumeric) {
|
||||||
|
const nums = list.map(v => parseInt(v, 10)).filter(Boolean)
|
||||||
|
const diffs = nums.slice(1).map((v, i) => v - nums[i])
|
||||||
|
if (diffs.every(d => d === 1) && nums[0] >= 35 && nums[0] <= 46) return 'ayk'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ana.includes('AYAKKABI') || alt.includes('AYAKKABI')) {
|
// 🔸 Yaş grubu (çocuk/garson)
|
||||||
return 'ayk'
|
if (kat.includes('GARSON') || kat.includes('ÇOCUK')) return 'yas'
|
||||||
}
|
|
||||||
|
|
||||||
let hasYasNumeric = false
|
// 🔸 Harfli beden varsa doğrudan "gom" (gömlek, üst giyim)
|
||||||
let hasAykNumeric = false
|
const harfliBedenler = ['XS','S','M','L','XL','XXL','3XL','4XL']
|
||||||
let hasPanNumeric = false
|
if (list.some(b => harfliBedenler.includes(b))) return 'gom'
|
||||||
|
|
||||||
for (const raw of list) {
|
|
||||||
const b = safeTrimUpperJs(raw)
|
|
||||||
|
|
||||||
switch (b) {
|
|
||||||
case 'XS':
|
|
||||||
case 'S':
|
|
||||||
case 'M':
|
|
||||||
case 'L':
|
|
||||||
case 'XL':
|
|
||||||
case '2XL':
|
|
||||||
case '3XL':
|
|
||||||
case '4XL':
|
|
||||||
case '5XL':
|
|
||||||
case '6XL':
|
|
||||||
case '7XL':
|
|
||||||
return 'gom'
|
|
||||||
}
|
|
||||||
|
|
||||||
const n = parseNumericSizeJs(b)
|
|
||||||
if (n == null) continue
|
|
||||||
|
|
||||||
if (n >= 2 && n <= 14) hasYasNumeric = true
|
|
||||||
if (n >= 39 && n <= 45) hasAykNumeric = true
|
|
||||||
if (n >= 38 && n <= 68) hasPanNumeric = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAykNumeric) return 'ayk'
|
|
||||||
if (ana.includes('PANTOLON')) return 'pan'
|
|
||||||
if (hasPanNumeric) return 'pan'
|
|
||||||
if (alt.includes('COCUK') || alt.includes('GARSON')) return 'yas'
|
|
||||||
if (hasYasNumeric) return 'yas'
|
|
||||||
|
|
||||||
|
// 🔸 Varsayılan: takım elbise
|
||||||
return 'tak'
|
return 'tak'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function toSummaryRowFromForm(form) {
|
export function toSummaryRowFromForm(form) {
|
||||||
if (!form) return null
|
if (!form) return null
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user