From 2f5fab9f8081740e0bbe01161b03def69bd8771a Mon Sep 17 00:00:00 2001 From: Asep Teguh Hidayat <114033670+Aguh18@users.noreply.github.com> Date: Thu, 13 Nov 2025 19:28:50 +0700 Subject: [PATCH] Feat[BE-222]: creating update DO(Unfinished) --- ...create_marketing_product_deliveries.up.sql | 2 +- .../marketing-delivery-products.repository.go | 17 ++ .../controllers/delivery-orders.controller.go | 2 +- .../dto/delivery-orders.dto.go | 170 +++++++++++- .../services/delivery-orders.service.go | 253 +++++++++++++----- .../services/sales-orders.service.go | 3 +- 6 files changed, 367 insertions(+), 80 deletions(-) diff --git a/internal/database/migrations/20251107015128_create_marketing_product_deliveries.up.sql b/internal/database/migrations/20251107015128_create_marketing_product_deliveries.up.sql index 09625c16..45ca0907 100644 --- a/internal/database/migrations/20251107015128_create_marketing_product_deliveries.up.sql +++ b/internal/database/migrations/20251107015128_create_marketing_product_deliveries.up.sql @@ -6,7 +6,7 @@ CREATE TABLE marketing_delivery_products ( total_weight NUMERIC(15, 3) NOT NULL, avg_weight NUMERIC(15, 3) NOT NULL, total_price NUMERIC(15, 3) NOT NULL, - delivery_date TIMESTAMPTZ, + delivery_date DATE, vehicle_number VARCHAR(50), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), diff --git a/internal/modules/inventory/marketing-delivery-products/repositories/marketing-delivery-products.repository.go b/internal/modules/inventory/marketing-delivery-products/repositories/marketing-delivery-products.repository.go index ce94a1eb..96590990 100644 --- a/internal/modules/inventory/marketing-delivery-products/repositories/marketing-delivery-products.repository.go +++ b/internal/modules/inventory/marketing-delivery-products/repositories/marketing-delivery-products.repository.go @@ -11,6 +11,7 @@ import ( type MarketingDeliveryProductRepository interface { repository.BaseRepository[entity.MarketingDeliveryProduct] GetByMarketingProductID(ctx context.Context, marketingProductID uint) (*entity.MarketingDeliveryProduct, error) + GetByMarketingId(ctx context.Context, marketingId uint) ([]entity.MarketingDeliveryProduct, error) } type MarketingDeliveryProductRepositoryImpl struct { @@ -30,3 +31,19 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetByMarketingProductID(ctx con } return &deliveryProduct, nil } + +func (r *MarketingDeliveryProductRepositoryImpl) GetByMarketingId(ctx context.Context, marketingId uint) ([]entity.MarketingDeliveryProduct, error) { + var deliveryProducts []entity.MarketingDeliveryProduct + + // Raw query untuk mengambil delivery products berdasarkan marketing ID dengan preload MarketingProduct + if err := r.DB().WithContext(ctx). + Preload("MarketingProduct"). + Joins("INNER JOIN marketing_products mp ON marketing_delivery_products.marketing_product_id = mp.id"). + Where("mp.marketing_id = ?", marketingId). + Order("marketing_delivery_products.id ASC"). + Find(&deliveryProducts).Error; err != nil { + return nil, err + } + + return deliveryProducts, nil +} diff --git a/internal/modules/marketing/delivery-orderss/controllers/delivery-orders.controller.go b/internal/modules/marketing/delivery-orderss/controllers/delivery-orders.controller.go index a5f2839a..8ca51dc5 100644 --- a/internal/modules/marketing/delivery-orderss/controllers/delivery-orders.controller.go +++ b/internal/modules/marketing/delivery-orderss/controllers/delivery-orders.controller.go @@ -119,7 +119,7 @@ func (u *DeliveryOrdersController) UpdateOne(c *fiber.Ctx) error { Code: fiber.StatusOK, Status: "success", Message: "Update deliveryOrders successfully", - Data: dto.ToDeliveryOrdersListDTO(*result), + Data: result, }) } diff --git a/internal/modules/marketing/delivery-orderss/dto/delivery-orders.dto.go b/internal/modules/marketing/delivery-orderss/dto/delivery-orders.dto.go index 2b2ea51e..6008269d 100644 --- a/internal/modules/marketing/delivery-orderss/dto/delivery-orders.dto.go +++ b/internal/modules/marketing/delivery-orderss/dto/delivery-orders.dto.go @@ -1,6 +1,8 @@ package dto import ( + "fmt" + "sort" "time" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" @@ -22,6 +24,22 @@ type MarketingDeliveryProductDTO struct { VehicleNumber string `json:"vehicle_number"` } +// DTO untuk grouping delivery products berdasarkan warehouse dan tanggal +type DeliveryGroupDTO struct { + WarehouseId uint `json:"warehouse_id"` + WarehouseName string `json:"warehouse_name"` + DeliveryDate *time.Time `json:"delivery_date"` + VehicleNumber string `json:"vehicle_number"` + TotalQty float64 `json:"total_qty"` + TotalWeight float64 `json:"total_weight"` + TotalPrice float64 `json:"total_price"` +} + +// DTO untuk Delivery Order (DO) - berisi data delivery yang sudah digroup +type DeliveryOrderDTO struct { + DeliveryGroups []DeliveryGroupDTO `json:"delivery_groups"` +} + type DeliveryOrdersBaseDTO struct { Id uint `json:"id,omitempty"` DeliveryNumber *string `json:"delivery_number,omitempty"` @@ -30,20 +48,33 @@ type DeliveryOrdersBaseDTO struct { Notes string `json:"notes,omitempty"` } +type MarketingProductDTO struct { + Id uint `json:"id"` + MarketingId uint `json:"marketing_id"` + ProductWarehouseId uint `json:"product_warehouse_id"` + Qty float64 `json:"qty"` + UnitPrice float64 `json:"unit_price"` + AvgWeight float64 `json:"avg_weight"` + TotalWeight float64 `json:"total_weight"` + TotalPrice float64 `json:"total_price"` + // Add product relation if needed +} + type MarketingBaseDTO struct { - Id uint `json:"id"` - SoNumber string `json:"so_number"` - SoDate time.Time `json:"so_date"` + Id uint `json:"id"` + SoNumber string `json:"so_number"` + SoDate time.Time `json:"so_date"` + Products []MarketingProductDTO `json:"products,omitempty"` } type DeliveryOrdersListDTO struct { DeliveryOrdersBaseDTO - Marketing *MarketingBaseDTO `json:"marketing,omitempty"` - DeliveryProducts []MarketingDeliveryProductDTO `json:"delivery_products,omitempty"` - CreatedUser *userDTO.UserBaseDTO `json:"created_user"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Approval *approvalDTO.ApprovalBaseDTO `json:"approval,omitempty"` + SalesOrder *MarketingBaseDTO `json:"sales_order,omitempty"` // SO - Sales Order data + DeliveryOrder *DeliveryOrderDTO `json:"delivery_order,omitempty"` // DO - Delivery Order data (grouped) + CreatedUser *userDTO.UserBaseDTO `json:"created_user"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Approval *approvalDTO.ApprovalBaseDTO `json:"approval,omitempty"` } type DeliveryOrdersDetailDTO struct { @@ -52,6 +83,19 @@ type DeliveryOrdersDetailDTO struct { // === Mapper Functions === +func ToMarketingProductDTO(e entity.MarketingProduct) MarketingProductDTO { + return MarketingProductDTO{ + Id: e.Id, + MarketingId: e.MarketingId, + ProductWarehouseId: e.ProductWarehouseId, + Qty: e.Qty, + UnitPrice: e.UnitPrice, + AvgWeight: e.AvgWeight, + TotalWeight: e.TotalWeight, + TotalPrice: e.TotalPrice, + } +} + func ToMarketingDeliveryProductDTO(e entity.MarketingDeliveryProduct) MarketingDeliveryProductDTO { return MarketingDeliveryProductDTO{ Id: e.Id, @@ -90,10 +134,19 @@ func ToDeliveryOrdersListDTO(e entity.DeliveryOrders) DeliveryOrdersListDTO { var marketing *MarketingBaseDTO if e.Marketing != nil && e.Marketing.Id != 0 { + var marketingProducts []MarketingProductDTO + if len(e.Marketing.Products) > 0 { + marketingProducts = make([]MarketingProductDTO, len(e.Marketing.Products)) + for i, product := range e.Marketing.Products { + marketingProducts[i] = ToMarketingProductDTO(product) + } + } + marketing = &MarketingBaseDTO{ Id: e.Marketing.Id, SoNumber: e.Marketing.SoNumber, SoDate: e.Marketing.SoDate, + Products: marketingProducts, } } @@ -105,10 +158,16 @@ func ToDeliveryOrdersListDTO(e entity.DeliveryOrders) DeliveryOrdersListDTO { } } + // Group delivery products by warehouse and delivery date + deliveryGroups := groupDeliveryProducts(deliveryProductsDTOs) + + // Create delivery order DTO with summary + deliveryOrder := createDeliveryOrderDTO(deliveryGroups) + return DeliveryOrdersListDTO{ DeliveryOrdersBaseDTO: ToDeliveryOrdersBaseDTO(e), - Marketing: marketing, - DeliveryProducts: deliveryProductsDTOs, + SalesOrder: marketing, + DeliveryOrder: deliveryOrder, CreatedAt: e.CreatedAt, UpdatedAt: e.UpdatedAt, CreatedUser: createdUser, @@ -124,10 +183,19 @@ func ToDeliveryOrdersListDTOWithProducts(e entity.DeliveryOrders, deliveryProduc var marketing *MarketingBaseDTO if e.Marketing != nil && e.Marketing.Id != 0 { + var marketingProducts []MarketingProductDTO + if len(e.Marketing.Products) > 0 { + marketingProducts = make([]MarketingProductDTO, len(e.Marketing.Products)) + for i, product := range e.Marketing.Products { + marketingProducts[i] = ToMarketingProductDTO(product) + } + } + marketing = &MarketingBaseDTO{ Id: e.Marketing.Id, SoNumber: e.Marketing.SoNumber, SoDate: e.Marketing.SoDate, + Products: marketingProducts, } } @@ -139,10 +207,16 @@ func ToDeliveryOrdersListDTOWithProducts(e entity.DeliveryOrders, deliveryProduc } } + // Group delivery products by warehouse and delivery date + deliveryGroups := groupDeliveryProducts(deliveryProductsDTOs) + + // Create delivery order DTO with summary + deliveryOrder := createDeliveryOrderDTO(deliveryGroups) + return DeliveryOrdersListDTO{ DeliveryOrdersBaseDTO: ToDeliveryOrdersBaseDTO(e), - Marketing: marketing, - DeliveryProducts: deliveryProductsDTOs, + SalesOrder: marketing, + DeliveryOrder: deliveryOrder, CreatedAt: e.CreatedAt, UpdatedAt: e.UpdatedAt, CreatedUser: createdUser, @@ -162,3 +236,73 @@ func ToDeliveryOrdersDetailDTO(e entity.DeliveryOrders) DeliveryOrdersDetailDTO DeliveryOrdersListDTO: ToDeliveryOrdersListDTO(e), } } + +// groupDeliveryProducts groups delivery products by warehouse and delivery date +func groupDeliveryProducts(products []MarketingDeliveryProductDTO) []DeliveryGroupDTO { + // Create a map to group products + groupMap := make(map[string]*DeliveryGroupDTO) + + for _, product := range products { + // Create unique key for grouping (warehouse_id + delivery_date) + // Since we're working with DTO, we need to handle the warehouse id differently + warehouseId := uint(0) + warehouseName := "" + + // Extract warehouse info from product (assuming it might be available in future) + // For now, we'll use a simple grouping by delivery date and vehicle number + var key string + if product.DeliveryDate != nil { + key = fmt.Sprintf("%s_%s", product.DeliveryDate.Format("2006-01-02"), product.VehicleNumber) + } else { + key = fmt.Sprintf("no_date_%s", product.VehicleNumber) + } + + // Get or create group + group, exists := groupMap[key] + if !exists { + group = &DeliveryGroupDTO{ + WarehouseId: warehouseId, + WarehouseName: warehouseName, + DeliveryDate: product.DeliveryDate, + VehicleNumber: product.VehicleNumber, + TotalQty: 0, + TotalWeight: 0, + TotalPrice: 0, + } + + groupMap[key] = group + } + + // Update totals + group.TotalQty += product.Qty + group.TotalWeight += product.TotalWeight + group.TotalPrice += product.TotalPrice + } + + // Convert map to slice + var groups []DeliveryGroupDTO + for _, group := range groupMap { + groups = append(groups, *group) + } + + // Sort groups by delivery date + sort.Slice(groups, func(i, j int) bool { + if groups[i].DeliveryDate == nil || groups[j].DeliveryDate == nil { + return false + } + return groups[i].DeliveryDate.Before(*groups[j].DeliveryDate) + }) + + return groups +} + +// createDeliveryOrderDTO creates delivery order DTO +func createDeliveryOrderDTO(deliveryGroups []DeliveryGroupDTO) *DeliveryOrderDTO { + if len(deliveryGroups) == 0 { + return nil + } + + return &DeliveryOrderDTO{ + DeliveryGroups: deliveryGroups, + } +} diff --git a/internal/modules/marketing/delivery-orderss/services/delivery-orders.service.go b/internal/modules/marketing/delivery-orderss/services/delivery-orders.service.go index 7a2efa3f..f090ac01 100644 --- a/internal/modules/marketing/delivery-orderss/services/delivery-orders.service.go +++ b/internal/modules/marketing/delivery-orderss/services/delivery-orders.service.go @@ -25,7 +25,7 @@ type DeliveryOrdersService interface { GetAll(ctx *fiber.Ctx, params *validation.Query) ([]dto.DeliveryOrdersListDTO, int64, error) GetOne(ctx *fiber.Ctx, id uint) (*dto.DeliveryOrdersListDTO, error) CreateOne(ctx *fiber.Ctx, req *validation.Create) (*dto.DeliveryOrdersListDTO, error) - UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.DeliveryOrders, error) + UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*dto.DeliveryOrdersListDTO, error) DeleteOne(ctx *fiber.Ctx, id uint) error Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.DeliveryOrders, error) } @@ -91,18 +91,11 @@ func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.Query) ([ // Load delivery products untuk setiap marketing result := make([]dto.DeliveryOrdersListDTO, len(marketings)) for i, marketing := range marketings { - // Get marketing delivery products - var allDeliveryProducts []entity.MarketingDeliveryProduct - if err := s.Repository.DB().WithContext(c.Context()). - Preload("MarketingProduct"). - Where("marketing_product_id IN (?)", - s.Repository.DB().WithContext(c.Context()). - Model(&entity.MarketingProduct{}). - Select("id"). - Where("marketing_id = ?", marketing.Id)). - Find(&allDeliveryProducts).Error; err != nil { + // Get marketing delivery products menggunakan repository method + allDeliveryProducts, err := s.MarketingDeliveryProductRepo.GetByMarketingId(c.Context(), marketing.Id) + if err != nil { s.Log.Errorf("Failed to load delivery products for marketing %d: %+v", marketing.Id, err) - // Continue without products + allDeliveryProducts = []entity.MarketingDeliveryProduct{} // Set empty slice jika gagal } // Build response DTO @@ -125,7 +118,8 @@ func (s deliveryOrdersService) GetOne(c *fiber.Ctx, id uint) (*dto.DeliveryOrder return db.Preload("CreatedUser"). Preload("Customer"). Preload("SalesPerson"). - Preload("Products.ProductWarehouse") + Preload("Products.ProductWarehouse.Product"). + Preload("Products.ProductWarehouse.Warehouse") }) if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Marketing not found") @@ -135,26 +129,52 @@ func (s deliveryOrdersService) GetOne(c *fiber.Ctx, id uint) (*dto.DeliveryOrder return nil, err } - // Get marketing delivery products - var allDeliveryProducts []entity.MarketingDeliveryProduct - if err := s.Repository.DB().WithContext(c.Context()). - Preload("MarketingProduct"). - Where("marketing_product_id IN (?)", - s.Repository.DB().WithContext(c.Context()). - Model(&entity.MarketingProduct{}). - Select("id"). - Where("marketing_id = ?", marketing.Id)). - Find(&allDeliveryProducts).Error; err != nil { - s.Log.Errorf("Failed to load delivery products for marketing %d: %+v", marketing.Id, err) - // Continue without products + // Get marketing delivery products menggunakan repository method + allDeliveryProducts, err := s.MarketingDeliveryProductRepo.GetByMarketingId(c.Context(), id) + if err != nil { + s.Log.Errorf("Failed to load delivery products for marketing %d: %+v", id, err) + allDeliveryProducts = []entity.MarketingDeliveryProduct{} // Set empty slice jika gagal } - // Build response DTO + // Debug: Log jumlah delivery products + s.Log.Infof("Found %d delivery products for marketing %d", len(allDeliveryProducts), id) + + // Jika tidak ada delivery products, buat dummy data untuk testing + if len(allDeliveryProducts) == 0 && len(marketing.Products) > 0 { + s.Log.Infof("Creating dummy delivery products for testing") + for i, product := range marketing.Products { + deliveryDate := marketing.SoDate.AddDate(0, 0, i+7) // 7 hari setelah SO + dummyDeliveryProduct := entity.MarketingDeliveryProduct{ + Id: uint(i + 1), + MarketingProductId: product.Id, + Qty: product.Qty / 2, // Setengah dari qty asli + UnitPrice: product.UnitPrice, + TotalWeight: product.TotalWeight / 2, + AvgWeight: product.AvgWeight, + TotalPrice: (product.Qty / 2) * product.UnitPrice, + DeliveryDate: &deliveryDate, + VehicleNumber: fmt.Sprintf("B%04d%s", (i+1)*1000, "ABC"), + } + allDeliveryProducts = append(allDeliveryProducts, dummyDeliveryProduct) + } + s.Log.Infof("Created %d dummy delivery products", len(allDeliveryProducts)) + } + + // Build response DTO dengan timestamps yang benar deliveryOrderResponse := &entity.DeliveryOrders{ MarketingId: marketing.Id, CreatedUser: &marketing.CreatedUser, Marketing: marketing, DeliveryProducts: allDeliveryProducts, + CreatedAt: marketing.CreatedAt, + UpdatedAt: marketing.UpdatedAt, + } + + // Set delivery_date dari delivery products atau fallback ke marketing date + if len(allDeliveryProducts) > 0 && allDeliveryProducts[0].DeliveryDate != nil { + deliveryOrderResponse.DeliveryDate = *allDeliveryProducts[0].DeliveryDate + } else { + deliveryOrderResponse.DeliveryDate = marketing.SoDate } responseDTO := dto.ToDeliveryOrdersListDTOWithProducts(*deliveryOrderResponse, allDeliveryProducts) @@ -255,6 +275,27 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) s.Log.Infof("Updated delivery product %d: qty=%v, unitPrice=%v, totalPrice=%v", deliveryProduct.Id, requestedProduct.Qty, requestedProduct.UnitPrice, requestedProduct.TotalPrice) } + approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction)) + actorID := uint(1) // TODO: ambil dari auth context + approvalAction := entity.ApprovalActionCreated + var notes *string + if req.Notes != "" { + notes = &req.Notes + } + if _, err := approvalSvcTx.CreateApproval( + c.Context(), + utils.ApprovalWorkflowMarketing, + req.MarketingId, + utils.MarketingDeliveryOrder, + &approvalAction, + actorID, + notes); err != nil { + if !errors.Is(err, gorm.ErrDuplicatedKey) { + s.Log.Errorf("Failed to create delivery order approval: %+v", err) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to create delivery order approval") + } + } + return nil }) @@ -262,7 +303,6 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) return nil, err } - // Fetch marketing dengan delivery products yang sudah di-updated marketing, err := s.MarketingRepo.GetByID(c.Context(), req.MarketingId, func(db *gorm.DB) *gorm.DB { return db.Preload("CreatedUser").Preload("Products.DeliveryProduct") }) @@ -271,18 +311,11 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch updated marketing") } - // Get marketing delivery products - var allDeliveryProducts []entity.MarketingDeliveryProduct - if err := s.MarketingDeliveryProductRepo.DB().WithContext(c.Context()). - Preload("MarketingProduct"). - Where("marketing_product_id IN (?)", - s.MarketingProductRepo.DB().WithContext(c.Context()). - Model(&entity.MarketingProduct{}). - Select("id"). - Where("marketing_id = ?", req.MarketingId)). - Find(&allDeliveryProducts).Error; err != nil { + // Get marketing delivery products menggunakan repository method + allDeliveryProducts, err := s.MarketingDeliveryProductRepo.GetByMarketingId(c.Context(), req.MarketingId) + if err != nil { s.Log.Errorf("Failed to load delivery products: %+v", err) - // Continue tanpa delivery products + allDeliveryProducts = []entity.MarketingDeliveryProduct{} // Set empty slice jika gagal } // Build response DTO @@ -298,38 +331,132 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) return &responseDTO, nil } -func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.DeliveryOrders, error) { +func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*dto.DeliveryOrdersListDTO, error) { if err := s.Validate.Struct(req); err != nil { return nil, err } - updateBody := make(map[string]any) - - if req.DeliveryDate != "" { - deliveryDate, err := time.Parse("2006-01-02", req.DeliveryDate) - if err != nil { - return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid delivery date format") - } - updateBody["delivery_date"] = deliveryDate - } - - if req.Notes != "" { - updateBody["notes"] = req.Notes - } - - if len(updateBody) == 0 { - return s.Repository.GetByID(c.Context(), id, s.withRelations) - } - - if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, fiber.NewError(fiber.StatusNotFound, "DeliveryOrders not found") - } - s.Log.Errorf("Failed to update deliveryOrders: %+v", err) + // Validate bahwa marketing ID yang di-update ada (id parameter adalah marketing_id untuk delivery orders) + if err := commonSvc.EnsureRelations(c.Context(), + commonSvc.RelationCheck{Name: "Marketing", ID: &id, Exists: s.MarketingRepo.IdExists}, + ); err != nil { return nil, err } - return s.Repository.GetByID(c.Context(), id, s.withRelations) + // Validate delivery products jika ada + if len(req.DeliveryProducts) > 0 { + for _, requestedProduct := range req.DeliveryProducts { + if err := commonSvc.EnsureRelations(c.Context(), + commonSvc.RelationCheck{Name: "MarketingProduct", ID: &requestedProduct.MarketingProductId, Exists: s.MarketingProductRepo.IdExists}, + ); err != nil { + return nil, err + } + } + } + + err := s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { + marketingProductRepositoryTx := marketingRepo.NewMarketingProductRepository(dbTransaction) + marketingDeliveryProductRepositoryTx := marketingDeliveryProductRepo.NewMarketingDeliveryProductRepository(dbTransaction) + + // Update delivery products jika ada dalam request + if len(req.DeliveryProducts) > 0 { + for _, requestedProduct := range req.DeliveryProducts { + // Validate bahwa marketing product ada untuk marketing ini + allMarketingProducts, err := marketingProductRepositoryTx.GetByMarketingID(c.Context(), id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("No marketing products found for marketing %d", id)) + } + s.Log.Errorf("Failed to fetch marketing products: %+v", err) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch marketing products") + } + + var foundMarketingProduct *entity.MarketingProduct + for i := range allMarketingProducts { + if allMarketingProducts[i].Id == requestedProduct.MarketingProductId { + foundMarketingProduct = &allMarketingProducts[i] + break + } + } + if foundMarketingProduct == nil { + return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Marketing product %d not found for this marketing", requestedProduct.MarketingProductId)) + } + + // Get existing delivery product + deliveryProduct, err := marketingDeliveryProductRepositoryTx.GetByMarketingProductID(c.Context(), foundMarketingProduct.Id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Delivery product for marketing product %d not found", requestedProduct.MarketingProductId)) + } + return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch delivery product") + } + + // Parse delivery date + var itemDeliveryDate time.Time + if requestedProduct.DeliveryDate != "" { + parsedDate, err := utils.ParseDateString(requestedProduct.DeliveryDate) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid delivery date format for product %d: %v", requestedProduct.MarketingProductId, err)) + } + itemDeliveryDate = parsedDate + } else if deliveryProduct.DeliveryDate != nil { + itemDeliveryDate = *deliveryProduct.DeliveryDate + } else { + itemDeliveryDate = time.Now() + } + + // Update delivery product + deliveryProduct.Qty = requestedProduct.Qty + deliveryProduct.UnitPrice = requestedProduct.UnitPrice + deliveryProduct.AvgWeight = requestedProduct.AvgWeight + deliveryProduct.TotalWeight = requestedProduct.TotalWeight + deliveryProduct.TotalPrice = requestedProduct.TotalPrice + deliveryProduct.DeliveryDate = &itemDeliveryDate + deliveryProduct.VehicleNumber = requestedProduct.VehicleNumber + + if err := marketingDeliveryProductRepositoryTx.UpdateOne(c.Context(), deliveryProduct.Id, deliveryProduct, nil); err != nil { + s.Log.Errorf("Failed to update marketing delivery product: %+v", err) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to update delivery product") + } + + s.Log.Infof("Updated delivery product %d: qty=%v, unitPrice=%v, totalPrice=%v", deliveryProduct.Id, requestedProduct.Qty, requestedProduct.UnitPrice, requestedProduct.TotalPrice) + } + } + + return nil + }) + + if err != nil { + return nil, err + } + + // Fetch updated marketing with delivery products + marketing, err := s.MarketingRepo.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB { + return db.Preload("CreatedUser").Preload("Products.DeliveryProduct") + }) + if err != nil { + s.Log.Errorf("Failed to fetch marketing after update: %+v", err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch updated marketing") + } + + // Get marketing delivery products menggunakan repository method + allDeliveryProducts, err := s.MarketingDeliveryProductRepo.GetByMarketingId(c.Context(), id) + if err != nil { + s.Log.Errorf("Failed to load delivery products: %+v", err) + allDeliveryProducts = []entity.MarketingDeliveryProduct{} // Set empty slice jika gagal + } + + // Build response DTO + deliveryOrderResponse := &entity.DeliveryOrders{ + MarketingId: id, + Notes: req.Notes, + CreatedUser: &marketing.CreatedUser, + Marketing: marketing, + DeliveryProducts: allDeliveryProducts, + } + + responseDTO := dto.ToDeliveryOrdersListDTOWithProducts(*deliveryOrderResponse, allDeliveryProducts) + return &responseDTO, nil } func (s deliveryOrdersService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entity.DeliveryOrders, error) { diff --git a/internal/modules/marketing/sales-orders/services/sales-orders.service.go b/internal/modules/marketing/sales-orders/services/sales-orders.service.go index 1fb36733..74244496 100644 --- a/internal/modules/marketing/sales-orders/services/sales-orders.service.go +++ b/internal/modules/marketing/sales-orders/services/sales-orders.service.go @@ -64,8 +64,7 @@ func (s salesOrdersService) withRelations(db *gorm.DB) *gorm.DB { Preload("Customer"). Preload("SalesPerson"). Preload("Products.ProductWarehouse.Product"). - Preload("Products.ProductWarehouse.Warehouse"). - Preload("Products.DeliveryProduct") + Preload("Products.ProductWarehouse.Warehouse") } func (s salesOrdersService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Marketing, int64, error) {