From 028c11e042530ec5ec5da833264a8c6e22388805 Mon Sep 17 00:00:00 2001 From: M_Kececi Date: Thu, 2 Apr 2026 16:30:19 +0300 Subject: [PATCH] Merge remote-tracking branch 'origin/master' --- svc/main.go | 10 + svc/queries/orderproduction_items.go | 431 ++++++++++++------ svc/queries/productnewcolor.go | 16 + svc/queries/productnewsecondcolor.go | 16 + svc/routes/orderproductionitems.go | 92 +++- svc/routes/productnewcolor.go | 45 ++ svc/routes/productnewsecondcolor.go | 51 +++ ...g.js.temporary.compiled.1775136347383.mjs} | 0 ui/src/pages/OrderProductionUpdate.vue | 147 +++++- ui/src/stores/OrderProductionItemStore.js | 150 +++++- 10 files changed, 783 insertions(+), 175 deletions(-) create mode 100644 svc/queries/productnewcolor.go create mode 100644 svc/queries/productnewsecondcolor.go create mode 100644 svc/routes/productnewcolor.go create mode 100644 svc/routes/productnewsecondcolor.go rename ui/{quasar.config.js.temporary.compiled.1775121813494.mjs => quasar.config.js.temporary.compiled.1775136347383.mjs} (100%) diff --git a/svc/main.go b/svc/main.go index dbf23af..37d5576 100644 --- a/svc/main.go +++ b/svc/main.go @@ -576,6 +576,11 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router "order", "view", wrapV3(http.HandlerFunc(routes.GetProductColorsHandler)), ) + bindV3(r, pgDB, + "/api/product-newcolors", "GET", + "order", "view", + wrapV3(http.HandlerFunc(routes.GetProductNewColorsHandler)), + ) bindV3(r, pgDB, "/api/product-colorsize", "GET", @@ -588,6 +593,11 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router "order", "view", wrapV3(http.HandlerFunc(routes.GetProductSecondColorsHandler)), ) + bindV3(r, pgDB, + "/api/product-newsecondcolor", "GET", + "order", "view", + wrapV3(http.HandlerFunc(routes.GetProductNewSecondColorsHandler)), + ) bindV3(r, pgDB, "/api/product-attributes", "GET", "order", "view", diff --git a/svc/queries/orderproduction_items.go b/svc/queries/orderproduction_items.go index 6ff46d0..fb8b008 100644 --- a/svc/queries/orderproduction_items.go +++ b/svc/queries/orderproduction_items.go @@ -65,10 +65,6 @@ func InsertMissingProductionVariants(mssql *sql.DB, orderHeaderID string, userna 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, @@ -77,7 +73,6 @@ INSERT INTO dbo.prItemVariant ( ItemDim1Code, ItemDim2Code, ItemDim3Code, - PLU, IsSalesOrderClosed, IsPurchaseOrderClosed, IsLocked, @@ -97,7 +92,6 @@ SELECT m.ItemDim1Code, m.ItemDim2Code, m.ItemDim3Code, - mp.BasePlu + ROW_NUMBER() OVER (ORDER BY m.ItemCode, m.ColorCode, m.ItemDim1Code, m.ItemDim2Code, m.ItemDim3Code), 0, 0, 0, @@ -109,8 +103,7 @@ SELECT NEWID(), 0, 0 -FROM Missing m -CROSS JOIN MaxPlu mp; +FROM Missing m; ` res, err := mssql.Exec(query, orderHeaderID, username) @@ -140,17 +133,67 @@ WHERE OrderHeaderID = @p1 AND OrderLineID = @p2 return itemTypeCode, dim1, dim2, dim3, err } +type OrderLineDims struct { + ItemTypeCode int16 + ItemDim1Code string + ItemDim2Code string + ItemDim3Code string +} + +func GetOrderLineDimsMap(mssql *sql.DB, orderHeaderID string) (map[string]OrderLineDims, error) { + rows, err := mssql.Query(` +SELECT + CAST(OrderLineID AS NVARCHAR(50)) AS OrderLineID, + ItemTypeCode, + ISNULL(ItemDim1Code,'') AS ItemDim1Code, + ISNULL(ItemDim2Code,'') AS ItemDim2Code, + ISNULL(ItemDim3Code,'') AS ItemDim3Code +FROM dbo.trOrderLine WITH(NOLOCK) +WHERE OrderHeaderID = @p1 +`, orderHeaderID) + if err != nil { + return nil, err + } + defer rows.Close() + + out := make(map[string]OrderLineDims, 128) + for rows.Next() { + var lineID string + var d OrderLineDims + if err := rows.Scan(&lineID, &d.ItemTypeCode, &d.ItemDim1Code, &d.ItemDim2Code, &d.ItemDim3Code); err != nil { + return nil, err + } + out[strings.TrimSpace(lineID)] = d + } + if err := rows.Err(); err != nil { + return nil, err + } + return out, nil +} + 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 ISNULL(LTRIM(RTRIM(ItemCode)),'') = ISNULL(LTRIM(RTRIM(@p2)),'') - AND ISNULL(LTRIM(RTRIM(ColorCode)),'') = ISNULL(LTRIM(RTRIM(@p3)),'') - AND ISNULL(LTRIM(RTRIM(ItemDim1Code)),'') = ISNULL(LTRIM(RTRIM(@p4)),'') - AND ISNULL(LTRIM(RTRIM(ItemDim2Code)),'') = ISNULL(LTRIM(RTRIM(@p5)),'') - AND ISNULL(LTRIM(RTRIM(ItemDim3Code)),'') = ISNULL(LTRIM(RTRIM(@p6)),'') + AND ItemCode = @p2 + AND ( + ColorCode = @p3 + OR (@p3 = '' AND (ColorCode IS NULL OR ColorCode = '')) + ) + AND ( + ItemDim1Code = @p4 + OR (@p4 = '' AND (ItemDim1Code IS NULL OR ItemDim1Code = '')) + ) + AND ( + ItemDim2Code = @p5 + OR (@p5 = '' AND (ItemDim2Code IS NULL OR ItemDim2Code = '')) + ) + AND ( + ItemDim3Code = @p6 + OR (@p6 = '' AND (ItemDim3Code IS NULL OR ItemDim3Code = '')) + ) `, itemTypeCode, itemCode, colorCode, dim1, dim2, dim3).Scan(&exists) if err == sql.ErrNoRows { return false, nil @@ -171,17 +214,24 @@ func InsertMissingVariantsTx( 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 ensuredItems := make(map[string]struct{}, len(missing)) - for i, v := range missing { + uniqueVariants := make([]models.OrderProductionMissingVariant, 0, len(missing)) + seenVariants := make(map[string]struct{}, len(missing)) + + for _, v := range missing { + variantKey := strconv.FormatInt(int64(v.ItemTypeCode), 10) + "|" + + strings.ToUpper(strings.TrimSpace(v.ItemCode)) + "|" + + strings.ToUpper(strings.TrimSpace(v.ColorCode)) + "|" + + strings.ToUpper(strings.TrimSpace(v.ItemDim1Code)) + "|" + + strings.ToUpper(strings.TrimSpace(v.ItemDim2Code)) + "|" + + strings.ToUpper(strings.TrimSpace(v.ItemDim3Code)) + if _, ok := seenVariants[variantKey]; ok { + continue + } + seenVariants[variantKey] = struct{}{} + uniqueVariants = append(uniqueVariants, v) + itemKey := strconv.FormatInt(int64(v.ItemTypeCode), 10) + "|" + v.ItemCode if _, ok := ensuredItems[itemKey]; !ok { draft, hasDraft := cdItemByCode[itemKey] @@ -198,55 +248,89 @@ FROM dbo.prItemVariant WITH (UPDLOCK, HOLDLOCK) } ensuredItems[itemKey] = struct{}{} } + } - plu := basePlu + int64(i) + 1 - res, err := tx.Exec(` -IF NOT EXISTS ( - SELECT 1 - FROM dbo.prItemVariant - WHERE ItemTypeCode = @p1 - AND ISNULL(LTRIM(RTRIM(ItemCode)),'') = ISNULL(LTRIM(RTRIM(@p2)),'') - AND ISNULL(LTRIM(RTRIM(ColorCode)),'') = ISNULL(LTRIM(RTRIM(@p3)),'') - AND ISNULL(LTRIM(RTRIM(ItemDim1Code)),'') = ISNULL(LTRIM(RTRIM(@p4)),'') - AND ISNULL(LTRIM(RTRIM(ItemDim2Code)),'') = ISNULL(LTRIM(RTRIM(@p5)),'') - AND ISNULL(LTRIM(RTRIM(ItemDim3Code)),'') = ISNULL(LTRIM(RTRIM(@p6)),'') + if len(uniqueVariants) == 0 { + return 0, nil + } + + args := make([]any, 0, len(uniqueVariants)*6+1) + valueRows := make([]string, 0, len(uniqueVariants)) + paramPos := 1 + for _, v := range uniqueVariants { + valueRows = append(valueRows, fmt.Sprintf("(@p%d,@p%d,@p%d,@p%d,@p%d,@p%d)", paramPos, paramPos+1, paramPos+2, paramPos+3, paramPos+4, paramPos+5)) + args = append(args, v.ItemTypeCode, v.ItemCode, v.ColorCode, v.ItemDim1Code, v.ItemDim2Code, v.ItemDim3Code) + paramPos += 6 + } + usernameParam := paramPos + args = append(args, username) + + query := fmt.Sprintf(` +SET NOCOUNT ON; +WITH Missing(ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code) AS ( + SELECT * + FROM (VALUES %s) AS v(ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code) ) INSERT INTO dbo.prItemVariant ( - ItemTypeCode, - ItemCode, - ColorCode, - ItemDim1Code, - ItemDim2Code, - ItemDim3Code, - PLU, - IsSalesOrderClosed, - IsPurchaseOrderClosed, - IsLocked, - IsBlocked, - CreatedUserName, - CreatedDate, - LastUpdatedUserName, - LastUpdatedDate, - RowGuid, - UseInternet, - IsStoreOrderClosed + ItemTypeCode, + ItemCode, + ColorCode, + ItemDim1Code, + ItemDim2Code, + ItemDim3Code, + IsSalesOrderClosed, + IsPurchaseOrderClosed, + IsLocked, + IsBlocked, + CreatedUserName, + CreatedDate, + LastUpdatedUserName, + LastUpdatedDate, + RowGuid, + UseInternet, + IsStoreOrderClosed ) -VALUES ( - @p1, @p2, @p3, @p4, @p5, @p6, - @p7, - 0, 0, 0, 0, - @p8, GETDATE(), @p8, GETDATE(), - NEWID(), - 0, - 0 -); -`, 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 - } +SELECT + m.ItemTypeCode, + m.ItemCode, + m.ColorCode, + m.ItemDim1Code, + m.ItemDim2Code, + m.ItemDim3Code, + 0, 0, 0, 0, + @p%d, GETDATE(), @p%d, GETDATE(), + NEWID(), + 0, + 0 +FROM Missing m +LEFT JOIN dbo.prItemVariant pv + ON pv.ItemTypeCode = m.ItemTypeCode + AND pv.ItemCode = m.ItemCode + AND ( + pv.ColorCode = m.ColorCode + OR (m.ColorCode = '' AND (pv.ColorCode IS NULL OR pv.ColorCode = '')) + ) + AND ( + pv.ItemDim1Code = m.ItemDim1Code + OR (m.ItemDim1Code = '' AND (pv.ItemDim1Code IS NULL OR pv.ItemDim1Code = '')) + ) + AND ( + pv.ItemDim2Code = m.ItemDim2Code + OR (m.ItemDim2Code = '' AND (pv.ItemDim2Code IS NULL OR pv.ItemDim2Code = '')) + ) + AND ( + pv.ItemDim3Code = m.ItemDim3Code + OR (m.ItemDim3Code = '' AND (pv.ItemDim3Code IS NULL OR pv.ItemDim3Code = '')) + ) +WHERE pv.ItemCode IS NULL; +`, strings.Join(valueRows, ","), usernameParam, usernameParam) + + res, err := tx.Exec(query, args...) + if err != nil { + return inserted, err + } + if rows, rowsErr := res.RowsAffected(); rowsErr == nil { + inserted += rows } return inserted, nil } @@ -341,7 +425,7 @@ BEGIN 'AD', '', 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, - '', '10%', '', '', '', + '', '%10', '', '', '', '', '', '0', '0', '0', '0', '', '', 0, '', '1', 0, 0, '1900-01-01', 0, 0, @@ -369,7 +453,15 @@ SET ProductHierarchyID = COALESCE(@p5, ProductHierarchyID), UnitOfMeasureCode1 = COALESCE(NULLIF(@p6,''), UnitOfMeasureCode1), ItemAccountGrCode = COALESCE(NULLIF(@p7,''), ItemAccountGrCode), - ItemTaxGrCode = COALESCE(NULLIF(@p8,''), ItemTaxGrCode), + ItemTaxGrCode = CASE + WHEN NULLIF(@p8,'') IS NULL THEN ItemTaxGrCode + WHEN EXISTS ( + SELECT 1 + FROM dbo.cdItemTaxGr g WITH(NOLOCK) + WHERE LTRIM(RTRIM(g.ItemTaxGrCode)) = LTRIM(RTRIM(@p8)) + ) THEN @p8 + ELSE ItemTaxGrCode + END, ItemPaymentPlanGrCode = COALESCE(NULLIF(@p9,''), ItemPaymentPlanGrCode), ItemDiscountGrCode = COALESCE(NULLIF(@p10,''), ItemDiscountGrCode), ItemVendorGrCode = COALESCE(NULLIF(@p11,''), ItemVendorGrCode), @@ -411,23 +503,67 @@ WHERE ItemTypeCode = @p1 } func UpdateOrderLinesTx(tx *sql.Tx, orderHeaderID string, lines []models.OrderProductionUpdateLine, username string) (int64, error) { + if len(lines) == 0 { + return 0, nil + } + + const chunkSize = 300 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 + + for i := 0; i < len(lines); i += chunkSize { + end := i + chunkSize + if end > len(lines) { + end = len(lines) } - if rows, err := res.RowsAffected(); err == nil { + chunk := lines[i:end] + + values := make([]string, 0, len(chunk)) + args := make([]any, 0, len(chunk)*5+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)) + args = append(args, + strings.TrimSpace(line.OrderLineID), + line.NewItemCode, + line.NewColor, + line.NewDim2, + line.NewDesc, + ) + paramPos += 5 + } + + orderHeaderParam := paramPos + usernameParam := paramPos + 1 + args = append(args, orderHeaderID, username) + + query := fmt.Sprintf(` +SET NOCOUNT ON; +WITH src (OrderLineID, NewItemCode, NewColor, NewDim2, NewDesc) AS ( + SELECT * + FROM (VALUES %s) AS v (OrderLineID, NewItemCode, NewColor, NewDim2, NewDesc) +) +UPDATE l +SET + l.ItemCode = s.NewItemCode, + l.ColorCode = s.NewColor, + l.ItemDim2Code = s.NewDim2, + l.LineDescription = COALESCE(NULLIF(s.NewDesc,''), l.LineDescription), + l.LastUpdatedUserName = @p%d, + l.LastUpdatedDate = GETDATE() +FROM dbo.trOrderLine l +JOIN src s + ON CAST(l.OrderLineID AS NVARCHAR(50)) = s.OrderLineID +WHERE l.OrderHeaderID = @p%d; +`, strings.Join(values, ","), usernameParam, orderHeaderParam) + + chunkStart := time.Now() + res, execErr := tx.Exec(query, args...) + if execErr != nil { + return updated, fmt.Errorf("update lines chunk failed chunkStart=%d chunkEnd=%d duration_ms=%d: %w", i, end, time.Since(chunkStart).Milliseconds(), execErr) + } + log.Printf("[UpdateOrderLinesTx] orderHeaderID=%s chunk=%d-%d duration_ms=%d", orderHeaderID, i, end, time.Since(chunkStart).Milliseconds()) + + if rows, rowsErr := res.RowsAffected(); rowsErr == nil { updated += rows } } @@ -439,58 +575,89 @@ func UpsertItemAttributesTx(tx *sql.Tx, attrs []models.OrderProductionItemAttrib return 0, nil } + // SQL Server parameter limiti (2100) nedeniyle batch'li set-based upsert kullanilir. + const chunkSize = 400 // 400 * 4 param + 1 username = 1601 var affected int64 - for _, a := range attrs { - res, err := tx.Exec(` -IF EXISTS ( - SELECT 1 - FROM dbo.prItemAttribute - WHERE ItemTypeCode = @p1 - AND ItemCode = @p2 - AND AttributeTypeCode = @p3 + for i := 0; i < len(attrs); i += chunkSize { + end := i + chunkSize + if end > len(attrs) { + end = len(attrs) + } + chunk := attrs[i:end] + + values := make([]string, 0, len(chunk)) + args := make([]any, 0, len(chunk)*4+1) + paramPos := 1 + for _, a := range chunk { + values = append(values, fmt.Sprintf("(@p%d,@p%d,@p%d,@p%d)", paramPos, paramPos+1, paramPos+2, paramPos+3)) + args = append(args, a.ItemTypeCode, a.ItemCode, a.AttributeTypeCode, a.AttributeCode) + paramPos += 4 + } + usernameParam := paramPos + args = append(args, username) + + query := fmt.Sprintf(` +SET NOCOUNT ON; +DECLARE @updated INT = 0; +DECLARE @inserted INT = 0; + +WITH src (ItemTypeCode, ItemCode, AttributeTypeCode, AttributeCode) AS ( + SELECT * + FROM (VALUES %s) AS v (ItemTypeCode, ItemCode, AttributeTypeCode, AttributeCode) ) -BEGIN - UPDATE dbo.prItemAttribute - SET - AttributeCode = @p4, - LastUpdatedUserName = @p5, - LastUpdatedDate = GETDATE() - WHERE ItemTypeCode = @p1 - AND ItemCode = @p2 - AND AttributeTypeCode = @p3 -END -ELSE -BEGIN - INSERT INTO dbo.prItemAttribute ( - ItemTypeCode, - ItemCode, - AttributeTypeCode, - AttributeCode, - CreatedUserName, - CreatedDate, - LastUpdatedUserName, - LastUpdatedDate, - RowGuid - ) - VALUES ( - @p1, - @p2, - @p3, - @p4, - @p5, - GETDATE(), - @p5, - GETDATE(), - NEWID() - ) -END -`, a.ItemTypeCode, a.ItemCode, a.AttributeTypeCode, a.AttributeCode, username) - if err != nil { +UPDATE tgt +SET + tgt.AttributeCode = src.AttributeCode, + tgt.LastUpdatedUserName = @p%d, + tgt.LastUpdatedDate = GETDATE() +FROM dbo.prItemAttribute tgt +JOIN src + ON src.ItemTypeCode = tgt.ItemTypeCode + AND src.ItemCode = tgt.ItemCode + AND src.AttributeTypeCode = tgt.AttributeTypeCode; +SET @updated = @@ROWCOUNT; + +WITH src (ItemTypeCode, ItemCode, AttributeTypeCode, AttributeCode) AS ( + SELECT * + FROM (VALUES %s) AS v (ItemTypeCode, ItemCode, AttributeTypeCode, AttributeCode) +) +INSERT INTO dbo.prItemAttribute ( + ItemTypeCode, + ItemCode, + AttributeTypeCode, + AttributeCode, + CreatedUserName, + CreatedDate, + LastUpdatedUserName, + LastUpdatedDate, + RowGuid +) +SELECT + src.ItemTypeCode, + src.ItemCode, + src.AttributeTypeCode, + src.AttributeCode, + @p%d, + GETDATE(), + @p%d, + GETDATE(), + NEWID() +FROM src +LEFT JOIN dbo.prItemAttribute tgt + ON src.ItemTypeCode = tgt.ItemTypeCode + AND src.ItemCode = tgt.ItemCode + AND src.AttributeTypeCode = tgt.AttributeTypeCode +WHERE tgt.ItemCode IS NULL; +SET @inserted = @@ROWCOUNT; + +SELECT (@updated + @inserted) AS Affected; +`, strings.Join(values, ","), usernameParam, strings.Join(values, ","), usernameParam, usernameParam) + + var chunkAffected int64 + if err := tx.QueryRow(query, args...).Scan(&chunkAffected); err != nil { return affected, err } - if rows, err := res.RowsAffected(); err == nil { - affected += rows - } + affected += chunkAffected } return affected, nil } diff --git a/svc/queries/productnewcolor.go b/svc/queries/productnewcolor.go new file mode 100644 index 0000000..bcc760e --- /dev/null +++ b/svc/queries/productnewcolor.go @@ -0,0 +1,16 @@ +package queries + +const GetProductNewColors = ` +SELECT + CAST(@p1 AS NVARCHAR(30)) AS ProductCode, + LTRIM(RTRIM(c.ColorCode)) AS ColorCode, + ISNULL(NULLIF(LTRIM(RTRIM(cd.ColorDescription)), ''), ISNULL(NULLIF(LTRIM(RTRIM(c.ColorHex)), ''), LTRIM(RTRIM(c.ColorCode)))) AS ColorDescription +FROM dbo.cdColor AS c WITH(NOLOCK) +LEFT JOIN dbo.cdColorDesc AS cd WITH(NOLOCK) + ON cd.ColorCode = c.ColorCode + AND cd.LangCode = 'TR' +WHERE ISNULL(c.IsBlocked, 0) = 0 + AND LEN(LTRIM(RTRIM(ISNULL(c.ColorCode, '')))) = 3 + AND LTRIM(RTRIM(ISNULL(c.ColorCatalogCode1, ''))) = N'ÜRÜN' +ORDER BY LTRIM(RTRIM(c.ColorCode)); +` diff --git a/svc/queries/productnewsecondcolor.go b/svc/queries/productnewsecondcolor.go new file mode 100644 index 0000000..9c80ed2 --- /dev/null +++ b/svc/queries/productnewsecondcolor.go @@ -0,0 +1,16 @@ +package queries + +const GetProductNewSecondColors = ` +SELECT + LTRIM(RTRIM(@ProductCode)) AS ProductCode, + LTRIM(RTRIM(ISNULL(@ColorCode, ''))) AS ColorCode, + LTRIM(RTRIM(d2.ItemDim2Code)) AS ItemDim2Code, + ISNULL(NULLIF(LTRIM(RTRIM(cd.ColorDescription)), ''), LTRIM(RTRIM(d2.ItemDim2Code))) AS ColorDescription +FROM dbo.cdItemDim2 AS d2 WITH(NOLOCK) +LEFT JOIN dbo.cdColorDesc AS cd WITH(NOLOCK) + ON cd.ColorCode = d2.ItemDim2Code + AND cd.LangCode = 'TR' +WHERE ISNULL(d2.IsBlocked, 0) = 0 + AND LEN(LTRIM(RTRIM(ISNULL(d2.ItemDim2Code, '')))) = 3 +ORDER BY LTRIM(RTRIM(d2.ItemDim2Code)); +` diff --git a/svc/routes/orderproductionitems.go b/svc/routes/orderproductionitems.go index 97a46a8..47c91bd 100644 --- a/svc/routes/orderproductionitems.go +++ b/svc/routes/orderproductionitems.go @@ -150,6 +150,9 @@ func OrderProductionInsertMissingRoute(mssql *sql.DB) http.Handler { 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") + rid := fmt.Sprintf("opv-%d", time.Now().UnixNano()) + w.Header().Set("X-Debug-Request-Id", rid) + start := time.Now() id := mux.Vars(r)["id"] if id == "" { @@ -167,11 +170,16 @@ func OrderProductionValidateRoute(mssql *sql.DB) http.Handler { return } + stepStart := time.Now() missing, err := buildMissingVariants(mssql, id, payload.Lines) if err != nil { + log.Printf("[OrderProductionValidateRoute] rid=%s orderHeaderID=%s step=build_missing failed duration_ms=%d err=%v", + rid, id, time.Since(stepStart).Milliseconds(), err) writeDBError(w, http.StatusInternalServerError, "validate_missing_variants", id, "", len(payload.Lines), err) return } + 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()) resp := map[string]any{ "missingCount": len(missing), @@ -189,6 +197,9 @@ func OrderProductionValidateRoute(mssql *sql.DB) http.Handler { 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") + rid := fmt.Sprintf("opa-%d", time.Now().UnixNano()) + w.Header().Set("X-Debug-Request-Id", rid) + start := time.Now() id := mux.Vars(r)["id"] if id == "" { @@ -206,13 +217,20 @@ func OrderProductionApplyRoute(mssql *sql.DB) http.Handler { return } + stepMissingStart := time.Now() missing, err := buildMissingVariants(mssql, id, payload.Lines) if err != nil { + log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=build_missing failed duration_ms=%d err=%v", + rid, id, time.Since(stepMissingStart).Milliseconds(), err) writeDBError(w, http.StatusInternalServerError, "apply_validate_missing_variants", id, "", len(payload.Lines), err) return } + 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()) 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()) w.WriteHeader(http.StatusConflict) _ = json.NewEncoder(w).Encode(map[string]any{ "missingCount": len(missing), @@ -231,43 +249,68 @@ func OrderProductionApplyRoute(mssql *sql.DB) http.Handler { username = "system" } + stepBeginStart := time.Now() tx, err := mssql.Begin() if err != nil { writeDBError(w, http.StatusInternalServerError, "begin_tx", id, username, len(payload.Lines), err) return } defer tx.Rollback() + log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=begin_tx duration_ms=%d", rid, id, time.Since(stepBeginStart).Milliseconds()) + + stepTxSettingsStart := time.Now() + if _, err := tx.Exec(`SET XACT_ABORT ON; SET LOCK_TIMEOUT 15000;`); err != nil { + writeDBError(w, http.StatusInternalServerError, "tx_settings", id, username, len(payload.Lines), err) + return + } + log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=tx_settings duration_ms=%d", rid, id, time.Since(stepTxSettingsStart).Milliseconds()) var inserted int64 if payload.InsertMissing { cdItemByCode := buildCdItemDraftMap(payload.CdItems) + stepInsertMissingStart := time.Now() inserted, err = queries.InsertMissingVariantsTx(tx, missing, username, cdItemByCode) if err != nil { writeDBError(w, http.StatusInternalServerError, "insert_missing_variants", id, username, len(missing), err) return } + log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=insert_missing inserted=%d duration_ms=%d", + rid, id, inserted, time.Since(stepInsertMissingStart).Milliseconds()) } + stepValidateAttrStart := time.Now() if err := validateProductAttributes(payload.ProductAttributes); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } - attributeAffected, err := queries.UpsertItemAttributesTx(tx, payload.ProductAttributes, username) - if err != nil { - writeDBError(w, http.StatusInternalServerError, "upsert_item_attributes", id, username, len(payload.ProductAttributes), err) - return - } + log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=validate_attributes count=%d duration_ms=%d", + rid, id, len(payload.ProductAttributes), time.Since(stepValidateAttrStart).Milliseconds()) + stepUpdateLinesStart := time.Now() updated, err := queries.UpdateOrderLinesTx(tx, id, payload.Lines, username) if err != nil { writeDBError(w, http.StatusInternalServerError, "update_order_lines", id, username, len(payload.Lines), err) return } + log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=update_lines updated=%d duration_ms=%d", + rid, id, updated, time.Since(stepUpdateLinesStart).Milliseconds()) + stepUpsertAttrStart := time.Now() + attributeAffected, err := queries.UpsertItemAttributesTx(tx, payload.ProductAttributes, username) + if err != nil { + writeDBError(w, http.StatusInternalServerError, "upsert_item_attributes", id, username, len(payload.ProductAttributes), err) + return + } + log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=upsert_attributes affected=%d duration_ms=%d", + rid, id, attributeAffected, time.Since(stepUpsertAttrStart).Milliseconds()) + + stepCommitStart := time.Now() if err := tx.Commit(); err != nil { writeDBError(w, http.StatusInternalServerError, "commit_tx", id, username, len(payload.Lines), err) return } + 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()) resp := map[string]any{ "updated": updated, @@ -319,7 +362,13 @@ func buildCdItemDraftMap(list []models.OrderProductionCdItemDraft) map[string]mo } func buildMissingVariants(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)) for _, line := range lines { lineID := strings.TrimSpace(line.OrderLineID) @@ -331,28 +380,43 @@ func buildMissingVariants(mssql *sql.DB, orderHeaderID string, lines []models.Or continue } - itemTypeCode, dim1, _, dim3, err := queries.GetOrderLineDims(mssql, orderHeaderID, lineID) - if err != nil { - return nil, err + dims, ok := lineDimsMap[lineID] + if !ok { + continue } - exists, err := queries.VariantExists(mssql, itemTypeCode, newItem, newColor, dim1, newDim2, dim3) - if err != nil { - return nil, err + 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)), + ) + exists, cached := existsCache[cacheKey] + if !cached { + var checkErr error + exists, checkErr = queries.VariantExists(mssql, dims.ItemTypeCode, newItem, newColor, dims.ItemDim1Code, newDim2, dims.ItemDim3Code) + if checkErr != nil { + return nil, checkErr + } + existsCache[cacheKey] = exists } if !exists { missing = append(missing, models.OrderProductionMissingVariant{ OrderLineID: lineID, - ItemTypeCode: itemTypeCode, + ItemTypeCode: dims.ItemTypeCode, ItemCode: newItem, ColorCode: newColor, - ItemDim1Code: dim1, + ItemDim1Code: dims.ItemDim1Code, ItemDim2Code: newDim2, - ItemDim3Code: dim3, + ItemDim3Code: dims.ItemDim3Code, }) } } + 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()) return missing, nil } diff --git a/svc/routes/productnewcolor.go b/svc/routes/productnewcolor.go new file mode 100644 index 0000000..5c109aa --- /dev/null +++ b/svc/routes/productnewcolor.go @@ -0,0 +1,45 @@ +package routes + +import ( + "bssapp-backend/auth" + "bssapp-backend/db" + "bssapp-backend/models" + "bssapp-backend/queries" + "encoding/json" + "log" + "net/http" +) + +func GetProductNewColorsHandler(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 gerekli", http.StatusBadRequest) + return + } + + rows, err := db.MssqlDB.Query(queries.GetProductNewColors, code) + if err != nil { + http.Error(w, "Yeni urun renk listesi alinamadi: "+err.Error(), http.StatusInternalServerError) + return + } + defer rows.Close() + + var list []models.ProductColor + for rows.Next() { + var c models.ProductColor + if err := rows.Scan(&c.ProductCode, &c.ColorCode, &c.ColorDescription); err != nil { + log.Println("Satir okunamadi:", err) + continue + } + list = append(list, c) + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + _ = json.NewEncoder(w).Encode(list) +} diff --git a/svc/routes/productnewsecondcolor.go b/svc/routes/productnewsecondcolor.go new file mode 100644 index 0000000..ee15ac5 --- /dev/null +++ b/svc/routes/productnewsecondcolor.go @@ -0,0 +1,51 @@ +package routes + +import ( + "bssapp-backend/auth" + "bssapp-backend/db" + "bssapp-backend/models" + "bssapp-backend/queries" + "database/sql" + "encoding/json" + "log" + "net/http" +) + +func GetProductNewSecondColorsHandler(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") + color := r.URL.Query().Get("color") + if code == "" || color == "" { + http.Error(w, "Eksik parametre: code ve color gerekli", http.StatusBadRequest) + return + } + + rows, err := db.MssqlDB.Query( + queries.GetProductNewSecondColors, + sql.Named("ProductCode", code), + sql.Named("ColorCode", color), + ) + if err != nil { + http.Error(w, "Yeni urun 2. renk listesi alinamadi: "+err.Error(), http.StatusInternalServerError) + return + } + defer rows.Close() + + var list []models.ProductSecondColor + for rows.Next() { + var c models.ProductSecondColor + if err := rows.Scan(&c.ProductCode, &c.ColorCode, &c.ItemDim2Code, &c.ColorDescription); err != nil { + log.Println("Satir okunamadi:", err) + continue + } + list = append(list, c) + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + _ = json.NewEncoder(w).Encode(list) +} diff --git a/ui/quasar.config.js.temporary.compiled.1775121813494.mjs b/ui/quasar.config.js.temporary.compiled.1775136347383.mjs similarity index 100% rename from ui/quasar.config.js.temporary.compiled.1775121813494.mjs rename to ui/quasar.config.js.temporary.compiled.1775136347383.mjs diff --git a/ui/src/pages/OrderProductionUpdate.vue b/ui/src/pages/OrderProductionUpdate.vue index b9e4324..508bcd1 100644 --- a/ui/src/pages/OrderProductionUpdate.vue +++ b/ui/src/pages/OrderProductionUpdate.vue @@ -216,6 +216,7 @@ filled label="Yeni 2. Renk" :disable="isColorSelectionLocked(props.row)" + @update:model-value="() => onNewDim2Change(props.row)" /> @@ -331,6 +332,13 @@ const store = useOrderProductionItemStore() const BAGGI_CODE_PATTERN = /^[A-Z][0-9]{3}-[A-Z]{3}[0-9]{5}$/ const BAGGI_CODE_ERROR = 'Girdiginiz kod BAGGI kod sistemine uyumlu degil. Format: X999-XXX99999' +function nowMs () { + if (typeof performance !== 'undefined' && typeof performance.now === 'function') { + return performance.now() + } + return Date.now() +} + const orderHeaderID = computed(() => String(route.params.orderHeaderID || '').trim()) const header = computed(() => store.header || {}) const cariLabel = computed(() => { @@ -517,8 +525,13 @@ function onNewItemChange (row, val, source = 'typed') { applyNewItemVisualState(row, source) row.NewColor = '' row.NewDim2 = '' + row.NewDesc = mergeDescWithAutoNote(row, row.NewDesc || row.OldDesc) if (row.NewItemCode) { - store.fetchColors(row.NewItemCode) + if (row.NewItemMode === 'new') { + store.fetchNewColors(row.NewItemCode) + } else { + store.fetchColors(row.NewItemCode) + } } if (row.NewItemMode === 'new' && isValidBaggiModelCode(row.NewItemCode) && row.NewItemCode !== prevCode) { openNewCodeSetupFlow(row.NewItemCode) @@ -560,14 +573,27 @@ function openNewCodeSetupFlow (itemCode) { function onNewColorChange (row) { row.NewColor = normalizeShortCode(row.NewColor, 3) row.NewDim2 = '' + row.NewDesc = mergeDescWithAutoNote(row, row.NewDesc || row.OldDesc) if (row.NewItemCode && row.NewColor) { - store.fetchSecondColors(row.NewItemCode, row.NewColor) + if (String(row?.NewItemMode || '').trim() === 'new') { + store.fetchNewSecondColors(row.NewItemCode, row.NewColor) + } else { + store.fetchSecondColors(row.NewItemCode, row.NewColor) + } } } +function onNewDim2Change (row) { + row.NewDim2 = normalizeShortCode(row.NewDim2, 3) + row.NewDesc = mergeDescWithAutoNote(row, row.NewDesc || row.OldDesc) +} + function getColorOptions (row) { const code = row?.NewItemCode || '' - const list = store.colorOptionsByCode[code] || [] + const isNewMode = String(row?.NewItemMode || '').trim() === 'new' + const list = isNewMode + ? (store.newColorOptionsByCode[code] || []) + : (store.colorOptionsByCode[code] || []) return list.map(c => ({ ...c, colorLabel: `${c.color_code} - ${c.color_description || ''}`.trim() @@ -578,7 +604,10 @@ function getSecondColorOptions (row) { const code = row?.NewItemCode || '' const color = row?.NewColor || '' const key = `${code}::${color}` - const list = store.secondColorOptionsByKey[key] || [] + const isNewMode = String(row?.NewItemMode || '').trim() === 'new' + const list = isNewMode + ? (store.newSecondColorOptionsByKey[key] || []) + : (store.secondColorOptionsByKey[key] || []) return list.map(c => ({ ...c, item_dim2_label: `${c.item_dim2_code} - ${c.color_description || ''}`.trim() @@ -609,6 +638,55 @@ function isValidBaggiModelCode (code) { return BAGGI_CODE_PATTERN.test(code) } +function formatCodeColorDim2 (itemCode, color, dim2) { + const item = String(itemCode || '').trim().toUpperCase() + const c1 = normalizeShortCode(color, 3) + const c2 = normalizeShortCode(dim2, 3) + const c1Safe = c1 || '-' + const c2Safe = c2 || '-' + return `${item}/${c1Safe}/${c2Safe}` +} + +function buildAutoUpdateNote (row) { + const oldInfo = formatCodeColorDim2(row?.OldItemCode, row?.OldColor, row?.OldDim2) + const nextInfo = formatCodeColorDim2(row?.NewItemCode, row?.NewColor, row?.NewDim2) + return `Bu siparis satirinda kod ${oldInfo} bilgisinden ${nextInfo} bilgisine guncellenmistir.` +} + +function isSelectionCompleteByOldShape (row) { + const hasModel = String(row?.NewItemCode || '').trim().length > 0 + if (!hasModel) return false + + const oldHasColor = String(row?.OldColor || '').trim().length > 0 + const oldHasDim2 = String(row?.OldDim2 || '').trim().length > 0 + const hasNewColor = normalizeShortCode(row?.NewColor, 3).length === 3 + const hasNewDim2 = normalizeShortCode(row?.NewDim2, 3).length === 3 + + if (oldHasDim2) return hasNewColor && hasNewDim2 + if (oldHasColor) return hasNewColor + return true +} + +function stripAutoUpdateNote (text) { + const desc = String(text || '').trim() + if (!desc) return '' + const marker = ' Bu siparis satirinda kod ' + const idx = desc.indexOf(marker) + if (idx > -1) return desc.slice(0, idx).trim() + if (desc.startsWith('Bu siparis satirinda kod ')) return '' + return desc +} + +function mergeDescWithAutoNote (row, baseDesc) { + const desc = stripAutoUpdateNote(baseDesc) + if (!isSelectionCompleteByOldShape(row)) return desc + const note = buildAutoUpdateNote(row) + if (!note) return desc + if (desc.includes(note)) return desc + if (!desc) return note + return `${desc} ${note}` +} + function validateRowInput (row) { const entryMode = String(row?.NewItemEntryMode || '').trim() const newItemCode = String(row.NewItemCode || '').trim().toUpperCase() @@ -631,6 +709,7 @@ function validateRowInput (row) { row.NewItemCode = newItemCode row.NewColor = newColor row.NewDim2 = newDim2 + row.NewDesc = mergeDescWithAutoNote(row, row.NewDesc || row.OldDesc) return '' } @@ -646,7 +725,7 @@ function collectLinesFromRows (selectedRows) { NewItemCode: String(row.NewItemCode || '').trim().toUpperCase(), NewColor: normalizeShortCode(row.NewColor, 3), NewDim2: normalizeShortCode(row.NewDim2, 3), - NewDesc: String((row.NewDesc || row.OldDesc) || '').trim() + NewDesc: mergeDescWithAutoNote(row, row.NewDesc || row.OldDesc) } for (const id of (row.OrderLineIDs || [])) { @@ -668,7 +747,7 @@ function createEmptyCdItemDraft (itemCode) { ProductHierarchyID: '', UnitOfMeasureCode1: 'AD', ItemAccountGrCode: '', - ItemTaxGrCode: '10%', + ItemTaxGrCode: '%10', ItemPaymentPlanGrCode: '', ItemDiscountGrCode: '', ItemVendorGrCode: '', @@ -715,11 +794,6 @@ function isDummyLookupOption (key, codeRaw, descRaw) { if (code === '0' || code === '00' || code === '000' || code === '0000') return true if (desc.includes('DUMMY')) return true - // Is plani dokumanindaki sari/default alanlar - if (key === 'unitOfMeasureCode1List' && code === 'AD') return true - if (key === 'itemTaxGrCodes' && code === '10%') return true - if (key === 'companyCodes' && code === '1') return true - return false } @@ -755,19 +829,19 @@ function normalizeCdItemDraftForPayload (draftRaw) { ItemTypeCode: toIntOrNil(d.ItemTypeCode) || 1, ItemCode: String(d.ItemCode || '').trim().toUpperCase(), ItemDimTypeCode: toIntOrNil(d.ItemDimTypeCode) || 1, - ProductTypeCode: 1, + ProductTypeCode: null, ProductHierarchyID: toIntOrNil(d.ProductHierarchyID), UnitOfMeasureCode1: 'AD', ItemAccountGrCode: null, - ItemTaxGrCode: '10%', + ItemTaxGrCode: '%10', ItemPaymentPlanGrCode: null, ItemDiscountGrCode: null, ItemVendorGrCode: null, PromotionGroupCode: null, - ProductCollectionGrCode: '0', - StorePriceLevelCode: '0', - PerceptionOfFashionCode: '0', - CommercialRoleCode: '0', + ProductCollectionGrCode: null, + StorePriceLevelCode: null, + PerceptionOfFashionCode: null, + CommercialRoleCode: null, StoreCapacityLevelCode: null, CustomsTariffNumberCode: null, CompanyCode: '1' @@ -956,6 +1030,11 @@ function buildProductionUpdateMailPayload (selectedRows) { async function sendUpdateMailAfterApply (selectedRows) { const orderId = String(orderHeaderID.value || '').trim() if (!orderId) return + const host = String(window?.location?.hostname || '').trim().toLowerCase() + const isLocalHost = host === 'localhost' || host === '127.0.0.1' + if (isLocalHost) { + return + } try { const payload = buildProductionUpdateMailPayload(selectedRows) @@ -1082,12 +1161,14 @@ async function refreshAll () { } async function onBulkSubmit () { + const flowStart = nowMs() const selectedRows = rows.value.filter(r => !!selectedMap.value[r.RowKey]) if (!selectedRows.length) { $q.notify({ type: 'warning', message: 'Lutfen en az bir satir seciniz.' }) return } + const prepStart = nowMs() const { errMsg, lines } = collectLinesFromRows(selectedRows) if (errMsg) { $q.notify({ type: 'negative', message: errMsg }) @@ -1112,8 +1193,24 @@ async function onBulkSubmit () { return } + console.info('[OrderProductionUpdate] onBulkSubmit prepared', { + orderHeaderID: orderHeaderID.value, + selectedRowCount: selectedRows.length, + lineCount: lines.length, + cdItemCount: cdItems.length, + attributeCount: productAttributes.length, + prepDurationMs: Math.round(nowMs() - prepStart) + }) + try { + const validateStart = nowMs() const validate = await store.validateUpdates(orderHeaderID.value, lines) + console.info('[OrderProductionUpdate] validate finished', { + orderHeaderID: orderHeaderID.value, + lineCount: lines.length, + missingCount: Number(validate?.missingCount || 0), + durationMs: Math.round(nowMs() - validateStart) + }) const missingCount = validate?.missingCount || 0 if (missingCount > 0) { const missingList = (validate?.missing || []).map(v => ( @@ -1126,7 +1223,13 @@ async function onBulkSubmit () { ok: { label: 'Ekle ve Guncelle', color: 'primary' }, cancel: { label: 'Vazgec', flat: true } }).onOk(async () => { + const applyStart = nowMs() await store.applyUpdates(orderHeaderID.value, lines, true, cdItems, productAttributes) + console.info('[OrderProductionUpdate] apply finished', { + orderHeaderID: orderHeaderID.value, + insertMissing: true, + durationMs: Math.round(nowMs() - applyStart) + }) await store.fetchItems(orderHeaderID.value) selectedMap.value = {} await sendUpdateMailAfterApply(selectedRows) @@ -1134,7 +1237,13 @@ async function onBulkSubmit () { return } + const applyStart = nowMs() await store.applyUpdates(orderHeaderID.value, lines, false, cdItems, productAttributes) + console.info('[OrderProductionUpdate] apply finished', { + orderHeaderID: orderHeaderID.value, + insertMissing: false, + durationMs: Math.round(nowMs() - applyStart) + }) await store.fetchItems(orderHeaderID.value) selectedMap.value = {} await sendUpdateMailAfterApply(selectedRows) @@ -1148,6 +1257,10 @@ async function onBulkSubmit () { }) $q.notify({ type: 'negative', message: store.error || 'Toplu kayit islemi basarisiz.' }) } + console.info('[OrderProductionUpdate] onBulkSubmit total', { + orderHeaderID: orderHeaderID.value, + durationMs: Math.round(nowMs() - flowStart) + }) } diff --git a/ui/src/stores/OrderProductionItemStore.js b/ui/src/stores/OrderProductionItemStore.js index 2d59276..59b7ab0 100644 --- a/ui/src/stores/OrderProductionItemStore.js +++ b/ui/src/stores/OrderProductionItemStore.js @@ -29,13 +29,26 @@ function logApiError (action, err, payload = null) { }) } +function nowMs () { + if (typeof performance !== 'undefined' && typeof performance.now === 'function') { + return performance.now() + } + return Date.now() +} + export const useOrderProductionItemStore = defineStore('orderproductionitems', { state: () => ({ items: [], header: null, products: [], colorOptionsByCode: {}, + newColorOptionsByCode: {}, secondColorOptionsByKey: {}, + newSecondColorOptionsByKey: {}, + colorRequestsByCode: {}, + newColorRequestsByCode: {}, + secondColorRequestsByKey: {}, + newSecondColorRequestsByKey: {}, productAttributesByItemType: {}, cdItemLookups: null, cdItemDraftsByCode: {}, @@ -127,16 +140,57 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', { if (this.colorOptionsByCode[code]) { return this.colorOptionsByCode[code] } + if (this.colorRequestsByCode[code]) { + return this.colorRequestsByCode[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 + this.colorRequestsByCode[code] = (async () => { + const t0 = nowMs() + console.info('[OrderProductionItemStore] fetchColors start', { code }) + const res = await api.get('/product-colors', { params: { code } }) + const data = res?.data + const list = Array.isArray(data) ? data : [] + this.colorOptionsByCode[code] = list + console.info('[OrderProductionItemStore] fetchColors done', { code, count: list.length, durationMs: Math.round(nowMs() - t0) }) + return list + })() + return await this.colorRequestsByCode[code] } catch (err) { this.error = err?.response?.data || err?.message || 'Renk listesi alinamadi' return [] + } finally { + delete this.colorRequestsByCode[code] + } + }, + async fetchNewColors (productCode) { + const code = String(productCode || '').trim() + if (!code) return [] + + if (this.newColorOptionsByCode[code]) { + return this.newColorOptionsByCode[code] + } + if (this.newColorRequestsByCode[code]) { + return this.newColorRequestsByCode[code] + } + + try { + this.newColorRequestsByCode[code] = (async () => { + const t0 = nowMs() + console.info('[OrderProductionItemStore] fetchNewColors start', { code }) + const res = await api.get('/product-newcolors', { params: { code } }) + const data = res?.data + const list = Array.isArray(data) ? data : [] + this.newColorOptionsByCode[code] = list + console.info('[OrderProductionItemStore] fetchNewColors done', { code, count: list.length, durationMs: Math.round(nowMs() - t0) }) + return list + })() + return await this.newColorRequestsByCode[code] + } catch (err) { + this.error = err?.response?.data || err?.message || 'Yeni urun renk listesi alinamadi' + return [] + } finally { + delete this.newColorRequestsByCode[code] } }, async fetchSecondColors (productCode, colorCode) { @@ -148,16 +202,59 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', { if (this.secondColorOptionsByKey[key]) { return this.secondColorOptionsByKey[key] } + if (this.secondColorRequestsByKey[key]) { + return this.secondColorRequestsByKey[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 + this.secondColorRequestsByKey[key] = (async () => { + const t0 = nowMs() + console.info('[OrderProductionItemStore] fetchSecondColors start', { code, color }) + const res = await api.get('/product-secondcolor', { params: { code, color } }) + const data = res?.data + const list = Array.isArray(data) ? data : [] + this.secondColorOptionsByKey[key] = list + console.info('[OrderProductionItemStore] fetchSecondColors done', { code, color, count: list.length, durationMs: Math.round(nowMs() - t0) }) + return list + })() + return await this.secondColorRequestsByKey[key] } catch (err) { this.error = err?.response?.data || err?.message || '2. renk listesi alinamadi' return [] + } finally { + delete this.secondColorRequestsByKey[key] + } + }, + async fetchNewSecondColors (productCode, colorCode) { + const code = String(productCode || '').trim() + const color = String(colorCode || '').trim() + if (!code || !color) return [] + + const key = `${code}::${color}` + if (this.newSecondColorOptionsByKey[key]) { + return this.newSecondColorOptionsByKey[key] + } + if (this.newSecondColorRequestsByKey[key]) { + return this.newSecondColorRequestsByKey[key] + } + + try { + this.newSecondColorRequestsByKey[key] = (async () => { + const t0 = nowMs() + console.info('[OrderProductionItemStore] fetchNewSecondColors start', { code, color }) + const res = await api.get('/product-newsecondcolor', { params: { code, color } }) + const data = res?.data + const list = Array.isArray(data) ? data : [] + this.newSecondColorOptionsByKey[key] = list + console.info('[OrderProductionItemStore] fetchNewSecondColors done', { code, color, count: list.length, durationMs: Math.round(nowMs() - t0) }) + return list + })() + return await this.newSecondColorRequestsByKey[key] + } catch (err) { + this.error = err?.response?.data || err?.message || 'Yeni urun 2. renk listesi alinamadi' + return [] + } finally { + delete this.newSecondColorRequestsByKey[key] } }, async fetchProductAttributes (itemTypeCode = 1) { @@ -231,11 +328,22 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', { this.error = null try { + const t0 = nowMs() + console.info('[OrderProductionItemStore] validateUpdates start', { orderHeaderID, lineCount: lines?.length || 0 }) const res = await api.post( `/orders/production-items/${encodeURIComponent(orderHeaderID)}/validate`, { lines } ) - return res?.data || { missingCount: 0, missing: [] } + const data = res?.data || { missingCount: 0, missing: [] } + const rid = res?.headers?.['x-debug-request-id'] || '' + console.info('[OrderProductionItemStore] validateUpdates done', { + orderHeaderID, + lineCount: lines?.length || 0, + missingCount: Number(data?.missingCount || 0), + requestId: rid, + durationMs: Math.round(nowMs() - t0) + }) + return data } catch (err) { logApiError('validateUpdates', err, { orderHeaderID, lineCount: lines?.length || 0 }) this.error = extractApiErrorMessage(err, 'Kontrol basarisiz') @@ -251,11 +359,29 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', { this.error = null try { + const t0 = nowMs() + console.info('[OrderProductionItemStore] applyUpdates start', { + orderHeaderID, + lineCount: lines?.length || 0, + insertMissing: !!insertMissing, + cdItemCount: cdItems?.length || 0, + attributeCount: productAttributes?.length || 0 + }) const res = await api.post( `/orders/production-items/${encodeURIComponent(orderHeaderID)}/apply`, { lines, insertMissing, cdItems, productAttributes } ) - return res?.data || { updated: 0, inserted: 0 } + const data = res?.data || { updated: 0, inserted: 0 } + const rid = res?.headers?.['x-debug-request-id'] || '' + console.info('[OrderProductionItemStore] applyUpdates done', { + orderHeaderID, + updated: Number(data?.updated || 0), + inserted: Number(data?.inserted || 0), + attributeUpserted: Number(data?.attributeUpserted || 0), + requestId: rid, + durationMs: Math.round(nowMs() - t0) + }) + return data } catch (err) { logApiError('applyUpdates', err, { orderHeaderID, lineCount: lines?.length || 0, insertMissing }) this.error = extractApiErrorMessage(err, 'Guncelleme basarisiz')