Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -560,6 +560,8 @@ func UpdateOrderLinesTx(tx *sql.Tx, orderHeaderID string, lines []models.OrderPr
|
|||||||
|
|
||||||
query := fmt.Sprintf(`
|
query := fmt.Sprintf(`
|
||||||
SET NOCOUNT ON;
|
SET NOCOUNT ON;
|
||||||
|
DECLARE @updated TABLE (OrderLineID UNIQUEIDENTIFIER);
|
||||||
|
|
||||||
;WITH src (OrderLineID, NewItemCode, NewColor, ItemDim1Code, NewDim2, NewDesc, OldDueDate, NewDueDate) AS (
|
;WITH src (OrderLineID, NewItemCode, NewColor, ItemDim1Code, NewDim2, NewDesc, OldDueDate, NewDueDate) AS (
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM (VALUES %s) AS v (OrderLineID, NewItemCode, NewColor, ItemDim1Code, NewDim2, NewDesc, OldDueDate, NewDueDate)
|
FROM (VALUES %s) AS v (OrderLineID, NewItemCode, NewColor, ItemDim1Code, NewDim2, NewDesc, OldDueDate, NewDueDate)
|
||||||
@@ -574,27 +576,114 @@ SET
|
|||||||
l.DeliveryDate = CASE WHEN ISDATE(s.NewDueDate) = 1 THEN CAST(s.NewDueDate AS DATETIME) ELSE l.DeliveryDate END,
|
l.DeliveryDate = CASE WHEN ISDATE(s.NewDueDate) = 1 THEN CAST(s.NewDueDate AS DATETIME) ELSE l.DeliveryDate END,
|
||||||
l.LastUpdatedUserName = @p%d,
|
l.LastUpdatedUserName = @p%d,
|
||||||
l.LastUpdatedDate = GETDATE()
|
l.LastUpdatedDate = GETDATE()
|
||||||
|
OUTPUT inserted.OrderLineID INTO @updated(OrderLineID)
|
||||||
FROM dbo.trOrderLine l
|
FROM dbo.trOrderLine l
|
||||||
JOIN src s
|
JOIN src s
|
||||||
ON CAST(l.OrderLineID AS NVARCHAR(50)) = s.OrderLineID
|
ON l.OrderLineID = CONVERT(UNIQUEIDENTIFIER, s.OrderLineID)
|
||||||
WHERE l.OrderHeaderID = @p%d;
|
WHERE l.OrderHeaderID = CONVERT(UNIQUEIDENTIFIER, @p%d);
|
||||||
|
|
||||||
|
SELECT COUNT(1) AS UpdatedCount FROM @updated;
|
||||||
`, strings.Join(values, ","), usernameParam, orderHeaderParam)
|
`, strings.Join(values, ","), usernameParam, orderHeaderParam)
|
||||||
|
|
||||||
chunkStart := time.Now()
|
chunkStart := time.Now()
|
||||||
res, execErr := tx.Exec(query, args...)
|
var chunkUpdated int64
|
||||||
|
execErr := tx.QueryRow(query, args...).Scan(&chunkUpdated)
|
||||||
if execErr != nil {
|
if execErr != nil {
|
||||||
log.Printf("[UpdateOrderLinesTx] ERROR orderHeaderID=%s chunk=%d-%d err=%v", orderHeaderID, i, end, execErr)
|
log.Printf("[UpdateOrderLinesTx] ERROR orderHeaderID=%s chunk=%d-%d err=%v", orderHeaderID, i, end, execErr)
|
||||||
return updated, fmt.Errorf("update lines chunk failed chunkStart=%d chunkEnd=%d duration_ms=%d: %w", i, end, time.Since(chunkStart).Milliseconds(), execErr)
|
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())
|
log.Printf("[UpdateOrderLinesTx] orderHeaderID=%s chunk=%d-%d updated=%d duration_ms=%d", orderHeaderID, i, end, chunkUpdated, time.Since(chunkStart).Milliseconds())
|
||||||
|
updated += chunkUpdated
|
||||||
if rows, rowsErr := res.RowsAffected(); rowsErr == nil {
|
|
||||||
updated += rows
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return updated, nil
|
return updated, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func VerifyOrderLineUpdatesTx(tx *sql.Tx, orderHeaderID string, lines []models.OrderProductionUpdateLine) (int64, []string, error) {
|
||||||
|
if len(lines) == 0 {
|
||||||
|
return 0, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunkSize = 300
|
||||||
|
var mismatchCount int64
|
||||||
|
samples := make([]string, 0, 5)
|
||||||
|
|
||||||
|
for i := 0; i < len(lines); i += chunkSize {
|
||||||
|
end := i + chunkSize
|
||||||
|
if end > len(lines) {
|
||||||
|
end = len(lines)
|
||||||
|
}
|
||||||
|
chunk := lines[i:end]
|
||||||
|
|
||||||
|
values := make([]string, 0, len(chunk))
|
||||||
|
args := make([]any, 0, len(chunk)*4+1)
|
||||||
|
paramPos := 1
|
||||||
|
for _, line := 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,
|
||||||
|
strings.TrimSpace(line.OrderLineID),
|
||||||
|
strings.ToUpper(strings.TrimSpace(line.NewItemCode)),
|
||||||
|
strings.ToUpper(strings.TrimSpace(line.NewColor)),
|
||||||
|
strings.ToUpper(strings.TrimSpace(line.NewDim2)),
|
||||||
|
)
|
||||||
|
paramPos += 4
|
||||||
|
}
|
||||||
|
orderHeaderParam := paramPos
|
||||||
|
args = append(args, orderHeaderID)
|
||||||
|
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
WITH src (OrderLineID, NewItemCode, NewColor, NewDim2) AS (
|
||||||
|
SELECT *
|
||||||
|
FROM (VALUES %s) v(OrderLineID, NewItemCode, NewColor, NewDim2)
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
s.OrderLineID,
|
||||||
|
ISNULL(UPPER(LTRIM(RTRIM(l.ItemCode))), '') AS ActualItemCode,
|
||||||
|
ISNULL(UPPER(LTRIM(RTRIM(l.ColorCode))), '') AS ActualColorCode,
|
||||||
|
ISNULL(UPPER(LTRIM(RTRIM(l.ItemDim2Code))), '') AS ActualDim2Code,
|
||||||
|
s.NewItemCode,
|
||||||
|
s.NewColor,
|
||||||
|
s.NewDim2
|
||||||
|
FROM src s
|
||||||
|
JOIN dbo.trOrderLine l
|
||||||
|
ON l.OrderLineID = CONVERT(UNIQUEIDENTIFIER, s.OrderLineID)
|
||||||
|
WHERE l.OrderHeaderID = CONVERT(UNIQUEIDENTIFIER, @p%d)
|
||||||
|
AND (
|
||||||
|
ISNULL(UPPER(LTRIM(RTRIM(l.ItemCode))), '') <> s.NewItemCode OR
|
||||||
|
ISNULL(UPPER(LTRIM(RTRIM(l.ColorCode))), '') <> s.NewColor OR
|
||||||
|
ISNULL(UPPER(LTRIM(RTRIM(l.ItemDim2Code))), '') <> s.NewDim2
|
||||||
|
);
|
||||||
|
`, strings.Join(values, ","), orderHeaderParam)
|
||||||
|
|
||||||
|
rows, err := tx.Query(query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return mismatchCount, samples, err
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
var lineID, actualItem, actualColor, actualDim2, expectedItem, expectedColor, expectedDim2 string
|
||||||
|
if err := rows.Scan(&lineID, &actualItem, &actualColor, &actualDim2, &expectedItem, &expectedColor, &expectedDim2); err != nil {
|
||||||
|
rows.Close()
|
||||||
|
return mismatchCount, samples, err
|
||||||
|
}
|
||||||
|
mismatchCount++
|
||||||
|
if len(samples) < 5 {
|
||||||
|
samples = append(samples, fmt.Sprintf(
|
||||||
|
"lineID=%s expected=(%s,%s,%s) actual=(%s,%s,%s)",
|
||||||
|
lineID, expectedItem, expectedColor, expectedDim2, actualItem, actualColor, actualDim2,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
rows.Close()
|
||||||
|
return mismatchCount, samples, err
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return mismatchCount, samples, nil
|
||||||
|
}
|
||||||
|
|
||||||
func UpdateOrderHeaderAverageDueDateTx(tx *sql.Tx, orderHeaderID string, averageDueDate *string, username string) error {
|
func UpdateOrderHeaderAverageDueDateTx(tx *sql.Tx, orderHeaderID string, averageDueDate *string, username string) error {
|
||||||
if averageDueDate == nil {
|
if averageDueDate == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -618,6 +707,24 @@ WHERE OrderHeaderID = @p3;
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TouchOrderHeaderTx(tx *sql.Tx, orderHeaderID string, username string) (int64, error) {
|
||||||
|
res, err := tx.Exec(`
|
||||||
|
UPDATE dbo.trOrderHeader
|
||||||
|
SET
|
||||||
|
LastUpdatedUserName = @p1,
|
||||||
|
LastUpdatedDate = GETDATE()
|
||||||
|
WHERE OrderHeaderID = @p2;
|
||||||
|
`, username, orderHeaderID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
rows, rowsErr := res.RowsAffected()
|
||||||
|
if rowsErr != nil {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return rows, nil
|
||||||
|
}
|
||||||
|
|
||||||
type sqlQueryRower interface {
|
type sqlQueryRower interface {
|
||||||
QueryRow(query string, args ...any) *sql.Row
|
QueryRow(query string, args ...any) *sql.Row
|
||||||
}
|
}
|
||||||
@@ -849,83 +956,10 @@ func InsertItemBarcodesTx(tx *sql.Tx, orderHeaderID string, lines []models.Order
|
|||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const chunkSize = 900
|
|
||||||
var inserted int64
|
var inserted int64
|
||||||
|
|
||||||
for i := 0; i < len(lineIDs); i += chunkSize {
|
singleLineQuery := `
|
||||||
|
SET NOCOUNT ON;
|
||||||
end := i + chunkSize
|
|
||||||
if end > len(lineIDs) {
|
|
||||||
end = len(lineIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
chunk := lineIDs[i:end]
|
|
||||||
|
|
||||||
values := make([]string, 0, len(chunk))
|
|
||||||
args := make([]any, 0, len(chunk)+2)
|
|
||||||
|
|
||||||
paramPos := 1
|
|
||||||
|
|
||||||
for _, lineID := range chunk {
|
|
||||||
values = append(values, fmt.Sprintf("(@p%d)", paramPos))
|
|
||||||
args = append(args, lineID)
|
|
||||||
paramPos++
|
|
||||||
}
|
|
||||||
|
|
||||||
orderHeaderParam := paramPos
|
|
||||||
usernameParam := paramPos + 1
|
|
||||||
|
|
||||||
args = append(args, orderHeaderID, username)
|
|
||||||
|
|
||||||
query := fmt.Sprintf(`
|
|
||||||
SET NOCOUNT ON
|
|
||||||
;
|
|
||||||
|
|
||||||
WITH srcLine (OrderLineID) AS (
|
|
||||||
SELECT *
|
|
||||||
FROM (VALUES %s) v(OrderLineID)
|
|
||||||
),
|
|
||||||
|
|
||||||
src AS (
|
|
||||||
SELECT DISTINCT
|
|
||||||
l.ItemTypeCode,
|
|
||||||
UPPER(LTRIM(RTRIM(ISNULL(l.ItemCode,'')))) ItemCode,
|
|
||||||
UPPER(LTRIM(RTRIM(ISNULL(l.ColorCode,'')))) ColorCode,
|
|
||||||
UPPER(LTRIM(RTRIM(ISNULL(l.ItemDim1Code,'')))) ItemDim1Code,
|
|
||||||
UPPER(LTRIM(RTRIM(ISNULL(l.ItemDim2Code,'')))) ItemDim2Code,
|
|
||||||
CAST('' AS NVARCHAR(50)) ItemDim3Code
|
|
||||||
FROM dbo.trOrderLine l
|
|
||||||
JOIN srcLine s
|
|
||||||
ON CAST(l.OrderLineID AS NVARCHAR(50)) = s.OrderLineID
|
|
||||||
WHERE l.OrderHeaderID = @p%d
|
|
||||||
AND NULLIF(LTRIM(RTRIM(ISNULL(l.ItemCode,''))), '') IS NOT NULL
|
|
||||||
),
|
|
||||||
|
|
||||||
missing AS (
|
|
||||||
SELECT
|
|
||||||
s.*,
|
|
||||||
ROW_NUMBER() OVER (
|
|
||||||
ORDER BY
|
|
||||||
s.ItemCode,
|
|
||||||
s.ColorCode,
|
|
||||||
s.ItemDim1Code,
|
|
||||||
s.ItemDim2Code,
|
|
||||||
s.ItemDim3Code
|
|
||||||
) RowNo
|
|
||||||
FROM src s
|
|
||||||
WHERE NOT EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM dbo.prItemBarcode b
|
|
||||||
WHERE b.BarcodeTypeCode = 'BAGGI3'
|
|
||||||
AND b.UnitOfMeasureCode = 'AD'
|
|
||||||
AND b.ItemTypeCode = s.ItemTypeCode
|
|
||||||
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemCode,'')))) = s.ItemCode
|
|
||||||
AND UPPER(LTRIM(RTRIM(ISNULL(b.ColorCode,'')))) = s.ColorCode
|
|
||||||
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemDim1Code,'')))) = s.ItemDim1Code
|
|
||||||
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemDim2Code,'')))) = s.ItemDim2Code
|
|
||||||
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemDim3Code,'')))) = s.ItemDim3Code
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
INSERT INTO dbo.prItemBarcode
|
INSERT INTO dbo.prItemBarcode
|
||||||
(
|
(
|
||||||
@@ -945,16 +979,147 @@ INSERT INTO dbo.prItemBarcode
|
|||||||
LastUpdatedDate,
|
LastUpdatedDate,
|
||||||
RowGuid
|
RowGuid
|
||||||
)
|
)
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
CAST(seed.MaxBarcode + m.RowNo AS NVARCHAR(50)),
|
CAST(seed.MaxBarcode + 1 AS NVARCHAR(50)),
|
||||||
'BAGGI3',
|
'BAGGI3',
|
||||||
m.ItemTypeCode,
|
src.ItemTypeCode,
|
||||||
m.ItemCode,
|
src.ItemCode,
|
||||||
m.ColorCode,
|
src.ColorCode,
|
||||||
m.ItemDim1Code,
|
src.ItemDim1Code,
|
||||||
m.ItemDim2Code,
|
src.ItemDim2Code,
|
||||||
m.ItemDim3Code,
|
src.ItemDim3Code,
|
||||||
|
'AD',
|
||||||
|
1,
|
||||||
|
@p3,
|
||||||
|
GETDATE(),
|
||||||
|
@p3,
|
||||||
|
GETDATE(),
|
||||||
|
NEWID()
|
||||||
|
FROM (
|
||||||
|
SELECT DISTINCT
|
||||||
|
l.ItemTypeCode,
|
||||||
|
UPPER(LTRIM(RTRIM(ISNULL(l.ItemCode,'')))) AS ItemCode,
|
||||||
|
UPPER(LTRIM(RTRIM(ISNULL(l.ColorCode,'')))) AS ColorCode,
|
||||||
|
UPPER(LTRIM(RTRIM(ISNULL(l.ItemDim1Code,'')))) AS ItemDim1Code,
|
||||||
|
UPPER(LTRIM(RTRIM(ISNULL(l.ItemDim2Code,'')))) AS ItemDim2Code,
|
||||||
|
UPPER(LTRIM(RTRIM(ISNULL(l.ItemDim3Code,'')))) AS ItemDim3Code
|
||||||
|
FROM dbo.trOrderLine l
|
||||||
|
WHERE l.OrderHeaderID = @p2
|
||||||
|
AND CAST(l.OrderLineID AS NVARCHAR(50)) = @p1
|
||||||
|
AND NULLIF(LTRIM(RTRIM(ISNULL(l.ItemCode,''))), '') IS NOT NULL
|
||||||
|
) src
|
||||||
|
CROSS JOIN (
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN ISNULL(MAX(
|
||||||
|
CASE
|
||||||
|
WHEN LTRIM(RTRIM(ISNULL(Barcode,''))) NOT LIKE '%%[^0-9]%%'
|
||||||
|
AND LEN(LTRIM(RTRIM(ISNULL(Barcode,'')))) BETWEEN 1 AND 18
|
||||||
|
THEN CAST(LTRIM(RTRIM(ISNULL(Barcode,''))) AS BIGINT)
|
||||||
|
ELSE NULL
|
||||||
|
END
|
||||||
|
), 0) < 36999999
|
||||||
|
THEN 36999999
|
||||||
|
ELSE ISNULL(MAX(
|
||||||
|
CASE
|
||||||
|
WHEN LTRIM(RTRIM(ISNULL(Barcode,''))) NOT LIKE '%%[^0-9]%%'
|
||||||
|
AND LEN(LTRIM(RTRIM(ISNULL(Barcode,'')))) BETWEEN 1 AND 18
|
||||||
|
THEN CAST(LTRIM(RTRIM(ISNULL(Barcode,''))) AS BIGINT)
|
||||||
|
ELSE NULL
|
||||||
|
END
|
||||||
|
), 0)
|
||||||
|
END AS MaxBarcode
|
||||||
|
FROM dbo.prItemBarcode
|
||||||
|
WHERE BarcodeTypeCode = 'BAGGI3'
|
||||||
|
AND LEN(LTRIM(RTRIM(ISNULL(Barcode,'')))) <= 8
|
||||||
|
) seed
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM dbo.prItemBarcode b
|
||||||
|
WHERE b.ItemTypeCode = src.ItemTypeCode
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemCode,'')))) = src.ItemCode
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ColorCode,'')))) = src.ColorCode
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemDim1Code,'')))) = src.ItemDim1Code
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemDim2Code,'')))) = src.ItemDim2Code
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemDim3Code,'')))) = src.ItemDim3Code
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
||||||
|
execSingle := func(globalIndex int, lineID string) error {
|
||||||
|
lineStart := time.Now()
|
||||||
|
res, err := tx.Exec(singleLineQuery, lineID, orderHeaderID, username)
|
||||||
|
if err != nil {
|
||||||
|
if isDuplicateBarcodeInsertErr(err) {
|
||||||
|
log.Printf("[InsertItemBarcodesTx] skip duplicate lineIndex=%d lineID=%s err=%v", globalIndex, lineID, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("upsert item barcodes chunk failed chunkStart=%d chunkEnd=%d duration_ms=%d: %w", globalIndex, globalIndex+1, time.Since(lineStart).Milliseconds(), err)
|
||||||
|
}
|
||||||
|
rows, _ := res.RowsAffected()
|
||||||
|
inserted += rows
|
||||||
|
log.Printf(
|
||||||
|
"[InsertItemBarcodesTx] lineIndex=%d lineID=%s inserted=%d cumulative=%d duration_ms=%d",
|
||||||
|
globalIndex,
|
||||||
|
lineID,
|
||||||
|
rows,
|
||||||
|
inserted,
|
||||||
|
time.Since(lineStart).Milliseconds(),
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunkSize = 200
|
||||||
|
for i := 0; i < len(lineIDs); i += chunkSize {
|
||||||
|
end := i + chunkSize
|
||||||
|
if end > len(lineIDs) {
|
||||||
|
end = len(lineIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk := lineIDs[i:end]
|
||||||
|
values := make([]string, 0, len(chunk))
|
||||||
|
args := make([]any, 0, len(chunk)+2)
|
||||||
|
paramPos := 1
|
||||||
|
for _, lineID := range chunk {
|
||||||
|
values = append(values, fmt.Sprintf("(@p%d)", paramPos))
|
||||||
|
args = append(args, lineID)
|
||||||
|
paramPos++
|
||||||
|
}
|
||||||
|
orderHeaderParam := paramPos
|
||||||
|
usernameParam := paramPos + 1
|
||||||
|
args = append(args, orderHeaderID, username)
|
||||||
|
|
||||||
|
batchQuery := fmt.Sprintf(`
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
INSERT INTO dbo.prItemBarcode
|
||||||
|
(
|
||||||
|
Barcode,
|
||||||
|
BarcodeTypeCode,
|
||||||
|
ItemTypeCode,
|
||||||
|
ItemCode,
|
||||||
|
ColorCode,
|
||||||
|
ItemDim1Code,
|
||||||
|
ItemDim2Code,
|
||||||
|
ItemDim3Code,
|
||||||
|
UnitOfMeasureCode,
|
||||||
|
Qty,
|
||||||
|
CreatedUserName,
|
||||||
|
CreatedDate,
|
||||||
|
LastUpdatedUserName,
|
||||||
|
LastUpdatedDate,
|
||||||
|
RowGuid
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
CAST(seed.MaxBarcode + ROW_NUMBER() OVER (
|
||||||
|
ORDER BY src.ItemTypeCode, src.ItemCode, src.ColorCode, src.ItemDim1Code, src.ItemDim2Code, src.ItemDim3Code
|
||||||
|
) AS NVARCHAR(50)),
|
||||||
|
'BAGGI3',
|
||||||
|
src.ItemTypeCode,
|
||||||
|
src.ItemCode,
|
||||||
|
src.ColorCode,
|
||||||
|
src.ItemDim1Code,
|
||||||
|
src.ItemDim2Code,
|
||||||
|
src.ItemDim3Code,
|
||||||
'AD',
|
'AD',
|
||||||
1,
|
1,
|
||||||
@p%d,
|
@p%d,
|
||||||
@@ -962,42 +1127,80 @@ SELECT
|
|||||||
@p%d,
|
@p%d,
|
||||||
GETDATE(),
|
GETDATE(),
|
||||||
NEWID()
|
NEWID()
|
||||||
|
FROM (
|
||||||
FROM missing m
|
SELECT DISTINCT
|
||||||
|
l.ItemTypeCode,
|
||||||
|
UPPER(LTRIM(RTRIM(ISNULL(l.ItemCode,'')))) AS ItemCode,
|
||||||
|
UPPER(LTRIM(RTRIM(ISNULL(l.ColorCode,'')))) AS ColorCode,
|
||||||
|
UPPER(LTRIM(RTRIM(ISNULL(l.ItemDim1Code,'')))) AS ItemDim1Code,
|
||||||
|
UPPER(LTRIM(RTRIM(ISNULL(l.ItemDim2Code,'')))) AS ItemDim2Code,
|
||||||
|
UPPER(LTRIM(RTRIM(ISNULL(l.ItemDim3Code,'')))) AS ItemDim3Code
|
||||||
|
FROM dbo.trOrderLine l
|
||||||
|
JOIN (VALUES %s) ids(OrderLineID)
|
||||||
|
ON CAST(l.OrderLineID AS NVARCHAR(50)) = ids.OrderLineID
|
||||||
|
WHERE l.OrderHeaderID = @p%d
|
||||||
|
AND NULLIF(LTRIM(RTRIM(ISNULL(l.ItemCode,''))), '') IS NOT NULL
|
||||||
|
) src
|
||||||
CROSS JOIN (
|
CROSS JOIN (
|
||||||
SELECT ISNULL(MAX(
|
SELECT
|
||||||
CASE
|
CASE
|
||||||
WHEN ISNUMERIC(Barcode)=1
|
WHEN ISNULL(MAX(
|
||||||
THEN CAST(Barcode AS BIGINT)
|
CASE
|
||||||
ELSE NULL
|
WHEN LTRIM(RTRIM(ISNULL(Barcode,''))) NOT LIKE '%%[^0-9]%%'
|
||||||
END
|
AND LEN(LTRIM(RTRIM(ISNULL(Barcode,'')))) BETWEEN 1 AND 18
|
||||||
),0) AS MaxBarcode
|
THEN CAST(LTRIM(RTRIM(ISNULL(Barcode,''))) AS BIGINT)
|
||||||
|
ELSE NULL
|
||||||
|
END
|
||||||
|
), 0) < 36999999
|
||||||
|
THEN 36999999
|
||||||
|
ELSE ISNULL(MAX(
|
||||||
|
CASE
|
||||||
|
WHEN LTRIM(RTRIM(ISNULL(Barcode,''))) NOT LIKE '%%[^0-9]%%'
|
||||||
|
AND LEN(LTRIM(RTRIM(ISNULL(Barcode,'')))) BETWEEN 1 AND 18
|
||||||
|
THEN CAST(LTRIM(RTRIM(ISNULL(Barcode,''))) AS BIGINT)
|
||||||
|
ELSE NULL
|
||||||
|
END
|
||||||
|
), 0)
|
||||||
|
END AS MaxBarcode
|
||||||
FROM dbo.prItemBarcode
|
FROM dbo.prItemBarcode
|
||||||
|
WHERE BarcodeTypeCode = 'BAGGI3'
|
||||||
|
AND LEN(LTRIM(RTRIM(ISNULL(Barcode,'')))) <= 8
|
||||||
) seed
|
) seed
|
||||||
`, strings.Join(values, ","), orderHeaderParam, usernameParam, usernameParam)
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM dbo.prItemBarcode b
|
||||||
|
WHERE b.ItemTypeCode = src.ItemTypeCode
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemCode,'')))) = src.ItemCode
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ColorCode,'')))) = src.ColorCode
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemDim1Code,'')))) = src.ItemDim1Code
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemDim2Code,'')))) = src.ItemDim2Code
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemDim3Code,'')))) = src.ItemDim3Code
|
||||||
|
);
|
||||||
|
`, usernameParam, usernameParam, strings.Join(values, ","), orderHeaderParam)
|
||||||
|
|
||||||
chunkStart := time.Now()
|
chunkStart := time.Now()
|
||||||
|
res, err := tx.Exec(batchQuery, args...)
|
||||||
res, err := tx.Exec(query, args...)
|
if err == nil {
|
||||||
|
rows, _ := res.RowsAffected()
|
||||||
if err != nil {
|
inserted += rows
|
||||||
log.Printf("[InsertItemBarcodesTx] ERROR chunk=%d-%d err=%v", i, end, err)
|
log.Printf(
|
||||||
return inserted, err
|
"[InsertItemBarcodesTx] batch=%d-%d inserted=%d cumulative=%d duration_ms=%d",
|
||||||
|
i,
|
||||||
|
end,
|
||||||
|
rows,
|
||||||
|
inserted,
|
||||||
|
time.Since(chunkStart).Milliseconds(),
|
||||||
|
)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, _ := res.RowsAffected()
|
log.Printf("[InsertItemBarcodesTx] batch fallback=%d-%d err=%v", i, end, err)
|
||||||
|
for j, lineID := range chunk {
|
||||||
inserted += rows
|
if lineErr := execSingle(i+j, lineID); lineErr != nil {
|
||||||
|
log.Printf("[InsertItemBarcodesTx] ERROR lineIndex=%d lineID=%s err=%v", i+j, lineID, lineErr)
|
||||||
log.Printf(
|
return inserted, lineErr
|
||||||
"[InsertItemBarcodesTx] chunk=%d-%d inserted=%d cumulative=%d duration_ms=%d",
|
}
|
||||||
i,
|
}
|
||||||
end,
|
|
||||||
rows,
|
|
||||||
inserted,
|
|
||||||
time.Since(chunkStart).Milliseconds(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf(
|
log.Printf(
|
||||||
@@ -1011,6 +1214,289 @@ CROSS JOIN (
|
|||||||
return inserted, nil
|
return inserted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func InsertItemBarcodesByTargetsTx(tx *sql.Tx, targets []models.OrderProductionMissingVariant, username string) (int64, error) {
|
||||||
|
start := time.Now()
|
||||||
|
if len(targets) == 0 {
|
||||||
|
log.Printf("[InsertItemBarcodesByTargetsTx] targets=0 inserted=0 duration_ms=0")
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
uniqueTargets := make([]models.OrderProductionMissingVariant, 0, len(targets))
|
||||||
|
seen := make(map[string]struct{}, len(targets))
|
||||||
|
for _, t := range targets {
|
||||||
|
itemCode := strings.ToUpper(strings.TrimSpace(t.ItemCode))
|
||||||
|
if itemCode == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := fmt.Sprintf("%d|%s|%s|%s|%s|%s",
|
||||||
|
t.ItemTypeCode,
|
||||||
|
itemCode,
|
||||||
|
strings.ToUpper(strings.TrimSpace(t.ColorCode)),
|
||||||
|
strings.ToUpper(strings.TrimSpace(t.ItemDim1Code)),
|
||||||
|
strings.ToUpper(strings.TrimSpace(t.ItemDim2Code)),
|
||||||
|
strings.ToUpper(strings.TrimSpace(t.ItemDim3Code)),
|
||||||
|
)
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
t.ItemCode = itemCode
|
||||||
|
t.ColorCode = strings.ToUpper(strings.TrimSpace(t.ColorCode))
|
||||||
|
t.ItemDim1Code = strings.ToUpper(strings.TrimSpace(t.ItemDim1Code))
|
||||||
|
t.ItemDim2Code = strings.ToUpper(strings.TrimSpace(t.ItemDim2Code))
|
||||||
|
t.ItemDim3Code = strings.ToUpper(strings.TrimSpace(t.ItemDim3Code))
|
||||||
|
uniqueTargets = append(uniqueTargets, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(uniqueTargets) == 0 {
|
||||||
|
log.Printf("[InsertItemBarcodesByTargetsTx] targets=%d unique=0 inserted=0 duration_ms=%d", len(targets), time.Since(start).Milliseconds())
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if err := ensureTxStillActive(tx, "InsertItemBarcodesByTargetsTx/start"); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Barcode seed'i hem prItemBarcode hem de (varsa) tbStokBarkodu uzerinden
|
||||||
|
// kilitli okuyarak hesapla; trigger tarafindaki duplicate riskini azalt.
|
||||||
|
var maxBarcode int64
|
||||||
|
maxPrQuery := `
|
||||||
|
SELECT ISNULL(MAX(v.BarcodeNum), 0)
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN LTRIM(RTRIM(ISNULL(pb.Barcode,''))) NOT LIKE '%[^0-9]%'
|
||||||
|
AND LEN(LTRIM(RTRIM(ISNULL(pb.Barcode,'')))) BETWEEN 1 AND 18
|
||||||
|
THEN CAST(LTRIM(RTRIM(ISNULL(pb.Barcode,''))) AS BIGINT)
|
||||||
|
ELSE NULL
|
||||||
|
END AS BarcodeNum
|
||||||
|
FROM dbo.prItemBarcode pb WITH (UPDLOCK, HOLDLOCK, TABLOCKX)
|
||||||
|
WHERE pb.BarcodeTypeCode = 'BAGGI3'
|
||||||
|
) v
|
||||||
|
WHERE v.BarcodeNum IS NOT NULL;
|
||||||
|
`
|
||||||
|
if err := tx.QueryRow(maxPrQuery).Scan(&maxBarcode); err != nil {
|
||||||
|
return 0, fmt.Errorf("barcode seed query failed: %w", err)
|
||||||
|
}
|
||||||
|
var hasTb int
|
||||||
|
if err := tx.QueryRow(`SELECT CASE WHEN OBJECT_ID(N'dbo.tbStokBarkodu', N'U') IS NULL THEN 0 ELSE 1 END`).Scan(&hasTb); err != nil {
|
||||||
|
return 0, fmt.Errorf("barcode seed object check failed: %w", err)
|
||||||
|
}
|
||||||
|
if hasTb == 1 {
|
||||||
|
var maxTb int64
|
||||||
|
maxTbQuery := `
|
||||||
|
SELECT ISNULL(MAX(v.BarcodeNum), 0)
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN LTRIM(RTRIM(ISNULL(sb.Barcode,''))) NOT LIKE '%[^0-9]%'
|
||||||
|
AND LEN(LTRIM(RTRIM(ISNULL(sb.Barcode,'')))) BETWEEN 1 AND 18
|
||||||
|
THEN CAST(LTRIM(RTRIM(ISNULL(sb.Barcode,''))) AS BIGINT)
|
||||||
|
ELSE NULL
|
||||||
|
END AS BarcodeNum
|
||||||
|
FROM dbo.tbStokBarkodu sb WITH (UPDLOCK, HOLDLOCK, TABLOCKX)
|
||||||
|
) v
|
||||||
|
WHERE v.BarcodeNum IS NOT NULL;
|
||||||
|
`
|
||||||
|
if err := tx.QueryRow(maxTbQuery).Scan(&maxTb); err != nil {
|
||||||
|
return 0, fmt.Errorf("barcode seed tbStokBarkodu query failed: %w", err)
|
||||||
|
}
|
||||||
|
if maxTb > maxBarcode {
|
||||||
|
maxBarcode = maxTb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if maxBarcode < 36999999 {
|
||||||
|
maxBarcode = 36999999
|
||||||
|
}
|
||||||
|
existsBarcodeQuery := `
|
||||||
|
SELECT CASE WHEN EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM dbo.prItemBarcode pb WITH (UPDLOCK, HOLDLOCK)
|
||||||
|
WHERE LTRIM(RTRIM(ISNULL(pb.Barcode,''))) = @p1
|
||||||
|
) THEN 1 ELSE 0 END;
|
||||||
|
`
|
||||||
|
existsBarcodeWithTbQuery := `
|
||||||
|
SELECT CASE WHEN EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM dbo.prItemBarcode pb WITH (UPDLOCK, HOLDLOCK)
|
||||||
|
WHERE LTRIM(RTRIM(ISNULL(pb.Barcode,''))) = @p1
|
||||||
|
) OR EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM dbo.tbStokBarkodu sb WITH (UPDLOCK, HOLDLOCK)
|
||||||
|
WHERE LTRIM(RTRIM(ISNULL(sb.Barcode,''))) = @p1
|
||||||
|
) THEN 1 ELSE 0 END;
|
||||||
|
`
|
||||||
|
hasVariantBarcodeQuery := `
|
||||||
|
SELECT CASE WHEN EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM dbo.prItemBarcode b WITH (UPDLOCK, HOLDLOCK)
|
||||||
|
WHERE b.ItemTypeCode = @p1
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemCode,'')))) = @p2
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ColorCode,'')))) = @p3
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemDim1Code,'')))) = @p4
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemDim2Code,'')))) = @p5
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemDim3Code,'')))) = @p6
|
||||||
|
) THEN 1 ELSE 0 END;
|
||||||
|
`
|
||||||
|
insertOneQuery := `
|
||||||
|
INSERT INTO dbo.prItemBarcode
|
||||||
|
(
|
||||||
|
Barcode,
|
||||||
|
BarcodeTypeCode,
|
||||||
|
ItemTypeCode,
|
||||||
|
ItemCode,
|
||||||
|
ColorCode,
|
||||||
|
ItemDim1Code,
|
||||||
|
ItemDim2Code,
|
||||||
|
ItemDim3Code,
|
||||||
|
UnitOfMeasureCode,
|
||||||
|
Qty,
|
||||||
|
CreatedUserName,
|
||||||
|
CreatedDate,
|
||||||
|
LastUpdatedUserName,
|
||||||
|
LastUpdatedDate,
|
||||||
|
RowGuid
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
@p1,
|
||||||
|
'BAGGI3',
|
||||||
|
@p2,
|
||||||
|
@p3,
|
||||||
|
@p4,
|
||||||
|
@p5,
|
||||||
|
@p6,
|
||||||
|
@p7,
|
||||||
|
'AD',
|
||||||
|
1,
|
||||||
|
@p8,
|
||||||
|
GETDATE(),
|
||||||
|
@p8,
|
||||||
|
GETDATE(),
|
||||||
|
NEWID()
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM dbo.prItemBarcode b
|
||||||
|
WHERE b.ItemTypeCode = @p2
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemCode,'')))) = @p3
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ColorCode,'')))) = @p4
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemDim1Code,'')))) = @p5
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemDim2Code,'')))) = @p6
|
||||||
|
AND UPPER(LTRIM(RTRIM(ISNULL(b.ItemDim3Code,'')))) = @p7
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
||||||
|
var inserted int64
|
||||||
|
for _, t := range uniqueTargets {
|
||||||
|
if err := ensureTxStillActive(tx, "InsertItemBarcodesByTargetsTx/before_target"); err != nil {
|
||||||
|
return inserted, err
|
||||||
|
}
|
||||||
|
var hasVariant int
|
||||||
|
if err := tx.QueryRow(
|
||||||
|
hasVariantBarcodeQuery,
|
||||||
|
t.ItemTypeCode,
|
||||||
|
t.ItemCode,
|
||||||
|
t.ColorCode,
|
||||||
|
t.ItemDim1Code,
|
||||||
|
t.ItemDim2Code,
|
||||||
|
t.ItemDim3Code,
|
||||||
|
).Scan(&hasVariant); err != nil {
|
||||||
|
return inserted, fmt.Errorf("variant barcode exists check failed: %w", err)
|
||||||
|
}
|
||||||
|
if hasVariant == 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
retry := 0
|
||||||
|
for {
|
||||||
|
retry++
|
||||||
|
if retry > 2000 {
|
||||||
|
return inserted, fmt.Errorf("barcode allocation exceeded retry limit item=%s color=%s dim1=%s", t.ItemCode, t.ColorCode, t.ItemDim1Code)
|
||||||
|
}
|
||||||
|
candidateNum := maxBarcode + 1
|
||||||
|
candidate := strconv.FormatInt(candidateNum, 10)
|
||||||
|
|
||||||
|
var exists int
|
||||||
|
if hasTb == 1 {
|
||||||
|
if err := tx.QueryRow(existsBarcodeWithTbQuery, candidate).Scan(&exists); err != nil {
|
||||||
|
return inserted, fmt.Errorf("barcode exists check(tb) failed: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := tx.QueryRow(existsBarcodeQuery, candidate).Scan(&exists); err != nil {
|
||||||
|
return inserted, fmt.Errorf("barcode exists check failed: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if exists == 1 {
|
||||||
|
maxBarcode = candidateNum
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := tx.Exec(
|
||||||
|
insertOneQuery,
|
||||||
|
candidate,
|
||||||
|
t.ItemTypeCode,
|
||||||
|
t.ItemCode,
|
||||||
|
t.ColorCode,
|
||||||
|
t.ItemDim1Code,
|
||||||
|
t.ItemDim2Code,
|
||||||
|
t.ItemDim3Code,
|
||||||
|
username,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
if isDuplicateBarcodeInsertErr(err) {
|
||||||
|
maxBarcode = candidateNum
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return inserted, fmt.Errorf("insert item barcode failed item=%s color=%s dim1=%s duration_ms=%d: %w",
|
||||||
|
t.ItemCode, t.ColorCode, t.ItemDim1Code, time.Since(start).Milliseconds(), err)
|
||||||
|
}
|
||||||
|
affected, _ := res.RowsAffected()
|
||||||
|
if affected > 0 {
|
||||||
|
inserted += affected
|
||||||
|
maxBarcode = candidateNum
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if txErr := ensureTxStillActive(tx, "InsertItemBarcodesByTargetsTx/after_batch"); txErr != nil {
|
||||||
|
return inserted, txErr
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[InsertItemBarcodesByTargetsTx] targets=%d unique=%d inserted=%d duration_ms=%d",
|
||||||
|
len(targets), len(uniqueTargets), inserted, time.Since(start).Milliseconds())
|
||||||
|
return inserted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureTxStillActive(tx *sql.Tx, where string) error {
|
||||||
|
if tx == nil {
|
||||||
|
return fmt.Errorf("tx is nil at %s", where)
|
||||||
|
}
|
||||||
|
var tranCount int
|
||||||
|
if err := tx.QueryRow(`SELECT @@TRANCOUNT`).Scan(&tranCount); err != nil {
|
||||||
|
return fmt.Errorf("tx state query failed at %s: %w", where, err)
|
||||||
|
}
|
||||||
|
if tranCount <= 0 {
|
||||||
|
return fmt.Errorf("tx closed unexpectedly at %s (trancount=%d)", where, tranCount)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDuplicateBarcodeInsertErr(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
msg := strings.ToLower(err.Error())
|
||||||
|
if !strings.Contains(msg, "duplicate key") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if strings.Contains(msg, "tbstokbarkodu") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.Contains(msg, "pritembarcode") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return strings.Contains(msg, "unique")
|
||||||
|
}
|
||||||
|
|
||||||
func UpsertItemAttributesTx(tx *sql.Tx, attrs []models.OrderProductionItemAttributeRow, username string) (int64, error) {
|
func UpsertItemAttributesTx(tx *sql.Tx, attrs []models.OrderProductionItemAttributeRow, username string) (int64, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
if len(attrs) == 0 {
|
if len(attrs) == 0 {
|
||||||
@@ -1018,6 +1504,28 @@ func UpsertItemAttributesTx(tx *sql.Tx, attrs []models.OrderProductionItemAttrib
|
|||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FK_prItemAttribute_ItemCode hatasini engellemek icin, attribute yazmadan once
|
||||||
|
// ilgili item kodlarinin cdItem tarafinda varligini transaction icinde garanti et.
|
||||||
|
seenCodes := make(map[string]struct{}, len(attrs))
|
||||||
|
for _, a := range attrs {
|
||||||
|
itemTypeCode := a.ItemTypeCode
|
||||||
|
if itemTypeCode <= 0 {
|
||||||
|
itemTypeCode = 1
|
||||||
|
}
|
||||||
|
itemCode := strings.ToUpper(strings.TrimSpace(a.ItemCode))
|
||||||
|
if itemCode == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := NormalizeCdItemMapKey(int16(itemTypeCode), itemCode)
|
||||||
|
if _, ok := seenCodes[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seenCodes[key] = struct{}{}
|
||||||
|
if err := ensureCdItemTx(tx, int16(itemTypeCode), itemCode, username, nil); err != nil {
|
||||||
|
return 0, fmt.Errorf("ensure cdItem before item attributes failed itemCode=%s: %w", itemCode, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SQL Server parameter limiti (2100) nedeniyle batch'li set-based upsert kullanilir.
|
// SQL Server parameter limiti (2100) nedeniyle batch'li set-based upsert kullanilir.
|
||||||
const chunkSize = 400 // 400 * 4 param + 1 username = 1601
|
const chunkSize = 400 // 400 * 4 param + 1 username = 1601
|
||||||
var affected int64
|
var affected int64
|
||||||
|
|||||||
@@ -180,16 +180,33 @@ func OrderProductionValidateRoute(mssql *sql.DB) http.Handler {
|
|||||||
log.Printf("[OrderProductionValidateRoute] rid=%s orderHeaderID=%s payload lineCount=%d insertMissing=%t cdItemCount=%d attributeCount=%d",
|
log.Printf("[OrderProductionValidateRoute] rid=%s orderHeaderID=%s payload lineCount=%d insertMissing=%t cdItemCount=%d attributeCount=%d",
|
||||||
rid, id, len(payload.Lines), payload.InsertMissing, len(payload.CdItems), len(payload.ProductAttributes))
|
rid, id, len(payload.Lines), payload.InsertMissing, len(payload.CdItems), len(payload.ProductAttributes))
|
||||||
|
|
||||||
|
newLines, existingLines := splitLinesByCdItemDraft(payload.Lines, payload.CdItems)
|
||||||
|
newCodes := uniqueCodesFromLines(newLines)
|
||||||
|
existingCodes := uniqueCodesFromLines(existingLines)
|
||||||
|
missing := make([]models.OrderProductionMissingVariant, 0)
|
||||||
|
targets := make([]models.OrderProductionMissingVariant, 0)
|
||||||
stepStart := time.Now()
|
stepStart := time.Now()
|
||||||
missing, err := buildMissingVariants(mssql, id, payload.Lines)
|
if len(newLines) > 0 {
|
||||||
if err != nil {
|
err := runWithTransientMSSQLRetry("validate_build_targets_missing", 3, 500*time.Millisecond, func() error {
|
||||||
log.Printf("[OrderProductionValidateRoute] rid=%s orderHeaderID=%s step=build_missing failed duration_ms=%d err=%v",
|
var stepErr error
|
||||||
rid, id, time.Since(stepStart).Milliseconds(), err)
|
targets, stepErr = buildTargetVariants(mssql, id, newLines)
|
||||||
writeDBError(w, http.StatusInternalServerError, "validate_missing_variants", id, "", len(payload.Lines), err)
|
if stepErr != nil {
|
||||||
return
|
return stepErr
|
||||||
|
}
|
||||||
|
missing, stepErr = buildMissingVariantsFromTargets(mssql, id, targets)
|
||||||
|
return stepErr
|
||||||
|
})
|
||||||
|
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(newLines), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
log.Printf("[OrderProductionValidateRoute] rid=%s orderHeaderID=%s lineCount=%d missingCount=%d build_missing_ms=%d total_ms=%d",
|
log.Printf("[OrderProductionValidateRoute] rid=%s orderHeaderID=%s lineCount=%d newLineCount=%d existingLineCount=%d targetVariantCount=%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())
|
rid, id, len(payload.Lines), len(newLines), len(existingLines), len(targets), len(missing), time.Since(stepStart).Milliseconds(), time.Since(start).Milliseconds())
|
||||||
|
log.Printf("[OrderProductionValidateRoute] rid=%s orderHeaderID=%s scope newCodes=%v existingCodes=%v",
|
||||||
|
rid, id, newCodes, existingCodes)
|
||||||
|
|
||||||
resp := map[string]any{
|
resp := map[string]any{
|
||||||
"missingCount": len(missing),
|
"missingCount": len(missing),
|
||||||
@@ -230,17 +247,57 @@ func OrderProductionApplyRoute(mssql *sql.DB, ml *mailer.GraphMailer) http.Handl
|
|||||||
}
|
}
|
||||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s payload lineCount=%d insertMissing=%t cdItemCount=%d attributeCount=%d",
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s payload lineCount=%d insertMissing=%t cdItemCount=%d attributeCount=%d",
|
||||||
rid, id, len(payload.Lines), payload.InsertMissing, len(payload.CdItems), len(payload.ProductAttributes))
|
rid, id, len(payload.Lines), payload.InsertMissing, len(payload.CdItems), len(payload.ProductAttributes))
|
||||||
|
if len(payload.Lines) > 0 {
|
||||||
stepMissingStart := time.Now()
|
limit := 5
|
||||||
missing, err := buildMissingVariants(mssql, id, payload.Lines)
|
if len(payload.Lines) < limit {
|
||||||
if err != nil {
|
limit = len(payload.Lines)
|
||||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=build_missing failed duration_ms=%d err=%v",
|
}
|
||||||
rid, id, time.Since(stepMissingStart).Milliseconds(), err)
|
samples := make([]string, 0, limit)
|
||||||
writeDBError(w, http.StatusInternalServerError, "apply_validate_missing_variants", id, "", len(payload.Lines), err)
|
for i := 0; i < limit; i++ {
|
||||||
return
|
ln := payload.Lines[i]
|
||||||
|
dim1 := ""
|
||||||
|
if ln.ItemDim1Code != nil {
|
||||||
|
dim1 = strings.TrimSpace(*ln.ItemDim1Code)
|
||||||
|
}
|
||||||
|
samples = append(samples, fmt.Sprintf(
|
||||||
|
"lineID=%s newItem=%s newColor=%s newDim1=%s newDim2=%s",
|
||||||
|
strings.TrimSpace(ln.OrderLineID),
|
||||||
|
strings.ToUpper(strings.TrimSpace(ln.NewItemCode)),
|
||||||
|
strings.ToUpper(strings.TrimSpace(ln.NewColor)),
|
||||||
|
strings.ToUpper(strings.TrimSpace(dim1)),
|
||||||
|
strings.ToUpper(strings.TrimSpace(ln.NewDim2)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s payload lineSamples=%v", rid, id, samples)
|
||||||
}
|
}
|
||||||
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())
|
newLines, existingLines := splitLinesByCdItemDraft(payload.Lines, payload.CdItems)
|
||||||
|
newCodes := uniqueCodesFromLines(newLines)
|
||||||
|
existingCodes := uniqueCodesFromLines(existingLines)
|
||||||
|
stepMissingStart := time.Now()
|
||||||
|
missing := make([]models.OrderProductionMissingVariant, 0)
|
||||||
|
barcodeTargets := make([]models.OrderProductionMissingVariant, 0)
|
||||||
|
if len(newLines) > 0 {
|
||||||
|
err := runWithTransientMSSQLRetry("apply_build_targets_missing", 3, 500*time.Millisecond, func() error {
|
||||||
|
var stepErr error
|
||||||
|
barcodeTargets, stepErr = buildTargetVariants(mssql, id, newLines)
|
||||||
|
if stepErr != nil {
|
||||||
|
return stepErr
|
||||||
|
}
|
||||||
|
missing, stepErr = buildMissingVariantsFromTargets(mssql, id, barcodeTargets)
|
||||||
|
return stepErr
|
||||||
|
})
|
||||||
|
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(newLines), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s lineCount=%d newLineCount=%d existingLineCount=%d targetVariantCount=%d missingCount=%d build_missing_ms=%d",
|
||||||
|
rid, id, len(payload.Lines), len(newLines), len(existingLines), len(barcodeTargets), len(missing), time.Since(stepMissingStart).Milliseconds())
|
||||||
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s scope newCodes=%v existingCodes=%v",
|
||||||
|
rid, id, newCodes, existingCodes)
|
||||||
|
|
||||||
if len(missing) > 0 && !payload.InsertMissing {
|
if len(missing) > 0 && !payload.InsertMissing {
|
||||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s early_exit=missing_variants total_ms=%d",
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s early_exit=missing_variants total_ms=%d",
|
||||||
@@ -269,30 +326,83 @@ func OrderProductionApplyRoute(mssql *sql.DB, ml *mailer.GraphMailer) http.Handl
|
|||||||
writeDBError(w, http.StatusInternalServerError, "begin_tx", id, username, len(payload.Lines), err)
|
writeDBError(w, http.StatusInternalServerError, "begin_tx", id, username, len(payload.Lines), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer tx.Rollback()
|
|
||||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=begin_tx duration_ms=%d", rid, id, time.Since(stepBeginStart).Milliseconds())
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=begin_tx duration_ms=%d", rid, id, time.Since(stepBeginStart).Milliseconds())
|
||||||
|
committed := false
|
||||||
|
currentStep := "begin_tx"
|
||||||
|
applyTxSettings := func(tx *sql.Tx) error {
|
||||||
|
// XACT_ABORT OFF:
|
||||||
|
// Barcode insert path intentionally tolerates duplicate-key errors (fallback/skip duplicate).
|
||||||
|
// With XACT_ABORT ON, that expected error aborts the whole transaction and causes COMMIT 3902.
|
||||||
|
_, execErr := tx.Exec(`SET XACT_ABORT OFF; SET LOCK_TIMEOUT 15000;`)
|
||||||
|
return execErr
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if committed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rbStart := time.Now()
|
||||||
|
if rbErr := tx.Rollback(); rbErr != nil && rbErr != sql.ErrTxDone {
|
||||||
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s rollback step=%s failed duration_ms=%d err=%v",
|
||||||
|
rid, id, currentStep, time.Since(rbStart).Milliseconds(), rbErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s rollback step=%s ok duration_ms=%d",
|
||||||
|
rid, id, currentStep, time.Since(rbStart).Milliseconds())
|
||||||
|
}()
|
||||||
|
|
||||||
stepTxSettingsStart := time.Now()
|
stepTxSettingsStart := time.Now()
|
||||||
if _, err := tx.Exec(`SET XACT_ABORT ON; SET LOCK_TIMEOUT 15000;`); err != nil {
|
currentStep = "tx_settings"
|
||||||
|
if err := applyTxSettings(tx); err != nil {
|
||||||
writeDBError(w, http.StatusInternalServerError, "tx_settings", id, username, len(payload.Lines), err)
|
writeDBError(w, http.StatusInternalServerError, "tx_settings", id, username, len(payload.Lines), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=tx_settings duration_ms=%d", rid, id, time.Since(stepTxSettingsStart).Milliseconds())
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=tx_settings duration_ms=%d", rid, id, time.Since(stepTxSettingsStart).Milliseconds())
|
||||||
|
if err := ensureTxAlive(tx, "after_tx_settings"); err != nil {
|
||||||
|
writeDBError(w, http.StatusInternalServerError, "tx_not_active_after_tx_settings", id, username, len(payload.Lines), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var inserted int64
|
var inserted int64
|
||||||
if payload.InsertMissing {
|
if payload.InsertMissing && len(newLines) > 0 {
|
||||||
|
currentStep = "insert_missing_variants"
|
||||||
cdItemByCode := buildCdItemDraftMap(payload.CdItems)
|
cdItemByCode := buildCdItemDraftMap(payload.CdItems)
|
||||||
stepInsertMissingStart := time.Now()
|
stepInsertMissingStart := time.Now()
|
||||||
inserted, err = queries.InsertMissingVariantsTx(tx, missing, username, cdItemByCode)
|
inserted, err = queries.InsertMissingVariantsTx(tx, missing, username, cdItemByCode)
|
||||||
|
if err != nil && isTransientMSSQLNetworkErr(err) {
|
||||||
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=insert_missing transient_error retry=1 err=%v",
|
||||||
|
rid, id, err)
|
||||||
|
_ = tx.Rollback()
|
||||||
|
tx, err = mssql.Begin()
|
||||||
|
if err != nil {
|
||||||
|
writeDBError(w, http.StatusInternalServerError, "begin_tx_retry_insert_missing", id, username, len(payload.Lines), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
currentStep = "tx_settings_retry_insert_missing"
|
||||||
|
if err = applyTxSettings(tx); err != nil {
|
||||||
|
writeDBError(w, http.StatusInternalServerError, "tx_settings_retry_insert_missing", id, username, len(payload.Lines), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = ensureTxAlive(tx, "after_tx_settings_retry_insert_missing"); err != nil {
|
||||||
|
writeDBError(w, http.StatusInternalServerError, "tx_not_active_after_tx_settings_retry_insert_missing", id, username, len(payload.Lines), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
currentStep = "insert_missing_variants_retry"
|
||||||
|
inserted, err = queries.InsertMissingVariantsTx(tx, missing, username, cdItemByCode)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeDBError(w, http.StatusInternalServerError, "insert_missing_variants", id, username, len(missing), err)
|
writeDBError(w, http.StatusInternalServerError, "insert_missing_variants", id, username, len(missing), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := ensureTxAlive(tx, "after_insert_missing_variants"); err != nil {
|
||||||
|
writeDBError(w, http.StatusInternalServerError, "tx_not_active_after_insert_missing_variants", id, username, len(missing), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=insert_missing inserted=%d duration_ms=%d",
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=insert_missing inserted=%d duration_ms=%d",
|
||||||
rid, id, inserted, time.Since(stepInsertMissingStart).Milliseconds())
|
rid, id, inserted, time.Since(stepInsertMissingStart).Milliseconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
stepValidateAttrStart := time.Now()
|
stepValidateAttrStart := time.Now()
|
||||||
|
currentStep = "validate_attributes"
|
||||||
if err := validateProductAttributes(payload.ProductAttributes); err != nil {
|
if err := validateProductAttributes(payload.ProductAttributes); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
@@ -300,37 +410,8 @@ func OrderProductionApplyRoute(mssql *sql.DB, ml *mailer.GraphMailer) http.Handl
|
|||||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=validate_attributes count=%d duration_ms=%d",
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=validate_attributes count=%d duration_ms=%d",
|
||||||
rid, id, len(payload.ProductAttributes), time.Since(stepValidateAttrStart).Milliseconds())
|
rid, id, len(payload.ProductAttributes), time.Since(stepValidateAttrStart).Milliseconds())
|
||||||
|
|
||||||
stepUpdateHeaderStart := time.Now()
|
|
||||||
if err := queries.UpdateOrderHeaderAverageDueDateTx(tx, id, payload.HeaderAverageDueDate, username); err != nil {
|
|
||||||
writeDBError(w, http.StatusInternalServerError, "update_order_header_average_due_date", id, username, 0, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=update_header_average_due_date changed=%t duration_ms=%d",
|
|
||||||
rid, id, payload.HeaderAverageDueDate != nil, time.Since(stepUpdateHeaderStart).Milliseconds())
|
|
||||||
|
|
||||||
stepUpdateLinesStart := time.Now()
|
|
||||||
updated, err := queries.UpdateOrderLinesTx(tx, id, payload.Lines, username)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=update_lines failed duration_ms=%d err=%v",
|
|
||||||
rid, id, time.Since(stepUpdateLinesStart).Milliseconds(), err)
|
|
||||||
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())
|
|
||||||
|
|
||||||
stepUpsertBarcodeStart := time.Now()
|
|
||||||
barcodeInserted, err := queries.InsertItemBarcodesTx(tx, id, payload.Lines, username)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=upsert_barcodes failed duration_ms=%d err=%v",
|
|
||||||
rid, id, time.Since(stepUpsertBarcodeStart).Milliseconds(), err)
|
|
||||||
writeDBError(w, http.StatusInternalServerError, "upsert_item_barcodes", id, username, len(payload.Lines), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=upsert_barcodes inserted=%d duration_ms=%d",
|
|
||||||
rid, id, barcodeInserted, time.Since(stepUpsertBarcodeStart).Milliseconds())
|
|
||||||
|
|
||||||
stepUpsertAttrStart := time.Now()
|
stepUpsertAttrStart := time.Now()
|
||||||
|
currentStep = "upsert_item_attributes"
|
||||||
attributeAffected, err := queries.UpsertItemAttributesTx(tx, payload.ProductAttributes, username)
|
attributeAffected, err := queries.UpsertItemAttributesTx(tx, payload.ProductAttributes, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=upsert_attributes failed duration_ms=%d err=%v",
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=upsert_attributes failed duration_ms=%d err=%v",
|
||||||
@@ -340,14 +421,120 @@ func OrderProductionApplyRoute(mssql *sql.DB, ml *mailer.GraphMailer) http.Handl
|
|||||||
}
|
}
|
||||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=upsert_attributes affected=%d duration_ms=%d",
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=upsert_attributes affected=%d duration_ms=%d",
|
||||||
rid, id, attributeAffected, time.Since(stepUpsertAttrStart).Milliseconds())
|
rid, id, attributeAffected, time.Since(stepUpsertAttrStart).Milliseconds())
|
||||||
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s table=prItemAttribute inputRows=%d affectedRows=%d",
|
||||||
|
rid, id, len(payload.ProductAttributes), attributeAffected)
|
||||||
|
if err := ensureTxAlive(tx, "after_upsert_item_attributes"); err != nil {
|
||||||
|
writeDBError(w, http.StatusInternalServerError, "tx_not_active_after_upsert_item_attributes", id, username, len(payload.ProductAttributes), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var barcodeInserted int64
|
||||||
|
// Barkod adimi:
|
||||||
|
// - Eski kodlara girmemeli
|
||||||
|
// - Yeni kod satirlari icin, varyant daha once olusmus olsa bile eksik barkod varsa tamamlamali
|
||||||
|
// Bu nedenle "inserted > 0" yerine "newLineCount > 0" kosulu kullanilir.
|
||||||
|
if len(newLines) > 0 && len(barcodeTargets) > 0 {
|
||||||
|
stepUpsertBarcodeStart := time.Now()
|
||||||
|
currentStep = "upsert_item_barcodes"
|
||||||
|
barcodeInserted, err = queries.InsertItemBarcodesByTargetsTx(tx, barcodeTargets, username)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=upsert_barcodes failed duration_ms=%d err=%v",
|
||||||
|
rid, id, time.Since(stepUpsertBarcodeStart).Milliseconds(), err)
|
||||||
|
writeDBError(w, http.StatusInternalServerError, "upsert_item_barcodes", id, username, len(barcodeTargets), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=upsert_barcodes inserted=%d duration_ms=%d",
|
||||||
|
rid, id, barcodeInserted, time.Since(stepUpsertBarcodeStart).Milliseconds())
|
||||||
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s table=prItemBarcode targetVariantRows=%d insertedRows=%d",
|
||||||
|
rid, id, len(barcodeTargets), barcodeInserted)
|
||||||
|
if err := ensureTxAlive(tx, "after_upsert_item_barcodes"); err != nil {
|
||||||
|
writeDBError(w, http.StatusInternalServerError, "tx_not_active_after_upsert_item_barcodes", id, username, len(barcodeTargets), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=upsert_barcodes skipped newLineCount=%d targetVariantRows=%d",
|
||||||
|
rid, id, len(newLines), len(barcodeTargets))
|
||||||
|
}
|
||||||
|
|
||||||
|
stepUpdateHeaderStart := time.Now()
|
||||||
|
currentStep = "update_order_header_average_due_date"
|
||||||
|
if err := queries.UpdateOrderHeaderAverageDueDateTx(tx, id, payload.HeaderAverageDueDate, username); err != nil {
|
||||||
|
writeDBError(w, http.StatusInternalServerError, "update_order_header_average_due_date", id, username, 0, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=update_header_average_due_date changed=%t duration_ms=%d",
|
||||||
|
rid, id, payload.HeaderAverageDueDate != nil, time.Since(stepUpdateHeaderStart).Milliseconds())
|
||||||
|
if err := ensureTxAlive(tx, "after_update_order_header_average_due_date"); err != nil {
|
||||||
|
writeDBError(w, http.StatusInternalServerError, "tx_not_active_after_update_order_header_average_due_date", id, username, 0, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentStep = "touch_order_header"
|
||||||
|
headerTouched, err := queries.TouchOrderHeaderTx(tx, id, username)
|
||||||
|
if err != nil {
|
||||||
|
writeDBError(w, http.StatusInternalServerError, "touch_order_header", id, username, len(payload.Lines), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s table=trOrderHeader touchedRows=%d",
|
||||||
|
rid, id, headerTouched)
|
||||||
|
if err := ensureTxAlive(tx, "after_touch_order_header"); err != nil {
|
||||||
|
writeDBError(w, http.StatusInternalServerError, "tx_not_active_after_touch_order_header", id, username, len(payload.Lines), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stepUpdateLinesStart := time.Now()
|
||||||
|
currentStep = "update_order_lines"
|
||||||
|
updated, err := queries.UpdateOrderLinesTx(tx, id, payload.Lines, username)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=update_lines failed duration_ms=%d err=%v",
|
||||||
|
rid, id, time.Since(stepUpdateLinesStart).Milliseconds(), err)
|
||||||
|
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())
|
||||||
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s table=trOrderLine targetRows=%d updatedRows=%d",
|
||||||
|
rid, id, len(payload.Lines), updated)
|
||||||
|
if err := ensureTxAlive(tx, "after_update_order_lines"); err != nil {
|
||||||
|
writeDBError(w, http.StatusInternalServerError, "tx_not_active_after_update_order_lines", id, username, len(payload.Lines), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentStep = "verify_order_lines"
|
||||||
|
verifyMismatchCount, verifySamples, verifyErr := queries.VerifyOrderLineUpdatesTx(tx, id, payload.Lines)
|
||||||
|
if verifyErr != nil {
|
||||||
|
writeDBError(w, http.StatusInternalServerError, "verify_order_lines", id, username, len(payload.Lines), verifyErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if verifyMismatchCount > 0 {
|
||||||
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s table=trOrderLine verifyMismatchCount=%d samples=%v",
|
||||||
|
rid, id, verifyMismatchCount, verifySamples)
|
||||||
|
currentStep = "verify_order_lines_mismatch"
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
"message": "Order satirlari beklenen kod/renk degerlerine guncellenemedi",
|
||||||
|
"step": "verify_order_lines_mismatch",
|
||||||
|
"detail": fmt.Sprintf("mismatchCount=%d", verifyMismatchCount),
|
||||||
|
"samples": verifySamples,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s table=trOrderLine verifyMismatchCount=0",
|
||||||
|
rid, id)
|
||||||
|
if err := ensureTxAlive(tx, "before_commit_tx"); err != nil {
|
||||||
|
writeDBError(w, http.StatusInternalServerError, "tx_not_active_before_commit_tx", id, username, len(payload.Lines), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
stepCommitStart := time.Now()
|
stepCommitStart := time.Now()
|
||||||
|
currentStep = "commit_tx"
|
||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=commit failed duration_ms=%d err=%v",
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=commit failed duration_ms=%d err=%v",
|
||||||
rid, id, time.Since(stepCommitStart).Milliseconds(), err)
|
rid, id, time.Since(stepCommitStart).Milliseconds(), err)
|
||||||
writeDBError(w, http.StatusInternalServerError, "commit_tx", id, username, len(payload.Lines), err)
|
writeDBError(w, http.StatusInternalServerError, "commit_tx", id, username, len(payload.Lines), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
committed = true
|
||||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s step=commit duration_ms=%d total_ms=%d",
|
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())
|
rid, id, time.Since(stepCommitStart).Milliseconds(), time.Since(start).Milliseconds())
|
||||||
|
|
||||||
@@ -372,6 +559,8 @@ func OrderProductionApplyRoute(mssql *sql.DB, ml *mailer.GraphMailer) http.Handl
|
|||||||
}
|
}
|
||||||
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s result updated=%d inserted=%d barcodeInserted=%d attributeUpserted=%d",
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s result updated=%d inserted=%d barcodeInserted=%d attributeUpserted=%d",
|
||||||
rid, id, updated, inserted, barcodeInserted, attributeAffected)
|
rid, id, updated, inserted, barcodeInserted, attributeAffected)
|
||||||
|
log.Printf("[OrderProductionApplyRoute] rid=%s orderHeaderID=%s summary tables cdItem/prItemVariant(newOnly)=%d trOrderLine(updated)=%d prItemBarcode(inserted,newOnly)=%d prItemAttribute(affected)=%d trOrderHeader(touched)=%d",
|
||||||
|
rid, id, inserted, updated, barcodeInserted, attributeAffected, headerTouched)
|
||||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||||
log.Printf("❌ encode error: %v", err)
|
log.Printf("❌ encode error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -416,6 +605,14 @@ func buildCdItemDraftMap(list []models.OrderProductionCdItemDraft) map[string]mo
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isNoCorrespondingBeginTxErr(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
msg := strings.ToLower(strings.TrimSpace(err.Error()))
|
||||||
|
return strings.Contains(msg, "commit transaction request has no corresponding begin transaction")
|
||||||
|
}
|
||||||
|
|
||||||
func buildTargetVariants(mssql *sql.DB, orderHeaderID string, lines []models.OrderProductionUpdateLine) ([]models.OrderProductionMissingVariant, error) {
|
func buildTargetVariants(mssql *sql.DB, orderHeaderID string, lines []models.OrderProductionUpdateLine) ([]models.OrderProductionMissingVariant, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
lineDimsMap, err := queries.GetOrderLineDimsMap(mssql, orderHeaderID)
|
lineDimsMap, err := queries.GetOrderLineDimsMap(mssql, orderHeaderID)
|
||||||
@@ -468,11 +665,15 @@ func buildTargetVariants(mssql *sql.DB, orderHeaderID string, lines []models.Ord
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildMissingVariants(mssql *sql.DB, orderHeaderID string, lines []models.OrderProductionUpdateLine) ([]models.OrderProductionMissingVariant, error) {
|
func buildMissingVariants(mssql *sql.DB, orderHeaderID string, lines []models.OrderProductionUpdateLine) ([]models.OrderProductionMissingVariant, error) {
|
||||||
start := time.Now()
|
|
||||||
targets, err := buildTargetVariants(mssql, orderHeaderID, lines)
|
targets, err := buildTargetVariants(mssql, orderHeaderID, lines)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return buildMissingVariantsFromTargets(mssql, orderHeaderID, targets)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildMissingVariantsFromTargets(mssql *sql.DB, orderHeaderID string, targets []models.OrderProductionMissingVariant) ([]models.OrderProductionMissingVariant, error) {
|
||||||
|
start := time.Now()
|
||||||
missing := make([]models.OrderProductionMissingVariant, 0, len(targets))
|
missing := make([]models.OrderProductionMissingVariant, 0, len(targets))
|
||||||
existsCache := make(map[string]bool, len(targets))
|
existsCache := make(map[string]bool, len(targets))
|
||||||
|
|
||||||
@@ -499,11 +700,69 @@ func buildMissingVariants(mssql *sql.DB, orderHeaderID string, lines []models.Or
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[buildMissingVariants] orderHeaderID=%s lineCount=%d dimMapCount=%d missingCount=%d total_ms=%d",
|
log.Printf("[buildMissingVariants] orderHeaderID=%s targetCount=%d missingCount=%d total_ms=%d",
|
||||||
orderHeaderID, len(lines), len(targets), len(missing), time.Since(start).Milliseconds())
|
orderHeaderID, len(targets), len(missing), time.Since(start).Milliseconds())
|
||||||
return missing, nil
|
return missing, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runWithTransientMSSQLRetry(op string, maxAttempts int, baseDelay time.Duration, fn func() error) error {
|
||||||
|
if maxAttempts <= 1 {
|
||||||
|
return fn()
|
||||||
|
}
|
||||||
|
var lastErr error
|
||||||
|
for attempt := 1; attempt <= maxAttempts; attempt++ {
|
||||||
|
err := fn()
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
lastErr = err
|
||||||
|
if !isTransientMSSQLNetworkErr(err) || attempt == maxAttempts {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
wait := time.Duration(attempt) * baseDelay
|
||||||
|
log.Printf("[MSSQLRetry] op=%s attempt=%d/%d wait_ms=%d err=%v",
|
||||||
|
op, attempt, maxAttempts, wait.Milliseconds(), err)
|
||||||
|
time.Sleep(wait)
|
||||||
|
}
|
||||||
|
return lastErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTransientMSSQLNetworkErr(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
msg := strings.ToLower(strings.TrimSpace(err.Error()))
|
||||||
|
needles := []string{
|
||||||
|
"wsarecv",
|
||||||
|
"read tcp",
|
||||||
|
"connection reset",
|
||||||
|
"connection refused",
|
||||||
|
"broken pipe",
|
||||||
|
"i/o timeout",
|
||||||
|
"timeout",
|
||||||
|
}
|
||||||
|
for _, needle := range needles {
|
||||||
|
if strings.Contains(msg, needle) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureTxAlive(tx *sql.Tx, where string) error {
|
||||||
|
if tx == nil {
|
||||||
|
return fmt.Errorf("tx is nil at %s", where)
|
||||||
|
}
|
||||||
|
var tranCount int
|
||||||
|
if err := tx.QueryRow(`SELECT @@TRANCOUNT`).Scan(&tranCount); err != nil {
|
||||||
|
return fmt.Errorf("tx state query failed at %s: %w", where, err)
|
||||||
|
}
|
||||||
|
if tranCount <= 0 {
|
||||||
|
return fmt.Errorf("transaction no longer active at %s (trancount=%d)", where, tranCount)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func validateUpdateLines(lines []models.OrderProductionUpdateLine) error {
|
func validateUpdateLines(lines []models.OrderProductionUpdateLine) error {
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
if strings.TrimSpace(line.OrderLineID) == "" {
|
if strings.TrimSpace(line.OrderLineID) == "" {
|
||||||
@@ -520,6 +779,54 @@ func validateUpdateLines(lines []models.OrderProductionUpdateLine) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func splitLinesByCdItemDraft(lines []models.OrderProductionUpdateLine, cdItems []models.OrderProductionCdItemDraft) ([]models.OrderProductionUpdateLine, []models.OrderProductionUpdateLine) {
|
||||||
|
if len(lines) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
newCodeSet := make(map[string]struct{}, len(cdItems))
|
||||||
|
for _, item := range cdItems {
|
||||||
|
code := strings.ToUpper(strings.TrimSpace(item.ItemCode))
|
||||||
|
if code == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newCodeSet[code] = struct{}{}
|
||||||
|
}
|
||||||
|
if len(newCodeSet) == 0 {
|
||||||
|
existingLines := make([]models.OrderProductionUpdateLine, 0, len(lines))
|
||||||
|
existingLines = append(existingLines, lines...)
|
||||||
|
return nil, existingLines
|
||||||
|
}
|
||||||
|
|
||||||
|
newLines := make([]models.OrderProductionUpdateLine, 0, len(lines))
|
||||||
|
existingLines := make([]models.OrderProductionUpdateLine, 0, len(lines))
|
||||||
|
for _, line := range lines {
|
||||||
|
code := strings.ToUpper(strings.TrimSpace(line.NewItemCode))
|
||||||
|
if _, ok := newCodeSet[code]; ok {
|
||||||
|
newLines = append(newLines, line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
existingLines = append(existingLines, line)
|
||||||
|
}
|
||||||
|
return newLines, existingLines
|
||||||
|
}
|
||||||
|
|
||||||
|
func uniqueCodesFromLines(lines []models.OrderProductionUpdateLine) []string {
|
||||||
|
set := make(map[string]struct{}, len(lines))
|
||||||
|
out := make([]string, 0, len(lines))
|
||||||
|
for _, line := range lines {
|
||||||
|
code := strings.ToUpper(strings.TrimSpace(line.NewItemCode))
|
||||||
|
if code == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := set[code]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
set[code] = struct{}{}
|
||||||
|
out = append(out, code)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func writeDBError(w http.ResponseWriter, status int, step string, orderHeaderID string, username string, lineCount int, err error) {
|
func writeDBError(w http.ResponseWriter, status int, step string, orderHeaderID string, username string, lineCount int, err error) {
|
||||||
var sqlErr mssql.Error
|
var sqlErr mssql.Error
|
||||||
if errors.As(err, &sqlErr) {
|
if errors.As(err, &sqlErr) {
|
||||||
|
|||||||
@@ -146,7 +146,9 @@ createQuasarApp(createApp, quasarUserOptions)
|
|||||||
|
|
||||||
return Promise[ method ]([
|
return Promise[ method ]([
|
||||||
|
|
||||||
import(/* webpackMode: "eager" */ 'boot/dayjs')
|
import(/* webpackMode: "eager" */ 'boot/dayjs'),
|
||||||
|
|
||||||
|
import(/* webpackMode: "eager" */ 'boot/resizeObserverGuard')
|
||||||
|
|
||||||
]).then(bootFiles => {
|
]).then(bootFiles => {
|
||||||
const boot = mapFn(bootFiles).filter(entry => typeof entry === 'function')
|
const boot = mapFn(bootFiles).filter(entry => typeof entry === 'function')
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
/**
|
|
||||||
* THIS FILE IS GENERATED AUTOMATICALLY.
|
|
||||||
* DO NOT EDIT.
|
|
||||||
*
|
|
||||||
* You are probably looking on adding startup/initialization code.
|
|
||||||
* Use "quasar new boot <name>" and add it there.
|
|
||||||
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
|
||||||
* boot: ['file', ...] // do not add ".js" extension to it.
|
|
||||||
*
|
|
||||||
* Boot files are your "main.js"
|
|
||||||
**/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import { Quasar } from 'quasar'
|
|
||||||
import { markRaw } from 'vue'
|
|
||||||
import RootComponent from 'app/src/App.vue'
|
|
||||||
|
|
||||||
import createStore from 'app/src/stores/index'
|
|
||||||
import createRouter from 'app/src/router/index'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default async function (createAppFn, quasarUserOptions) {
|
|
||||||
|
|
||||||
|
|
||||||
// Create the app instance.
|
|
||||||
// Here we inject into it the Quasar UI, the router & possibly the store.
|
|
||||||
const app = createAppFn(RootComponent)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
app.use(Quasar, quasarUserOptions)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const store = typeof createStore === 'function'
|
|
||||||
? await createStore({})
|
|
||||||
: createStore
|
|
||||||
|
|
||||||
|
|
||||||
app.use(store)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const router = markRaw(
|
|
||||||
typeof createRouter === 'function'
|
|
||||||
? await createRouter({store})
|
|
||||||
: createRouter
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
// make router instance available in store
|
|
||||||
|
|
||||||
store.use(({ store }) => { store.router = router })
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Expose the app, the router and the store.
|
|
||||||
// Note that we are not mounting the app here, since bootstrapping will be
|
|
||||||
// different depending on whether we are in a browser or on the server.
|
|
||||||
return {
|
|
||||||
app,
|
|
||||||
store,
|
|
||||||
router
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
/**
|
|
||||||
* THIS FILE IS GENERATED AUTOMATICALLY.
|
|
||||||
* DO NOT EDIT.
|
|
||||||
*
|
|
||||||
* You are probably looking on adding startup/initialization code.
|
|
||||||
* Use "quasar new boot <name>" and add it there.
|
|
||||||
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
|
||||||
* boot: ['file', ...] // do not add ".js" extension to it.
|
|
||||||
*
|
|
||||||
* Boot files are your "main.js"
|
|
||||||
**/
|
|
||||||
|
|
||||||
|
|
||||||
import { createApp } from 'vue'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import '@quasar/extras/roboto-font/roboto-font.css'
|
|
||||||
|
|
||||||
import '@quasar/extras/material-icons/material-icons.css'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// We load Quasar stylesheet file
|
|
||||||
import 'quasar/dist/quasar.sass'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import 'src/css/app.css'
|
|
||||||
|
|
||||||
|
|
||||||
import createQuasarApp from './app.js'
|
|
||||||
import quasarUserOptions from './quasar-user-options.js'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const publicPath = `/`
|
|
||||||
|
|
||||||
|
|
||||||
async function start ({
|
|
||||||
app,
|
|
||||||
router
|
|
||||||
, store
|
|
||||||
}, bootFiles) {
|
|
||||||
|
|
||||||
let hasRedirected = false
|
|
||||||
const getRedirectUrl = url => {
|
|
||||||
try { return router.resolve(url).href }
|
|
||||||
catch (err) {}
|
|
||||||
|
|
||||||
return Object(url) === url
|
|
||||||
? null
|
|
||||||
: url
|
|
||||||
}
|
|
||||||
const redirect = url => {
|
|
||||||
hasRedirected = true
|
|
||||||
|
|
||||||
if (typeof url === 'string' && /^https?:\/\//.test(url)) {
|
|
||||||
window.location.href = url
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const href = getRedirectUrl(url)
|
|
||||||
|
|
||||||
// continue if we didn't fail to resolve the url
|
|
||||||
if (href !== null) {
|
|
||||||
window.location.href = href
|
|
||||||
window.location.reload()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const urlPath = window.location.href.replace(window.location.origin, '')
|
|
||||||
|
|
||||||
for (let i = 0; hasRedirected === false && i < bootFiles.length; i++) {
|
|
||||||
try {
|
|
||||||
await bootFiles[i]({
|
|
||||||
app,
|
|
||||||
router,
|
|
||||||
store,
|
|
||||||
ssrContext: null,
|
|
||||||
redirect,
|
|
||||||
urlPath,
|
|
||||||
publicPath
|
|
||||||
})
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
if (err && err.url) {
|
|
||||||
redirect(err.url)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error('[Quasar] boot error:', err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasRedirected === true) return
|
|
||||||
|
|
||||||
|
|
||||||
app.use(router)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
app.mount('#q-app')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
createQuasarApp(createApp, quasarUserOptions)
|
|
||||||
|
|
||||||
.then(app => {
|
|
||||||
// eventually remove this when Cordova/Capacitor/Electron support becomes old
|
|
||||||
const [ method, mapFn ] = Promise.allSettled !== void 0
|
|
||||||
? [
|
|
||||||
'allSettled',
|
|
||||||
bootFiles => bootFiles.map(result => {
|
|
||||||
if (result.status === 'rejected') {
|
|
||||||
console.error('[Quasar] boot error:', result.reason)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return result.value.default
|
|
||||||
})
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
'all',
|
|
||||||
bootFiles => bootFiles.map(entry => entry.default)
|
|
||||||
]
|
|
||||||
|
|
||||||
return Promise[ method ]([
|
|
||||||
|
|
||||||
import(/* webpackMode: "eager" */ 'boot/dayjs')
|
|
||||||
|
|
||||||
]).then(bootFiles => {
|
|
||||||
const boot = mapFn(bootFiles).filter(entry => typeof entry === 'function')
|
|
||||||
start(app, boot)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
/**
|
|
||||||
* THIS FILE IS GENERATED AUTOMATICALLY.
|
|
||||||
* DO NOT EDIT.
|
|
||||||
*
|
|
||||||
* You are probably looking on adding startup/initialization code.
|
|
||||||
* Use "quasar new boot <name>" and add it there.
|
|
||||||
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
|
||||||
* boot: ['file', ...] // do not add ".js" extension to it.
|
|
||||||
*
|
|
||||||
* Boot files are your "main.js"
|
|
||||||
**/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import App from 'app/src/App.vue'
|
|
||||||
let appPrefetch = typeof App.preFetch === 'function'
|
|
||||||
? App.preFetch
|
|
||||||
: (
|
|
||||||
// Class components return the component options (and the preFetch hook) inside __c property
|
|
||||||
App.__c !== void 0 && typeof App.__c.preFetch === 'function'
|
|
||||||
? App.__c.preFetch
|
|
||||||
: false
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
function getMatchedComponents (to, router) {
|
|
||||||
const route = to
|
|
||||||
? (to.matched ? to : router.resolve(to).route)
|
|
||||||
: router.currentRoute.value
|
|
||||||
|
|
||||||
if (!route) { return [] }
|
|
||||||
|
|
||||||
const matched = route.matched.filter(m => m.components !== void 0)
|
|
||||||
|
|
||||||
if (matched.length === 0) { return [] }
|
|
||||||
|
|
||||||
return Array.prototype.concat.apply([], matched.map(m => {
|
|
||||||
return Object.keys(m.components).map(key => {
|
|
||||||
const comp = m.components[key]
|
|
||||||
return {
|
|
||||||
path: m.path,
|
|
||||||
c: comp
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addPreFetchHooks ({ router, store, publicPath }) {
|
|
||||||
// Add router hook for handling preFetch.
|
|
||||||
// Doing it after initial route is resolved so that we don't double-fetch
|
|
||||||
// the data that we already have. Using router.beforeResolve() so that all
|
|
||||||
// async components are resolved.
|
|
||||||
router.beforeResolve((to, from, next) => {
|
|
||||||
const
|
|
||||||
urlPath = window.location.href.replace(window.location.origin, ''),
|
|
||||||
matched = getMatchedComponents(to, router),
|
|
||||||
prevMatched = getMatchedComponents(from, router)
|
|
||||||
|
|
||||||
let diffed = false
|
|
||||||
const preFetchList = matched
|
|
||||||
.filter((m, i) => {
|
|
||||||
return diffed || (diffed = (
|
|
||||||
!prevMatched[i] ||
|
|
||||||
prevMatched[i].c !== m.c ||
|
|
||||||
m.path.indexOf('/:') > -1 // does it has params?
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.filter(m => m.c !== void 0 && (
|
|
||||||
typeof m.c.preFetch === 'function'
|
|
||||||
// Class components return the component options (and the preFetch hook) inside __c property
|
|
||||||
|| (m.c.__c !== void 0 && typeof m.c.__c.preFetch === 'function')
|
|
||||||
))
|
|
||||||
.map(m => m.c.__c !== void 0 ? m.c.__c.preFetch : m.c.preFetch)
|
|
||||||
|
|
||||||
|
|
||||||
if (appPrefetch !== false) {
|
|
||||||
preFetchList.unshift(appPrefetch)
|
|
||||||
appPrefetch = false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (preFetchList.length === 0) {
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
|
|
||||||
let hasRedirected = false
|
|
||||||
const redirect = url => {
|
|
||||||
hasRedirected = true
|
|
||||||
next(url)
|
|
||||||
}
|
|
||||||
const proceed = () => {
|
|
||||||
|
|
||||||
if (hasRedirected === false) { next() }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
preFetchList.reduce(
|
|
||||||
(promise, preFetch) => promise.then(() => hasRedirected === false && preFetch({
|
|
||||||
store,
|
|
||||||
currentRoute: to,
|
|
||||||
previousRoute: from,
|
|
||||||
redirect,
|
|
||||||
urlPath,
|
|
||||||
publicPath
|
|
||||||
})),
|
|
||||||
Promise.resolve()
|
|
||||||
)
|
|
||||||
.then(proceed)
|
|
||||||
.catch(e => {
|
|
||||||
console.error(e)
|
|
||||||
proceed()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
/**
|
|
||||||
* THIS FILE IS GENERATED AUTOMATICALLY.
|
|
||||||
* DO NOT EDIT.
|
|
||||||
*
|
|
||||||
* You are probably looking on adding startup/initialization code.
|
|
||||||
* Use "quasar new boot <name>" and add it there.
|
|
||||||
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
|
||||||
* boot: ['file', ...] // do not add ".js" extension to it.
|
|
||||||
*
|
|
||||||
* Boot files are your "main.js"
|
|
||||||
**/
|
|
||||||
|
|
||||||
import lang from 'quasar/lang/tr.js'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import {Loading,Dialog,Notify} from 'quasar'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default { config: {"notify":{"position":"top","timeout":2500}},lang,plugins: {Loading,Dialog,Notify} }
|
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ export default defineConfig(() => {
|
|||||||
/* =====================================================
|
/* =====================================================
|
||||||
BOOT FILES
|
BOOT FILES
|
||||||
===================================================== */
|
===================================================== */
|
||||||
boot: ['dayjs'],
|
boot: ['dayjs', 'resizeObserverGuard'],
|
||||||
|
|
||||||
/* =====================================================
|
/* =====================================================
|
||||||
GLOBAL CSS
|
GLOBAL CSS
|
||||||
@@ -56,6 +56,13 @@ export default defineConfig(() => {
|
|||||||
server: { type: 'http' },
|
server: { type: 'http' },
|
||||||
port: 9000,
|
port: 9000,
|
||||||
open: true,
|
open: true,
|
||||||
|
client: {
|
||||||
|
overlay: {
|
||||||
|
errors: true,
|
||||||
|
warnings: false,
|
||||||
|
runtimeErrors: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// DEV proxy (CORS'suz)
|
// DEV proxy (CORS'suz)
|
||||||
proxy: [
|
proxy: [
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ var quasar_config_default = defineConfig(() => {
|
|||||||
/* =====================================================
|
/* =====================================================
|
||||||
BOOT FILES
|
BOOT FILES
|
||||||
===================================================== */
|
===================================================== */
|
||||||
boot: ["dayjs"],
|
boot: ["dayjs", "resizeObserverGuard"],
|
||||||
/* =====================================================
|
/* =====================================================
|
||||||
GLOBAL CSS
|
GLOBAL CSS
|
||||||
===================================================== */
|
===================================================== */
|
||||||
@@ -62,6 +62,13 @@ var quasar_config_default = defineConfig(() => {
|
|||||||
server: { type: "http" },
|
server: { type: "http" },
|
||||||
port: 9e3,
|
port: 9e3,
|
||||||
open: true,
|
open: true,
|
||||||
|
client: {
|
||||||
|
overlay: {
|
||||||
|
errors: true,
|
||||||
|
warnings: false,
|
||||||
|
runtimeErrors: false
|
||||||
|
}
|
||||||
|
},
|
||||||
// DEV proxy (CORS'suz)
|
// DEV proxy (CORS'suz)
|
||||||
proxy: [
|
proxy: [
|
||||||
{
|
{
|
||||||
36
ui/src/boot/resizeObserverGuard.js
Normal file
36
ui/src/boot/resizeObserverGuard.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
export default () => {
|
||||||
|
if (process.env.PROD || typeof window === 'undefined') return
|
||||||
|
|
||||||
|
const isResizeObserverOverlayError = (message) => {
|
||||||
|
const text = String(message || '')
|
||||||
|
return (
|
||||||
|
text.includes('ResizeObserver loop completed with undelivered notifications') ||
|
||||||
|
text.includes('ResizeObserver loop limit exceeded')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener(
|
||||||
|
'error',
|
||||||
|
(event) => {
|
||||||
|
if (!isResizeObserverOverlayError(event?.message)) return
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopImmediatePropagation()
|
||||||
|
},
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
window.addEventListener(
|
||||||
|
'unhandledrejection',
|
||||||
|
(event) => {
|
||||||
|
const reason = event?.reason
|
||||||
|
const msg =
|
||||||
|
typeof reason === 'string'
|
||||||
|
? reason
|
||||||
|
: (reason?.message || reason?.toString?.() || '')
|
||||||
|
if (!isResizeObserverOverlayError(msg)) return
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopImmediatePropagation()
|
||||||
|
},
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -278,8 +278,8 @@
|
|||||||
color="primary"
|
color="primary"
|
||||||
icon="save"
|
icon="save"
|
||||||
class="q-ml-sm"
|
class="q-ml-sm"
|
||||||
:loading="orderStore.loading"
|
:loading="orderStore.loading || isSubmitAllInFlight"
|
||||||
:disable="!canSubmitOrder"
|
:disable="!canSubmitOrder || orderStore.loading || isSubmitAllInFlight"
|
||||||
@click="confirmAndSubmit"
|
@click="confirmAndSubmit"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -773,16 +773,18 @@
|
|||||||
v-if="canMutateRows"
|
v-if="canMutateRows"
|
||||||
:color="isEditing ? 'positive' : 'primary'"
|
:color="isEditing ? 'positive' : 'primary'"
|
||||||
:label="isEditing ? 'Güncelle' : 'Kaydet'"
|
:label="isEditing ? 'Güncelle' : 'Kaydet'"
|
||||||
|
:loading="isRowSaveInFlight"
|
||||||
@click="onSaveOrUpdateRow"
|
@click="onSaveOrUpdateRow"
|
||||||
:disable="isClosedRow || isViewOnly || !canMutateRows"
|
:disable="isClosedRow || isViewOnly || !canMutateRows || isRowSaveInFlight"
|
||||||
|
|
||||||
/>
|
/>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="canMutateRows"
|
v-if="canMutateRows"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
label="Kaydet ve Diğer Renge Geç"
|
label="Kaydet ve Diğer Renge Geç"
|
||||||
|
:loading="isRowSaveInFlight"
|
||||||
@click="onSaveAndNextColor"
|
@click="onSaveAndNextColor"
|
||||||
:disable="isClosedRow || isViewOnly || !canMutateRows"
|
:disable="isClosedRow || isViewOnly || !canMutateRows || isRowSaveInFlight"
|
||||||
/>
|
/>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="isEditing && canMutateRows"
|
v-if="isEditing && canMutateRows"
|
||||||
@@ -930,8 +932,60 @@ const aktifPB = ref('USD') // Varsayılan para birimi (Cari seç
|
|||||||
const productCache = reactive({})
|
const productCache = reactive({})
|
||||||
const showBulkDueDateDialog = ref(false)
|
const showBulkDueDateDialog = ref(false)
|
||||||
const bulkDueDateValue = ref('')
|
const bulkDueDateValue = ref('')
|
||||||
|
const isSubmitAllInFlight = ref(false)
|
||||||
|
const isRowSaveInFlight = ref(false)
|
||||||
|
|
||||||
|
function showEditorQtyPriceBlockingDialog(message, details = '') {
|
||||||
|
const detailHtml = details ? `<br><br><b>Detay:</b><br>${details}` : ''
|
||||||
|
$q.dialog({
|
||||||
|
title: 'Kayit Engellendi',
|
||||||
|
message: `${message}${detailHtml}`,
|
||||||
|
html: true,
|
||||||
|
ok: { label: 'Tamam', color: 'negative' }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateEditorRowBeforeSave() {
|
||||||
|
const adet = Number(form.adet || 0)
|
||||||
|
const fiyatRaw = String(form.fiyat ?? '').trim()
|
||||||
|
const fiyat = Number(form.fiyat || 0)
|
||||||
|
|
||||||
|
if (adet <= 0) {
|
||||||
|
showEditorQtyPriceBlockingDialog('Siparis adeti toplam 0 olamaz.')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!fiyatRaw || !Number.isFinite(fiyat) || fiyat <= 0) {
|
||||||
|
showEditorQtyPriceBlockingDialog('Urun fiyati girmeden ilerleyemezsiniz.')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateSummaryRowsBeforeSubmit() {
|
||||||
|
const rows = Array.isArray(orderStore.summaryRows) ? orderStore.summaryRows : []
|
||||||
|
const invalidRows = rows.filter(r => {
|
||||||
|
const adet = Number(r?.adet || 0)
|
||||||
|
const fiyatRaw = String(r?.fiyat ?? '').trim()
|
||||||
|
const fiyat = Number(r?.fiyat || 0)
|
||||||
|
return adet <= 0 || !fiyatRaw || !Number.isFinite(fiyat) || fiyat <= 0
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!invalidRows.length) return true
|
||||||
|
|
||||||
|
const preview = invalidRows
|
||||||
|
.slice(0, 8)
|
||||||
|
.map(r => `${String(r?.model || '').trim() || '-'} / ${String(r?.renk || '').trim() || '-'} (adet=${Number(r?.adet || 0)}, fiyat=${String(r?.fiyat ?? '')})`)
|
||||||
|
.join('<br>')
|
||||||
|
|
||||||
|
showEditorQtyPriceBlockingDialog(
|
||||||
|
'Urun fiyati girmeden ilerleyemezsiniz.',
|
||||||
|
preview
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
const confirmAndSubmit = async () => {
|
const confirmAndSubmit = async () => {
|
||||||
if (orderStore.loading) return
|
if (orderStore.loading || isSubmitAllInFlight.value) return
|
||||||
|
|
||||||
if (!hasSubmitPermission()) {
|
if (!hasSubmitPermission()) {
|
||||||
notifyNoPermission(
|
notifyNoPermission(
|
||||||
@@ -951,6 +1005,11 @@ const confirmAndSubmit = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!validateSummaryRowsBeforeSubmit()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isSubmitAllInFlight.value = true
|
||||||
try {
|
try {
|
||||||
// NEW veya EDIT ayrımı store.mode üzerinden
|
// NEW veya EDIT ayrımı store.mode üzerinden
|
||||||
await orderStore.submitAllReal(
|
await orderStore.submitAllReal(
|
||||||
@@ -962,6 +1021,8 @@ const confirmAndSubmit = async () => {
|
|||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('❌ confirmAndSubmit hata:', err)
|
console.error('❌ confirmAndSubmit hata:', err)
|
||||||
|
} finally {
|
||||||
|
isSubmitAllInFlight.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3077,6 +3138,8 @@ function warnIfSecondColorMissing() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onSaveOrUpdateRow = async () => {
|
const onSaveOrUpdateRow = async () => {
|
||||||
|
if (isRowSaveInFlight.value) return
|
||||||
|
|
||||||
if (!hasRowMutationPermission()) {
|
if (!hasRowMutationPermission()) {
|
||||||
notifyNoPermission(
|
notifyNoPermission(
|
||||||
isEditMode.value
|
isEditMode.value
|
||||||
@@ -3086,23 +3149,32 @@ const onSaveOrUpdateRow = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!validateEditorRowBeforeSave()) return
|
||||||
|
|
||||||
warnIfSecondColorMissing()
|
warnIfSecondColorMissing()
|
||||||
|
|
||||||
await orderStore.saveOrUpdateRowUnified({
|
isRowSaveInFlight.value = true
|
||||||
form,
|
try {
|
||||||
|
const ok = await orderStore.saveOrUpdateRowUnified({
|
||||||
|
form,
|
||||||
|
|
||||||
recalcVat: typeof recalcVat === 'function' ? recalcVat : null,
|
recalcVat: typeof recalcVat === 'function' ? recalcVat : null,
|
||||||
resetEditor: typeof resetEditor === 'function' ? resetEditor : null,
|
resetEditor: typeof resetEditor === 'function' ? resetEditor : null,
|
||||||
loadProductSizes: async () => {
|
loadProductSizes: async () => {
|
||||||
await orderStore.loadProductSizes(form, true, $q, productCache)
|
await orderStore.loadProductSizes(form, true, $q, productCache)
|
||||||
await loadOrderInventory(true)
|
await loadOrderInventory(true)
|
||||||
},
|
},
|
||||||
|
|
||||||
// gerekiyorsa pass edebilirsin (store tarafında zaten optional)
|
// gerekiyorsa pass edebilirsin (store tarafında zaten optional)
|
||||||
stockMap,
|
stockMap,
|
||||||
$q
|
$q
|
||||||
})
|
})
|
||||||
showEditor.value = false
|
if (ok !== false) {
|
||||||
|
showEditor.value = false
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
isRowSaveInFlight.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeColorValue(val) {
|
function normalizeColorValue(val) {
|
||||||
@@ -3122,6 +3194,8 @@ function getNextColorValue() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onSaveAndNextColor = async () => {
|
const onSaveAndNextColor = async () => {
|
||||||
|
if (isRowSaveInFlight.value) return
|
||||||
|
|
||||||
if (!hasRowMutationPermission()) {
|
if (!hasRowMutationPermission()) {
|
||||||
notifyNoPermission(
|
notifyNoPermission(
|
||||||
isEditMode.value
|
isEditMode.value
|
||||||
@@ -3141,19 +3215,27 @@ const onSaveAndNextColor = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!validateEditorRowBeforeSave()) return
|
||||||
|
|
||||||
warnIfSecondColorMissing()
|
warnIfSecondColorMissing()
|
||||||
|
|
||||||
const ok = await orderStore.saveOrUpdateRowUnified({
|
isRowSaveInFlight.value = true
|
||||||
form,
|
let ok = false
|
||||||
recalcVat: typeof recalcVat === 'function' ? recalcVat : null,
|
try {
|
||||||
resetEditor: () => {},
|
ok = await orderStore.saveOrUpdateRowUnified({
|
||||||
loadProductSizes: async () => {
|
form,
|
||||||
await orderStore.loadProductSizes(form, true, $q, productCache)
|
recalcVat: typeof recalcVat === 'function' ? recalcVat : null,
|
||||||
await loadOrderInventory(true)
|
resetEditor: () => {},
|
||||||
},
|
loadProductSizes: async () => {
|
||||||
stockMap,
|
await orderStore.loadProductSizes(form, true, $q, productCache)
|
||||||
$q
|
await loadOrderInventory(true)
|
||||||
})
|
},
|
||||||
|
stockMap,
|
||||||
|
$q
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
isRowSaveInFlight.value = false
|
||||||
|
}
|
||||||
|
|
||||||
if (!ok) return
|
if (!ok) return
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
icon="save"
|
icon="save"
|
||||||
label="Secili Degisiklikleri Kaydet"
|
label="Secili Degisiklikleri Kaydet"
|
||||||
:loading="store.saving"
|
:loading="store.saving"
|
||||||
:disable="store.loading"
|
:disable="store.loading || store.saving || isBulkSubmitting"
|
||||||
@click="onBulkSubmit"
|
@click="onBulkSubmit"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -391,7 +391,7 @@
|
|||||||
|
|
||||||
<q-card-actions align="right">
|
<q-card-actions align="right">
|
||||||
<q-btn flat label="Vazgec" color="grey-8" v-close-popup />
|
<q-btn flat label="Vazgec" color="grey-8" v-close-popup />
|
||||||
<q-btn color="primary" label="Ozellikleri Kaydet" @click="saveAttributeDraft" />
|
<q-btn color="primary" label="Ozellikleri Taslaga Kaydet" @click="saveAttributeDraft" />
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
@@ -441,10 +441,12 @@ const headerAverageDueDate = ref('')
|
|||||||
const cdItemDialogOpen = ref(false)
|
const cdItemDialogOpen = ref(false)
|
||||||
const cdItemTargetCode = ref('')
|
const cdItemTargetCode = ref('')
|
||||||
const copySourceCode = ref(null)
|
const copySourceCode = ref(null)
|
||||||
|
const suppressAutoSetupDialogs = ref(false)
|
||||||
const cdItemDraftForm = ref(createEmptyCdItemDraft(''))
|
const cdItemDraftForm = ref(createEmptyCdItemDraft(''))
|
||||||
const attributeDialogOpen = ref(false)
|
const attributeDialogOpen = ref(false)
|
||||||
const attributeTargetCode = ref('')
|
const attributeTargetCode = ref('')
|
||||||
const attributeRows = ref([])
|
const attributeRows = ref([])
|
||||||
|
const isBulkSubmitting = ref(false)
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'select', label: '', field: 'select', align: 'center', sortable: false, style: 'width:44px;', headerStyle: 'width:44px;' },
|
{ name: 'select', label: '', field: 'select', align: 'center', sortable: false, style: 'width:44px;', headerStyle: 'width:44px;' },
|
||||||
@@ -669,13 +671,14 @@ function onNewItemChange (row, val, source = 'typed') {
|
|||||||
row.NewColor = ''
|
row.NewColor = ''
|
||||||
row.NewDim2 = ''
|
row.NewDim2 = ''
|
||||||
row.NewDesc = mergeDescWithAutoNote(row, row.NewDesc || row.OldDesc)
|
row.NewDesc = mergeDescWithAutoNote(row, row.NewDesc || row.OldDesc)
|
||||||
if (row.NewItemCode) {
|
if (row.NewItemCode && isValidBaggiModelCode(row.NewItemCode)) {
|
||||||
if (row.NewItemMode === 'new') {
|
if (row.NewItemMode === 'new') {
|
||||||
store.fetchNewColors(row.NewItemCode)
|
store.fetchNewColors(row.NewItemCode)
|
||||||
} else {
|
} else {
|
||||||
store.fetchColors(row.NewItemCode)
|
store.fetchColors(row.NewItemCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (suppressAutoSetupDialogs.value) return
|
||||||
if (row.NewItemMode === 'new' && isValidBaggiModelCode(row.NewItemCode) && row.NewItemCode !== prevCode) {
|
if (row.NewItemMode === 'new' && isValidBaggiModelCode(row.NewItemCode) && row.NewItemCode !== prevCode) {
|
||||||
openNewCodeSetupFlow(row.NewItemCode)
|
openNewCodeSetupFlow(row.NewItemCode)
|
||||||
} else if (row.NewItemMode === 'existing' && isValidBaggiModelCode(row.NewItemCode) && row.NewItemCode !== prevCode) {
|
} else if (row.NewItemMode === 'existing' && isValidBaggiModelCode(row.NewItemCode) && row.NewItemCode !== prevCode) {
|
||||||
@@ -902,6 +905,59 @@ function collectLinesFromRows (selectedRows) {
|
|||||||
return { errMsg: '', lines }
|
return { errMsg: '', lines }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasRowChange (row) {
|
||||||
|
const newItemCode = String(row?.NewItemCode || '').trim().toUpperCase()
|
||||||
|
const newColor = normalizeShortCode(row?.NewColor, 3)
|
||||||
|
const newDim2 = normalizeShortCode(row?.NewDim2, 3)
|
||||||
|
const newDesc = mergeDescWithAutoNote(row, row?.NewDesc || row?.OldDesc)
|
||||||
|
const oldItemCode = String(row?.OldItemCode || '').trim().toUpperCase()
|
||||||
|
const oldColor = normalizeShortCode(row?.OldColor, 3)
|
||||||
|
const oldDim2 = normalizeShortCode(row?.OldDim2, 3)
|
||||||
|
const oldDesc = String(row?.OldDesc || '').trim()
|
||||||
|
const oldDueDateValue = row?.OldDueDate || ''
|
||||||
|
const newDueDateValue = row?.NewDueDate || ''
|
||||||
|
|
||||||
|
return (
|
||||||
|
newItemCode !== oldItemCode ||
|
||||||
|
newColor !== oldColor ||
|
||||||
|
newDim2 !== oldDim2 ||
|
||||||
|
String(newDesc || '').trim() !== oldDesc ||
|
||||||
|
newDueDateValue !== oldDueDateValue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectOptionalColorWarnings (rows) {
|
||||||
|
const warnings = []
|
||||||
|
for (const row of (rows || [])) {
|
||||||
|
const code = String(row?.NewItemCode || '').trim().toUpperCase()
|
||||||
|
if (!code) continue
|
||||||
|
const color = normalizeShortCode(row?.NewColor, 3)
|
||||||
|
const dim2 = normalizeShortCode(row?.NewDim2, 3)
|
||||||
|
if (!color) {
|
||||||
|
warnings.push(`${code} icin renk secmediniz.`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!dim2) {
|
||||||
|
warnings.push(`${code} icin 2. renk bos kalacak.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...new Set(warnings)]
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmOptionalColorWarnings (rows) {
|
||||||
|
const warnings = collectOptionalColorWarnings(rows)
|
||||||
|
if (!warnings.length) return Promise.resolve(true)
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
$q.dialog({
|
||||||
|
title: 'Renk Uyarisi',
|
||||||
|
message: `${warnings.join('<br>')}<br><br>Devam etmek istiyor musunuz?`,
|
||||||
|
html: true,
|
||||||
|
ok: { label: 'Evet, Devam Et', color: 'warning' },
|
||||||
|
cancel: { label: 'Vazgec', flat: true }
|
||||||
|
}).onOk(() => resolve(true)).onCancel(() => resolve(false)).onDismiss(() => resolve(false))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function createEmptyCdItemDraft (itemCode) {
|
function createEmptyCdItemDraft (itemCode) {
|
||||||
return {
|
return {
|
||||||
ItemTypeCode: '1',
|
ItemTypeCode: '1',
|
||||||
@@ -970,13 +1026,15 @@ async function copyFromOldProduct (targetType = 'cdItem') {
|
|||||||
if (targetType === 'cdItem') {
|
if (targetType === 'cdItem') {
|
||||||
const data = await store.fetchCdItemByCode(sourceCode)
|
const data = await store.fetchCdItemByCode(sourceCode)
|
||||||
if (data) {
|
if (data) {
|
||||||
const targetCode = cdItemTargetCode.value
|
const targetCode = String(cdItemTargetCode.value || '').trim().toUpperCase()
|
||||||
const draft = createEmptyCdItemDraft(targetCode)
|
const draft = createEmptyCdItemDraft(targetCode)
|
||||||
for (const k of Object.keys(draft)) {
|
for (const k of Object.keys(draft)) {
|
||||||
if (data[k] !== undefined && data[k] !== null) {
|
if (data[k] !== undefined && data[k] !== null) {
|
||||||
draft[k] = String(data[k])
|
draft[k] = String(data[k])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Source item kopyalansa da hedef popup kodu degismemeli.
|
||||||
|
draft.ItemCode = targetCode
|
||||||
cdItemDraftForm.value = draft
|
cdItemDraftForm.value = draft
|
||||||
persistCdItemDraft()
|
persistCdItemDraft()
|
||||||
$q.notify({ type: 'positive', message: 'Boyutlandirma bilgileri kopyalandi.' })
|
$q.notify({ type: 'positive', message: 'Boyutlandirma bilgileri kopyalandi.' })
|
||||||
@@ -1038,7 +1096,11 @@ async function openCdItemDialog (itemCode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function persistCdItemDraft () {
|
function persistCdItemDraft () {
|
||||||
const payload = normalizeCdItemDraftForPayload(cdItemDraftForm.value)
|
const targetCode = String(cdItemTargetCode.value || '').trim().toUpperCase()
|
||||||
|
const payload = normalizeCdItemDraftForPayload({
|
||||||
|
...(cdItemDraftForm.value || {}),
|
||||||
|
ItemCode: targetCode || String(cdItemDraftForm.value?.ItemCode || '').trim().toUpperCase()
|
||||||
|
})
|
||||||
if (!payload.ItemCode) return null
|
if (!payload.ItemCode) return null
|
||||||
store.setCdItemDraft(payload.ItemCode, payload)
|
store.setCdItemDraft(payload.ItemCode, payload)
|
||||||
return payload
|
return payload
|
||||||
@@ -1183,7 +1245,7 @@ async function openAttributeDialog (itemCode) {
|
|||||||
if (!code) return
|
if (!code) return
|
||||||
copySourceCode.value = null
|
copySourceCode.value = null
|
||||||
attributeTargetCode.value = code
|
attributeTargetCode.value = code
|
||||||
const existingDraft = store.getProductAttributeDraft(code)
|
const existingDraft = JSON.parse(JSON.stringify(store.getProductAttributeDraft(code) || []))
|
||||||
const modeInfo = store.classifyItemCode(code)
|
const modeInfo = store.classifyItemCode(code)
|
||||||
const fetched = await store.fetchProductAttributes(1)
|
const fetched = await store.fetchProductAttributes(1)
|
||||||
const fromLookup = buildAttributeRowsFromLookup(fetched)
|
const fromLookup = buildAttributeRowsFromLookup(fetched)
|
||||||
@@ -1197,6 +1259,32 @@ async function openAttributeDialog (itemCode) {
|
|||||||
$q.notify({ type: 'negative', message: 'Urun ozellikleri listesi alinamadi. Lutfen daha sonra tekrar deneyin.' })
|
$q.notify({ type: 'negative', message: 'Urun ozellikleri listesi alinamadi. Lutfen daha sonra tekrar deneyin.' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draft varsa popup her zaman draft'tan acilir (yeniden acinca secimler kaybolmasin).
|
||||||
|
if (Array.isArray(existingDraft) && existingDraft.length) {
|
||||||
|
attributeRows.value = JSON.parse(JSON.stringify(
|
||||||
|
mergeAttributeDraftWithLookupOptions(existingDraft, fromLookup)
|
||||||
|
))
|
||||||
|
console.info('[OrderProductionUpdate] openAttributeDialog rowsPrepared', {
|
||||||
|
code,
|
||||||
|
mode: modeInfo.mode,
|
||||||
|
useDraft: true,
|
||||||
|
rowCount: Array.isArray(attributeRows.value) ? attributeRows.value.length : 0,
|
||||||
|
optionCounts: (attributeRows.value || []).map(r => ({
|
||||||
|
type: Number(r?.AttributeTypeCodeNumber || 0),
|
||||||
|
options: Array.isArray(r?.Options) ? r.Options.length : 0,
|
||||||
|
allOptions: Array.isArray(r?.AllOptions) ? r.AllOptions.length : 0,
|
||||||
|
selected: String(r?.AttributeCode || '').trim()
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
for (const row of (attributeRows.value || [])) {
|
||||||
|
if (!Array.isArray(row.AllOptions)) row.AllOptions = Array.isArray(row.Options) ? [...row.Options] : []
|
||||||
|
if (!Array.isArray(row.Options)) row.Options = [...row.AllOptions]
|
||||||
|
}
|
||||||
|
attributeDialogOpen.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const dbCurrent = await store.fetchProductItemAttributes(code, 1, true)
|
const dbCurrent = await store.fetchProductItemAttributes(code, 1, true)
|
||||||
console.info('[OrderProductionUpdate] openAttributeDialog dbCurrent', {
|
console.info('[OrderProductionUpdate] openAttributeDialog dbCurrent', {
|
||||||
code,
|
code,
|
||||||
@@ -1232,13 +1320,11 @@ async function openAttributeDialog (itemCode) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const useDraft = Array.isArray(existingDraft) && existingDraft.length
|
const useDraft = Array.isArray(existingDraft) && existingDraft.length
|
||||||
attributeRows.value = useDraft
|
attributeRows.value = JSON.parse(JSON.stringify(baseRows))
|
||||||
? JSON.parse(JSON.stringify(mergeAttributeDraftWithLookupOptions(existingDraft, baseRows)))
|
|
||||||
: JSON.parse(JSON.stringify(baseRows))
|
|
||||||
console.info('[OrderProductionUpdate] openAttributeDialog rowsPrepared', {
|
console.info('[OrderProductionUpdate] openAttributeDialog rowsPrepared', {
|
||||||
code,
|
code,
|
||||||
mode: modeInfo.mode,
|
mode: modeInfo.mode,
|
||||||
useDraft,
|
useDraft: false,
|
||||||
rowCount: Array.isArray(attributeRows.value) ? attributeRows.value.length : 0,
|
rowCount: Array.isArray(attributeRows.value) ? attributeRows.value.length : 0,
|
||||||
optionCounts: (attributeRows.value || []).map(r => ({
|
optionCounts: (attributeRows.value || []).map(r => ({
|
||||||
type: Number(r?.AttributeTypeCodeNumber || 0),
|
type: Number(r?.AttributeTypeCodeNumber || 0),
|
||||||
@@ -1255,27 +1341,26 @@ async function openAttributeDialog (itemCode) {
|
|||||||
row.Options = [...row.AllOptions]
|
row.Options = [...row.AllOptions]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((!existingDraft || !existingDraft.length) && baseRows.length) {
|
|
||||||
store.setProductAttributeDraft(code, JSON.parse(JSON.stringify(baseRows)))
|
|
||||||
}
|
|
||||||
attributeDialogOpen.value = true
|
attributeDialogOpen.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveAttributeDraft () {
|
function saveAttributeDraft () {
|
||||||
const code = String(attributeTargetCode.value || '').trim().toUpperCase()
|
const code = String(attributeTargetCode.value || '').trim().toUpperCase()
|
||||||
if (!code) return
|
if (!code) return
|
||||||
for (const row of (attributeRows.value || [])) {
|
const rows = JSON.parse(JSON.stringify(attributeRows.value || []))
|
||||||
|
for (const row of rows) {
|
||||||
const selected = String(row?.AttributeCode || '').trim()
|
const selected = String(row?.AttributeCode || '').trim()
|
||||||
if (!selected) {
|
if (!selected) {
|
||||||
$q.notify({ type: 'negative', message: `Urun ozelliklerinde secim zorunlu: ${row?.TypeLabel || ''}` })
|
$q.notify({ type: 'negative', message: `Urun ozelliklerinde secim zorunlu: ${row?.TypeLabel || ''}` })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
store.setProductAttributeDraft(code, JSON.parse(JSON.stringify(attributeRows.value || [])))
|
store.setProductAttributeDraft(code, rows)
|
||||||
console.info('[OrderProductionUpdate] saveAttributeDraft', {
|
console.info('[OrderProductionUpdate] saveAttributeDraft', {
|
||||||
code,
|
code,
|
||||||
rowCount: (attributeRows.value || []).length,
|
rowCount: rows.length,
|
||||||
selected: (attributeRows.value || []).map(r => ({
|
selectedCount: rows.length,
|
||||||
|
selected: rows.map(r => ({
|
||||||
type: Number(r?.AttributeTypeCodeNumber || 0),
|
type: Number(r?.AttributeTypeCodeNumber || 0),
|
||||||
code: String(r?.AttributeCode || '').trim()
|
code: String(r?.AttributeCode || '').trim()
|
||||||
}))
|
}))
|
||||||
@@ -1293,17 +1378,6 @@ watch(
|
|||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
|
||||||
attributeRows,
|
|
||||||
(rows) => {
|
|
||||||
if (!attributeDialogOpen.value) return
|
|
||||||
const code = String(attributeTargetCode.value || '').trim().toUpperCase()
|
|
||||||
if (!code) return
|
|
||||||
store.setProductAttributeDraft(code, JSON.parse(JSON.stringify(rows || [])))
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
async function collectProductAttributesFromSelectedRows (selectedRows) {
|
async function collectProductAttributesFromSelectedRows (selectedRows) {
|
||||||
const codeSet = [...new Set(
|
const codeSet = [...new Set(
|
||||||
(selectedRows || [])
|
(selectedRows || [])
|
||||||
@@ -1315,22 +1389,24 @@ async function collectProductAttributesFromSelectedRows (selectedRows) {
|
|||||||
for (const code of codeSet) {
|
for (const code of codeSet) {
|
||||||
const modeInfo = store.classifyItemCode(code)
|
const modeInfo = store.classifyItemCode(code)
|
||||||
let rows = store.getProductAttributeDraft(code)
|
let rows = store.getProductAttributeDraft(code)
|
||||||
let dbMap = new Map()
|
const dbCurrent = await store.fetchProductItemAttributes(code, 1, true)
|
||||||
|
const dbMap = new Map(
|
||||||
|
(dbCurrent || []).map(x => [
|
||||||
|
Number(x?.attribute_type_code || x?.AttributeTypeCode || 0),
|
||||||
|
String(x?.attribute_code || x?.AttributeCode || '').trim()
|
||||||
|
]).filter(x => x[0] > 0)
|
||||||
|
)
|
||||||
|
const hasDbAttributes = dbMap.size > 0
|
||||||
|
const effectiveMode = hasDbAttributes ? 'existing' : modeInfo.mode
|
||||||
console.info('[OrderProductionUpdate] collectProductAttributes start', {
|
console.info('[OrderProductionUpdate] collectProductAttributes start', {
|
||||||
code,
|
code,
|
||||||
mode: modeInfo.mode,
|
mode: modeInfo.mode,
|
||||||
|
effectiveMode,
|
||||||
|
hasDbAttributes,
|
||||||
draftRowCount: Array.isArray(rows) ? rows.length : 0
|
draftRowCount: Array.isArray(rows) ? rows.length : 0
|
||||||
})
|
})
|
||||||
|
|
||||||
if (modeInfo.mode === 'existing') {
|
if (effectiveMode === 'existing') {
|
||||||
const dbCurrent = await store.fetchProductItemAttributes(code, 1, true)
|
|
||||||
dbMap = new Map(
|
|
||||||
(dbCurrent || []).map(x => [
|
|
||||||
Number(x?.attribute_type_code || x?.AttributeTypeCode || 0),
|
|
||||||
String(x?.attribute_code || x?.AttributeCode || '').trim()
|
|
||||||
]).filter(x => x[0] > 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Existing kodda kullanıcı değişiklik yaptıysa draftı koru.
|
// Existing kodda kullanıcı değişiklik yaptıysa draftı koru.
|
||||||
// Draft yoksa DB'den zorunlu/fresh çek.
|
// Draft yoksa DB'den zorunlu/fresh çek.
|
||||||
if (!Array.isArray(rows) || !rows.length) {
|
if (!Array.isArray(rows) || !rows.length) {
|
||||||
@@ -1360,26 +1436,7 @@ async function collectProductAttributesFromSelectedRows (selectedRows) {
|
|||||||
store.setProductAttributeDraft(code, JSON.parse(JSON.stringify(rows)))
|
store.setProductAttributeDraft(code, JSON.parse(JSON.stringify(rows)))
|
||||||
}
|
}
|
||||||
} else if (!Array.isArray(rows) || !rows.length) {
|
} else if (!Array.isArray(rows) || !rows.length) {
|
||||||
const lookup = await store.fetchProductAttributes(1)
|
return { errMsg: `${code} icin urun ozellikleri taslagi kaydedilmedi`, productAttributes: [] }
|
||||||
const baseRows = buildAttributeRowsFromLookup(lookup)
|
|
||||||
const dbCurrent = await store.fetchProductItemAttributes(code, 1, true)
|
|
||||||
const dbMap = new Map(
|
|
||||||
(dbCurrent || []).map(x => [
|
|
||||||
Number(x?.attribute_type_code || x?.AttributeTypeCode || 0),
|
|
||||||
String(x?.attribute_code || x?.AttributeCode || '').trim()
|
|
||||||
]).filter(x => x[0] > 0)
|
|
||||||
)
|
|
||||||
rows = baseRows.map(row => ({
|
|
||||||
...row,
|
|
||||||
AttributeCode: dbMap.get(Number(row.AttributeTypeCodeNumber || 0)) || ''
|
|
||||||
}))
|
|
||||||
console.info('[OrderProductionUpdate] collectProductAttributes new init', {
|
|
||||||
code,
|
|
||||||
lookupCount: Array.isArray(lookup) ? lookup.length : 0,
|
|
||||||
baseRowCount: baseRows.length,
|
|
||||||
dbCurrentCount: Array.isArray(dbCurrent) ? dbCurrent.length : 0
|
|
||||||
})
|
|
||||||
store.setProductAttributeDraft(code, JSON.parse(JSON.stringify(rows)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.isArray(rows) || !rows.length) {
|
if (!Array.isArray(rows) || !rows.length) {
|
||||||
@@ -1393,7 +1450,7 @@ async function collectProductAttributesFromSelectedRows (selectedRows) {
|
|||||||
return { errMsg: `${code} icin urun ozellikleri eksik`, productAttributes: [] }
|
return { errMsg: `${code} icin urun ozellikleri eksik`, productAttributes: [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modeInfo.mode === 'existing') {
|
if (effectiveMode === 'existing') {
|
||||||
const originalCode =
|
const originalCode =
|
||||||
dbMap.get(attributeTypeCode) ||
|
dbMap.get(attributeTypeCode) ||
|
||||||
String(row?.OriginalAttributeCode || '').trim()
|
String(row?.OriginalAttributeCode || '').trim()
|
||||||
@@ -1416,6 +1473,7 @@ async function collectProductAttributesFromSelectedRows (selectedRows) {
|
|||||||
console.info('[OrderProductionUpdate] collectProductAttributes done', {
|
console.info('[OrderProductionUpdate] collectProductAttributes done', {
|
||||||
code,
|
code,
|
||||||
mode: modeInfo.mode,
|
mode: modeInfo.mode,
|
||||||
|
effectiveMode,
|
||||||
outCount: out.filter(x => x.ItemCode === code).length,
|
outCount: out.filter(x => x.ItemCode === code).length,
|
||||||
rowCount: rows.length,
|
rowCount: rows.length,
|
||||||
optionCounts: rows.map(r => ({
|
optionCounts: rows.map(r => ({
|
||||||
@@ -1744,60 +1802,77 @@ async function refreshAll () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function onBulkSubmit () {
|
async function onBulkSubmit () {
|
||||||
|
if (isBulkSubmitting.value || store.saving) {
|
||||||
|
console.info('[OrderProductionUpdate] onBulkSubmit ignored (already running)', {
|
||||||
|
orderHeaderID: orderHeaderID.value,
|
||||||
|
isBulkSubmitting: isBulkSubmitting.value,
|
||||||
|
storeSaving: store.saving
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isBulkSubmitting.value = true
|
||||||
const flowStart = nowMs()
|
const flowStart = nowMs()
|
||||||
const selectedRows = rows.value.filter(r => !!selectedMap.value[r.RowKey])
|
|
||||||
const headerAverageDueDateValue = normalizeDateInput(headerAverageDueDate.value)
|
|
||||||
const headerDateChanged = hasHeaderAverageDueDateChange.value
|
|
||||||
if (!selectedRows.length && !headerDateChanged) {
|
|
||||||
$q.notify({ type: 'warning', message: 'Lutfen en az bir satir seciniz veya ustteki termin tarihini degistiriniz.' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const prepStart = nowMs()
|
|
||||||
const { errMsg, lines } = collectLinesFromRows(selectedRows)
|
|
||||||
if (errMsg) {
|
|
||||||
$q.notify({ type: 'negative', message: errMsg })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!lines.length && !headerDateChanged) {
|
|
||||||
$q.notify({ type: 'warning', message: 'Secili satirlarda degisiklik yok.' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let cdItems = []
|
|
||||||
let productAttributes = []
|
|
||||||
if (lines.length > 0) {
|
|
||||||
const { errMsg: cdErrMsg, cdItems: nextCdItems } = await collectCdItemsFromSelectedRows(selectedRows)
|
|
||||||
if (cdErrMsg) {
|
|
||||||
$q.notify({ type: 'negative', message: cdErrMsg })
|
|
||||||
const firstCode = String(cdErrMsg.split(' ')[0] || '').trim()
|
|
||||||
if (firstCode) openCdItemDialog(firstCode)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cdItems = nextCdItems
|
|
||||||
|
|
||||||
const { errMsg: attrErrMsg, productAttributes: nextProductAttributes } = await collectProductAttributesFromSelectedRows(selectedRows)
|
|
||||||
if (attrErrMsg) {
|
|
||||||
$q.notify({ type: 'negative', message: attrErrMsg })
|
|
||||||
const firstCode = String(attrErrMsg.split(' ')[0] || '').trim()
|
|
||||||
if (firstCode) openAttributeDialog(firstCode)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
productAttributes = nextProductAttributes
|
|
||||||
}
|
|
||||||
|
|
||||||
console.info('[OrderProductionUpdate] onBulkSubmit prepared', {
|
|
||||||
orderHeaderID: orderHeaderID.value,
|
|
||||||
selectedRowCount: selectedRows.length,
|
|
||||||
lineCount: lines.length,
|
|
||||||
cdItemCount: cdItems.length,
|
|
||||||
attributeCount: productAttributes.length,
|
|
||||||
headerAverageDueDate: headerAverageDueDateValue,
|
|
||||||
headerDateChanged,
|
|
||||||
prepDurationMs: Math.round(nowMs() - prepStart)
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
suppressAutoSetupDialogs.value = true
|
||||||
|
const selectedRows = rows.value.filter(r => !!selectedMap.value[r.RowKey])
|
||||||
|
const headerAverageDueDateValue = normalizeDateInput(headerAverageDueDate.value)
|
||||||
|
const headerDateChanged = hasHeaderAverageDueDateChange.value
|
||||||
|
if (!selectedRows.length && !headerDateChanged) {
|
||||||
|
$q.notify({ type: 'warning', message: 'Lutfen en az bir satir seciniz veya ustteki termin tarihini degistiriniz.' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const prepStart = nowMs()
|
||||||
|
const { errMsg, lines } = collectLinesFromRows(selectedRows)
|
||||||
|
if (errMsg) {
|
||||||
|
$q.notify({ type: 'negative', message: errMsg })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!lines.length && !headerDateChanged) {
|
||||||
|
$q.notify({ type: 'warning', message: 'Secili satirlarda degisiklik yok.' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lines.length > 0) {
|
||||||
|
const changedRows = selectedRows.filter(hasRowChange)
|
||||||
|
const confirmed = await confirmOptionalColorWarnings(changedRows)
|
||||||
|
if (!confirmed) return
|
||||||
|
}
|
||||||
|
|
||||||
|
let cdItems = []
|
||||||
|
let productAttributes = []
|
||||||
|
if (lines.length > 0) {
|
||||||
|
const { errMsg: cdErrMsg, cdItems: nextCdItems } = await collectCdItemsFromSelectedRows(selectedRows)
|
||||||
|
if (cdErrMsg) {
|
||||||
|
$q.notify({ type: 'negative', message: cdErrMsg })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cdItems = nextCdItems
|
||||||
|
|
||||||
|
const { errMsg: attrErrMsg, productAttributes: nextProductAttributes } = await collectProductAttributesFromSelectedRows(selectedRows)
|
||||||
|
if (attrErrMsg) {
|
||||||
|
$q.notify({ type: 'negative', message: attrErrMsg })
|
||||||
|
const firstCode = String(attrErrMsg.split(' ')[0] || '').trim().toUpperCase()
|
||||||
|
if (isValidBaggiModelCode(firstCode)) {
|
||||||
|
await openAttributeDialog(firstCode)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
productAttributes = nextProductAttributes
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info('[OrderProductionUpdate] onBulkSubmit prepared', {
|
||||||
|
orderHeaderID: orderHeaderID.value,
|
||||||
|
selectedRowCount: selectedRows.length,
|
||||||
|
lineCount: lines.length,
|
||||||
|
cdItemCount: cdItems.length,
|
||||||
|
attributeCount: productAttributes.length,
|
||||||
|
headerAverageDueDate: headerAverageDueDateValue,
|
||||||
|
headerDateChanged,
|
||||||
|
prepDurationMs: Math.round(nowMs() - prepStart)
|
||||||
|
})
|
||||||
|
|
||||||
const applyChanges = async (insertMissing) => {
|
const applyChanges = async (insertMissing) => {
|
||||||
const applyStart = nowMs()
|
const applyStart = nowMs()
|
||||||
const applyResult = await store.applyUpdates(
|
const applyResult = await store.applyUpdates(
|
||||||
@@ -1831,7 +1906,7 @@ async function onBulkSubmit () {
|
|||||||
|
|
||||||
if (lines.length > 0) {
|
if (lines.length > 0) {
|
||||||
const validateStart = nowMs()
|
const validateStart = nowMs()
|
||||||
const validate = await store.validateUpdates(orderHeaderID.value, lines)
|
const validate = await store.validateUpdates(orderHeaderID.value, lines, cdItems)
|
||||||
console.info('[OrderProductionUpdate] validate finished', {
|
console.info('[OrderProductionUpdate] validate finished', {
|
||||||
orderHeaderID: orderHeaderID.value,
|
orderHeaderID: orderHeaderID.value,
|
||||||
lineCount: lines.length,
|
lineCount: lines.length,
|
||||||
@@ -1875,11 +1950,14 @@ async function onBulkSubmit () {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
$q.notify({ type: 'negative', message: store.error || 'Toplu kayit islemi basarisiz.' })
|
$q.notify({ type: 'negative', message: store.error || 'Toplu kayit islemi basarisiz.' })
|
||||||
|
} finally {
|
||||||
|
isBulkSubmitting.value = false
|
||||||
|
suppressAutoSetupDialogs.value = false
|
||||||
|
console.info('[OrderProductionUpdate] onBulkSubmit total', {
|
||||||
|
orderHeaderID: orderHeaderID.value,
|
||||||
|
durationMs: Math.round(nowMs() - flowStart)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
console.info('[OrderProductionUpdate] onBulkSubmit total', {
|
|
||||||
orderHeaderID: orderHeaderID.value,
|
|
||||||
durationMs: Math.round(nowMs() - flowStart)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ function nowMs () {
|
|||||||
return Date.now()
|
return Date.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const applyInFlightByOrder = new Map()
|
||||||
|
|
||||||
const YAS_NUMERIC_SIZES = new Set(['2', '4', '6', '8', '10', '12', '14'])
|
const YAS_NUMERIC_SIZES = new Set(['2', '4', '6', '8', '10', '12', '14'])
|
||||||
|
|
||||||
function safeStr (value) {
|
function safeStr (value) {
|
||||||
@@ -423,7 +425,7 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
|
|||||||
if (!code) return []
|
if (!code) return []
|
||||||
return this.productAttributeDraftsByCode[code] || []
|
return this.productAttributeDraftsByCode[code] || []
|
||||||
},
|
},
|
||||||
async validateUpdates (orderHeaderID, lines) {
|
async validateUpdates (orderHeaderID, lines, cdItems = []) {
|
||||||
if (!orderHeaderID) return { missingCount: 0, missing: [] }
|
if (!orderHeaderID) return { missingCount: 0, missing: [] }
|
||||||
|
|
||||||
this.saving = true
|
this.saving = true
|
||||||
@@ -434,7 +436,7 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
|
|||||||
console.info('[OrderProductionItemStore] validateUpdates start', { orderHeaderID, lineCount: lines?.length || 0 })
|
console.info('[OrderProductionItemStore] validateUpdates start', { orderHeaderID, lineCount: lines?.length || 0 })
|
||||||
const res = await api.post(
|
const res = await api.post(
|
||||||
`/orders/production-items/${encodeURIComponent(orderHeaderID)}/validate`,
|
`/orders/production-items/${encodeURIComponent(orderHeaderID)}/validate`,
|
||||||
{ lines }
|
{ lines, cdItems }
|
||||||
)
|
)
|
||||||
const data = res?.data || { missingCount: 0, missing: [] }
|
const data = res?.data || { missingCount: 0, missing: [] }
|
||||||
const rid = res?.headers?.['x-debug-request-id'] || ''
|
const rid = res?.headers?.['x-debug-request-id'] || ''
|
||||||
@@ -458,48 +460,69 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
|
|||||||
async applyUpdates (orderHeaderID, lines, insertMissing, cdItems = [], productAttributes = [], headerAverageDueDate = null) {
|
async applyUpdates (orderHeaderID, lines, insertMissing, cdItems = [], productAttributes = [], headerAverageDueDate = null) {
|
||||||
if (!orderHeaderID) return { updated: 0, inserted: 0 }
|
if (!orderHeaderID) return { updated: 0, inserted: 0 }
|
||||||
|
|
||||||
this.saving = true
|
const orderKey = String(orderHeaderID).trim().toUpperCase()
|
||||||
this.error = null
|
if (applyInFlightByOrder.has(orderKey)) {
|
||||||
|
console.warn('[OrderProductionItemStore] applyUpdates deduped (in-flight)', {
|
||||||
|
orderHeaderID: orderKey,
|
||||||
|
lineCount: lines?.length || 0
|
||||||
|
})
|
||||||
|
return await applyInFlightByOrder.get(orderKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyPromise = (async () => {
|
||||||
|
this.saving = true
|
||||||
|
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,
|
||||||
|
headerAverageDueDate
|
||||||
|
})
|
||||||
|
const res = await api.post(
|
||||||
|
`/orders/production-items/${encodeURIComponent(orderHeaderID)}/apply`,
|
||||||
|
{
|
||||||
|
lines,
|
||||||
|
insertMissing,
|
||||||
|
cdItems,
|
||||||
|
productAttributes,
|
||||||
|
HeaderAverageDueDate: headerAverageDueDate
|
||||||
|
}
|
||||||
|
)
|
||||||
|
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),
|
||||||
|
barcodeInserted: Number(data?.barcodeInserted || 0),
|
||||||
|
attributeUpserted: Number(data?.attributeUpserted || 0),
|
||||||
|
headerUpdated: !!data?.headerUpdated,
|
||||||
|
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')
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
this.saving = false
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
applyInFlightByOrder.set(orderKey, applyPromise)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const t0 = nowMs()
|
return await applyPromise
|
||||||
console.info('[OrderProductionItemStore] applyUpdates start', {
|
|
||||||
orderHeaderID,
|
|
||||||
lineCount: lines?.length || 0,
|
|
||||||
insertMissing: !!insertMissing,
|
|
||||||
cdItemCount: cdItems?.length || 0,
|
|
||||||
attributeCount: productAttributes?.length || 0,
|
|
||||||
headerAverageDueDate
|
|
||||||
})
|
|
||||||
const res = await api.post(
|
|
||||||
`/orders/production-items/${encodeURIComponent(orderHeaderID)}/apply`,
|
|
||||||
{
|
|
||||||
lines,
|
|
||||||
insertMissing,
|
|
||||||
cdItems,
|
|
||||||
productAttributes,
|
|
||||||
HeaderAverageDueDate: headerAverageDueDate
|
|
||||||
}
|
|
||||||
)
|
|
||||||
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),
|
|
||||||
barcodeInserted: Number(data?.barcodeInserted || 0),
|
|
||||||
attributeUpserted: Number(data?.attributeUpserted || 0),
|
|
||||||
headerUpdated: !!data?.headerUpdated,
|
|
||||||
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')
|
|
||||||
throw err
|
|
||||||
} finally {
|
} finally {
|
||||||
this.saving = false
|
if (applyInFlightByOrder.get(orderKey) === applyPromise) {
|
||||||
|
applyInFlightByOrder.delete(orderKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user