diff --git a/svc/main.go b/svc/main.go index c9f1376..a8721ec 100644 --- a/svc/main.go +++ b/svc/main.go @@ -561,6 +561,18 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router wrapV3(http.HandlerFunc(routes.GetProductStockQueryHandler)), ) + bindV3(r, pgDB, + "/api/product-stock-attribute-options", "GET", + "order", "view", + wrapV3(http.HandlerFunc(routes.GetProductStockAttributeOptionsHandler)), + ) + + bindV3(r, pgDB, + "/api/product-stock-query-by-attributes", "GET", + "order", "view", + wrapV3(http.HandlerFunc(routes.GetProductStockQueryByAttributesHandler)), + ) + bindV3(r, pgDB, "/api/product-images", "GET", "order", "view", diff --git a/svc/queries/orderproduction_items.go b/svc/queries/orderproduction_items.go index e95b2a2..012f82c 100644 --- a/svc/queries/orderproduction_items.go +++ b/svc/queries/orderproduction_items.go @@ -2,6 +2,7 @@ package queries import ( "database/sql" + "strconv" "bssapp-backend/models" ) @@ -156,7 +157,16 @@ FROM dbo.prItemVariant WITH (UPDLOCK, HOLDLOCK) } var inserted int64 + ensuredItems := make(map[string]struct{}, len(missing)) for i, v := range missing { + itemKey := strconv.FormatInt(int64(v.ItemTypeCode), 10) + "|" + v.ItemCode + if _, ok := ensuredItems[itemKey]; !ok { + if err := ensureCdItemTx(tx, v.ItemTypeCode, v.ItemCode, username); err != nil { + return inserted, err + } + ensuredItems[itemKey] = struct{}{} + } + plu := basePlu + int64(i) + 1 res, err := tx.Exec(` IF NOT EXISTS ( @@ -197,6 +207,93 @@ VALUES ( return inserted, nil } +func ensureCdItemTx(tx *sql.Tx, itemTypeCode int16, itemCode string, username string) error { + _, err := tx.Exec(` +IF NOT EXISTS ( + SELECT 1 + FROM dbo.cdItem + WHERE ItemTypeCode = @p1 + AND ItemCode = @p2 +) +BEGIN + ;WITH Template AS ( + SELECT TOP 1 + ItemDimTypeCode, ProductTypeCode, ProductHierarchyID, + UnitOfMeasureCode1, UnitOfMeasureCode2, UnitConvertRate, UnitConvertRateNotFixed, + UseInternet, UsePOS, UseStore, EnablePartnerCompanies, UseManufacturing, UseSerialNumber, + GenerateOpticalDataMatrixCode, ByWeight, SupplyPeriod, GuaranteePeriod, ShelfLife, OrderLeadTime, + ItemAccountGrCode, ItemTaxGrCode, ItemPaymentPlanGrCode, ItemDiscountGrCode, ItemVendorGrCode, + PromotionGroupCode, PromotionGroupCode2, ProductCollectionGrCode, StorePriceLevelCode, PerceptionOfFashionCode, + CommercialRoleCode, StoreCapacityLevelCode, CustomsTariffNumberCode, IsFixedExpense, BOMEntityCode, CompanyCode, + IsBlocked, IsLocked, LockedDate, IsSalesOrderClosed, IsPurchaseOrderClosed, UseRoll, UseBatch, + MaxCreditCardInstallmentCount, GenerateSerialNumber, IsSubsequentDeliveryForR, IsSubsequentDeliveryForRI, + IGACommissionGroup, UniFreeCommissionGroup, CustomsProductGroupCode, IsUTSDeclaratedItem, IsStoreOrderClosed + FROM dbo.cdItem WITH (UPDLOCK, HOLDLOCK) + WHERE ItemTypeCode = @p1 + AND ItemCode LIKE 'U%' + ORDER BY CreatedDate DESC + ) + INSERT INTO dbo.cdItem ( + ItemTypeCode, ItemCode, + ItemDimTypeCode, ProductTypeCode, ProductHierarchyID, + UnitOfMeasureCode1, UnitOfMeasureCode2, UnitConvertRate, UnitConvertRateNotFixed, + UseInternet, UsePOS, UseStore, EnablePartnerCompanies, UseManufacturing, UseSerialNumber, + GenerateOpticalDataMatrixCode, ByWeight, SupplyPeriod, GuaranteePeriod, ShelfLife, OrderLeadTime, + 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, + UseRoll, UseBatch, MaxCreditCardInstallmentCount, GenerateSerialNumber, + IsSubsequentDeliveryForR, IsSubsequentDeliveryForRI, + IGACommissionGroup, UniFreeCommissionGroup, CustomsProductGroupCode, IsUTSDeclaratedItem, IsStoreOrderClosed + ) + SELECT + @p1, @p2, + t.ItemDimTypeCode, t.ProductTypeCode, t.ProductHierarchyID, + t.UnitOfMeasureCode1, t.UnitOfMeasureCode2, t.UnitConvertRate, t.UnitConvertRateNotFixed, + t.UseInternet, t.UsePOS, t.UseStore, t.EnablePartnerCompanies, t.UseManufacturing, t.UseSerialNumber, + t.GenerateOpticalDataMatrixCode, t.ByWeight, t.SupplyPeriod, t.GuaranteePeriod, t.ShelfLife, t.OrderLeadTime, + t.ItemAccountGrCode, t.ItemTaxGrCode, t.ItemPaymentPlanGrCode, t.ItemDiscountGrCode, t.ItemVendorGrCode, + t.PromotionGroupCode, t.PromotionGroupCode2, t.ProductCollectionGrCode, t.StorePriceLevelCode, t.PerceptionOfFashionCode, + t.CommercialRoleCode, t.StoreCapacityLevelCode, t.CustomsTariffNumberCode, t.IsFixedExpense, t.BOMEntityCode, t.CompanyCode, + t.IsBlocked, t.IsLocked, t.LockedDate, t.IsSalesOrderClosed, t.IsPurchaseOrderClosed, + @p3, GETDATE(), @p3, GETDATE(), NEWID(), + t.UseRoll, t.UseBatch, t.MaxCreditCardInstallmentCount, t.GenerateSerialNumber, + t.IsSubsequentDeliveryForR, t.IsSubsequentDeliveryForRI, + t.IGACommissionGroup, t.UniFreeCommissionGroup, t.CustomsProductGroupCode, t.IsUTSDeclaratedItem, t.IsStoreOrderClosed + FROM Template t; + + IF @@ROWCOUNT = 0 + BEGIN + INSERT INTO dbo.cdItem ( + ItemTypeCode, ItemCode, + ItemDimTypeCode, ProductTypeCode, ProductHierarchyID, + UnitOfMeasureCode1, UnitConvertRate, UnitConvertRateNotFixed, + UseInternet, UsePOS, UseStore, EnablePartnerCompanies, UseManufacturing, UseSerialNumber, + GenerateOpticalDataMatrixCode, ByWeight, SupplyPeriod, GuaranteePeriod, ShelfLife, OrderLeadTime, + IsFixedExpense, IsBlocked, IsLocked, LockedDate, IsSalesOrderClosed, IsPurchaseOrderClosed, + CreatedUserName, CreatedDate, LastUpdatedUserName, LastUpdatedDate, RowGuid, + UseRoll, UseBatch, MaxCreditCardInstallmentCount, GenerateSerialNumber, + IsSubsequentDeliveryForR, IsSubsequentDeliveryForRI, IsUTSDeclaratedItem, IsStoreOrderClosed + ) + VALUES ( + @p1, @p2, + 2, 1, 2, + 'AD', 0, 0, + 0, 1, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, '1900-01-01', 0, 0, + @p3, GETDATE(), @p3, GETDATE(), NEWID(), + 0, 0, 12, 0, + 0, 0, 0, 0 + ); + END +END +`, itemTypeCode, itemCode, username) + return err +} + func UpdateOrderLinesTx(tx *sql.Tx, orderHeaderID string, lines []models.OrderProductionUpdateLine, username string) (int64, error) { var updated int64 for _, line := range lines { diff --git a/svc/queries/productstockquery.go b/svc/queries/productstockquery.go index d84f27b..8504bab 100644 --- a/svc/queries/productstockquery.go +++ b/svc/queries/productstockquery.go @@ -1,11 +1,11 @@ package queries // GetProductStockQuery: -// Ürün kodu bazlı, optimize stok + attribute sorgusu. +// Urun kodu bazli, STOCK/PICK/RESERVE/DISP ayrik CTE ile optimize sorgu. const GetProductStockQuery = ` DECLARE @ProductCode NVARCHAR(50) = @p1; -;WITH INV AS +;WITH STOCK AS ( SELECT CompanyCode, @@ -19,180 +19,216 @@ DECLARE @ProductCode NVARCHAR(50) = @p1; ItemDim1Code, ItemDim2Code, ItemDim3Code, - SUM(PickingQty1) AS PickingQty1, - SUM(ReserveQty1) AS ReserveQty1, - SUM(DispOrderQty1) AS DispOrderQty1, - SUM(InventoryQty1) AS InventoryQty1 - FROM - ( - SELECT - CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode, - ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, - Qty1 AS PickingQty1, 0 AS ReserveQty1, 0 AS DispOrderQty1, 0 AS InventoryQty1 - FROM PickingStates - WHERE ItemTypeCode = 1 - AND ItemCode = @ProductCode - AND LEN(ItemCode) = 13 - AND LEN(@ProductCode) = 13 - - UNION ALL - SELECT - CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode, - ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, - 0, Qty1, 0, 0 - FROM ReserveStates - WHERE ItemTypeCode = 1 - AND ItemCode = @ProductCode - AND LEN(ItemCode) = 13 - AND LEN(@ProductCode) = 13 - - UNION ALL - SELECT - CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode, - ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, - 0, 0, Qty1, 0 - FROM DispOrderStates - WHERE ItemTypeCode = 1 - AND ItemCode = @ProductCode - AND LEN(ItemCode) = 13 - AND LEN(@ProductCode) = 13 - - UNION ALL - SELECT - CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode, - ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, - 0, 0, 0, SUM(In_Qty1 - Out_Qty1) - FROM trStock WITH (NOLOCK) - WHERE ItemTypeCode = 1 - AND ItemCode = @ProductCode - AND LEN(ItemCode) = 13 - AND LEN(@ProductCode) = 13 - GROUP BY - CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode, - ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code - ) X - GROUP BY - CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode, - ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code -), -Attr AS -( - SELECT TOP 1 - ProductCode, - ProductDescription, - ProductAtt01Desc, - ProductAtt02Desc, - ProductAtt10Desc, - ProductAtt11Desc, - ProductAtt21Desc, - ProductAtt22Desc, - ProductAtt23Desc, - ProductAtt24Desc, - ProductAtt25Desc, - ProductAtt26Desc, - ProductAtt27Desc, - ProductAtt28Desc, - ProductAtt29Desc, - ProductAtt30Desc, - ProductAtt31Desc, - ProductAtt32Desc, - ProductAtt33Desc, - ProductAtt34Desc, - ProductAtt35Desc, - ProductAtt36Desc, - ProductAtt37Desc, - ProductAtt38Desc, - ProductAtt39Desc, - ProductAtt40Desc, - ProductAtt41Desc, - ProductAtt42Desc, - ProductAtt43Desc, - ProductAtt44Desc, - ProductAtt45Desc, - ProductAtt46Desc - FROM ProductFilterWithDescription('TR') - WHERE ProductCode = @ProductCode - AND LEN(ProductCode) = 13 - AND LEN(@ProductCode) = 13 -), -Price AS -( - SELECT TOP 1 - Price - FROM prItemBasePrice WITH (NOLOCK) + SUM(In_Qty1 - Out_Qty1) AS InventoryQty1 + FROM trStock WITH(NOLOCK) WHERE ItemTypeCode = 1 AND ItemCode = @ProductCode AND LEN(ItemCode) = 13 AND LEN(@ProductCode) = 13 - ORDER BY PriceDate DESC + GROUP BY + CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode, + ItemTypeCode, ItemCode, ColorCode, + ItemDim1Code, ItemDim2Code, ItemDim3Code +), + +PICK AS +( + SELECT + CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode, + ItemTypeCode, ItemCode, ColorCode, + ItemDim1Code, ItemDim2Code, ItemDim3Code, + SUM(Qty1) AS PickingQty1 + FROM PickingStates + WHERE ItemTypeCode = 1 + AND ItemCode = @ProductCode + AND LEN(ItemCode) = 13 + AND LEN(@ProductCode) = 13 + GROUP BY + CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode, + ItemTypeCode, ItemCode, ColorCode, + ItemDim1Code, ItemDim2Code, ItemDim3Code +), + +RESERVE AS +( + SELECT + CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode, + ItemTypeCode, ItemCode, ColorCode, + ItemDim1Code, ItemDim2Code, ItemDim3Code, + SUM(Qty1) AS ReserveQty1 + FROM ReserveStates + WHERE ItemTypeCode = 1 + AND ItemCode = @ProductCode + AND LEN(ItemCode) = 13 + AND LEN(@ProductCode) = 13 + GROUP BY + CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode, + ItemTypeCode, ItemCode, ColorCode, + ItemDim1Code, ItemDim2Code, ItemDim3Code +), + +DISP AS +( + SELECT + CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode, + ItemTypeCode, ItemCode, ColorCode, + ItemDim1Code, ItemDim2Code, ItemDim3Code, + SUM(Qty1) AS DispOrderQty1 + FROM DispOrderStates + WHERE ItemTypeCode = 1 + AND ItemCode = @ProductCode + AND LEN(ItemCode) = 13 + AND LEN(@ProductCode) = 13 + GROUP BY + CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode, + ItemTypeCode, ItemCode, ColorCode, + ItemDim1Code, ItemDim2Code, ItemDim3Code ) + SELECT - I.WarehouseCode AS Depo_Kodu, + S.WarehouseCode AS Depo_Kodu, W.WarehouseDescription AS Depo_Adi, - IT.ItemTypeDescription AS InventoryType, - I.ItemCode AS Urun_Kodu, - A.ProductDescription AS Madde_Aciklamasi, - I.ColorCode AS Renk_Kodu, + + bsItemTypeDesc.ItemTypeDescription AS InventoryType, + + S.ItemCode AS Urun_Kodu, + P.ProductDescription AS Madde_Aciklamasi, + + S.ColorCode AS Renk_Kodu, C.ColorDescription AS Renk_Aciklamasi, - I.ItemDim1Code AS Beden, - I.ItemDim2Code AS Yaka, - ROUND(I.InventoryQty1 - I.PickingQty1 - I.ReserveQty1 - I.DispOrderQty1, U.RoundDigit) AS Kullanilabilir_Envanter, - A.ProductAtt01Desc AS URUN_ANA_GRUBU, - A.ProductAtt02Desc AS URUN_ALT_GRUBU, - A.ProductAtt10Desc AS MARKA, - A.ProductAtt11Desc AS DR, - A.ProductAtt21Desc AS KALIP, - A.ProductAtt22Desc AS IKINCI_PARCA_KALIP, - A.ProductAtt23Desc AS PACA_GENISLIGI, - A.ProductAtt24Desc AS UCUNCU_PARCA_KALIP, - A.ProductAtt25Desc AS UCUNCU_PARCA_MODEL, - A.ProductAtt26Desc AS BIRINCI_PARCA_KUMAS, - A.ProductAtt27Desc AS IKINCI_PARCA_KUMAS, - A.ProductAtt28Desc AS UCUNCU_PARCA_KUMAS, - A.ProductAtt29Desc AS BIRINCI_PARCA_KARISIM, - A.ProductAtt30Desc AS IKINCI_PARCA_KARISIM, - A.ProductAtt31Desc AS UCUNCU_PARCA_KARISIM, - A.ProductAtt32Desc AS YAKA_TIPI, - A.ProductAtt33Desc AS DUGME, - A.ProductAtt34Desc AS YIRTMAC, - A.ProductAtt35Desc AS SEZON_YILI, - A.ProductAtt36Desc AS MEVSIM, - A.ProductAtt37Desc AS TABAN, - A.ProductAtt38Desc AS BIRINCI_PARCA_FIT, - A.ProductAtt39Desc AS IKINCI_PARCA_FIT, - A.ProductAtt40Desc AS BOS2, - A.ProductAtt41Desc AS KISA_KAR, - A.ProductAtt42Desc AS SERI_FASON, - A.ProductAtt43Desc AS STOK_GIRIS_YONTEMI, - A.ProductAtt44Desc AS YETISKIN_GARSON, - A.ProductAtt45Desc AS ASKILI_YAN, - A.ProductAtt46Desc AS BOS3, - P.Price AS Fiyat -FROM INV I -JOIN cdItem CI WITH (NOLOCK) - ON CI.ItemTypeCode = I.ItemTypeCode - AND CI.ItemCode = I.ItemCode -LEFT JOIN cdUnitOfMeasure U WITH (NOLOCK) - ON U.UnitOfMeasureCode = CI.UnitOfMeasureCode1 -LEFT JOIN cdWarehouseDesc W WITH (NOLOCK) - ON W.WarehouseCode = I.WarehouseCode - AND W.LangCode = 'TR' -LEFT JOIN bsItemTypeDesc IT WITH (NOLOCK) - ON IT.ItemTypeCode = I.ItemTypeCode - AND IT.LangCode = 'TR' -LEFT JOIN cdColorDesc C WITH (NOLOCK) - ON C.ColorCode = I.ColorCode - AND C.LangCode = 'TR' -CROSS JOIN Attr A -OUTER APPLY (SELECT TOP 1 Price FROM Price) P + + S.ItemDim1Code AS Beden, + S.ItemDim2Code AS Yaka, + + ROUND( + S.InventoryQty1 + - ISNULL(PK.PickingQty1,0) + - ISNULL(RS.ReserveQty1,0) + - ISNULL(DP.DispOrderQty1,0), + cdUnitOfMeasure.RoundDigit + ) AS Kullanilabilir_Envanter, + + P.ProductAtt01Desc AS URUN_ANA_GRUBU, + P.ProductAtt02Desc AS URUN_ALT_GRUBU, + P.ProductAtt10Desc AS MARKA, + P.ProductAtt11Desc AS DR, + P.ProductAtt21Desc AS KALIP, + P.ProductAtt22Desc AS IKINCI_PARCA_KALIP, + P.ProductAtt23Desc AS PACA_GENISLIGI, + P.ProductAtt24Desc AS UCUNCU_PARCA_KALIP, + P.ProductAtt25Desc AS UCUNCU_PARCA_MODEL, + P.ProductAtt26Desc AS BIRINCI_PARCA_KUMAS, + P.ProductAtt27Desc AS IKINCI_PARCA_KUMAS, + P.ProductAtt28Desc AS UCUNCU_PARCA_KUMAS, + P.ProductAtt29Desc AS BIRINCI_PARCA_KARISIM, + P.ProductAtt30Desc AS IKINCI_PARCA_KARISIM, + P.ProductAtt31Desc AS UCUNCU_PARCA_KARISIM, + P.ProductAtt32Desc AS YAKA_TIPI, + P.ProductAtt33Desc AS DUGME, + P.ProductAtt34Desc AS YIRTMAC, + P.ProductAtt35Desc AS SEZON_YILI, + P.ProductAtt36Desc AS MEVSIM, + P.ProductAtt37Desc AS TABAN, + P.ProductAtt38Desc AS BIRINCI_PARCA_FIT, + P.ProductAtt39Desc AS IKINCI_PARCA_FIT, + P.ProductAtt40Desc AS BOS2, + P.ProductAtt41Desc AS KISA_KAR, + P.ProductAtt42Desc AS SERI_FASON, + P.ProductAtt43Desc AS STOK_GIRIS_YONTEMI, + P.ProductAtt44Desc AS YETISKIN_GARSON, + P.ProductAtt45Desc AS ASKILI_YAN, + P.ProductAtt46Desc AS BOS3, + + prFilteredBasePrice.Price AS Fiyat + +FROM STOCK S + +LEFT JOIN PICK PK +ON PK.CompanyCode=S.CompanyCode +AND PK.OfficeCode=S.OfficeCode +AND PK.StoreTypeCode=S.StoreTypeCode +AND PK.StoreCode=S.StoreCode +AND PK.WarehouseCode=S.WarehouseCode +AND PK.ItemTypeCode=S.ItemTypeCode +AND PK.ItemCode=S.ItemCode +AND PK.ColorCode=S.ColorCode +AND PK.ItemDim1Code=S.ItemDim1Code +AND PK.ItemDim2Code=S.ItemDim2Code +AND PK.ItemDim3Code=S.ItemDim3Code + +LEFT JOIN RESERVE RS +ON RS.CompanyCode=S.CompanyCode +AND RS.OfficeCode=S.OfficeCode +AND RS.StoreTypeCode=S.StoreTypeCode +AND RS.StoreCode=S.StoreCode +AND RS.WarehouseCode=S.WarehouseCode +AND RS.ItemTypeCode=S.ItemTypeCode +AND RS.ItemCode=S.ItemCode +AND RS.ColorCode=S.ColorCode +AND RS.ItemDim1Code=S.ItemDim1Code +AND RS.ItemDim2Code=S.ItemDim2Code +AND RS.ItemDim3Code=S.ItemDim3Code + +LEFT JOIN DISP DP +ON DP.CompanyCode=S.CompanyCode +AND DP.OfficeCode=S.OfficeCode +AND DP.StoreTypeCode=S.StoreTypeCode +AND DP.StoreCode=S.StoreCode +AND DP.WarehouseCode=S.WarehouseCode +AND DP.ItemTypeCode=S.ItemTypeCode +AND DP.ItemCode=S.ItemCode +AND DP.ColorCode=S.ColorCode +AND DP.ItemDim1Code=S.ItemDim1Code +AND DP.ItemDim2Code=S.ItemDim2Code +AND DP.ItemDim3Code=S.ItemDim3Code + +JOIN cdItem WITH(NOLOCK) +ON S.ItemCode = cdItem.ItemCode +AND S.ItemTypeCode = cdItem.ItemTypeCode + +LEFT JOIN cdUnitOfMeasure WITH(NOLOCK) +ON cdItem.UnitOfMeasureCode1 = cdUnitOfMeasure.UnitOfMeasureCode + +LEFT JOIN ProductFilterWithDescription('TR') P +ON P.ProductCode = S.ItemCode + +LEFT JOIN bsItemTypeDesc WITH(NOLOCK) +ON bsItemTypeDesc.ItemTypeCode = S.ItemTypeCode +AND bsItemTypeDesc.LangCode='TR' + +LEFT JOIN cdWarehouseDesc W WITH(NOLOCK) +ON W.WarehouseCode = S.WarehouseCode +AND W.LangCode='TR' + +LEFT JOIN cdColorDesc C WITH(NOLOCK) +ON C.ColorCode = S.ColorCode +AND C.LangCode='TR' + +LEFT JOIN ( + SELECT + ItemCode, + ItemTypeCode, + Price, + ROW_NUMBER() OVER (PARTITION BY ItemCode, ItemTypeCode ORDER BY PriceDate DESC) AS RowNum + FROM prItemBasePrice WITH(NOLOCK) +) prFilteredBasePrice +ON prFilteredBasePrice.ItemCode = S.ItemCode +AND prFilteredBasePrice.ItemTypeCode = S.ItemTypeCode +AND prFilteredBasePrice.RowNum = 1 + WHERE - I.ItemTypeCode = 1 - AND I.ItemCode = @ProductCode - AND LEN(I.ItemCode) = 13 + S.ItemTypeCode IN (1) + AND S.ItemCode = @ProductCode + AND LEN(S.ItemCode) = 13 AND LEN(@ProductCode) = 13 - AND (I.InventoryQty1 - I.PickingQty1 - I.ReserveQty1 - I.DispOrderQty1) > 0 - AND CI.IsBlocked = 0 - AND I.WarehouseCode IN + AND ( + S.InventoryQty1 + - ISNULL(PK.PickingQty1,0) + - ISNULL(RS.ReserveQty1,0) + - ISNULL(DP.DispOrderQty1,0) + ) > 0 + AND cdItem.IsBlocked = 0 + AND S.WarehouseCode IN ( '1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28', '1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3', diff --git a/svc/queries/productstockquery_by_attributes.go b/svc/queries/productstockquery_by_attributes.go new file mode 100644 index 0000000..9448280 --- /dev/null +++ b/svc/queries/productstockquery_by_attributes.go @@ -0,0 +1,239 @@ +package queries + +// GetProductStockAttributeOptionsQuery: +// Urun ozellik filtre secenekleri (distinct aciklamalar). +const GetProductStockAttributeOptionsQuery = ` +WITH PF AS +( + SELECT + LTRIM(RTRIM(ProductAtt01Desc)) AS ProductAtt01Desc, + LTRIM(RTRIM(ProductAtt02Desc)) AS ProductAtt02Desc, + LTRIM(RTRIM(ProductAtt10Desc)) AS ProductAtt10Desc, + LTRIM(RTRIM(ProductAtt11Desc)) AS ProductAtt11Desc, + LTRIM(RTRIM(ProductAtt21Desc)) AS ProductAtt21Desc, + LTRIM(RTRIM(ProductAtt35Desc)) AS ProductAtt35Desc, + LTRIM(RTRIM(ProductAtt36Desc)) AS ProductAtt36Desc, + LTRIM(RTRIM(ProductAtt44Desc)) AS ProductAtt44Desc + FROM ProductFilterWithDescription('TR') + WHERE LEN(ProductCode) = 13 +) +SELECT 'att01' AS FieldName, ProductAtt01Desc AS FieldValue FROM PF WHERE ProductAtt01Desc <> '' GROUP BY ProductAtt01Desc +UNION ALL +SELECT 'att02', ProductAtt02Desc FROM PF WHERE ProductAtt02Desc <> '' GROUP BY ProductAtt02Desc +UNION ALL +SELECT 'att10', ProductAtt10Desc FROM PF WHERE ProductAtt10Desc <> '' GROUP BY ProductAtt10Desc +UNION ALL +SELECT 'att11', ProductAtt11Desc FROM PF WHERE ProductAtt11Desc <> '' GROUP BY ProductAtt11Desc +UNION ALL +SELECT 'att21', ProductAtt21Desc FROM PF WHERE ProductAtt21Desc <> '' GROUP BY ProductAtt21Desc +UNION ALL +SELECT 'att35', ProductAtt35Desc FROM PF WHERE ProductAtt35Desc <> '' GROUP BY ProductAtt35Desc +UNION ALL +SELECT 'att36', ProductAtt36Desc FROM PF WHERE ProductAtt36Desc <> '' GROUP BY ProductAtt36Desc +UNION ALL +SELECT 'att44', ProductAtt44Desc FROM PF WHERE ProductAtt44Desc <> '' GROUP BY ProductAtt44Desc; +` + +// GetProductStockQueryByAttributes: +// Urun ozelliklerine gore stok detay sorgusu. +const GetProductStockQueryByAttributes = ` +DECLARE @Att01 NVARCHAR(100) = NULLIF(LTRIM(RTRIM(@p1)), ''); +DECLARE @Att02 NVARCHAR(100) = NULLIF(LTRIM(RTRIM(@p2)), ''); +DECLARE @Att10 NVARCHAR(100) = NULLIF(LTRIM(RTRIM(@p3)), ''); +DECLARE @Att11 NVARCHAR(100) = NULLIF(LTRIM(RTRIM(@p4)), ''); +DECLARE @Att21 NVARCHAR(100) = NULLIF(LTRIM(RTRIM(@p5)), ''); +DECLARE @Att35 NVARCHAR(100) = NULLIF(LTRIM(RTRIM(@p6)), ''); +DECLARE @Att36 NVARCHAR(100) = NULLIF(LTRIM(RTRIM(@p7)), ''); +DECLARE @Att44 NVARCHAR(100) = NULLIF(LTRIM(RTRIM(@p8)), ''); + +;WITH AttrFiltered AS +( + SELECT + ProductCode, + ProductDescription, + ProductAtt01Desc, + ProductAtt02Desc, + ProductAtt10Desc, + ProductAtt11Desc, + ProductAtt21Desc, + ProductAtt22Desc, + ProductAtt23Desc, + ProductAtt24Desc, + ProductAtt25Desc, + ProductAtt26Desc, + ProductAtt27Desc, + ProductAtt28Desc, + ProductAtt29Desc, + ProductAtt30Desc, + ProductAtt31Desc, + ProductAtt32Desc, + ProductAtt33Desc, + ProductAtt34Desc, + ProductAtt35Desc, + ProductAtt36Desc, + ProductAtt37Desc, + ProductAtt38Desc, + ProductAtt39Desc, + ProductAtt40Desc, + ProductAtt41Desc, + ProductAtt42Desc, + ProductAtt43Desc, + ProductAtt44Desc, + ProductAtt45Desc, + ProductAtt46Desc + FROM ProductFilterWithDescription('TR') + WHERE LEN(ProductCode) = 13 + AND (@Att01 IS NULL OR ProductAtt01Desc = @Att01) + AND (@Att02 IS NULL OR ProductAtt02Desc = @Att02) + AND (@Att10 IS NULL OR ProductAtt10Desc = @Att10) + AND (@Att11 IS NULL OR ProductAtt11Desc = @Att11) + AND (@Att21 IS NULL OR ProductAtt21Desc = @Att21) + AND (@Att35 IS NULL OR ProductAtt35Desc = @Att35) + AND (@Att36 IS NULL OR ProductAtt36Desc = @Att36) + AND (@Att44 IS NULL OR ProductAtt44Desc = @Att44) +), +INV AS +( + SELECT + X.CompanyCode, + X.OfficeCode, + X.StoreTypeCode, + X.StoreCode, + X.WarehouseCode, + X.ItemTypeCode, + X.ItemCode, + X.ColorCode, + X.ItemDim1Code, + X.ItemDim2Code, + X.ItemDim3Code, + SUM(X.PickingQty1) AS PickingQty1, + SUM(X.ReserveQty1) AS ReserveQty1, + SUM(X.DispOrderQty1) AS DispOrderQty1, + SUM(X.InventoryQty1) AS InventoryQty1 + FROM + ( + SELECT + P.CompanyCode, P.OfficeCode, P.StoreTypeCode, P.StoreCode, P.WarehouseCode, + P.ItemTypeCode, P.ItemCode, P.ColorCode, P.ItemDim1Code, P.ItemDim2Code, P.ItemDim3Code, + P.Qty1 AS PickingQty1, 0 AS ReserveQty1, 0 AS DispOrderQty1, 0 AS InventoryQty1 + FROM PickingStates P + INNER JOIN AttrFiltered AF ON AF.ProductCode = P.ItemCode + WHERE P.ItemTypeCode = 1 + AND LEN(P.ItemCode) = 13 + + UNION ALL + SELECT + R.CompanyCode, R.OfficeCode, R.StoreTypeCode, R.StoreCode, R.WarehouseCode, + R.ItemTypeCode, R.ItemCode, R.ColorCode, R.ItemDim1Code, R.ItemDim2Code, R.ItemDim3Code, + 0, R.Qty1, 0, 0 + FROM ReserveStates R + INNER JOIN AttrFiltered AF ON AF.ProductCode = R.ItemCode + WHERE R.ItemTypeCode = 1 + AND LEN(R.ItemCode) = 13 + + UNION ALL + SELECT + D.CompanyCode, D.OfficeCode, D.StoreTypeCode, D.StoreCode, D.WarehouseCode, + D.ItemTypeCode, D.ItemCode, D.ColorCode, D.ItemDim1Code, D.ItemDim2Code, D.ItemDim3Code, + 0, 0, D.Qty1, 0 + FROM DispOrderStates D + INNER JOIN AttrFiltered AF ON AF.ProductCode = D.ItemCode + WHERE D.ItemTypeCode = 1 + AND LEN(D.ItemCode) = 13 + + UNION ALL + SELECT + T.CompanyCode, T.OfficeCode, T.StoreTypeCode, T.StoreCode, T.WarehouseCode, + T.ItemTypeCode, T.ItemCode, T.ColorCode, T.ItemDim1Code, T.ItemDim2Code, T.ItemDim3Code, + 0, 0, 0, SUM(T.In_Qty1 - T.Out_Qty1) + FROM trStock T WITH (NOLOCK) + INNER JOIN AttrFiltered AF ON AF.ProductCode = T.ItemCode + WHERE T.ItemTypeCode = 1 + AND LEN(T.ItemCode) = 13 + GROUP BY + T.CompanyCode, T.OfficeCode, T.StoreTypeCode, T.StoreCode, T.WarehouseCode, + T.ItemTypeCode, T.ItemCode, T.ColorCode, T.ItemDim1Code, T.ItemDim2Code, T.ItemDim3Code + ) X + GROUP BY + X.CompanyCode, X.OfficeCode, X.StoreTypeCode, X.StoreCode, X.WarehouseCode, + X.ItemTypeCode, X.ItemCode, X.ColorCode, X.ItemDim1Code, X.ItemDim2Code, X.ItemDim3Code +) +SELECT + I.WarehouseCode AS Depo_Kodu, + W.WarehouseDescription AS Depo_Adi, + IT.ItemTypeDescription AS InventoryType, + I.ItemCode AS Urun_Kodu, + AF.ProductDescription AS Madde_Aciklamasi, + I.ColorCode AS Renk_Kodu, + C.ColorDescription AS Renk_Aciklamasi, + I.ItemDim1Code AS Beden, + I.ItemDim2Code AS Yaka, + ROUND(I.InventoryQty1 - I.PickingQty1 - I.ReserveQty1 - I.DispOrderQty1, U.RoundDigit) AS Kullanilabilir_Envanter, + AF.ProductAtt01Desc AS URUN_ANA_GRUBU, + AF.ProductAtt02Desc AS URUN_ALT_GRUBU, + AF.ProductAtt10Desc AS MARKA, + AF.ProductAtt11Desc AS DR, + AF.ProductAtt21Desc AS KALIP, + AF.ProductAtt22Desc AS IKINCI_PARCA_KALIP, + AF.ProductAtt23Desc AS PACA_GENISLIGI, + AF.ProductAtt24Desc AS UCUNCU_PARCA_KALIP, + AF.ProductAtt25Desc AS UCUNCU_PARCA_MODEL, + AF.ProductAtt26Desc AS BIRINCI_PARCA_KUMAS, + AF.ProductAtt27Desc AS IKINCI_PARCA_KUMAS, + AF.ProductAtt28Desc AS UCUNCU_PARCA_KUMAS, + AF.ProductAtt29Desc AS BIRINCI_PARCA_KARISIM, + AF.ProductAtt30Desc AS IKINCI_PARCA_KARISIM, + AF.ProductAtt31Desc AS UCUNCU_PARCA_KARISIM, + AF.ProductAtt32Desc AS YAKA_TIPI, + AF.ProductAtt33Desc AS DUGME, + AF.ProductAtt34Desc AS YIRTMAC, + AF.ProductAtt35Desc AS SEZON_YILI, + AF.ProductAtt36Desc AS MEVSIM, + AF.ProductAtt37Desc AS TABAN, + AF.ProductAtt38Desc AS BIRINCI_PARCA_FIT, + AF.ProductAtt39Desc AS IKINCI_PARCA_FIT, + AF.ProductAtt40Desc AS BOS2, + AF.ProductAtt41Desc AS KISA_KAR, + AF.ProductAtt42Desc AS SERI_FASON, + AF.ProductAtt43Desc AS STOK_GIRIS_YONTEMI, + AF.ProductAtt44Desc AS YETISKIN_GARSON, + AF.ProductAtt45Desc AS ASKILI_YAN, + AF.ProductAtt46Desc AS BOS3, + P.Price AS Fiyat +FROM INV I +INNER JOIN AttrFiltered AF + ON AF.ProductCode = I.ItemCode +JOIN cdItem CI WITH (NOLOCK) + ON CI.ItemTypeCode = I.ItemTypeCode + AND CI.ItemCode = I.ItemCode +LEFT JOIN cdUnitOfMeasure U WITH (NOLOCK) + ON U.UnitOfMeasureCode = CI.UnitOfMeasureCode1 +LEFT JOIN cdWarehouseDesc W WITH (NOLOCK) + ON W.WarehouseCode = I.WarehouseCode + AND W.LangCode = 'TR' +LEFT JOIN bsItemTypeDesc IT WITH (NOLOCK) + ON IT.ItemTypeCode = I.ItemTypeCode + AND IT.LangCode = 'TR' +LEFT JOIN cdColorDesc C WITH (NOLOCK) + ON C.ColorCode = I.ColorCode + AND C.LangCode = 'TR' +OUTER APPLY ( + SELECT TOP 1 Price + FROM prItemBasePrice PB WITH (NOLOCK) + WHERE PB.ItemTypeCode = 1 + AND PB.ItemCode = I.ItemCode + AND LEN(PB.ItemCode) = 13 + ORDER BY PB.PriceDate DESC +) P +WHERE + I.ItemTypeCode = 1 + AND LEN(I.ItemCode) = 13 + AND (I.InventoryQty1 - I.PickingQty1 - I.ReserveQty1 - I.DispOrderQty1) > 0 + AND CI.IsBlocked = 0 + AND I.WarehouseCode IN + ( + '1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28', + '1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3', + '1-0-33','101','1-014','1-0-49','1-0-36' + ); +` diff --git a/svc/routes/orderproductionitems.go b/svc/routes/orderproductionitems.go index 53f4ff2..3d4cc70 100644 --- a/svc/routes/orderproductionitems.go +++ b/svc/routes/orderproductionitems.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/gorilla/mux" + mssql "github.com/microsoft/go-mssqldb" ) // ====================================================== @@ -136,8 +137,7 @@ func OrderProductionValidateRoute(mssql *sql.DB) http.Handler { missing, err := buildMissingVariants(mssql, id, payload.Lines) if err != nil { - log.Printf("❌ validate error: %v", err) - http.Error(w, "Veritabani hatasi", http.StatusInternalServerError) + writeDBError(w, http.StatusInternalServerError, "validate_missing_variants", id, "", len(payload.Lines), err) return } @@ -176,8 +176,7 @@ func OrderProductionApplyRoute(mssql *sql.DB) http.Handler { missing, err := buildMissingVariants(mssql, id, payload.Lines) if err != nil { - log.Printf("❌ apply validate error: %v", err) - http.Error(w, "Veritabani hatasi", http.StatusInternalServerError) + writeDBError(w, http.StatusInternalServerError, "apply_validate_missing_variants", id, "", len(payload.Lines), err) return } @@ -202,7 +201,7 @@ func OrderProductionApplyRoute(mssql *sql.DB) http.Handler { tx, err := mssql.Begin() if err != nil { - http.Error(w, "Veritabani hatasi", http.StatusInternalServerError) + writeDBError(w, http.StatusInternalServerError, "begin_tx", id, username, len(payload.Lines), err) return } defer tx.Rollback() @@ -211,22 +210,19 @@ func OrderProductionApplyRoute(mssql *sql.DB) http.Handler { if payload.InsertMissing { inserted, err = queries.InsertMissingVariantsTx(tx, missing, username) if err != nil { - log.Printf("❌ insert missing error: %v", err) - http.Error(w, "Veritabani hatasi", http.StatusInternalServerError) + writeDBError(w, http.StatusInternalServerError, "insert_missing_variants", id, username, len(missing), err) return } } updated, err := queries.UpdateOrderLinesTx(tx, id, payload.Lines, username) if err != nil { - log.Printf("❌ update order lines error: %v", err) - http.Error(w, "Veritabani hatasi", http.StatusInternalServerError) + writeDBError(w, http.StatusInternalServerError, "update_order_lines", id, username, len(payload.Lines), err) return } if err := tx.Commit(); err != nil { - log.Printf("❌ commit error: %v", err) - http.Error(w, "Veritabani hatasi", http.StatusInternalServerError) + writeDBError(w, http.StatusInternalServerError, "commit_tx", id, username, len(payload.Lines), err) return } @@ -289,3 +285,26 @@ func validateUpdateLines(lines []models.OrderProductionUpdateLine) error { } return nil } + +func writeDBError(w http.ResponseWriter, status int, step string, orderHeaderID string, username string, lineCount int, err error) { + var sqlErr mssql.Error + if errors.As(err, &sqlErr) { + log.Printf( + "❌ SQL error step=%s orderHeaderID=%s user=%s lineCount=%d number=%d state=%d class=%d server=%s proc=%s line=%d message=%s", + step, orderHeaderID, username, lineCount, + sqlErr.Number, sqlErr.State, sqlErr.Class, sqlErr.ServerName, sqlErr.ProcName, sqlErr.LineNo, sqlErr.Message, + ) + } else { + log.Printf( + "❌ DB error step=%s orderHeaderID=%s user=%s lineCount=%d err=%v", + step, orderHeaderID, username, lineCount, err, + ) + } + + w.WriteHeader(status) + _ = json.NewEncoder(w).Encode(map[string]any{ + "message": "Veritabani hatasi", + "step": step, + "detail": err.Error(), + }) +} diff --git a/svc/routes/product_stock_query_by_attributes.go b/svc/routes/product_stock_query_by_attributes.go new file mode 100644 index 0000000..9a1cf9b --- /dev/null +++ b/svc/routes/product_stock_query_by_attributes.go @@ -0,0 +1,128 @@ +package routes + +import ( + "bssapp-backend/db" + "bssapp-backend/queries" + "context" + "encoding/json" + "log" + "net/http" + "strings" + "time" +) + +// GetProductStockAttributeOptionsHandler +// GET /api/product-stock-attribute-options +func GetProductStockAttributeOptionsHandler(w http.ResponseWriter, _ *http.Request) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + rows, err := db.MssqlDB.QueryContext(ctx, queries.GetProductStockAttributeOptionsQuery) + if err != nil { + log.Printf("[PRODUCT-STOCK-ATTR-OPTIONS] SQL hatasi: %v", err) + http.Error(w, "SQL hatasi: "+err.Error(), http.StatusInternalServerError) + return + } + defer rows.Close() + + result := map[string][]string{ + "att01": {}, + "att02": {}, + "att10": {}, + "att11": {}, + "att21": {}, + "att35": {}, + "att36": {}, + "att44": {}, + } + + for rows.Next() { + var fieldName, fieldValue string + if err := rows.Scan(&fieldName, &fieldValue); err != nil { + continue + } + fieldName = strings.TrimSpace(fieldName) + fieldValue = strings.TrimSpace(fieldValue) + if fieldName == "" || fieldValue == "" { + continue + } + result[fieldName] = append(result[fieldName], fieldValue) + } + + if err := rows.Err(); err != nil { + http.Error(w, "Satir okuma hatasi: "+err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + _ = json.NewEncoder(w).Encode(result) +} + +// GetProductStockQueryByAttributesHandler +// GET /api/product-stock-query-by-attributes?att01=...&att02=... +func GetProductStockQueryByAttributesHandler(w http.ResponseWriter, r *http.Request) { + att01 := strings.TrimSpace(r.URL.Query().Get("att01")) + att02 := strings.TrimSpace(r.URL.Query().Get("att02")) + att10 := strings.TrimSpace(r.URL.Query().Get("att10")) + att11 := strings.TrimSpace(r.URL.Query().Get("att11")) + att21 := strings.TrimSpace(r.URL.Query().Get("att21")) + att35 := strings.TrimSpace(r.URL.Query().Get("att35")) + att36 := strings.TrimSpace(r.URL.Query().Get("att36")) + att44 := strings.TrimSpace(r.URL.Query().Get("att44")) + + hasAny := att01 != "" || att02 != "" || att10 != "" || att11 != "" || att21 != "" || att35 != "" || att36 != "" || att44 != "" + if !hasAny { + http.Error(w, "En az bir urun ozelligi secilmelidir", http.StatusBadRequest) + return + } + + start := time.Now() + ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) + defer cancel() + log.Printf( + "[PRODUCT-STOCK-BY-ATTRS] request att01=%q att02=%q att10=%q att11=%q att21=%q att35=%q att36=%q att44=%q", + att01, att02, att10, att11, att21, att35, att36, att44, + ) + + rows, err := db.MssqlDB.QueryContext(ctx, queries.GetProductStockQueryByAttributes, att01, att02, att10, att11, att21, att35, att36, att44) + if err != nil { + log.Printf("[PRODUCT-STOCK-BY-ATTRS] SQL hatasi: %v", err) + http.Error(w, "SQL hatasi: "+err.Error(), http.StatusInternalServerError) + return + } + defer rows.Close() + + columns, err := rows.Columns() + if err != nil { + http.Error(w, "Kolon bilgisi alinamadi", http.StatusInternalServerError) + return + } + + result := make([]map[string]interface{}, 0, 256) + for rows.Next() { + raw := make([]interface{}, len(columns)) + dest := make([]interface{}, len(columns)) + for i := range raw { + dest[i] = &raw[i] + } + if err := rows.Scan(dest...); err != nil { + continue + } + + rowMap := make(map[string]interface{}, len(columns)) + for i, c := range columns { + rowMap[c] = normalizeSQLValue(raw[i]) + } + result = append(result, rowMap) + } + + if err := rows.Err(); err != nil { + log.Printf("[PRODUCT-STOCK-BY-ATTRS] rows err elapsed=%s err=%v", time.Since(start), err) + http.Error(w, "Satir okuma hatasi: "+err.Error(), http.StatusInternalServerError) + return + } + log.Printf("[PRODUCT-STOCK-BY-ATTRS] success rows=%d elapsed=%s", len(result), time.Since(start)) + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + _ = json.NewEncoder(w).Encode(result) +} diff --git a/ui/.quasar/prod-spa/app.js b/ui/.quasar/prod-spa/app.js deleted file mode 100644 index caeaac1..0000000 --- a/ui/.quasar/prod-spa/app.js +++ /dev/null @@ -1,75 +0,0 @@ -/* eslint-disable */ -/** - * THIS FILE IS GENERATED AUTOMATICALLY. - * DO NOT EDIT. - * - * You are probably looking on adding startup/initialization code. - * Use "quasar new boot " 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 - } -} diff --git a/ui/.quasar/prod-spa/client-entry.js b/ui/.quasar/prod-spa/client-entry.js deleted file mode 100644 index 5de66d0..0000000 --- a/ui/.quasar/prod-spa/client-entry.js +++ /dev/null @@ -1,154 +0,0 @@ -/* eslint-disable */ -/** - * THIS FILE IS GENERATED AUTOMATICALLY. - * DO NOT EDIT. - * - * You are probably looking on adding startup/initialization code. - * Use "quasar new boot " 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) - }) - }) - diff --git a/ui/.quasar/prod-spa/client-prefetch.js b/ui/.quasar/prod-spa/client-prefetch.js deleted file mode 100644 index 9bbe3c5..0000000 --- a/ui/.quasar/prod-spa/client-prefetch.js +++ /dev/null @@ -1,116 +0,0 @@ -/* eslint-disable */ -/** - * THIS FILE IS GENERATED AUTOMATICALLY. - * DO NOT EDIT. - * - * You are probably looking on adding startup/initialization code. - * Use "quasar new boot " 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() - }) - }) -} diff --git a/ui/.quasar/prod-spa/quasar-user-options.js b/ui/.quasar/prod-spa/quasar-user-options.js deleted file mode 100644 index ac1dae3..0000000 --- a/ui/.quasar/prod-spa/quasar-user-options.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -/** - * THIS FILE IS GENERATED AUTOMATICALLY. - * DO NOT EDIT. - * - * You are probably looking on adding startup/initialization code. - * Use "quasar new boot " 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} } - diff --git a/ui/quasar.config.js.temporary.compiled.1772552660663.mjs b/ui/quasar.config.js.temporary.compiled.1772606929877.mjs similarity index 100% rename from ui/quasar.config.js.temporary.compiled.1772552660663.mjs rename to ui/quasar.config.js.temporary.compiled.1772606929877.mjs diff --git a/ui/src/layouts/MainLayout.vue b/ui/src/layouts/MainLayout.vue index 06a05c8..6979f7b 100644 --- a/ui/src/layouts/MainLayout.vue +++ b/ui/src/layouts/MainLayout.vue @@ -270,6 +270,11 @@ const menuItems = [ label: 'Ürün Kodundan Stok Sorgula', to: '/app/product-stock-query', permission: 'order:view' + }, + { + label: 'Ürün Özelliklerinden Stok Bul', + to: '/app/product-stock-by-attributes', + permission: 'order:view' } ] }, diff --git a/ui/src/pages/OrderProductionUpdate.vue b/ui/src/pages/OrderProductionUpdate.vue index 6c766ea..e3b4274 100644 --- a/ui/src/pages/OrderProductionUpdate.vue +++ b/ui/src/pages/OrderProductionUpdate.vue @@ -556,7 +556,14 @@ async function onBulkSubmit () { await store.fetchItems(orderHeaderID.value) selectedMap.value = {} } catch (err) { - $q.notify({ type: 'negative', message: 'Toplu kayit islemi basarisiz.' }) + console.error('[OrderProductionUpdate] onBulkSubmit failed', { + orderHeaderID: orderHeaderID.value, + selectedRowCount: selectedRows.length, + lineCount: lines.length, + apiError: err?.response?.data, + message: err?.message + }) + $q.notify({ type: 'negative', message: store.error || 'Toplu kayit islemi basarisiz.' }) } } diff --git a/ui/src/pages/ProductStockByAttributes.vue b/ui/src/pages/ProductStockByAttributes.vue new file mode 100644 index 0000000..99a5381 --- /dev/null +++ b/ui/src/pages/ProductStockByAttributes.vue @@ -0,0 +1,1127 @@ + + + + + + + + + + + diff --git a/ui/src/router/routes.js b/ui/src/router/routes.js index a76935f..c2260a9 100644 --- a/ui/src/router/routes.js +++ b/ui/src/router/routes.js @@ -297,6 +297,12 @@ const routes = [ component: () => import('pages/ProductStockQuery.vue'), meta: { permission: 'order:view' } }, + { + path: 'product-stock-by-attributes', + name: 'product-stock-by-attributes', + component: () => import('pages/ProductStockByAttributes.vue'), + meta: { permission: 'order:view' } + }, /* ================= PASSWORD ================= */ diff --git a/ui/src/stores/OrderProductionItemStore.js b/ui/src/stores/OrderProductionItemStore.js index 60644a1..3159099 100644 --- a/ui/src/stores/OrderProductionItemStore.js +++ b/ui/src/stores/OrderProductionItemStore.js @@ -2,6 +2,67 @@ import { defineStore } from 'pinia' 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 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) { + const data = err?.response?.data + if (typeof data === 'string' && data.trim()) return data + if (data && typeof data === 'object') { + const msg = String(data.message || '').trim() + const step = String(data.step || '').trim() + const detail = String(data.detail || '').trim() + const parts = [msg] + if (step) parts.push(`step=${step}`) + if (detail) parts.push(detail) + const merged = parts.filter(Boolean).join(' | ') + if (merged) return merged + } + return err?.message || fallback +} + +function logApiError (action, err, payload = null) { + const status = err?.response?.status + const data = err?.response?.data + console.error(`[OrderProductionItemStore] ${action} failed`, { + status, + payload, + data, + message: err?.message + }) +} + export const useOrderProductionItemStore = defineStore('orderproductionitems', { state: () => ({ items: [], @@ -118,7 +179,8 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', { ) return res?.data || { missingCount: 0, missing: [] } } catch (err) { - this.error = err?.response?.data || err?.message || 'Kontrol basarisiz' + logApiError('validateUpdates', err, { orderHeaderID, lineCount: lines?.length || 0 }) + this.error = extractApiErrorMessage(err, 'Kontrol basarisiz') throw err } finally { this.saving = false @@ -137,7 +199,8 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', { ) return res?.data || { updated: 0, inserted: 0 } } catch (err) { - this.error = err?.response?.data || err?.message || 'Guncelleme basarisiz' + logApiError('applyUpdates', err, { orderHeaderID, lineCount: lines?.length || 0, insertMissing }) + this.error = extractApiErrorMessage(err, 'Guncelleme basarisiz') throw err } finally { this.saving = false diff --git a/ui/src/stores/orderentryStore.js b/ui/src/stores/orderentryStore.js index 7b09a22..7b2ae0e 100644 --- a/ui/src/stores/orderentryStore.js +++ b/ui/src/stores/orderentryStore.js @@ -3438,17 +3438,28 @@ export function normalizeBeden(v) { - Core logic aligned with backend detectBedenGroupGo - Keeps frontend aksbir bucket for accessory lines =========================================================== */ -export function detectBedenGroup(bedenList, urunAnaGrubu = '', urunKategori = '') { +export function detectBedenGroup(bedenList, urunAnaGrubu = '', urunKategori = '', yetiskinGarson = '') { const list = Array.isArray(bedenList) && bedenList.length > 0 ? bedenList.map(v => (v || '').toString().trim().toUpperCase()) : [' '] - const rawAna = (urunAnaGrubu || '').toString().toUpperCase() - const rawKat = (urunKategori || '').toString().toUpperCase() - const hasGarson = rawAna.includes('GARSON') || rawKat.includes('GARSON') || - rawAna.includes('GARSON') || rawKat.includes('GARSON') - const hasAyakkabi = rawAna.includes('AYAKKABI') || rawKat.includes('AYAKKABI') || - rawAna.includes('AYAKKABI') || rawKat.includes('AYAKKABI') + const rawAna = normalizeTextForMatch(urunAnaGrubu || '') + const rawKat = normalizeTextForMatch(urunKategori || '') + const rawYetiskinGarson = normalizeTextForMatch(yetiskinGarson || '') + + // Ozel kural: + // YETISKIN/GARSON = GARSON ve URUN ANA GRUBU "GOMLEK ATA YAKA" veya "GOMLEK KLASIK" ise + // sonuc "yas" olmalidir. + const isGarsonGomlekAnaGrubu = + rawAna.includes('GOMLEK ATA YAKA') || + rawAna.includes('GOMLEK KLASIK') + const hasGarsonSignal = rawAna.includes('GARSON') || rawKat.includes('GARSON') || rawYetiskinGarson.includes('GARSON') + if (isGarsonGomlekAnaGrubu && (rawKat.includes('GARSON') || rawYetiskinGarson.includes('GARSON'))) { + return 'yas' + } + + const hasGarson = hasGarsonSignal + const hasAyakkabi = rawAna.includes('AYAKKABI') || rawKat.includes('AYAKKABI') if (hasGarson && hasAyakkabi) return 'ayk_garson' if (hasGarson) return 'yas' @@ -3457,28 +3468,27 @@ export function detectBedenGroup(bedenList, urunAnaGrubu = '', urunKategori = '' const harfliBedenler = ['XS','S','M','L','XL','2XL','3XL','4XL','5XL','6XL','7XL'] if (list.some(b => harfliBedenler.includes(b))) return 'gom' - const ana = (urunAnaGrubu || '') - .toUpperCase() + const ana = normalizeTextForMatch(urunAnaGrubu || '') .trim() .replace(/\(.*?\)/g, '') - .replace(/[^A-ZÇĞİÖŞÜ0-9\s]/g, '') + .replace(/[^A-Z0-9\s]/g, '') .replace(/\s+/g, ' ') - const kat = (urunKategori || '').toUpperCase().trim() + const kat = normalizeTextForMatch(urunKategori || '').trim() // 🔸 Aksesuar ise "aksbir" const aksesuarGruplari = [ - 'AKSESUAR','KRAVAT','PAPYON','KEMER','CORAP','ÇORAP', - 'FULAR','MENDIL','MENDİL','KASKOL','ASKI', - 'YAKA','KOL DUGMESI','KOL DÜĞMESİ' + 'AKSESUAR','KRAVAT','PAPYON','KEMER','CORAP', + 'FULAR','MENDIL','KASKOL','ASKI', + 'YAKA','KOL DUGMESI' ] - const giyimGruplari = ['GÖMLEK','CEKET','PANTOLON','MONT','YELEK','TAKIM','TSHIRT','TİŞÖRT'] + const giyimGruplari = ['GOMLEK','CEKET','PANTOLON','MONT','YELEK','TAKIM','TSHIRT','TISORT'] // 🔸 Pantolon özel durumu if ( aksesuarGruplari.some(g => ana.includes(g) || kat.includes(g)) && !giyimGruplari.some(g => ana.includes(g)) ) return 'aksbir' - if (ana.includes('PANTOLON') && kat.includes('YETİŞKİN')) return 'pan' + if (ana.includes('PANTOLON') && kat.includes('YETISKIN')) return 'pan' // 🔸 Tamamen numerik (örneğin 39-44 arası) → ayakkabı const allNumeric = list.every(v => /^\d+$/.test(v)) if (allNumeric) { @@ -3488,7 +3498,7 @@ export function detectBedenGroup(bedenList, urunAnaGrubu = '', urunKategori = '' } // 🔸 Yaş grubu (çocuk/garson) - if (kat.includes('GARSON') || kat.includes('ÇOCUK')) return 'yas' + if (kat.includes('GARSON') || kat.includes('COCUK')) return 'yas' // 🔸 Varsayılan: takım elbise return 'tak'