Compare commits

...

2 Commits

Author SHA1 Message Date
M_Kececi
ed80e4f492 Merge remote-tracking branch 'origin/master' 2026-03-31 12:46:48 +03:00
M_Kececi
d7d871fb8a Merge remote-tracking branch 'origin/master' 2026-03-31 12:45:22 +03:00
20 changed files with 1672 additions and 167 deletions

View File

@@ -522,6 +522,7 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
{"/api/order/get/{id}", "GET", "view", routes.GetOrderByIDHandler(mssql)}, {"/api/order/get/{id}", "GET", "view", routes.GetOrderByIDHandler(mssql)},
{"/api/orders/list", "GET", "view", routes.OrderListRoute(mssql)}, {"/api/orders/list", "GET", "view", routes.OrderListRoute(mssql)},
{"/api/orders/production-list", "GET", "update", routes.OrderProductionListRoute(mssql)}, {"/api/orders/production-list", "GET", "update", routes.OrderProductionListRoute(mssql)},
{"/api/orders/production-items/cditem-lookups", "GET", "view", routes.OrderProductionCdItemLookupsRoute(mssql)},
{"/api/orders/production-items/{id}", "GET", "view", routes.OrderProductionItemsRoute(mssql)}, {"/api/orders/production-items/{id}", "GET", "view", routes.OrderProductionItemsRoute(mssql)},
{"/api/orders/production-items/{id}/insert-missing", "POST", "update", routes.OrderProductionInsertMissingRoute(mssql)}, {"/api/orders/production-items/{id}/insert-missing", "POST", "update", routes.OrderProductionInsertMissingRoute(mssql)},
{"/api/orders/production-items/{id}/validate", "POST", "update", routes.OrderProductionValidateRoute(mssql)}, {"/api/orders/production-items/{id}/validate", "POST", "update", routes.OrderProductionValidateRoute(mssql)},
@@ -587,6 +588,11 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
"order", "view", "order", "view",
wrapV3(http.HandlerFunc(routes.GetProductSecondColorsHandler)), wrapV3(http.HandlerFunc(routes.GetProductSecondColorsHandler)),
) )
bindV3(r, pgDB,
"/api/product-attributes", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductAttributesHandler)),
)
bindV3(r, pgDB, bindV3(r, pgDB,
"/api/product-stock-query", "GET", "/api/product-stock-query", "GET",
"order", "view", "order", "view",

View File

@@ -11,6 +11,8 @@ type OrderProductionUpdateLine struct {
type OrderProductionUpdatePayload struct { type OrderProductionUpdatePayload struct {
Lines []OrderProductionUpdateLine `json:"lines"` Lines []OrderProductionUpdateLine `json:"lines"`
InsertMissing bool `json:"insertMissing"` InsertMissing bool `json:"insertMissing"`
CdItems []OrderProductionCdItemDraft `json:"cdItems"`
ProductAttributes []OrderProductionItemAttributeRow `json:"productAttributes"`
} }
type OrderProductionMissingVariant struct { type OrderProductionMissingVariant struct {
@@ -22,3 +24,57 @@ type OrderProductionMissingVariant struct {
ItemDim2Code string `json:"ItemDim2Code"` ItemDim2Code string `json:"ItemDim2Code"`
ItemDim3Code string `json:"ItemDim3Code"` ItemDim3Code string `json:"ItemDim3Code"`
} }
type OrderProductionCdItemDraft struct {
ItemTypeCode int16 `json:"ItemTypeCode"`
ItemCode string `json:"ItemCode"`
ItemDimTypeCode *int16 `json:"ItemDimTypeCode"`
ProductTypeCode *int16 `json:"ProductTypeCode"`
ProductHierarchyID *int `json:"ProductHierarchyID"`
UnitOfMeasureCode1 *string `json:"UnitOfMeasureCode1"`
ItemAccountGrCode *string `json:"ItemAccountGrCode"`
ItemTaxGrCode *string `json:"ItemTaxGrCode"`
ItemPaymentPlanGrCode *string `json:"ItemPaymentPlanGrCode"`
ItemDiscountGrCode *string `json:"ItemDiscountGrCode"`
ItemVendorGrCode *string `json:"ItemVendorGrCode"`
PromotionGroupCode *string `json:"PromotionGroupCode"`
ProductCollectionGrCode *string `json:"ProductCollectionGrCode"`
StorePriceLevelCode *string `json:"StorePriceLevelCode"`
PerceptionOfFashionCode *string `json:"PerceptionOfFashionCode"`
CommercialRoleCode *string `json:"CommercialRoleCode"`
StoreCapacityLevelCode *string `json:"StoreCapacityLevelCode"`
CustomsTariffNumberCode *string `json:"CustomsTariffNumberCode"`
CompanyCode *string `json:"CompanyCode"`
}
type OrderProductionLookupOption struct {
Code string `json:"code"`
Description string `json:"description"`
}
type OrderProductionItemAttributeRow struct {
ItemTypeCode int16 `json:"ItemTypeCode"`
ItemCode string `json:"ItemCode"`
AttributeTypeCode int `json:"AttributeTypeCode"`
AttributeCode string `json:"AttributeCode"`
}
type OrderProductionCdItemLookups struct {
ItemDimTypeCodes []OrderProductionLookupOption `json:"itemDimTypeCodes"`
ProductTypeCodes []OrderProductionLookupOption `json:"productTypeCodes"`
ProductHierarchyIDs []OrderProductionLookupOption `json:"productHierarchyIDs"`
UnitOfMeasureCode1List []OrderProductionLookupOption `json:"unitOfMeasureCode1List"`
ItemAccountGrCodes []OrderProductionLookupOption `json:"itemAccountGrCodes"`
ItemTaxGrCodes []OrderProductionLookupOption `json:"itemTaxGrCodes"`
ItemPaymentPlanGrCodes []OrderProductionLookupOption `json:"itemPaymentPlanGrCodes"`
ItemDiscountGrCodes []OrderProductionLookupOption `json:"itemDiscountGrCodes"`
ItemVendorGrCodes []OrderProductionLookupOption `json:"itemVendorGrCodes"`
PromotionGroupCodes []OrderProductionLookupOption `json:"promotionGroupCodes"`
ProductCollectionGrCodes []OrderProductionLookupOption `json:"productCollectionGrCodes"`
StorePriceLevelCodes []OrderProductionLookupOption `json:"storePriceLevelCodes"`
PerceptionOfFashionCodes []OrderProductionLookupOption `json:"perceptionOfFashionCodes"`
CommercialRoleCodes []OrderProductionLookupOption `json:"commercialRoleCodes"`
StoreCapacityLevelCodes []OrderProductionLookupOption `json:"storeCapacityLevelCodes"`
CustomsTariffNumbers []OrderProductionLookupOption `json:"customsTariffNumbers"`
CompanyCodes []OrderProductionLookupOption `json:"companyCodes"`
}

View File

@@ -0,0 +1,9 @@
package models
type ProductAttributeOption struct {
ItemTypeCode int16 `json:"item_type_code"`
AttributeTypeCode int `json:"attribute_type_code"`
AttributeTypeDescription string `json:"attribute_type_description"`
AttributeCode string `json:"attribute_code"`
AttributeDescription string `json:"attribute_description"`
}

View File

@@ -4,4 +4,5 @@ type ProductSecondColor struct {
ProductCode string `json:"product_code"` ProductCode string `json:"product_code"`
ColorCode string `json:"color_code"` ColorCode string `json:"color_code"`
ItemDim2Code string `json:"item_dim2_code"` ItemDim2Code string `json:"item_dim2_code"`
ColorDescription string `json:"color_description"`
} }

View File

@@ -3,6 +3,7 @@ package queries
import ( import (
"database/sql" "database/sql"
"strconv" "strconv"
"strings"
"bssapp-backend/models" "bssapp-backend/models"
) )
@@ -74,10 +75,17 @@ INSERT INTO dbo.prItemVariant (
ItemDim2Code, ItemDim2Code,
ItemDim3Code, ItemDim3Code,
PLU, PLU,
IsSalesOrderClosed,
IsPurchaseOrderClosed,
IsLocked,
IsBlocked,
CreatedUserName, CreatedUserName,
CreatedDate, CreatedDate,
LastUpdatedUserName, LastUpdatedUserName,
LastUpdatedDate LastUpdatedDate,
RowGuid,
UseInternet,
IsStoreOrderClosed
) )
SELECT SELECT
m.ItemTypeCode, m.ItemTypeCode,
@@ -87,10 +95,17 @@ SELECT
m.ItemDim2Code, m.ItemDim2Code,
m.ItemDim3Code, m.ItemDim3Code,
mp.BasePlu + ROW_NUMBER() OVER (ORDER BY m.ItemCode, m.ColorCode, m.ItemDim1Code, m.ItemDim2Code, m.ItemDim3Code), mp.BasePlu + ROW_NUMBER() OVER (ORDER BY m.ItemCode, m.ColorCode, m.ItemDim1Code, m.ItemDim2Code, m.ItemDim3Code),
0,
0,
0,
0,
@p2, @p2,
GETDATE(), GETDATE(),
@p2, @p2,
GETDATE() GETDATE(),
NEWID(),
0,
0
FROM Missing m FROM Missing m
CROSS JOIN MaxPlu mp; CROSS JOIN MaxPlu mp;
` `
@@ -143,7 +158,12 @@ WHERE ItemTypeCode = @p1
return true, nil return true, nil
} }
func InsertMissingVariantsTx(tx *sql.Tx, missing []models.OrderProductionMissingVariant, username string) (int64, error) { func InsertMissingVariantsTx(
tx *sql.Tx,
missing []models.OrderProductionMissingVariant,
username string,
cdItemByCode map[string]models.OrderProductionCdItemDraft,
) (int64, error) {
if len(missing) == 0 { if len(missing) == 0 {
return 0, nil return 0, nil
} }
@@ -161,7 +181,16 @@ FROM dbo.prItemVariant WITH (UPDLOCK, HOLDLOCK)
for i, v := range missing { for i, v := range missing {
itemKey := strconv.FormatInt(int64(v.ItemTypeCode), 10) + "|" + v.ItemCode itemKey := strconv.FormatInt(int64(v.ItemTypeCode), 10) + "|" + v.ItemCode
if _, ok := ensuredItems[itemKey]; !ok { if _, ok := ensuredItems[itemKey]; !ok {
if err := ensureCdItemTx(tx, v.ItemTypeCode, v.ItemCode, username); err != nil { draft, hasDraft := cdItemByCode[itemKey]
if !hasDraft {
draft, hasDraft = cdItemByCode[NormalizeCdItemMapKey(v.ItemTypeCode, v.ItemCode)]
}
var draftPtr *models.OrderProductionCdItemDraft
if hasDraft {
tmp := draft
draftPtr = &tmp
}
if err := ensureCdItemTx(tx, v.ItemTypeCode, v.ItemCode, username, draftPtr); err != nil {
return inserted, err return inserted, err
} }
ensuredItems[itemKey] = struct{}{} ensuredItems[itemKey] = struct{}{}
@@ -187,14 +216,26 @@ INSERT INTO dbo.prItemVariant (
ItemDim2Code, ItemDim2Code,
ItemDim3Code, ItemDim3Code,
PLU, PLU,
IsSalesOrderClosed,
IsPurchaseOrderClosed,
IsLocked,
IsBlocked,
CreatedUserName, CreatedUserName,
CreatedDate, CreatedDate,
LastUpdatedUserName, LastUpdatedUserName,
LastUpdatedDate LastUpdatedDate,
RowGuid,
UseInternet,
IsStoreOrderClosed
) )
VALUES ( VALUES (
@p1, @p2, @p3, @p4, @p5, @p6, @p1, @p2, @p3, @p4, @p5, @p6,
@p7, @p8, GETDATE(), @p8, GETDATE() @p7,
0, 0, 0, 0,
@p8, GETDATE(), @p8, GETDATE(),
NEWID(),
0,
0
); );
`, v.ItemTypeCode, v.ItemCode, v.ColorCode, v.ItemDim1Code, v.ItemDim2Code, v.ItemDim3Code, plu, username) `, v.ItemTypeCode, v.ItemCode, v.ColorCode, v.ItemDim1Code, v.ItemDim2Code, v.ItemDim3Code, plu, username)
if err != nil { if err != nil {
@@ -207,7 +248,17 @@ VALUES (
return inserted, nil return inserted, nil
} }
func ensureCdItemTx(tx *sql.Tx, itemTypeCode int16, itemCode string, username string) error { func NormalizeCdItemMapKey(itemTypeCode int16, itemCode string) string {
return strconv.FormatInt(int64(itemTypeCode), 10) + "|" + strings.ToUpper(strings.TrimSpace(itemCode))
}
func ensureCdItemTx(
tx *sql.Tx,
itemTypeCode int16,
itemCode string,
username string,
draft *models.OrderProductionCdItemDraft,
) error {
_, err := tx.Exec(` _, err := tx.Exec(`
IF NOT EXISTS ( IF NOT EXISTS (
SELECT 1 SELECT 1
@@ -269,28 +320,90 @@ BEGIN
INSERT INTO dbo.cdItem ( INSERT INTO dbo.cdItem (
ItemTypeCode, ItemCode, ItemTypeCode, ItemCode,
ItemDimTypeCode, ProductTypeCode, ProductHierarchyID, ItemDimTypeCode, ProductTypeCode, ProductHierarchyID,
UnitOfMeasureCode1, UnitConvertRate, UnitConvertRateNotFixed, UnitOfMeasureCode1, UnitOfMeasureCode2, UnitConvertRate, UnitConvertRateNotFixed,
UseInternet, UsePOS, UseStore, EnablePartnerCompanies, UseManufacturing, UseSerialNumber, UseInternet, UsePOS, UseStore, EnablePartnerCompanies, UseManufacturing, UseSerialNumber,
GenerateOpticalDataMatrixCode, ByWeight, SupplyPeriod, GuaranteePeriod, ShelfLife, OrderLeadTime, GenerateOpticalDataMatrixCode, ByWeight, SupplyPeriod, GuaranteePeriod, ShelfLife, OrderLeadTime,
IsFixedExpense, IsBlocked, IsLocked, LockedDate, IsSalesOrderClosed, IsPurchaseOrderClosed, ItemAccountGrCode, ItemTaxGrCode, ItemPaymentPlanGrCode, ItemDiscountGrCode, ItemVendorGrCode,
PromotionGroupCode, PromotionGroupCode2, ProductCollectionGrCode, StorePriceLevelCode, PerceptionOfFashionCode,
CommercialRoleCode, StoreCapacityLevelCode, CustomsTariffNumberCode, IsFixedExpense, BOMEntityCode, CompanyCode,
IsBlocked, IsLocked, LockedDate, IsSalesOrderClosed, IsPurchaseOrderClosed,
CreatedUserName, CreatedDate, LastUpdatedUserName, LastUpdatedDate, RowGuid, CreatedUserName, CreatedDate, LastUpdatedUserName, LastUpdatedDate, RowGuid,
UseRoll, UseBatch, MaxCreditCardInstallmentCount, GenerateSerialNumber, UseRoll, UseBatch, MaxCreditCardInstallmentCount, GenerateSerialNumber,
IsSubsequentDeliveryForR, IsSubsequentDeliveryForRI, IsUTSDeclaratedItem, IsStoreOrderClosed IsSubsequentDeliveryForR, IsSubsequentDeliveryForRI,
IGACommissionGroup, UniFreeCommissionGroup, CustomsProductGroupCode, IsUTSDeclaratedItem, IsStoreOrderClosed
) )
VALUES ( VALUES (
@p1, @p2, @p1, @p2,
2, 1, 2, 2, 1, 2,
'AD', 0, 0, 'AD', '', 0, 0,
0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, '1900-01-01', 0, 0, '', '10%', '', '', '',
'', '', '0', '0', '0',
'0', '', '', 0, '', '1',
0, 0, '1900-01-01', 0, 0,
@p3, GETDATE(), @p3, GETDATE(), NEWID(), @p3, GETDATE(), @p3, GETDATE(), NEWID(),
0, 0, 12, 0, 0, 0, 12, 0,
0, 0, 0, 0 0, 0,
'', '', '0', 0, 0
); );
END END
END END
`, itemTypeCode, itemCode, username) `, itemTypeCode, itemCode, username)
if err != nil {
return err
}
if draft == nil {
return nil
}
_, err = tx.Exec(`
UPDATE dbo.cdItem
SET
ItemDimTypeCode = COALESCE(@p3, ItemDimTypeCode),
ProductTypeCode = COALESCE(@p4, ProductTypeCode),
ProductHierarchyID = COALESCE(@p5, ProductHierarchyID),
UnitOfMeasureCode1 = COALESCE(NULLIF(@p6,''), UnitOfMeasureCode1),
ItemAccountGrCode = COALESCE(NULLIF(@p7,''), ItemAccountGrCode),
ItemTaxGrCode = COALESCE(NULLIF(@p8,''), ItemTaxGrCode),
ItemPaymentPlanGrCode = COALESCE(NULLIF(@p9,''), ItemPaymentPlanGrCode),
ItemDiscountGrCode = COALESCE(NULLIF(@p10,''), ItemDiscountGrCode),
ItemVendorGrCode = COALESCE(NULLIF(@p11,''), ItemVendorGrCode),
PromotionGroupCode = COALESCE(NULLIF(@p12,''), PromotionGroupCode),
ProductCollectionGrCode = COALESCE(NULLIF(@p13,''), ProductCollectionGrCode),
StorePriceLevelCode = COALESCE(NULLIF(@p14,''), StorePriceLevelCode),
PerceptionOfFashionCode = COALESCE(NULLIF(@p15,''), PerceptionOfFashionCode),
CommercialRoleCode = COALESCE(NULLIF(@p16,''), CommercialRoleCode),
StoreCapacityLevelCode = COALESCE(NULLIF(@p17,''), StoreCapacityLevelCode),
CustomsTariffNumberCode = COALESCE(NULLIF(@p18,''), CustomsTariffNumberCode),
CompanyCode = COALESCE(NULLIF(@p19,''), CompanyCode),
LastUpdatedUserName = @p20,
LastUpdatedDate = GETDATE()
WHERE ItemTypeCode = @p1
AND ItemCode = @p2;
`,
itemTypeCode,
itemCode,
draft.ItemDimTypeCode,
draft.ProductTypeCode,
draft.ProductHierarchyID,
draft.UnitOfMeasureCode1,
draft.ItemAccountGrCode,
draft.ItemTaxGrCode,
draft.ItemPaymentPlanGrCode,
draft.ItemDiscountGrCode,
draft.ItemVendorGrCode,
draft.PromotionGroupCode,
draft.ProductCollectionGrCode,
draft.StorePriceLevelCode,
draft.PerceptionOfFashionCode,
draft.CommercialRoleCode,
draft.StoreCapacityLevelCode,
draft.CustomsTariffNumberCode,
draft.CompanyCode,
username,
)
return err return err
} }
@@ -317,3 +430,141 @@ WHERE OrderHeaderID = @p6 AND OrderLineID = @p7
} }
return updated, nil return updated, nil
} }
func UpsertItemAttributesTx(tx *sql.Tx, attrs []models.OrderProductionItemAttributeRow, username string) (int64, error) {
if len(attrs) == 0 {
return 0, nil
}
var affected int64
for _, a := range attrs {
res, err := tx.Exec(`
IF EXISTS (
SELECT 1
FROM dbo.prItemAttribute
WHERE ItemTypeCode = @p1
AND ItemCode = @p2
AND AttributeTypeCode = @p3
)
BEGIN
UPDATE dbo.prItemAttribute
SET
AttributeCode = @p4,
LastUpdatedUserName = @p5,
LastUpdatedDate = GETDATE()
WHERE ItemTypeCode = @p1
AND ItemCode = @p2
AND AttributeTypeCode = @p3
END
ELSE
BEGIN
INSERT INTO dbo.prItemAttribute (
ItemTypeCode,
ItemCode,
AttributeTypeCode,
AttributeCode,
CreatedUserName,
CreatedDate,
LastUpdatedUserName,
LastUpdatedDate,
RowGuid
)
VALUES (
@p1,
@p2,
@p3,
@p4,
@p5,
GETDATE(),
@p5,
GETDATE(),
NEWID()
)
END
`, a.ItemTypeCode, a.ItemCode, a.AttributeTypeCode, a.AttributeCode, username)
if err != nil {
return affected, err
}
if rows, err := res.RowsAffected(); err == nil {
affected += rows
}
}
return affected, nil
}
func GetOrderProductionLookupOptions(mssql *sql.DB) (models.OrderProductionCdItemLookups, error) {
out := models.OrderProductionCdItemLookups{}
queryPairs := []struct {
Query string
Target *[]models.OrderProductionLookupOption
}{
{`SELECT
CAST(t.ItemDimTypeCode AS NVARCHAR(50)) AS Code,
ISNULL(d.ItemDimTypeDescription, CAST(t.ItemDimTypeCode AS NVARCHAR(50))) AS [Description]
FROM dbo.bsItemDimType t WITH(NOLOCK)
LEFT JOIN dbo.bsItemDimTypeDesc d WITH(NOLOCK)
ON d.ItemDimTypeCode = t.ItemDimTypeCode
AND d.LangCode = 'TR'
WHERE ISNULL(t.IsBlocked, 0) = 0
ORDER BY t.ItemDimTypeCode`, &out.ItemDimTypeCodes},
{`SELECT DISTINCT CAST(ProductTypeCode AS NVARCHAR(50)) AS Code, CAST(ProductTypeCode AS NVARCHAR(50)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE ProductTypeCode IS NOT NULL ORDER BY Code`, &out.ProductTypeCodes},
{`SELECT
CAST(h.ProductHierarchyID AS NVARCHAR(50)) AS Code,
LTRIM(RTRIM(
CONCAT(
CAST(ISNULL(h.ProductHierarchyLevelCode01, 0) AS NVARCHAR(50)),
CASE
WHEN ISNULL(d.ProductHierarchyLevelDescription, '') <> '' THEN CONCAT(' - ', d.ProductHierarchyLevelDescription)
ELSE ''
END
)
)) AS [Description]
FROM dbo.dfProductHierarchy h WITH(NOLOCK)
LEFT JOIN dbo.cdProductHierarchyLevelDesc d WITH(NOLOCK)
ON d.ProductHierarchyLevelCode = h.ProductHierarchyLevelCode01
AND d.LangCode = 'TR'
ORDER BY h.ProductHierarchyID`, &out.ProductHierarchyIDs},
{`SELECT DISTINCT CAST(UnitOfMeasureCode1 AS NVARCHAR(50)) AS Code, CAST(UnitOfMeasureCode1 AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE ISNULL(UnitOfMeasureCode1,'') <> '' ORDER BY Code`, &out.UnitOfMeasureCode1List},
{`SELECT DISTINCT CAST(ItemAccountGrCode AS NVARCHAR(50)) AS Code, CAST(ItemAccountGrCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE ISNULL(ItemAccountGrCode,'') <> '' ORDER BY Code`, &out.ItemAccountGrCodes},
{`SELECT DISTINCT CAST(ItemTaxGrCode AS NVARCHAR(50)) AS Code, CAST(ItemTaxGrCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE ISNULL(ItemTaxGrCode,'') <> '' ORDER BY Code`, &out.ItemTaxGrCodes},
{`SELECT DISTINCT CAST(ItemPaymentPlanGrCode AS NVARCHAR(50)) AS Code, CAST(ItemPaymentPlanGrCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE ISNULL(ItemPaymentPlanGrCode,'') <> '' ORDER BY Code`, &out.ItemPaymentPlanGrCodes},
{`SELECT DISTINCT CAST(ItemDiscountGrCode AS NVARCHAR(50)) AS Code, CAST(ItemDiscountGrCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE ISNULL(ItemDiscountGrCode,'') <> '' ORDER BY Code`, &out.ItemDiscountGrCodes},
{`SELECT DISTINCT CAST(ItemVendorGrCode AS NVARCHAR(50)) AS Code, CAST(ItemVendorGrCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE ISNULL(ItemVendorGrCode,'') <> '' ORDER BY Code`, &out.ItemVendorGrCodes},
{`SELECT DISTINCT CAST(PromotionGroupCode AS NVARCHAR(50)) AS Code, CAST(PromotionGroupCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE ISNULL(PromotionGroupCode,'') <> '' ORDER BY Code`, &out.PromotionGroupCodes},
{`SELECT DISTINCT CAST(ProductCollectionGrCode AS NVARCHAR(50)) AS Code, CAST(ProductCollectionGrCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE ISNULL(ProductCollectionGrCode,'') <> '' ORDER BY Code`, &out.ProductCollectionGrCodes},
{`SELECT DISTINCT CAST(StorePriceLevelCode AS NVARCHAR(50)) AS Code, CAST(StorePriceLevelCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE ISNULL(StorePriceLevelCode,'') <> '' ORDER BY Code`, &out.StorePriceLevelCodes},
{`SELECT DISTINCT CAST(PerceptionOfFashionCode AS NVARCHAR(50)) AS Code, CAST(PerceptionOfFashionCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE ISNULL(PerceptionOfFashionCode,'') <> '' ORDER BY Code`, &out.PerceptionOfFashionCodes},
{`SELECT DISTINCT CAST(CommercialRoleCode AS NVARCHAR(50)) AS Code, CAST(CommercialRoleCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE ISNULL(CommercialRoleCode,'') <> '' ORDER BY Code`, &out.CommercialRoleCodes},
{`SELECT DISTINCT CAST(StoreCapacityLevelCode AS NVARCHAR(50)) AS Code, CAST(StoreCapacityLevelCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE ISNULL(StoreCapacityLevelCode,'') <> '' ORDER BY Code`, &out.StoreCapacityLevelCodes},
{`SELECT DISTINCT CAST(CustomsTariffNumberCode AS NVARCHAR(50)) AS Code, CAST(CustomsTariffNumberCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE ISNULL(CustomsTariffNumberCode,'') <> '' ORDER BY Code`, &out.CustomsTariffNumbers},
{`SELECT DISTINCT CAST(CompanyCode AS NVARCHAR(50)) AS Code, CAST(CompanyCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE ISNULL(CompanyCode,'') <> '' ORDER BY Code`, &out.CompanyCodes},
}
for _, pair := range queryPairs {
rows, err := mssql.Query(pair.Query)
if err != nil {
return out, err
}
list := make([]models.OrderProductionLookupOption, 0, 64)
for rows.Next() {
var item models.OrderProductionLookupOption
if err := rows.Scan(&item.Code, &item.Description); err != nil {
rows.Close()
return out, err
}
item.Code = strings.TrimSpace(item.Code)
item.Description = strings.TrimSpace(item.Description)
list = append(list, item)
}
if err := rows.Err(); err != nil {
rows.Close()
return out, err
}
rows.Close()
*pair.Target = list
}
return out, nil
}

View File

@@ -0,0 +1,42 @@
package queries
const GetProductAttributes = `
;WITH TypeDesc AS (
SELECT
t.ItemTypeCode,
t.AttributeTypeCode,
ISNULL(t.AttributeTypeDescription, CAST(t.AttributeTypeCode AS NVARCHAR(30))) AS AttributeTypeDescription
FROM dbo.cdItemAttributeTypeDesc AS t WITH(NOLOCK)
WHERE t.ItemTypeCode = @p1
AND t.LangCode = 'TR'
),
Attr AS (
SELECT
a.ItemTypeCode,
a.AttributeTypeCode,
ISNULL(a.AttributeCode, '') AS AttributeCode,
ISNULL(d.AttributeDescription, ISNULL(a.AttributeCode, '')) AS AttributeDescription
FROM dbo.cdItemAttribute AS a WITH(NOLOCK)
LEFT JOIN dbo.cdItemAttributeDesc AS d WITH(NOLOCK)
ON d.ItemTypeCode = a.ItemTypeCode
AND d.AttributeTypeCode = a.AttributeTypeCode
AND d.AttributeCode = a.AttributeCode
AND d.LangCode = 'TR'
WHERE a.ItemTypeCode = @p1
AND ISNULL(a.IsBlocked, 0) = 0
),
SELECT
a.ItemTypeCode,
a.AttributeTypeCode,
ISNULL(NULLIF(td.AttributeTypeDescription, ''), CAST(a.AttributeTypeCode AS NVARCHAR(30))) AS AttributeTypeDescription,
a.AttributeCode,
a.AttributeDescription
FROM Attr a
LEFT JOIN TypeDesc td
ON td.ItemTypeCode = a.ItemTypeCode
AND td.AttributeTypeCode = a.AttributeTypeCode
ORDER BY
a.AttributeTypeCode,
CASE WHEN a.AttributeCode = '-' THEN 0 ELSE 1 END,
a.AttributeCode;
`

View File

@@ -4,7 +4,8 @@ const GetProductSecondColors = `
SELECT DISTINCT SELECT DISTINCT
Product.ProductCode, Product.ProductCode,
ISNULL(prItemVariant.ColorCode, '') AS ColorCode, ISNULL(prItemVariant.ColorCode, '') AS ColorCode,
ISNULL(prItemVariant.ItemDim2Code, '') AS ItemDim2Code ISNULL(prItemVariant.ItemDim2Code, '') AS ItemDim2Code,
ISNULL(ColorDesc.ColorDescription, '') AS ColorDescription
FROM prItemVariant WITH(NOLOCK) FROM prItemVariant WITH(NOLOCK)
INNER JOIN ProductFilterWithDescription('TR') AS Product INNER JOIN ProductFilterWithDescription('TR') AS Product
ON prItemVariant.ItemCode = Product.ProductCode ON prItemVariant.ItemCode = Product.ProductCode
@@ -14,5 +15,10 @@ FROM prItemVariant WITH(NOLOCK)
WHERE Product.ProductCode = @ProductCode WHERE Product.ProductCode = @ProductCode
AND prItemVariant.ColorCode = @ColorCode AND prItemVariant.ColorCode = @ColorCode
AND ISNULL(prItemVariant.ItemDim2Code, '') <> '' AND ISNULL(prItemVariant.ItemDim2Code, '') <> ''
GROUP BY Product.ProductCode, prItemVariant.ItemDim2Code, prItemVariant.ColorCode GROUP BY
Product.ProductCode,
prItemVariant.ItemDim2Code,
prItemVariant.ColorCode,
ColorDesc.ColorDescription
ORDER BY prItemVariant.ItemDim2Code
` `

View File

@@ -1597,6 +1597,17 @@ func renderOrderGrid(pdf *gofpdf.Fpdf, header *OrderHeader, rows []PdfRow, hasVa
layout := newPdfLayout(pdf) layout := newPdfLayout(pdf)
catSizes := buildCategorySizeMap(rows) catSizes := buildCategorySizeMap(rows)
normalizeYetiskinGarsonTokenGo := func(v string) string {
s := strings.ToUpper(strings.TrimSpace(v))
if strings.Contains(s, "GARSON") {
return "GARSON"
}
if strings.Contains(s, "YETISKIN") || strings.Contains(s, "YETİSKİN") {
return "YETISKIN"
}
return "GENEL"
}
// Grup: ÜRÜN ANA GRUBU // Grup: ÜRÜN ANA GRUBU
type group struct { type group struct {
Name string Name string
@@ -1609,15 +1620,19 @@ func renderOrderGrid(pdf *gofpdf.Fpdf, header *OrderHeader, rows []PdfRow, hasVa
var order []string var order []string
for _, r := range rows { for _, r := range rows {
name := strings.TrimSpace(r.GroupMain) ana := strings.TrimSpace(r.GroupMain)
if name == "" { if ana == "" {
name = "GENEL" ana = "GENEL"
} }
g, ok := groups[name] yg := normalizeYetiskinGarsonTokenGo(r.YetiskinGarson)
name := strings.TrimSpace(fmt.Sprintf("%s %s", yg, ana))
groupKey := fmt.Sprintf("%s::%s", yg, ana)
g, ok := groups[groupKey]
if !ok { if !ok {
g = &group{Name: name} g = &group{Name: name}
groups[name] = g groups[groupKey] = g
order = append(order, name) order = append(order, groupKey)
} }
g.Rows = append(g.Rows, r) g.Rows = append(g.Rows, r)
g.Adet += r.TotalQty g.Adet += r.TotalQty
@@ -1673,8 +1688,8 @@ func renderOrderGrid(pdf *gofpdf.Fpdf, header *OrderHeader, rows []PdfRow, hasVa
newPage(firstPage, true) newPage(firstPage, true)
firstPage = false firstPage = false
for _, name := range order { for _, key := range order {
g := groups[name] g := groups[key]
for _, row := range g.Rows { for _, row := range g.Rows {
rh := calcRowHeight(pdf, layout, row) rh := calcRowHeight(pdf, layout, row)

View File

@@ -9,12 +9,15 @@ import (
"errors" "errors"
"log" "log"
"net/http" "net/http"
"regexp"
"strings" "strings"
"github.com/gorilla/mux" "github.com/gorilla/mux"
mssql "github.com/microsoft/go-mssqldb" mssql "github.com/microsoft/go-mssqldb"
) )
var baggiModelCodeRegex = regexp.MustCompile(`^[A-Z][0-9]{3}-[A-Z]{3}[0-9]{5}$`)
// ====================================================== // ======================================================
// 📌 OrderProductionItemsRoute — U ürün satırları // 📌 OrderProductionItemsRoute — U ürün satırları
// ====================================================== // ======================================================
@@ -73,6 +76,23 @@ func OrderProductionItemsRoute(mssql *sql.DB) http.Handler {
}) })
} }
func OrderProductionCdItemLookupsRoute(mssql *sql.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
lookups, err := queries.GetOrderProductionLookupOptions(mssql)
if err != nil {
log.Printf("[OrderProductionCdItemLookupsRoute] lookup error: %v", err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
if err := json.NewEncoder(w).Encode(lookups); err != nil {
log.Printf("[OrderProductionCdItemLookupsRoute] encode error: %v", err)
}
})
}
// ====================================================== // ======================================================
// 📌 OrderProductionInsertMissingRoute — eksik varyantları ekler // 📌 OrderProductionInsertMissingRoute — eksik varyantları ekler
// ====================================================== // ======================================================
@@ -208,13 +228,24 @@ func OrderProductionApplyRoute(mssql *sql.DB) http.Handler {
var inserted int64 var inserted int64
if payload.InsertMissing { if payload.InsertMissing {
inserted, err = queries.InsertMissingVariantsTx(tx, missing, username) cdItemByCode := buildCdItemDraftMap(payload.CdItems)
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 := validateProductAttributes(payload.ProductAttributes); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
attributeAffected, err := queries.UpsertItemAttributesTx(tx, payload.ProductAttributes, username)
if err != nil {
writeDBError(w, http.StatusInternalServerError, "upsert_item_attributes", id, username, len(payload.ProductAttributes), err)
return
}
updated, err := queries.UpdateOrderLinesTx(tx, id, payload.Lines, username) updated, err := queries.UpdateOrderLinesTx(tx, id, payload.Lines, username)
if err != nil { if err != nil {
writeDBError(w, http.StatusInternalServerError, "update_order_lines", id, username, len(payload.Lines), err) writeDBError(w, http.StatusInternalServerError, "update_order_lines", id, username, len(payload.Lines), err)
@@ -229,6 +260,7 @@ func OrderProductionApplyRoute(mssql *sql.DB) http.Handler {
resp := map[string]any{ resp := map[string]any{
"updated": updated, "updated": updated,
"inserted": inserted, "inserted": inserted,
"attributeUpserted": attributeAffected,
} }
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)
@@ -236,6 +268,44 @@ func OrderProductionApplyRoute(mssql *sql.DB) http.Handler {
}) })
} }
func validateProductAttributes(attrs []models.OrderProductionItemAttributeRow) error {
for _, a := range attrs {
if strings.TrimSpace(a.ItemCode) == "" {
return errors.New("Urun ozellikleri icin ItemCode zorunlu")
}
if !baggiModelCodeRegex.MatchString(strings.ToUpper(strings.TrimSpace(a.ItemCode))) {
return errors.New("Girdiginiz kod BAGGI kod sistemine uyumlu degil. Format: X999-XXX99999")
}
if a.ItemTypeCode <= 0 {
return errors.New("Urun ozellikleri icin ItemTypeCode zorunlu")
}
if a.AttributeTypeCode <= 0 {
return errors.New("Urun ozellikleri icin AttributeTypeCode zorunlu")
}
if strings.TrimSpace(a.AttributeCode) == "" {
return errors.New("Urun ozellikleri icin AttributeCode zorunlu")
}
}
return nil
}
func buildCdItemDraftMap(list []models.OrderProductionCdItemDraft) map[string]models.OrderProductionCdItemDraft {
out := make(map[string]models.OrderProductionCdItemDraft, len(list))
for _, item := range list {
code := strings.ToUpper(strings.TrimSpace(item.ItemCode))
if code == "" {
continue
}
item.ItemCode = code
if item.ItemTypeCode == 0 {
item.ItemTypeCode = 1
}
key := queries.NormalizeCdItemMapKey(item.ItemTypeCode, item.ItemCode)
out[key] = item
}
return out
}
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) {
missing := make([]models.OrderProductionMissingVariant, 0) missing := make([]models.OrderProductionMissingVariant, 0)
@@ -279,9 +349,13 @@ func validateUpdateLines(lines []models.OrderProductionUpdateLine) error {
if strings.TrimSpace(line.OrderLineID) == "" { if strings.TrimSpace(line.OrderLineID) == "" {
return errors.New("OrderLineID zorunlu") return errors.New("OrderLineID zorunlu")
} }
if strings.TrimSpace(line.NewItemCode) == "" { code := strings.ToUpper(strings.TrimSpace(line.NewItemCode))
if code == "" {
return errors.New("Yeni urun kodu zorunlu") return errors.New("Yeni urun kodu zorunlu")
} }
if !baggiModelCodeRegex.MatchString(code) {
return errors.New("Girdiginiz kod BAGGI kod sistemine uyumlu degil. Format: X999-XXX99999")
}
} }
return nil return nil
} }

View File

@@ -22,18 +22,69 @@ type ProductSizeMatchResponse struct {
Schemas map[string][]string `json:"schemas"` Schemas map[string][]string `json:"schemas"`
} }
func defaultSizeSchemas() map[string][]string { func fallbackTakSchema() map[string][]string {
return map[string][]string{ return map[string][]string{
"tak": {"44", "46", "48", "50", "52", "54", "56", "58", "60", "62", "64", "66", "68", "70", "72", "74"}, "tak": {"44", "46", "48", "50", "52", "54", "56", "58", "60", "62", "64", "66", "68", "70", "72", "74"},
"ayk": {"39", "40", "41", "42", "43", "44", "45"},
"ayk_garson": {"22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "STD"},
"yas": {"2", "4", "6", "8", "10", "12", "14"},
"pan": {"38", "40", "42", "44", "46", "48", "50", "52", "54", "56", "58", "60", "62", "64", "66", "68"},
"gom": {"XS", "S", "M", "L", "XL", "2XL", "3XL", "4XL", "5XL", "6XL", "7XL"},
"aksbir": {" ", "44", "STD", "110", "115", "120", "125", "130", "135"},
} }
} }
func parseSizeValuesCSV(raw string) []string {
parts := strings.Split(raw, ",")
out := make([]string, 0, len(parts))
seen := map[string]struct{}{}
for _, p := range parts {
v := strings.TrimSpace(p)
if v == "" {
v = " "
}
if _, ok := seen[v]; ok {
continue
}
seen[v] = struct{}{}
out = append(out, v)
}
return out
}
func loadSizeSchemas(pgDB *sql.DB) (map[string][]string, error) {
rows, err := pgDB.Query(`
SELECT
COALESCE(group_key, ''),
COALESCE(size_values, '')
FROM mk_size_group
`)
if err != nil {
return nil, err
}
defer rows.Close()
schemas := map[string][]string{}
for rows.Next() {
var groupKey string
var sizeValues string
if err := rows.Scan(&groupKey, &sizeValues); err != nil {
return nil, err
}
key := strings.TrimSpace(groupKey)
if key == "" {
continue
}
schemas[key] = parseSizeValuesCSV(sizeValues)
}
if err := rows.Err(); err != nil {
return nil, err
}
if len(schemas) == 0 {
schemas = fallbackTakSchema()
}
if _, ok := schemas["tak"]; !ok {
schemas["tak"] = fallbackTakSchema()["tak"]
}
return schemas, nil
}
func loadProductSizeMatchData(pgDB *sql.DB) (*ProductSizeMatchResponse, error) { func loadProductSizeMatchData(pgDB *sql.DB) (*ProductSizeMatchResponse, error) {
rows, err := pgDB.Query(` rows, err := pgDB.Query(`
SELECT SELECT
@@ -58,9 +109,13 @@ func loadProductSizeMatchData(pgDB *sql.DB) (*ProductSizeMatchResponse, error) {
} }
defer rows.Close() defer rows.Close()
schemas, err := loadSizeSchemas(pgDB)
if err != nil {
schemas = fallbackTakSchema()
}
resp := &ProductSizeMatchResponse{ resp := &ProductSizeMatchResponse{
Rules: make([]ProductSizeMatchRule, 0), Rules: make([]ProductSizeMatchRule, 0),
Schemas: defaultSizeSchemas(), Schemas: schemas,
} }
for rows.Next() { for rows.Next() {

View File

@@ -0,0 +1,54 @@
package routes
import (
"bssapp-backend/auth"
"bssapp-backend/db"
"bssapp-backend/models"
"bssapp-backend/queries"
"encoding/json"
"net/http"
"strconv"
)
func GetProductAttributesHandler(w http.ResponseWriter, r *http.Request) {
claims, ok := auth.GetClaimsFromContext(r.Context())
if !ok || claims == nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
itemTypeCode := int16(1)
if raw := r.URL.Query().Get("itemTypeCode"); raw != "" {
v, err := strconv.Atoi(raw)
if err != nil || v <= 0 {
http.Error(w, "itemTypeCode gecersiz", http.StatusBadRequest)
return
}
itemTypeCode = int16(v)
}
rows, err := db.MssqlDB.Query(queries.GetProductAttributes, itemTypeCode)
if err != nil {
http.Error(w, "Product attributes alinamadi: "+err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
list := make([]models.ProductAttributeOption, 0, 256)
for rows.Next() {
var x models.ProductAttributeOption
if err := rows.Scan(
&x.ItemTypeCode,
&x.AttributeTypeCode,
&x.AttributeTypeDescription,
&x.AttributeCode,
&x.AttributeDescription,
); err != nil {
continue
}
list = append(list, x)
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(list)
}

View File

@@ -45,7 +45,7 @@ func GetProductSecondColorsHandler(w http.ResponseWriter, r *http.Request) {
var list []models.ProductSecondColor var list []models.ProductSecondColor
for rows.Next() { for rows.Next() {
var c models.ProductSecondColor var c models.ProductSecondColor
if err := rows.Scan(&c.ProductCode, &c.ColorCode, &c.ItemDim2Code); err != nil { if err := rows.Scan(&c.ProductCode, &c.ColorCode, &c.ItemDim2Code, &c.ColorDescription); err != nil {
log.Println("⚠️ Satır okunamadı:", err) log.Println("⚠️ Satır okunamadı:", err)
continue continue
} }

View File

@@ -0,0 +1,75 @@
/* 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
}
}

View File

@@ -0,0 +1,154 @@
/* 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)
})
})

View File

@@ -0,0 +1,116 @@
/* 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()
})
})
}

View File

@@ -0,0 +1,23 @@
/* 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} }

View File

@@ -323,12 +323,12 @@
======================================================== --> ======================================================== -->
<div class="order-scroll-y" :class="{ 'compact-grid-header': compactGridHeader }"> <!-- ✅ YENİ: Grid + Editor ortak dikey scroll --> <div class="order-scroll-y" :class="{ 'compact-grid-header': compactGridHeader }"> <!-- ✅ YENİ: Grid + Editor ortak dikey scroll -->
<div class="order-grid-body"> <div class="order-grid-body">
<template v-for="grp in groupedRows" :key="grp.name"> <template v-for="grp in groupedRows" :key="grp.groupKey">
<div :class="['summary-group', grp.open ? 'open' : 'closed']"> <div :class="['summary-group', grp.open ? 'open' : 'closed']">
<!-- 🟡 Sub-header --> <!-- 🟡 Sub-header -->
<div class="order-sub-header" @click="toggleGroup(grp.name)"> <div class="order-sub-header" @click="toggleGroup(grp.groupKey)">
<div class="sub-left">{{ grp.name }}</div> <div class="sub-left">{{ grp.displayName }}</div>
<div class="sub-center"> <div class="sub-center">
<div <div
@@ -348,10 +348,10 @@
<div class="order-text-caption"> <div class="order-text-caption">
Toplam {{ grp.name }} Adet: {{ grp.toplamAdet }} Toplam {{ grp.displayName }} Adet: {{ grp.toplamAdet }}
</div> </div>
<div class="order-text-caption"> <div class="order-text-caption">
Toplam {{ grp.name }} Tutar: Toplam {{ grp.displayName }} Tutar:
{{ Number(grp.toplamTutar || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) }} {{ Number(grp.toplamTutar || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) }}
{{ form.pb || aktifPB }} {{ form.pb || aktifPB }}
</div> </div>
@@ -747,6 +747,14 @@
@click="removeSelected" @click="removeSelected"
:disable="isClosedRow || isViewOnly || !canMutateRows" :disable="isClosedRow || isViewOnly || !canMutateRows"
/> />
<q-btn
v-if="canMutateRows"
flat
color="warning"
label="Bedenleri Sıfırla"
@click="onZeroBedenClick"
:disable="isClosedRow || isViewOnly || !canMutateRows"
/>
<q-btn <q-btn
v-if="canMutateRows" v-if="canMutateRows"
flat flat
@@ -1362,6 +1370,21 @@ const selectedRow = computed(() => {
=========================================================== */ =========================================================== */
const groupOpen = reactive({}) const groupOpen = reactive({})
function normalizeYetiskinGarsonToken (row) {
const raw = String(
row?.yetiskinGarson ||
row?.YETISKIN_GARSON ||
row?.YetiskinGarson ||
row?.kategori ||
row?.Kategori ||
''
).toUpperCase()
if (raw.includes('GARSON')) return 'GARSON'
if (raw.includes('YETISKIN') || raw.includes('YETİSKİN')) return 'YETISKIN'
return 'GENEL'
}
const groupedRows = computed(() => { const groupedRows = computed(() => {
const rows = Array.isArray(summaryRows.value) ? summaryRows.value : [] const rows = Array.isArray(summaryRows.value) ? summaryRows.value : []
const buckets = {} const buckets = {}
@@ -1376,33 +1399,44 @@ const groupedRows = computed(() => {
const ana = (row?.urunAnaGrubu || 'GENEL') const ana = (row?.urunAnaGrubu || 'GENEL')
.toUpperCase() .toUpperCase()
.trim() .trim()
const yg = normalizeYetiskinGarsonToken(row)
const grpKey = String(row?.grpKey || 'tak').trim() || 'tak'
const bucketKey = `${yg}::${ana}::${grpKey}`
if (!buckets[ana]) { if (!buckets[bucketKey]) {
buckets[ana] = { buckets[bucketKey] = {
name: ana, name: ana,
yg,
displayName: `${yg} ${ana}`,
rows: [], rows: [],
toplamAdet: 0, toplamAdet: 0,
toplamTutar: 0, toplamTutar: 0,
open: groupOpen[ana] ?? true, open: groupOpen[bucketKey] ?? true,
// 🔑 TEK KAYNAK // 🔑 TEK KAYNAK
grpKey: row.grpKey grpKey
} }
order.push(ana) order.push(bucketKey)
} }
const bucket = buckets[ana] const bucket = buckets[bucketKey]
bucket.rows.push(row) bucket.rows.push(row)
bucket.toplamAdet += Number(row.adet || 0) bucket.toplamAdet += Number(row.adet || 0)
bucket.toplamTutar += Number(row.tutar || 0) bucket.toplamTutar += Number(row.tutar || 0)
} }
return order.map(name => { return order.map(bucketKey => {
const grp = buckets[name] const grp = buckets[bucketKey]
const schema = schemaMap?.[grp.grpKey] const schema = schemaMap?.[grp.grpKey]
const schemaTitle = String(schema?.title || grp.grpKey || '').trim()
const displayName = schemaTitle
? `${grp.yg} ${grp.name} (${schemaTitle})`
: `${grp.yg} ${grp.name}`
return { return {
...grp, ...grp,
displayName,
groupKey: bucketKey,
bedenValues: schema?.values || [] bedenValues: schema?.values || []
} }
}) })
@@ -3016,6 +3050,25 @@ const onSaveAndNextColor = async () => {
}) })
} }
function onZeroBedenClick () {
if (!Array.isArray(form.bedenLabels) || !form.bedenLabels.length) {
$q.notify({
type: 'warning',
message: 'Sıfırlanacak beden alanı bulunamadı.'
})
return
}
form.bedenler = form.bedenLabels.map(() => 0)
updateTotals(form)
$q.notify({
type: 'info',
message: 'Beden adetleri sıfırlandı.',
position: 'top-right'
})
}

View File

@@ -114,7 +114,8 @@
filled filled
maxlength="13" maxlength="13"
label="Yeni Urun" label="Yeni Urun"
@update:model-value="val => onNewItemChange(props.row, val)" :class="newItemInputClass(props.row)"
@update:model-value="val => onNewItemChange(props.row, val, 'typed')"
> >
<template #append> <template #append>
<q-icon name="arrow_drop_down" class="cursor-pointer" /> <q-icon name="arrow_drop_down" class="cursor-pointer" />
@@ -145,6 +146,32 @@
</div> </div>
</q-menu> </q-menu>
</q-input> </q-input>
<div v-if="props.row.NewItemMode && props.row.NewItemMode !== 'empty'" class="q-mt-xs row items-center no-wrap">
<q-badge :color="newItemBadgeColor(props.row)" text-color="white">
{{ newItemBadgeLabel(props.row) }}
</q-badge>
<span class="text-caption q-ml-sm text-grey-8">{{ newItemHintText(props.row) }}</span>
<q-btn
v-if="props.row.NewItemMode === 'new'"
class="q-ml-sm"
dense
flat
size="sm"
color="warning"
label="cdItem Bilgisi"
@click="openCdItemDialog(props.row.NewItemCode)"
/>
<q-btn
v-if="props.row.NewItemMode === 'new'"
class="q-ml-xs"
dense
flat
size="sm"
color="primary"
label="Urun Ozellikleri"
@click="openAttributeDialog(props.row.NewItemCode)"
/>
</div>
</q-td> </q-td>
</template> </template>
@@ -157,13 +184,10 @@
option-value="color_code" option-value="color_code"
emit-value emit-value
map-options map-options
use-input
new-value-mode="add-unique"
dense dense
filled filled
label="Yeni Renk" label="Yeni Renk"
@update:model-value="() => onNewColorChange(props.row)" @update:model-value="() => onNewColorChange(props.row)"
@new-value="(val, done) => onCreateColorValue(props.row, val, done)"
/> />
</q-td> </q-td>
</template> </template>
@@ -173,16 +197,13 @@
<q-select <q-select
v-model="props.row.NewDim2" v-model="props.row.NewDim2"
:options="getSecondColorOptions(props.row)" :options="getSecondColorOptions(props.row)"
option-label="item_dim2_code" option-label="item_dim2_label"
option-value="item_dim2_code" option-value="item_dim2_code"
emit-value emit-value
map-options map-options
use-input
new-value-mode="add-unique"
dense dense
filled filled
label="Yeni 2. Renk" label="Yeni 2. Renk"
@new-value="(val, done) => onCreateSecondColorValue(props.row, val, done)"
/> />
</q-td> </q-td>
</template> </template>
@@ -205,6 +226,127 @@
<q-banner v-if="store.error" class="bg-red text-white q-mt-sm"> <q-banner v-if="store.error" class="bg-red text-white q-mt-sm">
Hata: {{ store.error }} Hata: {{ store.error }}
</q-banner> </q-banner>
<q-dialog v-model="cdItemDialogOpen" persistent>
<q-card style="min-width: 980px; max-width: 98vw;">
<q-card-section class="row items-center q-pb-none">
<div class="text-h6">Yeni Kod cdItem Bilgileri</div>
<q-space />
<q-badge color="warning" text-color="black">
{{ cdItemTargetCode || '-' }}
</q-badge>
</q-card-section>
<q-card-section class="q-pt-md">
<div class="row q-col-gutter-sm">
<div class="col-12 col-md-4">
<q-select v-model="cdItemDraftForm.ItemDimTypeCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('itemDimTypeCodes')" label="ItemDimTypeCode" />
</div>
<div class="col-12 col-md-4">
<q-select v-model="cdItemDraftForm.ProductTypeCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('productTypeCodes')" label="ProductTypeCode" />
</div>
<div class="col-12 col-md-4">
<q-select v-model="cdItemDraftForm.ProductHierarchyID" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('productHierarchyIDs')" label="ProductHierarchyID" />
</div>
<div class="col-12 col-md-4">
<q-select v-model="cdItemDraftForm.UnitOfMeasureCode1" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('unitOfMeasureCode1List')" label="UnitOfMeasureCode1" />
</div>
<div class="col-12 col-md-4">
<q-select v-model="cdItemDraftForm.ItemAccountGrCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('itemAccountGrCodes')" label="ItemAccountGrCode" />
</div>
<div class="col-12 col-md-4">
<q-select v-model="cdItemDraftForm.ItemTaxGrCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('itemTaxGrCodes')" label="ItemTaxGrCode" />
</div>
<div class="col-12 col-md-4">
<q-select v-model="cdItemDraftForm.ItemPaymentPlanGrCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('itemPaymentPlanGrCodes')" label="ItemPaymentPlanGrCode" />
</div>
<div class="col-12 col-md-4">
<q-select v-model="cdItemDraftForm.ItemDiscountGrCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('itemDiscountGrCodes')" label="ItemDiscountGrCode" />
</div>
<div class="col-12 col-md-4">
<q-select v-model="cdItemDraftForm.ItemVendorGrCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('itemVendorGrCodes')" label="ItemVendorGrCode" />
</div>
<div class="col-12 col-md-4">
<q-select v-model="cdItemDraftForm.PromotionGroupCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('promotionGroupCodes')" label="PromotionGroupCode" />
</div>
<div class="col-12 col-md-4">
<q-select v-model="cdItemDraftForm.ProductCollectionGrCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('productCollectionGrCodes')" label="ProductCollectionGrCode" />
</div>
<div class="col-12 col-md-4">
<q-select v-model="cdItemDraftForm.StorePriceLevelCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('storePriceLevelCodes')" label="StorePriceLevelCode" />
</div>
<div class="col-12 col-md-4">
<q-select v-model="cdItemDraftForm.PerceptionOfFashionCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('perceptionOfFashionCodes')" label="PerceptionOfFashionCode" />
</div>
<div class="col-12 col-md-4">
<q-select v-model="cdItemDraftForm.CommercialRoleCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('commercialRoleCodes')" label="CommercialRoleCode" />
</div>
<div class="col-12 col-md-4">
<q-select v-model="cdItemDraftForm.StoreCapacityLevelCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('storeCapacityLevelCodes')" label="StoreCapacityLevelCode" />
</div>
<div class="col-12 col-md-6">
<q-select v-model="cdItemDraftForm.CustomsTariffNumberCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('customsTariffNumbers')" label="CustomsTariffNumberCode" />
</div>
<div class="col-12 col-md-6">
<q-select v-model="cdItemDraftForm.CompanyCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('companyCodes')" label="CompanyCode" />
</div>
</div>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Vazgec" color="grey-8" v-close-popup />
<q-btn color="primary" label="Taslagi Kaydet" @click="saveCdItemDraft" />
</q-card-actions>
</q-card>
</q-dialog>
<q-dialog v-model="attributeDialogOpen" persistent>
<q-card style="min-width: 1100px; max-width: 98vw;">
<q-card-section class="row items-center q-pb-none">
<div class="text-h6">Urun Ozellikleri (2. Pop-up)</div>
<q-space />
<q-badge color="primary">{{ attributeTargetCode || '-' }}</q-badge>
</q-card-section>
<q-card-section style="max-height: 68vh; overflow: auto;">
<div class="text-caption text-grey-7 q-mb-sm">
Ilk etap dummy: isBlocked=0 kabul edilmis satirlar gibi listelenir.
</div>
<div
v-for="(row, idx) in attributeRows"
:key="`${row.AttributeTypeCodeNumber}-${idx}`"
class="row q-col-gutter-sm q-mb-xs items-center"
>
<div class="col-12 col-md-5">
<q-input :model-value="row.TypeLabel" dense filled readonly />
</div>
<div class="col-12 col-md-7">
<q-select
v-model="row.AttributeCode"
dense
filled
emit-value
map-options
option-label="label"
option-value="value"
:options="row.Options"
label="AttributeCode - AttributeDescription"
/>
</div>
</div>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Vazgec" color="grey-8" v-close-popup />
<q-btn color="primary" label="Ozellikleri Kaydet" @click="saveAttributeDraft" />
</q-card-actions>
</q-card>
</q-dialog>
</q-page> </q-page>
</template> </template>
@@ -219,6 +361,8 @@ import { normalizeSearchText } from 'src/utils/searchText'
const route = useRoute() const route = useRoute()
const $q = useQuasar() const $q = useQuasar()
const store = useOrderProductionItemStore() const store = useOrderProductionItemStore()
const BAGGI_CODE_PATTERN = /^[A-Z][0-9]{3}-[A-Z]{3}[0-9]{5}$/
const BAGGI_CODE_ERROR = 'Girdiginiz kod BAGGI kod sistemine uyumlu degil. Format: X999-XXX99999'
const orderHeaderID = computed(() => String(route.params.orderHeaderID || '').trim()) const orderHeaderID = computed(() => String(route.params.orderHeaderID || '').trim())
const header = computed(() => store.header || {}) const header = computed(() => store.header || {})
@@ -235,6 +379,12 @@ const descFilter = ref('')
const productOptions = ref([]) const productOptions = ref([])
const productSearch = ref('') const productSearch = ref('')
const selectedMap = ref({}) const selectedMap = ref({})
const cdItemDialogOpen = ref(false)
const cdItemTargetCode = ref('')
const cdItemDraftForm = ref(createEmptyCdItemDraft(''))
const attributeDialogOpen = ref(false)
const attributeTargetCode = ref('')
const attributeRows = ref([])
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;' },
@@ -300,24 +450,70 @@ const selectedVisibleCount = computed(() => visibleRowKeys.value.filter(k => !!s
const allSelectedVisible = computed(() => visibleRowKeys.value.length > 0 && selectedVisibleCount.value === visibleRowKeys.value.length) const allSelectedVisible = computed(() => visibleRowKeys.value.length > 0 && selectedVisibleCount.value === visibleRowKeys.value.length)
const someSelectedVisible = computed(() => selectedVisibleCount.value > 0) const someSelectedVisible = computed(() => selectedVisibleCount.value > 0)
function onSelectProduct (row, code) { function applyNewItemVisualState (row, source = 'typed') {
productSearch.value = '' const info = store.classifyItemCode(row?.NewItemCode || '')
onNewItemChange(row, code) row.NewItemCode = info.normalized
row.NewItemMode = info.mode
row.NewItemSource = info.mode === 'empty' ? '' : source
} }
function onNewItemChange (row, val) { function newItemInputClass (row) {
return {
'new-item-existing': row?.NewItemMode === 'existing',
'new-item-new': row?.NewItemMode === 'new'
}
}
function newItemBadgeColor (row) {
return row?.NewItemMode === 'existing' ? 'positive' : 'warning'
}
function newItemBadgeLabel (row) {
return row?.NewItemMode === 'existing' ? 'MEVCUT KOD' : 'YENI KOD'
}
function newItemHintText (row) {
if (row?.NewItemMode === 'existing') {
return row?.NewItemSource === 'selected'
? 'Urun listesinden secildi'
: 'Elle girildi (sistemde bulundu)'
}
if (row?.NewItemMode === 'new') {
return store.getCdItemDraft(row?.NewItemCode) ? 'Yeni kod: cdItem taslagi hazir' : 'Yeni kod: cdItem taslagi gerekli'
}
return ''
}
function onSelectProduct (row, code) {
productSearch.value = ''
onNewItemChange(row, code, 'selected')
}
function onNewItemChange (row, val, source = 'typed') {
const prevCode = String(row?.NewItemCode || '').trim().toUpperCase()
const next = String(val || '').trim().toUpperCase() const next = String(val || '').trim().toUpperCase()
if (next.length > 13) { if (next.length > 13) {
$q.notify({ type: 'negative', message: 'Model kodu en fazla 13 karakter olabilir.' }) $q.notify({ type: 'negative', message: 'Model kodu en fazla 13 karakter olabilir.' })
row.NewItemCode = next.slice(0, 13) row.NewItemCode = next.slice(0, 13)
applyNewItemVisualState(row, source)
return
}
if (next.length === 13 && !isValidBaggiModelCode(next)) {
$q.notify({ type: 'negative', message: BAGGI_CODE_ERROR })
row.NewItemCode = prevCode
applyNewItemVisualState(row, source)
return return
} }
row.NewItemCode = next ? next.toUpperCase() : '' row.NewItemCode = next ? next.toUpperCase() : ''
applyNewItemVisualState(row, source)
row.NewColor = '' row.NewColor = ''
row.NewDim2 = '' row.NewDim2 = ''
if (row.NewItemCode) { if (row.NewItemCode) {
store.fetchColors(row.NewItemCode) store.fetchColors(row.NewItemCode)
} }
if (row.NewItemMode === 'new' && isValidBaggiModelCode(row.NewItemCode) && row.NewItemCode !== prevCode) {
openCdItemDialog(row.NewItemCode)
}
} }
function onNewColorChange (row) { function onNewColorChange (row) {
@@ -341,7 +537,11 @@ function getSecondColorOptions (row) {
const code = row?.NewItemCode || '' const code = row?.NewItemCode || ''
const color = row?.NewColor || '' const color = row?.NewColor || ''
const key = `${code}::${color}` const key = `${code}::${color}`
return store.secondColorOptionsByKey[key] || [] const list = store.secondColorOptionsByKey[key] || []
return list.map(c => ({
...c,
item_dim2_label: `${c.item_dim2_code} - ${c.color_description || ''}`.trim()
}))
} }
function toggleRowSelection (rowKey, checked) { function toggleRowSelection (rowKey, checked) {
@@ -360,33 +560,12 @@ function toggleSelectAllVisible (checked) {
selectedMap.value = next selectedMap.value = next
} }
function onCreateColorValue (row, val, done) {
const code = normalizeShortCode(val, 3)
if (!code) {
done(null)
return
}
row.NewColor = code
onNewColorChange(row)
done(code, 'add-unique')
}
function onCreateSecondColorValue (row, val, done) {
const code = normalizeShortCode(val, 3)
if (!code) {
done(null)
return
}
row.NewDim2 = code
done(code, 'add-unique')
}
function normalizeShortCode (value, maxLen) { function normalizeShortCode (value, maxLen) {
return String(value || '').trim().toUpperCase().slice(0, maxLen) return String(value || '').trim().toUpperCase().slice(0, maxLen)
} }
function isValidBaggiModelCode (code) { function isValidBaggiModelCode (code) {
return /^[A-Z][0-9]{3}-[A-Z]{3}[0-9]{5}$/.test(code) return BAGGI_CODE_PATTERN.test(code)
} }
function validateRowInput (row) { function validateRowInput (row) {
@@ -398,7 +577,7 @@ function validateRowInput (row) {
if (!newItemCode) return 'Yeni model kodu zorunludur.' if (!newItemCode) return 'Yeni model kodu zorunludur.'
if (!isValidBaggiModelCode(newItemCode)) { if (!isValidBaggiModelCode(newItemCode)) {
return 'Girdiginiz yapi BAGGI kod yapisina uygun degildir. Format: X999-XXX99999' return BAGGI_CODE_ERROR
} }
if (oldColor && !newColor) return 'Eski kayitta 1. renk oldugu icin yeni 1. renk zorunludur.' if (oldColor && !newColor) return 'Eski kayitta 1. renk oldugu icin yeni 1. renk zorunludur.'
if (newColor && newColor.length !== 3) return 'Yeni 1. renk kodu 3 karakter olmalidir.' if (newColor && newColor.length !== 3) return 'Yeni 1. renk kodu 3 karakter olmalidir.'
@@ -437,6 +616,238 @@ function collectLinesFromRows (selectedRows) {
return { errMsg: '', lines } return { errMsg: '', lines }
} }
function createEmptyCdItemDraft (itemCode) {
return {
ItemTypeCode: '1',
ItemCode: String(itemCode || '').trim().toUpperCase(),
ItemDimTypeCode: '',
ProductTypeCode: '',
ProductHierarchyID: '',
UnitOfMeasureCode1: '',
ItemAccountGrCode: '',
ItemTaxGrCode: '',
ItemPaymentPlanGrCode: '',
ItemDiscountGrCode: '',
ItemVendorGrCode: '',
PromotionGroupCode: '',
ProductCollectionGrCode: '',
StorePriceLevelCode: '',
PerceptionOfFashionCode: '',
CommercialRoleCode: '',
StoreCapacityLevelCode: '',
CustomsTariffNumberCode: '',
CompanyCode: ''
}
}
function lookupOptions (key) {
const list = store.cdItemLookups?.[key] || []
return list.map(x => {
const code = String(x?.code || '').trim()
const desc = String(x?.description || '').trim()
return {
value: code,
label: desc ? `${code} - ${desc}` : code
}
})
}
async function openCdItemDialog (itemCode) {
const code = String(itemCode || '').trim().toUpperCase()
if (!code) return
await store.fetchCdItemLookups()
cdItemTargetCode.value = code
const existing = store.getCdItemDraft(code)
const draft = createEmptyCdItemDraft(code)
if (existing) {
for (const [k, v] of Object.entries(existing)) {
if (v == null) continue
draft[k] = String(v)
}
}
cdItemDraftForm.value = draft
cdItemDialogOpen.value = true
}
function normalizeCdItemDraftForPayload (draftRaw) {
const d = draftRaw || {}
const toIntOrNil = (v) => {
const n = Number(v)
return Number.isFinite(n) && n > 0 ? n : null
}
const toStrOrNil = (v) => {
const s = String(v || '').trim()
return s || null
}
return {
ItemTypeCode: toIntOrNil(d.ItemTypeCode) || 1,
ItemCode: String(d.ItemCode || '').trim().toUpperCase(),
ItemDimTypeCode: toIntOrNil(d.ItemDimTypeCode),
ProductTypeCode: toIntOrNil(d.ProductTypeCode),
ProductHierarchyID: toIntOrNil(d.ProductHierarchyID),
UnitOfMeasureCode1: toStrOrNil(d.UnitOfMeasureCode1),
ItemAccountGrCode: toStrOrNil(d.ItemAccountGrCode),
ItemTaxGrCode: toStrOrNil(d.ItemTaxGrCode),
ItemPaymentPlanGrCode: toStrOrNil(d.ItemPaymentPlanGrCode),
ItemDiscountGrCode: toStrOrNil(d.ItemDiscountGrCode),
ItemVendorGrCode: toStrOrNil(d.ItemVendorGrCode),
PromotionGroupCode: toStrOrNil(d.PromotionGroupCode),
ProductCollectionGrCode: toStrOrNil(d.ProductCollectionGrCode),
StorePriceLevelCode: toStrOrNil(d.StorePriceLevelCode),
PerceptionOfFashionCode: toStrOrNil(d.PerceptionOfFashionCode),
CommercialRoleCode: toStrOrNil(d.CommercialRoleCode),
StoreCapacityLevelCode: toStrOrNil(d.StoreCapacityLevelCode),
CustomsTariffNumberCode: toStrOrNil(d.CustomsTariffNumberCode),
CompanyCode: toStrOrNil(d.CompanyCode)
}
}
function saveCdItemDraft () {
const payload = normalizeCdItemDraftForPayload(cdItemDraftForm.value)
if (!payload.ItemCode) {
$q.notify({ type: 'negative', message: 'ItemCode bos olamaz.' })
return
}
store.setCdItemDraft(payload.ItemCode, payload)
cdItemDialogOpen.value = false
}
function createDummyAttributeRows () {
const sharedOptions = [
{ value: 'DAMATLIK', label: 'DAMATLIK - DAMATLIK' },
{ value: 'TAKIM', label: 'TAKIM - TAKIM ELBISE' },
{ value: 'CEKET', label: 'CEKET - CEKET' },
{ value: 'PANTOLON', label: 'PANTOLON - PANTOLON' }
]
const rows = [{
AttributeTypeCodeNumber: 1,
TypeLabel: '1-001 Urun Ana Grubu',
AttributeCode: '',
Options: sharedOptions
}]
for (let i = 2; i <= 50; i++) {
const code = String(i).padStart(3, '0')
rows.push({
AttributeTypeCodeNumber: i,
TypeLabel: `1-${code} Dummy Ozellik ${i}`,
AttributeCode: '',
Options: sharedOptions
})
}
return rows
}
function buildAttributeRowsFromLookup (list) {
const grouped = new Map()
for (const it of (list || [])) {
const typeCode = Number(it?.attribute_type_code || 0)
if (!typeCode) continue
if (!grouped.has(typeCode)) {
grouped.set(typeCode, {
typeCode,
typeDesc: String(it?.attribute_type_description || '').trim() || String(typeCode),
options: []
})
}
const g = grouped.get(typeCode)
const code = String(it?.attribute_code || '').trim()
const desc = String(it?.attribute_description || '').trim()
g.options.push({
value: code,
label: `${code} - ${desc || code}`
})
}
const rows = [...grouped.values()]
.sort((a, b) => a.typeCode - b.typeCode)
.map(g => ({
AttributeTypeCodeNumber: g.typeCode,
TypeLabel: `${g.typeCode}-${g.typeDesc}`,
AttributeCode: '',
Options: g.options
}))
return rows
}
async function openAttributeDialog (itemCode) {
const code = String(itemCode || '').trim().toUpperCase()
if (!code) return
attributeTargetCode.value = code
const existing = store.getProductAttributeDraft(code)
const fetched = await store.fetchProductAttributes(1)
const fromLookup = buildAttributeRowsFromLookup(fetched)
const baseRows = fromLookup.length ? fromLookup : createDummyAttributeRows()
attributeRows.value = Array.isArray(existing) && existing.length
? JSON.parse(JSON.stringify(existing))
: baseRows
attributeDialogOpen.value = true
}
function saveAttributeDraft () {
const code = String(attributeTargetCode.value || '').trim().toUpperCase()
if (!code) return
for (const row of (attributeRows.value || [])) {
const selected = String(row?.AttributeCode || '').trim()
if (!selected) {
$q.notify({ type: 'negative', message: `Urun ozelliklerinde secim zorunlu: ${row?.TypeLabel || ''}` })
return
}
}
store.setProductAttributeDraft(code, JSON.parse(JSON.stringify(attributeRows.value || [])))
attributeDialogOpen.value = false
$q.notify({ type: 'positive', message: 'Urun ozellikleri taslagi kaydedildi.' })
}
function collectProductAttributesFromSelectedRows (selectedRows) {
const codeSet = [...new Set(
(selectedRows || [])
.map(r => String(r?.NewItemCode || '').trim().toUpperCase())
.filter(Boolean)
)]
const out = []
for (const code of codeSet) {
const rows = store.getProductAttributeDraft(code)
if (!Array.isArray(rows) || !rows.length) {
return { errMsg: `${code} icin urun ozellikleri secilmedi`, productAttributes: [] }
}
for (const row of rows) {
const attributeTypeCode = Number(row?.AttributeTypeCodeNumber || 0)
const attributeCode = String(row?.AttributeCode || '').trim()
if (!attributeTypeCode || !attributeCode) {
return { errMsg: `${code} icin urun ozellikleri eksik`, productAttributes: [] }
}
out.push({
ItemTypeCode: 1,
ItemCode: code,
AttributeTypeCode: attributeTypeCode,
AttributeCode: attributeCode
})
}
}
return { errMsg: '', productAttributes: out }
}
function collectCdItemsFromSelectedRows (selectedRows) {
const codes = [...new Set(
(selectedRows || [])
.filter(r => r?.NewItemMode === 'new' && String(r?.NewItemCode || '').trim())
.map(r => String(r.NewItemCode).trim().toUpperCase())
)]
if (!codes.length) return { errMsg: '', cdItems: [] }
const out = []
for (const code of codes) {
const draft = store.getCdItemDraft(code)
if (!draft) {
return { errMsg: `${code} icin cdItem bilgisi eksik`, cdItems: [] }
}
out.push(normalizeCdItemDraftForPayload(draft))
}
return { errMsg: '', cdItems: out }
}
function buildMailLineLabelFromRow (row) { function buildMailLineLabelFromRow (row) {
const item = String(row?.NewItemCode || row?.OldItemCode || '').trim().toUpperCase() const item = String(row?.NewItemCode || row?.OldItemCode || '').trim().toUpperCase()
const color1 = String(row?.NewColor || row?.OldColor || '').trim().toUpperCase() const color1 = String(row?.NewColor || row?.OldColor || '').trim().toUpperCase()
@@ -516,14 +927,23 @@ function formatSizes (sizeMap) {
function groupItems (items, prevRows = []) { function groupItems (items, prevRows = []) {
const prevMap = new Map() const prevMap = new Map()
for (const r of prevRows || []) { for (const r of prevRows || []) {
if (r?.RowKey) prevMap.set(r.RowKey, String(r.NewDesc || '').trim()) if (!r?.RowKey) continue
prevMap.set(r.RowKey, {
NewDesc: String(r.NewDesc || '').trim(),
NewItemCode: String(r.NewItemCode || '').trim().toUpperCase(),
NewColor: String(r.NewColor || '').trim().toUpperCase(),
NewDim2: String(r.NewDim2 || '').trim().toUpperCase(),
NewItemMode: String(r.NewItemMode || '').trim(),
NewItemSource: String(r.NewItemSource || '').trim()
})
} }
const map = new Map() const map = new Map()
for (const it of items) { for (const it of items) {
const key = buildGroupKey(it) const key = buildGroupKey(it)
if (!map.has(key)) { if (!map.has(key)) {
const prevDesc = prevMap.get(key) || '' const prev = prevMap.get(key) || {}
const prevDesc = prev.NewDesc || ''
const fallbackDesc = String((it?.NewDesc || it?.OldDesc) || '').trim() const fallbackDesc = String((it?.NewDesc || it?.OldDesc) || '').trim()
map.set(key, { map.set(key, {
RowKey: key, RowKey: key,
@@ -536,10 +956,12 @@ function groupItems (items, prevRows = []) {
OrderLineIDs: [], OrderLineIDs: [],
OldSizes: [], OldSizes: [],
OldSizesLabel: '', OldSizesLabel: '',
NewItemCode: '', NewItemCode: prev.NewItemCode || '',
NewColor: '', NewColor: prev.NewColor || '',
NewDim2: '', NewDim2: prev.NewDim2 || '',
NewDesc: prevDesc || fallbackDesc, NewDesc: prevDesc || fallbackDesc,
NewItemMode: prev.NewItemMode || 'empty',
NewItemSource: prev.NewItemSource || '',
IsVariantMissing: !!it.IsVariantMissing IsVariantMissing: !!it.IsVariantMissing
}) })
} }
@@ -560,6 +982,10 @@ function groupItems (items, prevRows = []) {
const sizes = formatSizes(g.__sizeMap || {}) const sizes = formatSizes(g.__sizeMap || {})
g.OldSizes = sizes.list g.OldSizes = sizes.list
g.OldSizesLabel = sizes.label g.OldSizesLabel = sizes.label
const info = store.classifyItemCode(g.NewItemCode)
g.NewItemCode = info.normalized
g.NewItemMode = info.mode
if (info.mode === 'empty') g.NewItemSource = ''
delete g.__sizeMap delete g.__sizeMap
out.push(g) out.push(g)
} }
@@ -589,6 +1015,20 @@ async function onBulkSubmit () {
$q.notify({ type: 'negative', message: 'Secili satirlarda guncellenecek kayit bulunamadi.' }) $q.notify({ type: 'negative', message: 'Secili satirlarda guncellenecek kayit bulunamadi.' })
return return
} }
const { errMsg: cdErrMsg, cdItems } = collectCdItemsFromSelectedRows(selectedRows)
if (cdErrMsg) {
$q.notify({ type: 'negative', message: cdErrMsg })
const firstCode = String(cdErrMsg.split(' ')[0] || '').trim()
if (firstCode) openCdItemDialog(firstCode)
return
}
const { errMsg: attrErrMsg, productAttributes } = collectProductAttributesFromSelectedRows(selectedRows)
if (attrErrMsg) {
$q.notify({ type: 'negative', message: attrErrMsg })
const firstCode = String(attrErrMsg.split(' ')[0] || '').trim()
if (firstCode) openAttributeDialog(firstCode)
return
}
try { try {
const validate = await store.validateUpdates(orderHeaderID.value, lines) const validate = await store.validateUpdates(orderHeaderID.value, lines)
@@ -604,7 +1044,7 @@ async function onBulkSubmit () {
ok: { label: 'Ekle ve Guncelle', color: 'primary' }, ok: { label: 'Ekle ve Guncelle', color: 'primary' },
cancel: { label: 'Vazgec', flat: true } cancel: { label: 'Vazgec', flat: true }
}).onOk(async () => { }).onOk(async () => {
await store.applyUpdates(orderHeaderID.value, lines, true) await store.applyUpdates(orderHeaderID.value, lines, true, cdItems, productAttributes)
await store.fetchItems(orderHeaderID.value) await store.fetchItems(orderHeaderID.value)
selectedMap.value = {} selectedMap.value = {}
await sendUpdateMailAfterApply(selectedRows) await sendUpdateMailAfterApply(selectedRows)
@@ -612,7 +1052,7 @@ async function onBulkSubmit () {
return return
} }
await store.applyUpdates(orderHeaderID.value, lines, false) await store.applyUpdates(orderHeaderID.value, lines, false, cdItems, productAttributes)
await store.fetchItems(orderHeaderID.value) await store.fetchItems(orderHeaderID.value)
selectedMap.value = {} selectedMap.value = {}
await sendUpdateMailAfterApply(selectedRows) await sendUpdateMailAfterApply(selectedRows)
@@ -738,6 +1178,16 @@ async function onBulkSubmit () {
background: #e3f3ff; background: #e3f3ff;
} }
.prod-table :deep(.new-item-existing .q-field__control) {
background: #eaf9ef !important;
border-left: 3px solid #21ba45;
}
.prod-table :deep(.new-item-new .q-field__control) {
background: #fff5e9 !important;
border-left: 3px solid #f2a100;
}
.prod-table :deep(td.col-desc), .prod-table :deep(td.col-desc),
.prod-table :deep(th.col-desc), .prod-table :deep(th.col-desc),
.prod-table :deep(td.col-wrap), .prod-table :deep(td.col-wrap),

View File

@@ -2,40 +2,6 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import api from 'src/services/api' import api from 'src/services/api'
function normalizeTextForMatch (v) {
return String(v || '')
.trim()
.toUpperCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
}
// Production ekranlari icin beden grup tespiti helper'i.
// Ozel kural:
// YETISKIN/GARSON = GARSON ve URUN ANA GRUBU "GOMLEK ATA YAKA" veya "GOMLEK KLASIK" ise => yas
export function detectProductionBedenGroup (bedenList, urunAnaGrubu = '', urunKategori = '', yetiskinGarson = '') {
const list = Array.isArray(bedenList) ? bedenList : []
const hasLetterSizes = list
.map(v => String(v || '').trim().toUpperCase())
.some(v => ['XS', 'S', 'M', 'L', 'XL', '2XL', '3XL', '4XL', '5XL', '6XL', '7XL'].includes(v))
const ana = normalizeTextForMatch(urunAnaGrubu)
const kat = normalizeTextForMatch(urunKategori)
const yg = normalizeTextForMatch(yetiskinGarson)
if ((kat.includes('GARSON') || yg.includes('GARSON')) &&
(ana.includes('GOMLEK ATAYAKA') || ana.includes('GOMLEK ATA YAKA') || ana.includes('GOMLEK KLASIK'))) {
return 'yas'
}
if (hasLetterSizes) return 'gom'
if ((ana.includes('AYAKKABI') || kat.includes('AYAKKABI')) && (kat.includes('GARSON') || yg.includes('GARSON'))) return 'ayk_garson'
if (kat.includes('GARSON') || yg.includes('GARSON') || ana.includes('GARSON')) return 'yas'
if (ana.includes('PANTOLON') && kat.includes('YETISKIN')) return 'pan'
if (ana.includes('AKSESUAR')) return 'aksbir'
return 'tak'
}
function extractApiErrorMessage (err, fallback) { function extractApiErrorMessage (err, fallback) {
const data = err?.response?.data const data = err?.response?.data
if (typeof data === 'string' && data.trim()) return data if (typeof data === 'string' && data.trim()) return data
@@ -70,12 +36,40 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
products: [], products: [],
colorOptionsByCode: {}, colorOptionsByCode: {},
secondColorOptionsByKey: {}, secondColorOptionsByKey: {},
productAttributesByItemType: {},
cdItemLookups: null,
cdItemDraftsByCode: {},
productAttributeDraftsByCode: {},
loading: false, loading: false,
saving: false, saving: false,
error: null error: null
}), }),
getters: {
productCodeSet (state) {
const set = new Set()
for (const p of (state.products || [])) {
const code = String(p?.ProductCode || '').trim().toUpperCase()
if (code) set.add(code)
}
return set
}
},
actions: { actions: {
classifyItemCode (value) {
const normalized = String(value || '').trim().toUpperCase()
if (!normalized) {
return { normalized: '', mode: 'empty', exists: false }
}
const exists = this.productCodeSet.has(normalized)
return {
normalized,
mode: exists ? 'existing' : 'new',
exists
}
},
async fetchHeader (orderHeaderID) { async fetchHeader (orderHeaderID) {
if (!orderHeaderID) { if (!orderHeaderID) {
this.header = null this.header = null
@@ -166,6 +160,62 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
return [] return []
} }
}, },
async fetchProductAttributes (itemTypeCode = 1) {
const key = String(itemTypeCode || 1)
if (this.productAttributesByItemType[key]) {
return this.productAttributesByItemType[key]
}
try {
const res = await api.get('/product-attributes', { params: { itemTypeCode } })
const list = Array.isArray(res?.data) ? res.data : []
this.productAttributesByItemType[key] = list
return list
} catch (err) {
this.error = err?.response?.data || err?.message || 'Urun ozellikleri alinamadi'
return []
}
},
async fetchCdItemLookups (force = false) {
if (this.cdItemLookups && !force) return this.cdItemLookups
try {
const res = await api.get('/orders/production-items/cditem-lookups')
this.cdItemLookups = res?.data || null
return this.cdItemLookups
} catch (err) {
this.error = err?.response?.data || err?.message || 'cdItem lookup listesi alinamadi'
return null
}
},
setCdItemDraft (itemCode, draft) {
const code = String(itemCode || '').trim().toUpperCase()
if (!code) return
this.cdItemDraftsByCode = {
...this.cdItemDraftsByCode,
[code]: {
...(draft || {}),
ItemCode: code,
ItemTypeCode: Number(draft?.ItemTypeCode || 1)
}
}
},
getCdItemDraft (itemCode) {
const code = String(itemCode || '').trim().toUpperCase()
if (!code) return null
return this.cdItemDraftsByCode[code] || null
},
setProductAttributeDraft (itemCode, rows) {
const code = String(itemCode || '').trim().toUpperCase()
if (!code) return
this.productAttributeDraftsByCode = {
...this.productAttributeDraftsByCode,
[code]: Array.isArray(rows) ? rows : []
}
},
getProductAttributeDraft (itemCode) {
const code = String(itemCode || '').trim().toUpperCase()
if (!code) return []
return this.productAttributeDraftsByCode[code] || []
},
async validateUpdates (orderHeaderID, lines) { async validateUpdates (orderHeaderID, lines) {
if (!orderHeaderID) return { missingCount: 0, missing: [] } if (!orderHeaderID) return { missingCount: 0, missing: [] }
@@ -186,7 +236,7 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
this.saving = false this.saving = false
} }
}, },
async applyUpdates (orderHeaderID, lines, insertMissing) { async applyUpdates (orderHeaderID, lines, insertMissing, cdItems = [], productAttributes = []) {
if (!orderHeaderID) return { updated: 0, inserted: 0 } if (!orderHeaderID) return { updated: 0, inserted: 0 }
this.saving = true this.saving = true
@@ -195,7 +245,7 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
try { try {
const res = await api.post( const res = await api.post(
`/orders/production-items/${encodeURIComponent(orderHeaderID)}/apply`, `/orders/production-items/${encodeURIComponent(orderHeaderID)}/apply`,
{ lines, insertMissing } { lines, insertMissing, cdItems, productAttributes }
) )
return res?.data || { updated: 0, inserted: 0 } return res?.data || { updated: 0, inserted: 0 }
} catch (err) { } catch (err) {

View File

@@ -42,20 +42,21 @@ export function buildComboKey(row, beden) {
export const BEDEN_SCHEMA = [ const SIZE_GROUP_TITLES = {
{ key: 'tak', title: 'TAKIM ELBISE', values: ['44','46','48','50','52','54','56','58','60','62','64','66','68','70','72','74'] }, tak: 'TAKIM ELBISE',
{ key: 'ayk', title: 'AYAKKABI', values: ['39','40','41','42','43','44','45'] }, ayk: 'AYAKKABI',
{ key: 'ayk_garson', title: 'AYAKKABI GARSON', values: ['22','23','24','25','26','27','28','29','30','31','32','33','34','35','STD'] }, ayk_garson: 'AYAKKABI GARSON',
{ key: 'yas', title: 'YAS', values: ['2','4','6','8','10','12','14'] }, yas: 'YAS',
{ key: 'pan', title: 'PANTOLON', values: ['38','40','42','44','46','48','50','52','54','56','58','60','62','64','66','68'] }, pan: 'PANTOLON',
{ key: 'gom', title: 'GOMLEK', values: ['XS','S','M','L','XL','2XL','3XL','4XL','5XL','6XL','7XL'] }, gom: 'GOMLEK',
{ key: 'aksbir', title: 'AKSESUAR', values: [' ', '44', 'STD', '110', '115', '120', '125', '130', '135'] } aksbir: 'AKSESUAR'
] }
export const schemaByKey = BEDEN_SCHEMA.reduce((m, g) => { const FALLBACK_SCHEMA_MAP = {
m[g.key] = g tak: { key: 'tak', title: 'TAKIM ELBISE', values: ['44', '46', '48', '50', '52', '54', '56', '58', '60', '62', '64', '66', '68', '70', '72', '74'] }
return m }
}, {})
export const schemaByKey = { ...FALLBACK_SCHEMA_MAP }
const productSizeMatchCache = { const productSizeMatchCache = {
loaded: false, loaded: false,
@@ -111,6 +112,23 @@ function setProductSizeMatchCache(payload) {
productSizeMatchCache.schemas = normalizedSchemas productSizeMatchCache.schemas = normalizedSchemas
} }
function buildSchemaMapFromCacheSchemas() {
const out = {}
const src = productSizeMatchCache.schemas || {}
for (const [keyRaw, valuesRaw] of Object.entries(src)) {
const key = String(keyRaw || '').trim()
if (!key) continue
const values = Array.isArray(valuesRaw) ? valuesRaw : []
out[key] = {
key,
title: SIZE_GROUP_TITLES[key] || key.toUpperCase(),
values: values.map(v => String(v == null ? '' : v))
}
}
if (!out.tak) out.tak = { ...FALLBACK_SCHEMA_MAP.tak }
return out
}
export const stockMap = ref({}) export const stockMap = ref({})
export const bedenStock = ref([]) export const bedenStock = ref([])
@@ -257,24 +275,16 @@ export const useOrderEntryStore = defineStore('orderentry', {
, ,
/* =========================================================== /* ===========================================================
🧩 initSchemaMap — BEDEN ŞEMA İNİT 🧩 initSchemaMap — BEDEN ŞEMA İNİT
- TEK SOURCE OF TRUTH: BEDEN_SCHEMA - TEK SOURCE OF TRUTH: SQL mk_size_group (cache)
=========================================================== */ =========================================================== */
initSchemaMap() { initSchemaMap() {
if (this.schemaMap && Object.keys(this.schemaMap).length > 0) { if (this.schemaMap && Object.keys(this.schemaMap).length > 0) {
return return
} }
this.schemaMap = buildSchemaMapFromCacheSchemas()
const map = {} if (!Object.keys(this.schemaMap).length) {
this.schemaMap = { ...FALLBACK_SCHEMA_MAP }
for (const g of BEDEN_SCHEMA) {
map[g.key] = {
key: g.key,
title: g.title,
values: [...g.values]
} }
}
this.schemaMap = map
console.log( console.log(
'🧩 schemaMap INIT edildi:', '🧩 schemaMap INIT edildi:',
@@ -284,17 +294,20 @@ export const useOrderEntryStore = defineStore('orderentry', {
async ensureProductSizeMatchRules($q = null, force = false) { async ensureProductSizeMatchRules($q = null, force = false) {
if (!force && productSizeMatchCache.loaded && productSizeMatchCache.rules.length > 0) { if (!force && productSizeMatchCache.loaded && productSizeMatchCache.rules.length > 0) {
this.schemaMap = buildSchemaMapFromCacheSchemas()
return true return true
} }
try { try {
const res = await api.get('/product-size-match/rules') const res = await api.get('/product-size-match/rules')
setProductSizeMatchCache(res?.data || {}) setProductSizeMatchCache(res?.data || {})
this.schemaMap = buildSchemaMapFromCacheSchemas()
return true return true
} catch (err) { } catch (err) {
if (force) { if (force) {
resetProductSizeMatchCache() resetProductSizeMatchCache()
} }
this.schemaMap = { ...FALLBACK_SCHEMA_MAP }
console.warn('⚠ product-size-match rules alınamadı:', err) console.warn('⚠ product-size-match rules alınamadı:', err)
$q?.notify?.({ $q?.notify?.({
type: 'warning', type: 'warning',
@@ -4044,6 +4057,8 @@ export function toSummaryRowFromForm(form) {
urunAnaGrubu: form.urunAnaGrubu || '', urunAnaGrubu: form.urunAnaGrubu || '',
urunAltGrubu: form.urunAltGrubu || '', urunAltGrubu: form.urunAltGrubu || '',
kategori: form.kategori || '',
yetiskinGarson: form.yetiskinGarson || form.askiliyan || '',
aciklama: form.aciklama || '', aciklama: form.aciklama || '',
fiyat: Number(form.fiyat || 0), fiyat: Number(form.fiyat || 0),