Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
}
|
||||||
|
|||||||
9
svc/models/productattributes.go
Normal file
9
svc/models/productattributes.go
Normal 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"`
|
||||||
|
}
|
||||||
@@ -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"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
42
svc/queries/productattributes.go
Normal file
42
svc/queries/productattributes.go
Normal 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;
|
||||||
|
`
|
||||||
@@ -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
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
54
svc/routes/productattributes.go
Normal file
54
svc/routes/productattributes.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
75
ui/.quasar/prod-spa/app.js
Normal file
75
ui/.quasar/prod-spa/app.js
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
154
ui/.quasar/prod-spa/client-entry.js
Normal file
154
ui/.quasar/prod-spa/client-entry.js
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
116
ui/.quasar/prod-spa/client-prefetch.js
Normal file
116
ui/.quasar/prod-spa/client-prefetch.js
Normal 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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
23
ui/.quasar/prod-spa/quasar-user-options.js
Normal file
23
ui/.quasar/prod-spa/quasar-user-options.js
Normal 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} }
|
||||||
|
|
||||||
@@ -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'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
Reference in New Issue
Block a user