From aa4da6868067bc189a70974e2bf9fdce41f326b0 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Tue, 20 Jan 2026 22:07:07 +0700 Subject: [PATCH 1/9] refactor: unify GetOne method to return approval alongside transfer laying --- .../controllers/transfer_laying.controller.go | 2 +- .../services/transfer_laying.service.go | 32 +++++++++---------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/internal/modules/production/transfer_layings/controllers/transfer_laying.controller.go b/internal/modules/production/transfer_layings/controllers/transfer_laying.controller.go index 13c39334..d0ee5061 100644 --- a/internal/modules/production/transfer_layings/controllers/transfer_laying.controller.go +++ b/internal/modules/production/transfer_layings/controllers/transfer_laying.controller.go @@ -70,7 +70,7 @@ func (u *TransferLayingController) GetOne(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") } - result, approval, err := u.TransferLayingService.GetOneWithApproval(c, uint(id)) + result, approval, err := u.TransferLayingService.GetOne(c, uint(id)) if err != nil { return err } diff --git a/internal/modules/production/transfer_layings/services/transfer_laying.service.go b/internal/modules/production/transfer_layings/services/transfer_laying.service.go index e64b9cc2..8e0269cf 100644 --- a/internal/modules/production/transfer_layings/services/transfer_laying.service.go +++ b/internal/modules/production/transfer_layings/services/transfer_laying.service.go @@ -28,8 +28,7 @@ import ( type TransferLayingService interface { GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.LayingTransfer, int64, error) - GetOne(ctx *fiber.Ctx, id uint) (*entity.LayingTransfer, error) - GetOneWithApproval(ctx *fiber.Ctx, id uint) (*entity.LayingTransfer, *entity.Approval, error) + GetOne(ctx *fiber.Ctx, id uint) (*entity.LayingTransfer, *entity.Approval, error) CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.LayingTransfer, error) UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.LayingTransfer, error) DeleteOne(ctx *fiber.Ctx, id uint) error @@ -156,14 +155,15 @@ func (s transferLayingService) GetAll(c *fiber.Ctx, params *validation.Query) ([ return transferLayings, total, nil } -func (s transferLayingService) GetOne(c *fiber.Ctx, id uint) (*entity.LayingTransfer, error) { +func (s transferLayingService) GetOne(c *fiber.Ctx, id uint) (*entity.LayingTransfer, *entity.Approval, error) { transferLaying, err := s.Repository.GetByID(c.Context(), id, s.withRelations) + if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, fiber.NewError(fiber.StatusNotFound, "TransferLaying not found") + return nil, nil, fiber.NewError(fiber.StatusNotFound, "TransferLaying not found") } if err != nil { s.Log.Errorf("Failed get transferLaying by id: %+v", err) - return nil, err + return nil, nil, err } approvalRepo := commonRepo.NewApprovalRepository(s.Repository.DB()) @@ -174,15 +174,6 @@ func (s transferLayingService) GetOne(c *fiber.Ctx, id uint) (*entity.LayingTran transferLaying.LatestApproval = latestApproval } - return transferLaying, nil -} - -func (s transferLayingService) GetOneWithApproval(c *fiber.Ctx, id uint) (*entity.LayingTransfer, *entity.Approval, error) { - transferLaying, err := s.GetOne(c, id) - if err != nil { - return nil, nil, err - } - return transferLaying, transferLaying.LatestApproval, nil } @@ -406,7 +397,12 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create) return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat transfer laying") } - return s.GetOne(c, createBody.Id) + laying_transfer, _, err := s.GetOne(c, createBody.Id) + if err != nil { + return nil, err + } + return laying_transfer, nil + } func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.LayingTransfer, error) { @@ -582,7 +578,9 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, return nil, err } - return s.GetOne(c, id) + layingTransfer, _, err := s.GetOne(c, id) + + return layingTransfer, err } func (s transferLayingService) DeleteOne(c *fiber.Ctx, id uint) error { @@ -773,7 +771,7 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) ( updated := make([]entity.LayingTransfer, 0, len(approvableIDs)) for _, approvableID := range approvableIDs { - transfer, err := s.GetOne(c, approvableID) + transfer, _, err := s.GetOne(c, approvableID) if err != nil { return nil, err } From dd4dcc1c39573eef867d0f71e32b3a193a07d421 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Tue, 20 Jan 2026 22:10:47 +0700 Subject: [PATCH 2/9] FEAT[BE[: add avg weight and avg amount on get penjualan harian --- .../repports/dto/repportMarketing.dto.go | 296 +++++++----------- .../repports/services/repport.service.go | 2 +- 2 files changed, 118 insertions(+), 180 deletions(-) diff --git a/internal/modules/repports/dto/repportMarketing.dto.go b/internal/modules/repports/dto/repportMarketing.dto.go index 92ee9a77..edb2887f 100644 --- a/internal/modules/repports/dto/repportMarketing.dto.go +++ b/internal/modules/repports/dto/repportMarketing.dto.go @@ -40,99 +40,24 @@ type RepportMarketingItemDTO struct { type Summary struct { TotalQty int `json:"total_qty"` TotalWeightKg float64 `json:"total_weight_kg"` + AverageWeightKg float64 `json:"average_weight_kg"` + AverageSalesAmount float64 `json:"average_sales_amount"` TotalSalesAmount int64 `json:"total_sales_amount"` TotalHppAmount int64 `json:"total_hpp_amount"` TotalHppPricePerKg float64 `json:"total_hpp_price_per_kg"` } -type RepportMarketingResponseDTO struct { - Items []RepportMarketingItemDTO `json:"items"` - Total *Summary `json:"total,omitempty"` -} - type ProductRelationDTOFixed struct { productDTO.ProductRelationDTO ProductPrice float64 `json:"product_price"` SellingPrice *float64 `json:"selling_price,omitempty"` } -func ToRepportMarketingItemDTO(mdp entity.MarketingDeliveryProduct, hppPricePerKg float64, category string) RepportMarketingItemDTO { - soDate := time.Time{} - agingDays := 0 - if mdp.MarketingProduct.Marketing.SoDate.Year() > 1 { - soDate = mdp.MarketingProduct.Marketing.SoDate - agingDays = int(time.Since(soDate).Hours() / 24) - } - - realizationDate := time.Time{} - if mdp.DeliveryDate != nil { - realizationDate = *mdp.DeliveryDate - } - - doNumber := marketingDTO.GenerateDeliveryOrderNumber(mdp.MarketingProduct.Marketing.SoNumber, mdp.DeliveryDate, mdp.MarketingProduct.ProductWarehouse.WarehouseId) - - totalWeightKg := mdp.UsageQty * mdp.AvgWeight - salesAmount := totalWeightKg * mdp.UnitPrice - - var hpp float64 - var hppAmount float64 - if isProductEligibleForHpp(mdp, category) { - hpp = hppPricePerKg - hppAmount = totalWeightKg * hppPricePerKg - } - - item := RepportMarketingItemDTO{ - ID: int(mdp.Id), - SoDate: soDate, - RealizationDate: realizationDate, - AgingDays: agingDays, - DoNumber: doNumber, - MarketingType: getMarketingType(mdp), - Qty: mdp.UsageQty, - AverageWeightKg: mdp.AvgWeight, - TotalWeightKg: totalWeightKg, - SalesPricePerKg: mdp.UnitPrice, - HppPricePerKg: hpp, - SalesAmount: salesAmount, - HppAmount: hppAmount, - } - - if mdp.MarketingProduct.ProductWarehouse.WarehouseId != 0 { - mapped := warehouseDTO.ToWarehouseRelationDTO(mdp.MarketingProduct.ProductWarehouse.Warehouse) - item.Warehouse = &mapped - } - - if mdp.MarketingProduct.Marketing.CustomerId != 0 { - mapped := customerDTO.ToCustomerRelationDTO(mdp.MarketingProduct.Marketing.Customer) - item.Customer = &mapped - } - - if mdp.MarketingProduct.Marketing.SalesPersonId != 0 { - mapped := userDTO.ToUserRelationDTO(mdp.MarketingProduct.Marketing.SalesPerson) - item.Sales = &mapped - } - - item.VehicleNumber = mdp.VehicleNumber - - if mdp.MarketingProduct.ProductWarehouse.ProductId != 0 { - mapped := productDTO.ToProductRelationDTO(mdp.MarketingProduct.ProductWarehouse.Product) - item.Product = newProductRelationDTOFixedPtr(&mapped) - } - - return item -} - -func ToRepportMarketingItemDTOs(mdps []entity.MarketingDeliveryProduct, hppPricePerKg float64, category string) []RepportMarketingItemDTO { +func ToMarketingReportItems(mdps []entity.MarketingDeliveryProduct, hppMap map[uint]float64) []RepportMarketingItemDTO { items := make([]RepportMarketingItemDTO, 0, len(mdps)) - for _, mdp := range mdps { - items = append(items, ToRepportMarketingItemDTO(mdp, hppPricePerKg, category)) - } - return items -} -func ToRepportMarketingItemDTOsWithHppMap(mdps []entity.MarketingDeliveryProduct, hppMap map[uint]float64) []RepportMarketingItemDTO { - items := make([]RepportMarketingItemDTO, 0, len(mdps)) for _, mdp := range mdps { + // Get HPP and category from map hppPerKg := float64(0) category := "" if projectFlockKandang := mdp.MarketingProduct.ProductWarehouse.ProjectFlockKandang; projectFlockKandang != nil { @@ -142,101 +67,111 @@ func ToRepportMarketingItemDTOsWithHppMap(mdps []entity.MarketingDeliveryProduct category = projectFlockKandang.ProjectFlock.Category } - item := ToRepportMarketingItemDTO(mdp, hppPerKg, category) + // Calculate dates + soDate := time.Time{} + agingDays := 0 + if mdp.MarketingProduct.Marketing.SoDate.Year() > 1 { + soDate = mdp.MarketingProduct.Marketing.SoDate + agingDays = int(time.Since(soDate).Hours() / 24) + } + + realizationDate := time.Time{} + if mdp.DeliveryDate != nil { + realizationDate = *mdp.DeliveryDate + } + + totalWeightKg := mdp.UsageQty * mdp.AvgWeight + salesAmount := totalWeightKg * mdp.UnitPrice + + var hpp float64 + var hppAmount float64 + + var hasAyam, hasTelur, hasTrading bool + for _, flag := range mdp.MarketingProduct.ProductWarehouse.Product.Flags { + ft := utils.FlagType(flag.Name) + + if ft == utils.FlagAyamAfkir || ft == utils.FlagAyamCulling || ft == utils.FlagAyamMati || + ft == utils.FlagDOC || ft == utils.FlagPullet || ft == utils.FlagLayer { + hasAyam = true + } + + if ft == utils.FlagTelur || ft == utils.FlagTelurUtuh || ft == utils.FlagTelurPecah || + ft == utils.FlagTelurPutih || ft == utils.FlagTelurRetak { + hasTelur = true + } + + if ft == utils.FlagOVK || ft == utils.FlagObat || ft == utils.FlagVitamin || ft == utils.FlagKimia || + ft == utils.FlagPakan || ft == utils.FlagPreStarter || ft == utils.FlagStarter || ft == utils.FlagFinisher { + hasTrading = true + } + } + + // Determine marketing type + marketingType := "trading" + if hasTrading { + marketingType = "trading" + } else if hasTelur { + marketingType = "telur" + } else if hasAyam { + marketingType = "ayam" + } + + eligibleForHpp := false + + if utils.ProjectFlockCategory(category) == utils.ProjectFlockCategoryGrowing { + eligibleForHpp = hasAyam + } else { + eligibleForHpp = hasAyam || hasTelur + } + + if eligibleForHpp { + hpp = hppPerKg + hppAmount = totalWeightKg * hppPerKg + } + + item := RepportMarketingItemDTO{ + ID: int(mdp.Id), + SoDate: soDate, + RealizationDate: realizationDate, + AgingDays: agingDays, + DoNumber: marketingDTO.GenerateDeliveryOrderNumber(mdp.MarketingProduct.Marketing.SoNumber, mdp.DeliveryDate, mdp.MarketingProduct.ProductWarehouse.WarehouseId), + MarketingType: marketingType, + Qty: mdp.UsageQty, + AverageWeightKg: mdp.AvgWeight, + TotalWeightKg: totalWeightKg, + SalesPricePerKg: mdp.UnitPrice, + HppPricePerKg: hpp, + SalesAmount: salesAmount, + HppAmount: hppAmount, + VehicleNumber: mdp.VehicleNumber, + } + + if mdp.MarketingProduct.ProductWarehouse.WarehouseId != 0 { + mapped := warehouseDTO.ToWarehouseRelationDTO(mdp.MarketingProduct.ProductWarehouse.Warehouse) + item.Warehouse = &mapped + } + + if mdp.MarketingProduct.Marketing.CustomerId != 0 { + mapped := customerDTO.ToCustomerRelationDTO(mdp.MarketingProduct.Marketing.Customer) + item.Customer = &mapped + } + + if mdp.MarketingProduct.Marketing.SalesPersonId != 0 { + mapped := userDTO.ToUserRelationDTO(mdp.MarketingProduct.Marketing.SalesPerson) + item.Sales = &mapped + } + + if mdp.MarketingProduct.ProductWarehouse.ProductId != 0 { + mapped := productDTO.ToProductRelationDTO(mdp.MarketingProduct.ProductWarehouse.Product) + item.Product = newProductRelationDTOFixedPtr(&mapped) + } + items = append(items, item) } + return items } -func getMarketingType(mdp entity.MarketingDeliveryProduct) string { - hasAyam, hasTelur, hasTrading := checkProductFlags(mdp.MarketingProduct.ProductWarehouse.Product.Flags) - - if hasAyam { - return "ayam" - } - if hasTelur { - return "telur" - } - if hasTrading { - return "trading" - } - return "trading" // default to trading if no flags found -} - -func checkProductFlags(flags []entity.Flag) (hasAyam, hasTelur, hasTrading bool) { - if len(flags) == 0 { - return false, false, false - } - - for _, flag := range flags { - ft := utils.FlagType(flag.Name) - - if ft == utils.FlagAyamAfkir || ft == utils.FlagAyamCulling || ft == utils.FlagAyamMati || - ft == utils.FlagDOC || ft == utils.FlagPullet || ft == utils.FlagLayer { - hasAyam = true - } - - if ft == utils.FlagTelur || ft == utils.FlagTelurUtuh || ft == utils.FlagTelurPecah || - ft == utils.FlagTelurPutih || ft == utils.FlagTelurRetak { - hasTelur = true - } - - if ft == utils.FlagOVK || ft == utils.FlagObat || ft == utils.FlagVitamin || ft == utils.FlagKimia || - ft == utils.FlagPakan || ft == utils.FlagPreStarter || ft == utils.FlagStarter || ft == utils.FlagFinisher { - hasTrading = true - } - } - - return hasAyam, hasTelur, hasTrading -} - -func isProductEligibleForHpp(mdp entity.MarketingDeliveryProduct, category string) bool { - hasAyam, hasTelur, _ := checkProductFlags(mdp.MarketingProduct.ProductWarehouse.Product.Flags) - - if utils.ProjectFlockCategory(category) == utils.ProjectFlockCategoryGrowing { - return hasAyam - } - - return hasAyam || hasTelur -} - -func ToSummary(mdps []entity.MarketingDeliveryProduct, hppPricePerKg float64, category string) *Summary { - if len(mdps) == 0 { - return nil - } - - totalQty := 0 - totalWeightKg := 0.0 - totalEligibleWeightKg := 0.0 - totalSalesAmount := int64(0) - totalHppAmount := int64(0) - - for _, mdp := range mdps { - calculatedTotalWeight := mdp.UsageQty * mdp.AvgWeight - totalQty += int(mdp.UsageQty) - totalWeightKg += calculatedTotalWeight - totalSalesAmount += int64(calculatedTotalWeight * mdp.UnitPrice) - - if isProductEligibleForHpp(mdp, category) { - totalEligibleWeightKg += calculatedTotalWeight - totalHppAmount += int64(calculatedTotalWeight * hppPricePerKg) - } - } - - totalHppPricePerKg := float64(0) - if totalEligibleWeightKg > 0 { - totalHppPricePerKg = float64(totalHppAmount) / totalEligibleWeightKg - } - - return &Summary{ - TotalQty: totalQty, - TotalWeightKg: totalWeightKg, - TotalSalesAmount: totalSalesAmount, - TotalHppAmount: totalHppAmount, - TotalHppPricePerKg: totalHppPricePerKg, - } -} - func ToSummaryFromDTOItems(items []RepportMarketingItemDTO) *Summary { if len(items) == 0 { return nil @@ -244,6 +179,8 @@ func ToSummaryFromDTOItems(items []RepportMarketingItemDTO) *Summary { totalQty := 0 totalWeightKg := 0.0 + avgSalesAmount := 0.0 + avgWeightKg := 0.0 totalSalesAmount := int64(0) totalHppAmount := int64(0) @@ -259,25 +196,26 @@ func ToSummaryFromDTOItems(items []RepportMarketingItemDTO) *Summary { totalHppPricePerKg = float64(totalHppAmount) / totalWeightKg } + if len(items) > 0 { + avgSalesAmount = float64(totalSalesAmount) / float64(len(items)) + } + + if totalQty > 0 { + avgWeightKg = totalWeightKg / float64(totalQty) + avgSalesAmount = float64(totalSalesAmount) / float64(totalQty) // ← TAMBAHAN INI + } + return &Summary{ TotalQty: totalQty, TotalWeightKg: totalWeightKg, + AverageWeightKg: avgWeightKg, + AverageSalesAmount: avgSalesAmount, TotalSalesAmount: totalSalesAmount, TotalHppAmount: totalHppAmount, TotalHppPricePerKg: totalHppPricePerKg, } } -func ToRepportMarketingResponseDTO(mdps []entity.MarketingDeliveryProduct, hppPricePerKg float64, category string) RepportMarketingResponseDTO { - items := ToRepportMarketingItemDTOs(mdps, hppPricePerKg, category) - total := ToSummary(mdps, hppPricePerKg, category) - - return RepportMarketingResponseDTO{ - Items: items, - Total: total, - } -} - func newProductRelationDTOFixedPtr(original *productDTO.ProductRelationDTO) *ProductRelationDTOFixed { if original == nil { return nil diff --git a/internal/modules/repports/services/repport.service.go b/internal/modules/repports/services/repport.service.go index a0e0f350..090a284b 100644 --- a/internal/modules/repports/services/repport.service.go +++ b/internal/modules/repports/services/repport.service.go @@ -181,7 +181,7 @@ func (s *repportService) GetMarketing(c *fiber.Ctx, params *validation.Marketing } } - items := dto.ToRepportMarketingItemDTOsWithHppMap(deliveryProducts, hppMap) + items := dto.ToMarketingReportItems(deliveryProducts, hppMap) return items, total, nil } From ad3bb0e29a348e5e8f2fb021663b01a331a47984 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Tue, 20 Jan 2026 22:28:34 +0700 Subject: [PATCH 3/9] FEAT[BE[: enhance marketing report items with aging days calculation --- .../repports/dto/repportMarketing.dto.go | 17 ++- .../repports/services/repport.service.go | 104 ++++++++---------- 2 files changed, 54 insertions(+), 67 deletions(-) diff --git a/internal/modules/repports/dto/repportMarketing.dto.go b/internal/modules/repports/dto/repportMarketing.dto.go index edb2887f..751796e9 100644 --- a/internal/modules/repports/dto/repportMarketing.dto.go +++ b/internal/modules/repports/dto/repportMarketing.dto.go @@ -53,11 +53,10 @@ type ProductRelationDTOFixed struct { SellingPrice *float64 `json:"selling_price,omitempty"` } -func ToMarketingReportItems(mdps []entity.MarketingDeliveryProduct, hppMap map[uint]float64) []RepportMarketingItemDTO { +func ToMarketingReportItems(mdps []entity.MarketingDeliveryProduct, hppMap map[uint]float64, agingMap map[int]int) []RepportMarketingItemDTO { items := make([]RepportMarketingItemDTO, 0, len(mdps)) for _, mdp := range mdps { - // Get HPP and category from map hppPerKg := float64(0) category := "" if projectFlockKandang := mdp.MarketingProduct.ProductWarehouse.ProjectFlockKandang; projectFlockKandang != nil { @@ -67,12 +66,15 @@ func ToMarketingReportItems(mdps []entity.MarketingDeliveryProduct, hppMap map[u category = projectFlockKandang.ProjectFlock.Category } - // Calculate dates soDate := time.Time{} agingDays := 0 if mdp.MarketingProduct.Marketing.SoDate.Year() > 1 { soDate = mdp.MarketingProduct.Marketing.SoDate - agingDays = int(time.Since(soDate).Hours() / 24) + if ag, exists := agingMap[int(mdp.Id)]; exists { + agingDays = ag + } else { + agingDays = int(time.Since(soDate).Hours() / 24) + } } realizationDate := time.Time{} @@ -106,7 +108,6 @@ func ToMarketingReportItems(mdps []entity.MarketingDeliveryProduct, hppMap map[u } } - // Determine marketing type marketingType := "trading" if hasTrading { marketingType = "trading" @@ -196,13 +197,9 @@ func ToSummaryFromDTOItems(items []RepportMarketingItemDTO) *Summary { totalHppPricePerKg = float64(totalHppAmount) / totalWeightKg } - if len(items) > 0 { - avgSalesAmount = float64(totalSalesAmount) / float64(len(items)) - } - if totalQty > 0 { avgWeightKg = totalWeightKg / float64(totalQty) - avgSalesAmount = float64(totalSalesAmount) / float64(totalQty) // ← TAMBAHAN INI + avgSalesAmount = float64(totalSalesAmount) / float64(totalQty) } return &Summary{ diff --git a/internal/modules/repports/services/repport.service.go b/internal/modules/repports/services/repport.service.go index 090a284b..579436eb 100644 --- a/internal/modules/repports/services/repport.service.go +++ b/internal/modules/repports/services/repport.service.go @@ -165,6 +165,47 @@ func (s *repportService) GetMarketing(c *fiber.Ctx, params *validation.Marketing return nil, 0, err } + customerGroups := make(map[uint][]entity.MarketingDeliveryProduct) + for _, dp := range deliveryProducts { + customerID := dp.MarketingProduct.Marketing.CustomerId + customerGroups[customerID] = append(customerGroups[customerID], dp) + } + + agingMap := make(map[int]int) + for customerID := range customerGroups { + transactions, err := s.CustomerPaymentRepo.GetCustomerPaymentTransactions(c.Context(), &customerID) + if err != nil { + s.Log.Warnf("Failed to get transactions for customer %d: %v", customerID, err) + continue + } + + initialBalance, err := s.CustomerPaymentRepo.GetInitialBalanceByCustomer(c.Context(), customerID) + if err != nil { + initialBalance = 0 + } + + runningBalance := initialBalance + for i, tx := range transactions { + if tx.TransactionType == "SALES" { + previousBalance := runningBalance + runningBalance -= tx.TotalPrice + currentBalance := runningBalance + + _, paymentDate := s.determineSalesStatusAndPaymentDate(transactions, i, previousBalance, currentBalance) + + if paymentDate != nil { + agingDays := int(paymentDate.Sub(tx.TransDate).Hours() / 24) + agingMap[int(tx.TransactionID)] = agingDays + } else { + agingDays := int(time.Since(tx.TransDate).Hours() / 24) + agingMap[int(tx.TransactionID)] = agingDays + } + } else if tx.TransactionType == "PAYMENT" { + runningBalance += tx.PaymentAmount + } + } + } + projectFlockIDMap := make(map[uint]bool) hppMap := make(map[uint]float64) @@ -181,7 +222,7 @@ func (s *repportService) GetMarketing(c *fiber.Ctx, params *validation.Marketing } } - items := dto.ToMarketingReportItems(deliveryProducts, hppMap) + items := dto.ToMarketingReportItems(deliveryProducts, hppMap, agingMap) return items, total, nil } @@ -422,12 +463,10 @@ func (s *repportService) GetCustomerPayment(ctx *fiber.Ctx, params *validation.C return nil, 0, err } - // Determine customer IDs to process var customerIDs []uint var totalCustomers int64 if len(params.CustomerIDs) > 0 { - // Specific customer IDs mode (no pagination) customerIDs = params.CustomerIDs totalCustomers = int64(len(customerIDs)) @@ -435,7 +474,6 @@ func (s *repportService) GetCustomerPayment(ctx *fiber.Ctx, params *validation.C return []dto.CustomerPaymentReportItem{}, 0, nil } } else { - // Multiple customers mode with pagination page := params.Page limit := params.Limit if page < 1 { @@ -574,15 +612,7 @@ func (s *repportService) processCustomerPayment(ctx context.Context, customerID func (s *repportService) determineSalesStatusAndPaymentDate(transactions []repportRepo.CustomerPaymentTransaction, currentIndex int, previousBalance, currentBalance float64) (string, *time.Time) { currentSales := transactions[currentIndex] - // Status Logic: - // 1. LUNAS: previousBalance >= salesAmount (paid from deposit) - // 2. LUNAS: future payments make AR >= 0 (eventually paid) - // 3. DIBAYAR SEBAGIAN: has payment but not enough - // 4. BELUM LUNAS: no payment at all - if previousBalance >= currentSales.TotalPrice { - // Cari payment yang digunakan untuk melunasi sales ini dengan FIFO - // Track payment allocations that are consumed by previous sales type paymentAllocation struct { date time.Time amount float64 @@ -591,7 +621,6 @@ func (s *repportService) determineSalesStatusAndPaymentDate(transactions []reppo allocations := []paymentAllocation{} runningBalance := 0.0 - // Process all transactions before current sales to build allocation map for i := 0; i < currentIndex; i++ { if transactions[i].TransactionType == "PAYMENT" { allocations = append(allocations, paymentAllocation{ @@ -604,7 +633,6 @@ func (s *repportService) determineSalesStatusAndPaymentDate(transactions []reppo salesAmount := transactions[i].TotalPrice remainingToConsume := salesAmount - // Consume from oldest allocations first (FIFO) for j := range allocations { if remainingToConsume <= 0 { break @@ -623,22 +651,18 @@ func (s *repportService) determineSalesStatusAndPaymentDate(transactions []reppo } } - // Now find which allocation covers the current sales amountNeeded := currentSales.TotalPrice for _, alloc := range allocations { available := alloc.amount - alloc.consumed if available > 0 { if amountNeeded <= available { - // This allocation fully covers the sales return "LUNAS", &alloc.date } else { - // This allocation partially covers, continue to next amountNeeded -= available } } } - // If we get here, use the oldest allocation if len(allocations) > 0 { return "LUNAS", &allocations[0].date } @@ -690,7 +714,6 @@ func mapRecordingToProductionResultDTO(record entity.Recording) dto.ProductionRe if record.Day != nil { result.Woa = float64(*record.Day) } - // avgWeight := calculateAverageBodyWeight(record.BodyWeights) avgWeight := 1.0 if avgWeight > 0 { result.Bw = avgWeight @@ -1570,12 +1593,9 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes dataRows := make([]dto.HppPerKandangRowDTO, 0, len(repoRows)) perRangeMap := make(map[weightRangeKey]*weightRangeAggregate) var totalBirds int64 - // var totalWeight float64 var totalEggPieces int64 var totalEggKg float64 - // var totalRemainingValueRp int64 var totalEggValueRp int64 - // var totalHppSum float64 var totalHppCount int var totalDocPriceSum float64 var totalDocPriceCount int @@ -1589,14 +1609,6 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes continue } - // birdsFloat := row.RemainingChickenBirds - // if math.IsNaN(birdsFloat) || math.IsInf(birdsFloat, 0) { - // birdsFloat = 0 - // } - // weightFloat := row.RemainingChickenWeight - // if math.IsNaN(weightFloat) || math.IsInf(weightFloat, 0) { - // weightFloat = 0 - // } eggPiecesFloatRemaining := row.EggProductionPiecesRemaining if math.IsNaN(eggPiecesFloatRemaining) || math.IsInf(eggPiecesFloatRemaining, 0) { eggPiecesFloatRemaining = 0 @@ -1632,13 +1644,8 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes weightMax := weightMin + 0.09 rangeKey := weightRangeKey{Min: weightMin, Max: weightMax} - // rowBirds := int64(math.Round(birdsFloat)) costEntry := costMap[row.ProjectFlockKandangID] totalCost := costEntry.FeedCost + costEntry.OvkCost + costEntry.DocCost + costEntry.BudgetCost + costEntry.ExpenseCost - // hppRp := 0.0 - // if weightFloat > 0 { - // hppRp = totalCost / weightFloat - // } eggHpp := 0.0 if eggWeightFloat > 0 { eggHpp = (totalCost / eggWeightFloat) / 1000 @@ -1646,7 +1653,6 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes rowEggPieces := int64(math.Round(eggPiecesFloatRemaining)) rowEggValue := int64(eggHpp * eggRemainingWeightFloatRemaining) - // rowRemainingValue := int64(hppRp * weightFloat) avgDocPrice := int64(0) if costEntry.DocQty > 0 { avgDocPrice = int64(math.Round(costEntry.DocCost / costEntry.DocQty)) @@ -1673,35 +1679,22 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes WeightMin: weightMin, WeightMax: weightMax, }, - AvgWeightKg: avgWeight, - NameWithPeriode: nameWithPeriod, - // FeedCostRp: costEntry.FeedCost, - // OvkCostRp: costEntry.OvkCost, + AvgWeightKg: avgWeight, + NameWithPeriode: nameWithPeriod, DocSuppliers: docSupplierMap[row.ProjectFlockKandangID], FeedSuppliers: feedSupplierMap[row.ProjectFlockKandangID], EggProductionPieces: int64(math.Round(eggPiecesFloatRemaining)), EggProductionKg: eggRemainingWeightFloatRemaining, - // EggProductionTotalWeightKg: eggWeightFloat, - // EggProductionTotalPieces: int64(math.Round(eggTotalPiecesFloat)), - AverageDocPriceRp: avgDocPrice, - // HppRp: hppRp, - EggHppRpPerKg: eggHpp, - // RemainingValueRp: rowRemainingValue, - EggValueRp: rowEggValue, + AverageDocPriceRp: avgDocPrice, + EggHppRpPerKg: eggHpp, + EggValueRp: rowEggValue, }) - // totalBirds += rowBirds - // totalWeight += weightFloat totalEggPieces += rowEggPieces totalEggKg += eggRemainingWeightFloatRemaining - // totalRemainingValueRp += rowRemainingValue totalEggValueRp += rowEggValue totalAvgWeightSum += avgWeight totalAvgWeightCount++ - // if weightFloat > 0 { - // totalHppSum += hppRp - // totalHppCount++ - // } if avgDocPrice > 0 { totalDocPriceSum += float64(avgDocPrice) totalDocPriceCount++ @@ -1728,8 +1721,6 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes } rangeSummary := rangeAgg.Summary - // rangeAgg.RemainingBirds += rowBirds - // rangeAgg.RemainingWeightKg += row.RemainingChickenWeight rangeAgg.AvgWeightSum += avgWeight rangeAgg.AvgWeightCount++ for _, supplier := range feedSupplierMap[row.ProjectFlockKandangID] { @@ -1744,7 +1735,6 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes } rangeSummary.EggProductionPieces += rowEggPieces rangeSummary.EggProductionKg += eggRemainingWeightFloatRemaining - // rangeSummary.RemainingValueRp += rowRemainingValue rangeSummary.EggValueRp += rowEggValue if eggWeightFloat > 0 { rangeAgg.EggHppSum += eggHpp From d50ab7cc977c0724dada0abc7050dbdadd43d881 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Tue, 20 Jan 2026 22:42:16 +0700 Subject: [PATCH 4/9] FEAT[BE]: add default filterby become so_date in report markeing --- .../salesorder_delivery_product.repository.go | 10 +++++++--- internal/modules/repports/services/repport.service.go | 1 - .../modules/repports/validations/repport.validation.go | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/internal/modules/marketing/repositories/salesorder_delivery_product.repository.go b/internal/modules/marketing/repositories/salesorder_delivery_product.repository.go index e219b041..1ec0bddf 100644 --- a/internal/modules/marketing/repositories/salesorder_delivery_product.repository.go +++ b/internal/modules/marketing/repositories/salesorder_delivery_product.repository.go @@ -225,8 +225,12 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C } } - if filters.FilterBy != "" && (filters.StartDate != "" || filters.EndDate != "") { - if filters.FilterBy == "so_date" { + if filters.StartDate != "" || filters.EndDate != "" { + filterBy := filters.FilterBy + if filterBy == "" { + filterBy = "so_date" + } + if filterBy == "so_date" { if filters.StartDate != "" { if startDate, err := utils.ParseDateString(filters.StartDate); err == nil { db = db.Where("marketings.so_date >= ?", startDate) @@ -238,7 +242,7 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C db = db.Where("marketings.so_date < ?", nextDate) } } - } else if filters.FilterBy == "realization_date" { + } else if filterBy == "realization_date" { if filters.StartDate != "" { if startDate, err := utils.ParseDateString(filters.StartDate); err == nil { db = db.Where("marketing_delivery_products.delivery_date >= ?", startDate) diff --git a/internal/modules/repports/services/repport.service.go b/internal/modules/repports/services/repport.service.go index 579436eb..03b1b370 100644 --- a/internal/modules/repports/services/repport.service.go +++ b/internal/modules/repports/services/repport.service.go @@ -175,7 +175,6 @@ func (s *repportService) GetMarketing(c *fiber.Ctx, params *validation.Marketing for customerID := range customerGroups { transactions, err := s.CustomerPaymentRepo.GetCustomerPaymentTransactions(c.Context(), &customerID) if err != nil { - s.Log.Warnf("Failed to get transactions for customer %d: %v", customerID, err) continue } diff --git a/internal/modules/repports/validations/repport.validation.go b/internal/modules/repports/validations/repport.validation.go index e0161b5c..8047f718 100644 --- a/internal/modules/repports/validations/repport.validation.go +++ b/internal/modules/repports/validations/repport.validation.go @@ -26,7 +26,7 @@ type MarketingQuery struct { AreaId int64 `query:"area_id" validate:"omitempty"` LocationId int64 `query:"location_id" validate:"omitempty"` MarketingType string `query:"marketing_type" validate:"omitempty,oneof=ayam telur trading"` - FilterBy string `query:"filter_by" validate:"omitempty,oneof=so_date realization_date"` + FilterBy string `query:"filter_by" validate:"omitempty,oneof= so_date realization_date"` StartDate string `query:"start_date" validate:"omitempty,datetime=2006-01-02"` EndDate string `query:"end_date" validate:"omitempty,datetime=2006-01-02"` SortBy string `query:"sort_by" validate:"omitempty,oneof=so_date realization_date customer warehouse product sales_person vehicle_number sales_amount hpp_amount qty average_weight total_weight sales_price hpp_price aging_days"` From d0625e7d21d128c3bfb894c2895bc476c6605d9e Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Wed, 21 Jan 2026 09:45:19 +0700 Subject: [PATCH 5/9] FIX[BE]: fixing closing penjualan add sumary --- .../controllers/closing.controller.go | 4 +- .../closings/dto/closingMarketing.dto.go | 99 +++++++++++-------- 2 files changed, 59 insertions(+), 44 deletions(-) diff --git a/internal/modules/closings/controllers/closing.controller.go b/internal/modules/closings/controllers/closing.controller.go index a43687ac..8b79fc92 100644 --- a/internal/modules/closings/controllers/closing.controller.go +++ b/internal/modules/closings/controllers/closing.controller.go @@ -160,7 +160,7 @@ func (u *ClosingController) GetPenjualan(c *fiber.Ctx) error { Code: fiber.StatusOK, Status: "success", Message: "Get closing penjualan successfully", - Data: dto.ToPenjualanRealisasiResponseDTO(uint(projectFlockID), result), + Data: dto.ToPenjualanRealisasiResponseDTO(result), }) } @@ -190,7 +190,7 @@ func (u *ClosingController) GetPenjualanByProjectFlockKandang(c *fiber.Ctx) erro Code: fiber.StatusOK, Status: "success", Message: "Get closing penjualan by project flock kandang successfully", - Data: dto.ToPenjualanRealisasiResponseDTO(uint(projectFlockID), result), + Data: dto.ToPenjualanRealisasiResponseDTO(result), }) } diff --git a/internal/modules/closings/dto/closingMarketing.dto.go b/internal/modules/closings/dto/closingMarketing.dto.go index 1a790ad6..223b9d11 100644 --- a/internal/modules/closings/dto/closingMarketing.dto.go +++ b/internal/modules/closings/dto/closingMarketing.dto.go @@ -12,23 +12,31 @@ import ( // === Response DTO === type SalesDTO struct { - Id uint `json:"id"` - RealizationDate time.Time `json:"realization_date"` - Age int `json:"age"` - DoNumber string `json:"do_number"` - Product *productDTO.ProductRelationDTO `json:"product,omitempty"` - Customer *customerDTO.CustomerRelationDTO `json:"customer,omitempty"` - Qty float64 `json:"qty"` - Weight float64 `json:"weight"` - AvgWeight float64 `json:"avg_weight"` - Price float64 `json:"price"` - TotalPrice float64 `json:"total_price"` - Kandang *kandangDTO.KandangRelationDTO `json:"kandang,omitempty"` - PaymentStatus string `json:"payment_status"` + Id uint `json:"id"` + RealizationDate time.Time `json:"realization_date"` + Age int `json:"age"` + DoNumber string `json:"do_number"` + Product *productDTO.ProductRelationDTO `json:"product,omitempty"` + Customer *customerDTO.CustomerRelationDTO `json:"customer,omitempty"` + Qty float64 `json:"qty"` + Weight float64 `json:"weight"` + AvgWeight float64 `json:"avg_weight"` + SalesPrice float64 `json:"sales_price"` + TotalSalesPrice float64 `json:"total_sales_price"` + ActualPrice float64 `json:"actual_price"` + TotalActualPrice float64 `json:"total_actual_price"` + Kandang *kandangDTO.KandangRelationDTO `json:"kandang,omitempty"` +} +type SummaryDTO struct { + TotalSalesPrice float64 `json:"total_sales_price"` + AvgSalesPrice float64 `json:"avg_sales_price"` + TotalActualPrice float64 `json:"total_actual_price"` + AvgActualPrice float64 `json:"avg_actual_price"` } type PenjualanRealisasiResponseDTO struct { - Sales []SalesDTO `json:"sales"` + Sales []SalesDTO `json:"sales"` + Summary SummaryDTO `json:"summary"` } // === Mapper Functions === @@ -63,19 +71,38 @@ func ToSalesDTO(e entity.MarketingDeliveryProduct) SalesDTO { doNumber := deliveryOrdersDTO.GenerateDeliveryOrderNumber(e.MarketingProduct.Marketing.SoNumber, e.DeliveryDate, e.MarketingProduct.ProductWarehouse.Warehouse.Id) return SalesDTO{ - Id: e.Id, - RealizationDate: realizationDate, - Age: age, - DoNumber: doNumber, - Product: product, - Customer: customer, - Qty: e.UsageQty, - Weight: e.TotalWeight, - AvgWeight: e.AvgWeight, - Price: e.UnitPrice, - TotalPrice: e.TotalPrice, - Kandang: kandang, - PaymentStatus: "Paid", + Id: e.Id, + RealizationDate: realizationDate, + Age: age, + DoNumber: doNumber, + Product: product, + Customer: customer, + Qty: e.UsageQty, + Weight: e.TotalWeight, + AvgWeight: e.AvgWeight, + SalesPrice: e.MarketingProduct.UnitPrice, + TotalSalesPrice: e.MarketingProduct.TotalPrice, + ActualPrice: e.UnitPrice, + TotalActualPrice: e.TotalPrice, + Kandang: kandang, + } +} + +func ToSummaryDto(e []entity.MarketingDeliveryProduct) SummaryDTO { + + var totalSalesPrice, totalActualPrice float64 + count := len(e) + + for _, item := range e { + totalSalesPrice += item.MarketingProduct.TotalPrice + totalActualPrice += item.TotalPrice + } + + return SummaryDTO{ + TotalSalesPrice: totalSalesPrice, + TotalActualPrice: totalActualPrice, + AvgSalesPrice: totalSalesPrice / float64(count), + AvgActualPrice: totalActualPrice / float64(count), } } @@ -87,25 +114,13 @@ func ToSalesDTOs(e []entity.MarketingDeliveryProduct) []SalesDTO { return result } -func ToPenjualanRealisasiResponseDTO(projectFlockID uint, e []entity.MarketingDeliveryProduct) PenjualanRealisasiResponseDTO { - +func ToPenjualanRealisasiResponseDTO(e []entity.MarketingDeliveryProduct) PenjualanRealisasiResponseDTO { return PenjualanRealisasiResponseDTO{ - - Sales: ToSalesDTOs(e), + Sales: ToSalesDTOs(e), + Summary: ToSummaryDto(e), } } -func extractPeriodFromRealisasi(realisasi []entity.MarketingDeliveryProduct) int { - if len(realisasi) > 0 { - for _, item := range realisasi { - if item.MarketingProduct.ProductWarehouse.ProjectFlockKandang != nil { - return item.MarketingProduct.ProductWarehouse.ProjectFlockKandang.Period - } - } - } - return 0 -} - func calculateAgeFromChickin(projectFlockKandang *entity.ProjectFlockKandang, deliveryDate *time.Time) int { if projectFlockKandang == nil || deliveryDate == nil || len(projectFlockKandang.Chickins) == 0 { return 0 From 894efa7aa54ccdcc88173eb35b19eea6af11c678 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Wed, 21 Jan 2026 09:46:03 +0700 Subject: [PATCH 6/9] FIX[BE]: fixing report penjualan add avg weight and price to response --- .../modules/repports/dto/repportMarketing.dto.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/modules/repports/dto/repportMarketing.dto.go b/internal/modules/repports/dto/repportMarketing.dto.go index 751796e9..336b6576 100644 --- a/internal/modules/repports/dto/repportMarketing.dto.go +++ b/internal/modules/repports/dto/repportMarketing.dto.go @@ -41,7 +41,7 @@ type Summary struct { TotalQty int `json:"total_qty"` TotalWeightKg float64 `json:"total_weight_kg"` AverageWeightKg float64 `json:"average_weight_kg"` - AverageSalesAmount float64 `json:"average_sales_amount"` + AverageSalesPrice float64 `json:"average_sales_price"` TotalSalesAmount int64 `json:"total_sales_amount"` TotalHppAmount int64 `json:"total_hpp_amount"` TotalHppPricePerKg float64 `json:"total_hpp_price_per_kg"` @@ -180,7 +180,7 @@ func ToSummaryFromDTOItems(items []RepportMarketingItemDTO) *Summary { totalQty := 0 totalWeightKg := 0.0 - avgSalesAmount := 0.0 + avgSalesPrice := 0.0 avgWeightKg := 0.0 totalSalesAmount := int64(0) totalHppAmount := int64(0) @@ -190,6 +190,7 @@ func ToSummaryFromDTOItems(items []RepportMarketingItemDTO) *Summary { totalWeightKg += item.TotalWeightKg totalSalesAmount += int64(item.SalesAmount) totalHppAmount += int64(item.HppAmount) + avgSalesPrice += item.SalesPricePerKg } totalHppPricePerKg := float64(0) @@ -197,16 +198,19 @@ func ToSummaryFromDTOItems(items []RepportMarketingItemDTO) *Summary { totalHppPricePerKg = float64(totalHppAmount) / totalWeightKg } + if len(items) > 0 { + avgSalesPrice = avgSalesPrice / float64(len(items)) + } + if totalQty > 0 { avgWeightKg = totalWeightKg / float64(totalQty) - avgSalesAmount = float64(totalSalesAmount) / float64(totalQty) } return &Summary{ TotalQty: totalQty, TotalWeightKg: totalWeightKg, AverageWeightKg: avgWeightKg, - AverageSalesAmount: avgSalesAmount, + AverageSalesPrice: avgSalesPrice, TotalSalesAmount: totalSalesAmount, TotalHppAmount: totalHppAmount, TotalHppPricePerKg: totalHppPricePerKg, From c2d2701d728c8b33a8cfce0858d5f86d2c3c13c0 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Wed, 21 Jan 2026 09:57:44 +0700 Subject: [PATCH 7/9] FIX[BE] fix wrong calculation on summary report marketing --- internal/modules/closings/dto/closingMarketing.dto.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/modules/closings/dto/closingMarketing.dto.go b/internal/modules/closings/dto/closingMarketing.dto.go index 223b9d11..eb6ff23f 100644 --- a/internal/modules/closings/dto/closingMarketing.dto.go +++ b/internal/modules/closings/dto/closingMarketing.dto.go @@ -90,19 +90,22 @@ func ToSalesDTO(e entity.MarketingDeliveryProduct) SalesDTO { func ToSummaryDto(e []entity.MarketingDeliveryProduct) SummaryDTO { - var totalSalesPrice, totalActualPrice float64 + var totalSalesPrice, totalActualPrice, sumSales, sumActual float64 count := len(e) for _, item := range e { totalSalesPrice += item.MarketingProduct.TotalPrice totalActualPrice += item.TotalPrice + sumSales += item.MarketingProduct.UnitPrice + sumActual += item.UnitPrice + } return SummaryDTO{ TotalSalesPrice: totalSalesPrice, TotalActualPrice: totalActualPrice, - AvgSalesPrice: totalSalesPrice / float64(count), - AvgActualPrice: totalActualPrice / float64(count), + AvgSalesPrice: sumSales / float64(count), + AvgActualPrice: sumActual / float64(count), } } From e8a89f0f17aa8ecb465f421ee985a71bf22b41a1 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Wed, 21 Jan 2026 13:52:46 +0700 Subject: [PATCH 8/9] FEAT[BE]: update warehouse DTO references in product warehouse and add UOM preload --- .../dto/product_warehouse.dto.go | 143 ++++-------------- .../services/product_warehouse.service.go | 1 + .../marketing/dto/deliveryorder.dto.go | 11 +- 3 files changed, 35 insertions(+), 120 deletions(-) diff --git a/internal/modules/inventory/product-warehouses/dto/product_warehouse.dto.go b/internal/modules/inventory/product-warehouses/dto/product_warehouse.dto.go index 57a13021..b8f51c52 100644 --- a/internal/modules/inventory/product-warehouses/dto/product_warehouse.dto.go +++ b/internal/modules/inventory/product-warehouses/dto/product_warehouse.dto.go @@ -5,6 +5,7 @@ import ( entity "gitlab.com/mbugroup/lti-api.git/internal/entities" productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto" + warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto" ) // === DTO Structs === @@ -16,60 +17,29 @@ type ProductWarehouseRelationDTO struct { Quantity float64 `json:"quantity"` } -type ProductWarehousNestedDTO struct { - Id uint `json:"id"` - Product *productDTO.ProductRelationDTO `json:"product,omitempty"` - Warehouse *WarehouseRelationDTO `json:"warehouse,omitempty"` -} - type ProductWarehouseListDTO struct { ProductWarehouseRelationDTO - Product *productDTO.ProductRelationDTO `json:"product,omitempty"` - Warehouse *WarehouseRelationDTO `json:"warehouse,omitempty"` - ProjectFlockKandang *ProjectFlockKandangRelationDTO `json:"project_flock_kandang,omitempty"` - CreatedUser *UserRelationDTO `json:"created_user,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -type UserRelationDTO struct { - Id uint `json:"id"` - Username string `json:"username"` + Product *productDTO.ProductRelationDTO `json:"product,omitempty"` + Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"` + ProjectFlockKandang *ProjectFlockKandangRelationDTO `json:"project_flock_kandang,omitempty"` + CreatedUser *UserRelationDTO `json:"created_user,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } type ProductWarehouseDetailDTO struct { ProductWarehouseListDTO } -// Nested DTOs for relations -type ProductRelationDTO struct { - Id uint `json:"id"` - Name string `json:"name"` - Sku string `json:"sku"` - Flags []string `json:"flags"` +type ProductWarehousNestedDTO struct { + Id uint `json:"id"` + Product *productDTO.ProductRelationDTO `json:"product,omitempty"` + Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"` } -type WarehouseRelationDTO struct { - Id uint `json:"id"` - Name string `json:"name"` - Kandang *KandangRelationDTO `json:"kandang,omitempty"` - Location *LocationRelationDTO `json:"location,omitempty"` - Area *AreaRelationDTO `json:"area,omitempty"` -} - -type KandangRelationDTO struct { - Id uint `json:"id"` - Name string `json:"name"` -} - -type LocationRelationDTO struct { - Id uint `json:"id"` - Name string `json:"name"` -} - -type AreaRelationDTO struct { - Id uint `json:"id"` - Name string `json:"name"` +type UserRelationDTO struct { + Id uint `json:"id"` + Username string `json:"username"` } type ProjectFlockKandangRelationDTO struct { @@ -96,65 +66,28 @@ func ToProductWarehouseRelationDTO(e entity.ProductWarehouse) ProductWarehouseRe } } -func ToProductWarehouseNestedDTO(e entity.ProductWarehouse) ProductWarehousNestedDTO { - product := productDTO.ToProductRelationDTO(e.Product) - - return ProductWarehousNestedDTO{ - Id: e.Id, - Product: &product, - Warehouse: &WarehouseRelationDTO{ - Id: e.Warehouse.Id, - Name: e.Warehouse.Name, - }, - } -} - func ToProductWarehouseListDTO(e entity.ProductWarehouse) ProductWarehouseListDTO { dto := ProductWarehouseListDTO{ ProductWarehouseRelationDTO: ToProductWarehouseRelationDTO(e), - // CreatedAt: e.CreatedAt, - // UpdatedAt: e.UpdatedAt, } // Map Product relation jika ada if e.Product.Id != 0 { product := productDTO.ToProductRelationDTO(e.Product) - // Tambahkan flock name ke product name jika ada project flock + // Create a copy with flock name appended if exists if e.ProjectFlockKandang != nil && e.ProjectFlockKandang.ProjectFlock.Id != 0 { - product.Name = product.Name + " (" + e.ProjectFlockKandang.ProjectFlock.FlockName + ")" + productCopy := product + productCopy.Name = product.Name + " (" + e.ProjectFlockKandang.ProjectFlock.FlockName + ")" + dto.Product = &productCopy + } else { + dto.Product = &product } - - dto.Product = &product } // Map Warehouse relation jika ada if e.Warehouse.Id != 0 { - warehouse := WarehouseRelationDTO{ - Id: e.Warehouse.Id, - Name: e.Warehouse.Name, - } - // Map Kandang jika ada - if e.Warehouse.Kandang != nil && e.Warehouse.Kandang.Id != 0 { - warehouse.Kandang = &KandangRelationDTO{ - Id: e.Warehouse.Kandang.Id, - Name: e.Warehouse.Kandang.Name, - } - } - // Map Location jika ada - if e.Warehouse.Location != nil && e.Warehouse.Location.Id != 0 { - warehouse.Location = &LocationRelationDTO{ - Id: e.Warehouse.Location.Id, - Name: e.Warehouse.Location.Name, - } - } - - if e.Warehouse.Area.Id != 0 { - warehouse.Area = &AreaRelationDTO{ - Id: e.Warehouse.Area.Id, - Name: e.Warehouse.Area.Name, - } - } + warehouse := warehouseDTO.ToWarehouseRelationDTO(e.Warehouse) dto.Warehouse = &warehouse } @@ -168,7 +101,6 @@ func ToProductWarehouseListDTO(e entity.ProductWarehouse) ProductWarehouseListDT Period: e.ProjectFlockKandang.Period, } - // Map ProjectFlock jika ada if e.ProjectFlockKandang.ProjectFlock.Id != 0 { pfkDTO.ProjectFlock = &ProjectFlockRelationDTO{ Id: e.ProjectFlockKandang.ProjectFlock.Id, @@ -179,15 +111,6 @@ func ToProductWarehouseListDTO(e entity.ProductWarehouse) ProductWarehouseListDT dto.ProjectFlockKandang = pfkDTO } - // Map CreatedUser relation jika ada - // if e.CreatedUser.Id != 0 { - // user := UserRelationDTO{ - // Id: e.CreatedUser.Id, - // Username: e.CreatedUser.Name, - // } - // dto.CreatedUser = &user - // } - return dto } @@ -205,23 +128,13 @@ func ToProductWarehouseDetailDTO(e entity.ProductWarehouse) ProductWarehouseDeta } } -func ToKandangRelationDTO(e entity.Kandang) KandangRelationDTO { - return KandangRelationDTO{ - Id: e.Id, - Name: e.Name, - } -} +func ToProductWarehouseNestedDTO(e entity.ProductWarehouse) ProductWarehousNestedDTO { + product := productDTO.ToProductRelationDTO(e.Product) + warehouse := warehouseDTO.ToWarehouseRelationDTO(e.Warehouse) -func ToLocationRelationDTO(e entity.Location) LocationRelationDTO { - return LocationRelationDTO{ - Id: e.Id, - Name: e.Name, - } -} - -func ToAreaRelationDTO(e entity.Area) AreaRelationDTO { - return AreaRelationDTO{ - Id: e.Id, - Name: e.Name, + return ProductWarehousNestedDTO{ + Id: e.Id, + Product: &product, + Warehouse: &warehouse, } } diff --git a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go index 152bfa24..ea194c36 100644 --- a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go +++ b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go @@ -40,6 +40,7 @@ func (s productWarehouseService) withRelations(db *gorm.DB) *gorm.DB { return db. Preload("Product.Flags"). Preload("Product"). + Preload("Product.Uom"). Preload("Warehouse"). Preload("Warehouse.Location"). Preload("Warehouse.Area"). diff --git a/internal/modules/marketing/dto/deliveryorder.dto.go b/internal/modules/marketing/dto/deliveryorder.dto.go index a6eea180..4bcbacca 100644 --- a/internal/modules/marketing/dto/deliveryorder.dto.go +++ b/internal/modules/marketing/dto/deliveryorder.dto.go @@ -9,6 +9,7 @@ import ( approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto" productwarehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/dto" customerDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/dto" + warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto" userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" ) @@ -68,10 +69,10 @@ type DeliveryItemDTO struct { } type DeliveryGroupDTO struct { - DoNumber string `json:"do_number"` - DeliveryDate *time.Time `json:"delivery_date"` - Warehouse *productwarehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"` - Deliveries []DeliveryItemDTO `json:"deliveries"` + DoNumber string `json:"do_number"` + DeliveryDate *time.Time `json:"delivery_date"` + Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"` + Deliveries []DeliveryItemDTO `json:"deliveries"` } type DeliveryMarketingProductDTO struct { @@ -286,7 +287,7 @@ func groupDeliveryProducts(products []MarketingDeliveryProductDTO, soNumber stri if !exists { group = &DeliveryGroupDTO{ DeliveryDate: product.DeliveryDate, - Warehouse: &productwarehouseDTO.WarehouseRelationDTO{ + Warehouse: &warehouseDTO.WarehouseRelationDTO{ Id: warehouseId, Name: warehouseName, }, From a73b44808ff2e6bebbc090d7e309f7016c9ede6d Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Wed, 21 Jan 2026 15:16:30 +0700 Subject: [PATCH 9/9] FIX[BE]: fixing wrong index data on adjustment. change get from stocklogs to adjustment table --- internal/entities/adjustment_stock.go | 29 ++---- .../adjustments/dto/adjustment.dto.go | 30 +++--- .../adjustment_stock.repository.go | 8 ++ .../services/adjustment.service.go | 99 ++++++++++++------- .../services/product_warehouse.service.go | 7 +- 5 files changed, 99 insertions(+), 74 deletions(-) diff --git a/internal/entities/adjustment_stock.go b/internal/entities/adjustment_stock.go index bbc93167..ef27d0c2 100644 --- a/internal/entities/adjustment_stock.go +++ b/internal/entities/adjustment_stock.go @@ -2,28 +2,17 @@ package entities import "time" -// AdjustmentStock tracks FIFO allocation for stock adjustments -// - For INCREASE adjustments (Stockable): Tracks stock added to warehouse -// - For DECREASE adjustments (Usable): Tracks stock consumed from warehouse type AdjustmentStock struct { - Id uint `gorm:"primaryKey"` - StockLogId uint `gorm:"column:stock_log_id;not null;index"` - ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null"` + Id uint `gorm:"primaryKey"` + StockLogId uint `gorm:"column:stock_log_id;not null;index"` + ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null"` + TotalQty float64 `gorm:"column:total_qty;default:0"` + TotalUsed float64 `gorm:"column:total_used;default:0"` + UsageQty float64 `gorm:"column:usage_qty;default:0"` + PendingQty float64 `gorm:"column:pending_qty;default:0"` + CreatedAt time.Time `gorm:"column:created_at;autoCreateTime"` + UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime"` - // === FIFO FIELDS FOR INCREASE ADJUSTMENT (Stockable) === - // Tracks stock added to warehouse via adjustment INCREASE - TotalQty float64 `gorm:"column:total_qty;default:0"` // Total lot quantity available - TotalUsed float64 `gorm:"column:total_used;default:0"` // Quantity already used from this lot - - // === FIFO FIELDS FOR DECREASE ADJUSTMENT (Usable) === - // Tracks stock consumed from warehouse via adjustment DECREASE - UsageQty float64 `gorm:"column:usage_qty;default:0"` // Actual quantity consumed - PendingQty float64 `gorm:"column:pending_qty;default:0"` // Pending quantity (waiting for stock) - - CreatedAt time.Time `gorm:"column:created_at;autoCreateTime"` - UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime"` - - // Relations StockLog *StockLog `gorm:"foreignKey:StockLogId;references:Id"` ProductWarehouse *ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"` } diff --git a/internal/modules/inventory/adjustments/dto/adjustment.dto.go b/internal/modules/inventory/adjustments/dto/adjustment.dto.go index 008f9966..1ce3da1b 100644 --- a/internal/modules/inventory/adjustments/dto/adjustment.dto.go +++ b/internal/modules/inventory/adjustments/dto/adjustment.dto.go @@ -100,38 +100,42 @@ func ToProductWarehouseDTO(e *entity.ProductWarehouse) *ProductWarehouseDTO { } } -func ToAdjustmentRelationDTO(e *entity.StockLog) AdjustmentRelationDTO { +func ToAdjustmentRelationDTO(e *entity.AdjustmentStock) AdjustmentRelationDTO { return AdjustmentRelationDTO{ Id: e.Id, - Note: e.Notes, - Increase: e.Increase, - Decrease: e.Decrease, + Note: e.StockLog.Notes, + Increase: e.TotalQty, + Decrease: e.UsageQty, ProductWarehouseId: e.ProductWarehouseId, ProductWarehouse: ToProductWarehouseDTO(e.ProductWarehouse), } } -func ToAdjustmentListDTO(e *entity.StockLog) AdjustmentListDTO { +func ToAdjustmentListDTO(e *entity.AdjustmentStock) AdjustmentListDTO { var createdUser *userDTO.UserRelationDTO - if e.CreatedUser != nil { + if e.StockLog != nil && e.StockLog.CreatedUser != nil { createdUser = &userDTO.UserRelationDTO{ - Id: e.CreatedUser.Id, - IdUser: e.CreatedUser.IdUser, - Email: e.CreatedUser.Email, - Name: e.CreatedUser.Name, + Id: e.StockLog.CreatedUser.Id, + IdUser: e.StockLog.CreatedUser.IdUser, + Email: e.StockLog.CreatedUser.Email, + Name: e.StockLog.CreatedUser.Name, } } + createdAt := time.Time{} + if e.StockLog != nil { + createdAt = e.StockLog.CreatedAt + } + return AdjustmentListDTO{ AdjustmentRelationDTO: ToAdjustmentRelationDTO(e), CreatedUser: createdUser, - CreatedAt: e.CreatedAt, + CreatedAt: createdAt, } } -func ToAdjustmentDetailDTO(e *entity.StockLog) AdjustmentDetailDTO { +func ToAdjustmentDetailDTO(e *entity.AdjustmentStock) AdjustmentDetailDTO { return AdjustmentDetailDTO{ AdjustmentListDTO: ToAdjustmentListDTO(e), - // UpdatedAt: e.UpdatedAt, } } diff --git a/internal/modules/inventory/adjustments/repositories/adjustment_stock.repository.go b/internal/modules/inventory/adjustments/repositories/adjustment_stock.repository.go index 8d62b05c..fa2685e7 100644 --- a/internal/modules/inventory/adjustments/repositories/adjustment_stock.repository.go +++ b/internal/modules/inventory/adjustments/repositories/adjustment_stock.repository.go @@ -33,6 +33,14 @@ func (r *adjustmentStockRepositoryImpl) CreateOne(ctx context.Context, data *ent func (r *adjustmentStockRepositoryImpl) GetByStockLogID(ctx context.Context, stockLogID uint) (*entity.AdjustmentStock, error) { var record entity.AdjustmentStock err := r.db.WithContext(ctx). + Preload("StockLog"). + Preload("StockLog.ProductWarehouse"). + Preload("StockLog.ProductWarehouse.Product"). + Preload("StockLog.ProductWarehouse.Warehouse"). + Preload("StockLog.CreatedUser"). + Preload("ProductWarehouse"). + Preload("ProductWarehouse.Product"). + Preload("ProductWarehouse.Warehouse"). Where("stock_log_id = ?", stockLogID). First(&record).Error if err != nil { diff --git a/internal/modules/inventory/adjustments/services/adjustment.service.go b/internal/modules/inventory/adjustments/services/adjustment.service.go index 71b985c2..c92d059b 100644 --- a/internal/modules/inventory/adjustments/services/adjustment.service.go +++ b/internal/modules/inventory/adjustments/services/adjustment.service.go @@ -25,9 +25,9 @@ import ( ) type AdjustmentService interface { - Adjustment(ctx *fiber.Ctx, req *validation.Create) (*entity.StockLog, error) - GetOne(ctx *fiber.Ctx, id uint) (*entity.StockLog, error) - AdjustmentHistory(ctx *fiber.Ctx, query *validation.Query) ([]*entity.StockLog, int64, error) + Adjustment(ctx *fiber.Ctx, req *validation.Create) (*entity.AdjustmentStock, error) + GetOne(ctx *fiber.Ctx, id uint) (*entity.AdjustmentStock, error) + AdjustmentHistory(ctx *fiber.Ctx, query *validation.Query) ([]*entity.AdjustmentStock, int64, error) } type adjustmentService struct { @@ -73,10 +73,8 @@ func (s *adjustmentService) withRelations(db *gorm.DB) *gorm.DB { Preload("CreatedUser") } -func (s *adjustmentService) GetOne(c *fiber.Ctx, id uint) (*entity.StockLog, error) { - stockLog, err := s.StockLogsRepository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB { - return s.withRelations(db).Preload("ProductWarehouse.Product.ProductCategory") - }) +func (s *adjustmentService) GetOne(c *fiber.Ctx, id uint) (*entity.AdjustmentStock, error) { + adjustmentStock, err := s.AdjustmentStockRepository.GetByStockLogID(c.Context(), id) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Adjustment not found") @@ -85,14 +83,10 @@ func (s *adjustmentService) GetOne(c *fiber.Ctx, id uint) (*entity.StockLog, err return nil, err } - if stockLog.LoggableType != string(utils.StockLogTypeAdjustment) { - return nil, fiber.NewError(fiber.StatusNotFound, "Adjustment not found") - } - - return stockLog, nil + return adjustmentStock, nil } -func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*entity.StockLog, error) { +func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*entity.AdjustmentStock, error) { if err := s.Validate.Struct(req); err != nil { return nil, err } @@ -111,12 +105,13 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e if req.Quantity <= 0 { return nil, fiber.NewError(fiber.StatusBadRequest, "Quantity must be greater than zero") } + transactionType := strings.ToUpper(req.TransactionType) if transactionType != string(utils.StockLogTransactionTypeIncrease) && transactionType != string(utils.StockLogTransactionTypeDecrease) { return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid transaction type") } - var createdLogId uint + var createdAdjustmentStockId uint var projectFlockKandangID *uint pfkID, err := s.getActiveProjectFlockKandangID(ctx, uint(req.WarehouseID)) @@ -151,7 +146,8 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e return nil, err } err = s.StockLogsRepository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { - productWarehouse, err := s.ProductWarehouseRepo.GetProductWarehouseByProductAndWarehouseID(ctx, uint(req.ProductID), uint(req.WarehouseID)) + + productWarehouse, err := s.ProductWarehouseRepo.FindByProductWarehouseAndPfk(ctx, uint(req.ProductID), uint(req.WarehouseID), projectFlockKandangID) if err != nil { s.Log.Errorf("Failed to get product warehouse: %+v", err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to get product warehouse") @@ -171,14 +167,14 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e newLog.Increase = afterQuantity } else { if productWarehouse.Quantity < req.Quantity { - return fiber.NewError(fiber.StatusBadRequest, "Insufficient stock for adjustment") + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Insufficient stock. Current: %.2f, Requested: %.2f", productWarehouse.Quantity, req.Quantity)) } afterQuantity -= req.Quantity newLog.Decrease = afterQuantity } if err := s.StockLogsRepository.WithTx(tx).CreateOne(ctx, newLog, nil); err != nil { - s.Log.Errorf("Failed to create stock log: %+v", err) + return err } @@ -187,7 +183,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e ProductWarehouseId: productWarehouse.Id, } if err := s.AdjustmentStockRepository.WithTx(tx).CreateOne(ctx, adjustmentStock, nil); err != nil { - s.Log.Errorf("Failed to create adjustment stock: %+v", err) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to create adjustment stock record") } @@ -212,7 +208,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e UsableID: adjustmentStock.Id, ProductWarehouseID: uint(productWarehouse.Id), Quantity: req.Quantity, - AllowPending: false, // Don't allow pending for adjustment + AllowPending: false, Tx: tx, }) if err != nil { @@ -220,24 +216,27 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e } } - // Update ProductWarehouse quantity (for backward compatibility/reporting) - + // LEGACY: Update ProductWarehouse quantity (for backward compatibility/reporting) productWarehouse.Quantity = afterQuantity if err := s.ProductWarehouseRepo.WithTx(tx).UpdateOne(ctx, productWarehouse.Id, productWarehouse, nil); err != nil { s.Log.Errorf("Failed to update product warehouse quantity: %+v", err) return err } - createdLogId = newLog.Id + createdAdjustmentStockId = adjustmentStock.Id return nil }) if err != nil { s.Log.Errorf("Transaction failed in CreateOne: %+v", err) + var fiberErr *fiber.Error + if errors.As(err, &fiberErr) { + return nil, err + } return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to process adjustment transaction") } - return s.GetOne(c, createdLogId) + return s.GetOne(c, createdAdjustmentStockId) } func (s *adjustmentService) getActiveProjectFlockKandangID(ctx context.Context, warehouseID uint) (uint, error) { @@ -266,13 +265,15 @@ func (s *adjustmentService) getActiveProjectFlockKandangID(ctx context.Context, return uint(projectFlockKandang.Id), nil } -func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Query) ([]*entity.StockLog, int64, error) { +func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Query) ([]*entity.AdjustmentStock, int64, error) { if err := s.Validate.Struct(query); err != nil { return nil, 0, err } offset := (query.Page - 1) * query.Limit + var isProductsExist bool isWarehousesExist, err := s.WarehouseRepo.IdExists(c.Context(), uint(query.WarehouseID)) + if err != nil { return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate warehouse") } @@ -280,7 +281,8 @@ func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Qu return nil, 0, fiber.NewError(fiber.StatusNotFound, "Warehouse not found") } - isProductsExist, err := s.ProductRepo.IdExists(c.Context(), uint(query.ProductID)) + isProductsExist, err = s.ProductRepo.IdExists(c.Context(), uint(query.ProductID)) + if err != nil { s.Log.Errorf("Failed to check product existence: %+v", err) return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate product") @@ -289,28 +291,51 @@ func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Qu return nil, 0, fiber.NewError(fiber.StatusNotFound, "Product not found") } - stockLogs, total, err := s.StockLogsRepository.GetAll(c.Context(), offset, query.Limit, func(db *gorm.DB) *gorm.DB { + var adjustmentStocks []entity.AdjustmentStock + var total int64 - db = s.withRelations(db) + q := s.AdjustmentStockRepository.DB().WithContext(c.Context()).Model(&entity.AdjustmentStock{}). + Preload("StockLog"). + Preload("StockLog.ProductWarehouse"). + Preload("StockLog.ProductWarehouse.Product"). + Preload("StockLog.ProductWarehouse.Warehouse"). + Preload("StockLog.CreatedUser"). + Preload("ProductWarehouse"). + Preload("ProductWarehouse.Product"). + Preload("ProductWarehouse.Warehouse") - db = db.Where("loggable_type = ?", string(utils.StockLogTypeAdjustment)) + if query.ProductID > 0 { + q = q.Joins("JOIN stock_logs ON stock_logs.id = adjustment_stocks.stock_log_id"). + Joins("JOIN product_warehouses ON product_warehouses.id = stock_logs.product_warehouse_id"). + Where("product_warehouses.product_id = ?", query.ProductID) + } - if query.TransactionType != "" { - db = db.Where("transaction_type = ?", strings.ToUpper(query.TransactionType)) - } - db = s.StockLogsRepository.ApplyProductWarehouseFilters(db, uint(query.ProductID), uint(query.WarehouseID)) + if query.WarehouseID > 0 { + q = q.Joins("JOIN stock_logs ON stock_logs.id = adjustment_stocks.stock_log_id"). + Joins("JOIN product_warehouses ON product_warehouses.id = stock_logs.product_warehouse_id"). + Where("product_warehouses.warehouse_id = ?", query.WarehouseID) + } - return db.Order("created_at DESC") - }) + if query.TransactionType != "" { + q = q.Joins("JOIN stock_logs ON stock_logs.id = adjustment_stocks.stock_log_id"). + Where("stock_logs.transaction_type = ?", strings.ToUpper(query.TransactionType)) + } + + if err = q.Count(&total).Error; err != nil { + s.Log.Errorf("Failed to get adjustments: %+v", err) + return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to get adjustment history") + } + + err = q.Offset(offset).Limit(query.Limit).Order("created_at DESC").Find(&adjustmentStocks).Error if err != nil { s.Log.Errorf("Failed to get adjustments: %+v", err) return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to get adjustment history") } - result := make([]*entity.StockLog, len(stockLogs)) - for i, v := range stockLogs { - result[i] = &v + result := make([]*entity.AdjustmentStock, len(adjustmentStocks)) + for i := range adjustmentStocks { + result[i] = &adjustmentStocks[i] } return result, total, nil diff --git a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go index ea194c36..5b89808c 100644 --- a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go +++ b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go @@ -3,15 +3,14 @@ package service import ( "errors" + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "github.com/sirupsen/logrus" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/validations" kandangrepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" - - "github.com/go-playground/validator/v10" - "github.com/gofiber/fiber/v2" - "github.com/sirupsen/logrus" "gorm.io/gorm" )