diff --git a/internal/modules/marketing/repositories/salesorder_delivery_product.repository.go b/internal/modules/marketing/repositories/salesorder_delivery_product.repository.go index 94d23103..8d895e34 100644 --- a/internal/modules/marketing/repositories/salesorder_delivery_product.repository.go +++ b/internal/modules/marketing/repositories/salesorder_delivery_product.repository.go @@ -135,7 +135,19 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C } if filters.FilterBy != "" && (filters.StartDate != "" || filters.EndDate != "") { - if filters.FilterBy == "delivery_date" { + if filters.FilterBy == "so_date" { + if filters.StartDate != "" { + if startDate, err := utils.ParseDateString(filters.StartDate); err == nil { + db = db.Where("marketings.so_date >= ?", startDate) + } + } + if filters.EndDate != "" { + if endDate, err := utils.ParseDateString(filters.EndDate); err == nil { + nextDate := endDate.AddDate(0, 0, 1) + db = db.Where("marketings.so_date < ?", nextDate) + } + } + } else if filters.FilterBy == "realization_date" { if filters.StartDate != "" { if startDate, err := utils.ParseDateString(filters.StartDate); err == nil { db = db.Where("marketing_delivery_products.delivery_date >= ?", startDate) @@ -147,18 +159,6 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C db = db.Where("marketing_delivery_products.delivery_date < ?", nextDate) } } - } else if filters.FilterBy == "realization_date" { - if filters.StartDate != "" { - if startDate, err := utils.ParseDateString(filters.StartDate); err == nil { - db = db.Where("marketings.created_at >= ?", startDate) - } - } - if filters.EndDate != "" { - if endDate, err := utils.ParseDateString(filters.EndDate); err == nil { - nextDate := endDate.AddDate(0, 0, 1) - db = db.Where("marketings.created_at < ?", nextDate) - } - } } } @@ -167,7 +167,9 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C if filters.SortBy != "" { switch filters.SortBy { - case "delivery_date": + case "so_date": + sortColumn = "marketings.so_date" + case "realization_date": sortColumn = "marketing_delivery_products.delivery_date" case "customer": sortColumn = "customers.name" diff --git a/internal/modules/repports/controllers/repport.controller.go b/internal/modules/repports/controllers/repport.controller.go index b94ec8c2..d00a3ff5 100644 --- a/internal/modules/repports/controllers/repport.controller.go +++ b/internal/modules/repports/controllers/repport.controller.go @@ -11,6 +11,17 @@ import ( "github.com/gofiber/fiber/v2" ) +// === Marketing Report Response === + +type MarketingReportResponse struct { + Code int `json:"code"` + Status string `json:"status"` + Message string `json:"message"` + Meta response.Meta `json:"meta"` + Data []dto.RepportMarketingItemDTO `json:"data"` + Total *dto.Summary `json:"total,omitempty"` +} + type RepportController struct { RepportService service.RepportService } @@ -85,8 +96,31 @@ func (c *RepportController) GetMarketing(ctx *fiber.Ctx) error { return err } + // Calculate total summary from result items + var total *dto.Summary + if len(result) > 0 { + totalQty := 0 + totalWeightKg := 0.0 + totalSalesAmount := int64(0) + totalHppAmount := int64(0) + + for _, item := range result { + totalQty += int(item.Qty) + totalWeightKg += item.TotalWeightKg + totalSalesAmount += int64(item.SalesAmount) + totalHppAmount += int64(item.HppAmount) + } + + total = &dto.Summary{ + TotalQty: totalQty, + TotalWeightKg: totalWeightKg, + TotalSalesAmount: totalSalesAmount, + TotalHppAmount: totalHppAmount, + } + } + return ctx.Status(fiber.StatusOK). - JSON(response.SuccessWithPaginate[dto.RepportMarketingItemDTO]{ + JSON(MarketingReportResponse{ Code: fiber.StatusOK, Status: "success", Message: "Get marketing report successfully", @@ -96,6 +130,7 @@ func (c *RepportController) GetMarketing(ctx *fiber.Ctx) error { TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), TotalResults: totalResults, }, - Data: result, + Data: result, + Total: total, }) } diff --git a/internal/modules/repports/dto/repportMarketing.dto.go b/internal/modules/repports/dto/repportMarketing.dto.go index 77c5f5d8..98ec9888 100644 --- a/internal/modules/repports/dto/repportMarketing.dto.go +++ b/internal/modules/repports/dto/repportMarketing.dto.go @@ -1,6 +1,9 @@ package dto import ( + "fmt" + "time" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" customerDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/dto" productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto" @@ -8,11 +11,10 @@ import ( userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" ) -// === Main Report Item DTO === - type RepportMarketingItemDTO struct { - DoDate string `json:"do_date"` - RealizationDate string `json:"realization_date"` + ID int `json:"id"` + SoDate time.Time `json:"so_date"` + RealizationDate time.Time `json:"realization_date"` AgingDays int `json:"aging_days"` Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"` Customer *customerDTO.CustomerRelationDTO `json:"customer,omitempty"` @@ -30,70 +32,70 @@ type RepportMarketingItemDTO struct { HppAmount float64 `json:"hpp_amount"` } -// === Report Response DTO === +type Summary struct { + TotalQty int `json:"total_qty"` + TotalWeightKg float64 `json:"total_weight_kg"` + TotalSalesAmount int64 `json:"total_sales_amount"` + TotalHppAmount int64 `json:"total_hpp_amount"` +} type RepportMarketingResponseDTO struct { Items []RepportMarketingItemDTO `json:"items"` + Total *Summary `json:"total,omitempty"` } -// === MAPPERS === - -// ToRepportMarketingItemDTO maps marketing delivery product to detailed report item func ToRepportMarketingItemDTO(mdp entity.MarketingDeliveryProduct, hppPricePerKg float64) RepportMarketingItemDTO { + soDate := time.Time{} agingDays := 0 - - doDate := "" - if mdp.DeliveryDate != nil { - doDate = mdp.DeliveryDate.Format("02-Jan-2006") + if mdp.MarketingProduct.Marketing.SoDate.Year() > 1 { + soDate = mdp.MarketingProduct.Marketing.SoDate + agingDays = int(time.Now().Sub(soDate).Hours() / 24) } - realizationDate := "" + realizationDate := time.Time{} if mdp.DeliveryDate != nil { - realizationDate = mdp.DeliveryDate.Format("02-Jan-2006") + realizationDate = *mdp.DeliveryDate } - // Calculate sales_amount = total_weight_kg * sales_price_per_kg - salesAmount := mdp.TotalWeight * mdp.UnitPrice - // Calculate hpp_amount = total_weight_kg * hpp_price_per_kg - hppAmount := mdp.TotalWeight * hppPricePerKg + doNumber := generateDoNumber(mdp.MarketingProduct.Marketing.SoNumber, mdp.DeliveryDate, mdp.MarketingProduct.ProductWarehouse.WarehouseId) + + totalWeightKg := mdp.Qty * mdp.AvgWeight + salesAmount := totalWeightKg * mdp.UnitPrice + hppAmount := totalWeightKg * hppPricePerKg item := RepportMarketingItemDTO{ - DoDate: doDate, + ID: int(mdp.Id), + SoDate: soDate, RealizationDate: realizationDate, AgingDays: agingDays, - DoNumber: mdp.MarketingProduct.Marketing.SoNumber, + DoNumber: doNumber, MarketingType: "ayam", Qty: mdp.Qty, AverageWeightKg: mdp.AvgWeight, - TotalWeightKg: mdp.TotalWeight, + TotalWeightKg: totalWeightKg, SalesPricePerKg: mdp.UnitPrice, HppPricePerKg: hppPricePerKg, SalesAmount: salesAmount, HppAmount: hppAmount, } - // Map warehouse with full details if mdp.MarketingProduct.ProductWarehouse.WarehouseId != 0 { mapped := warehouseDTO.ToWarehouseRelationDTO(mdp.MarketingProduct.ProductWarehouse.Warehouse) item.Warehouse = &mapped } - // Map customer using CustomerRelationDTO if mdp.MarketingProduct.Marketing.CustomerId != 0 { mapped := customerDTO.ToCustomerRelationDTO(mdp.MarketingProduct.Marketing.Customer) item.Customer = &mapped } - // Map sales person if mdp.MarketingProduct.Marketing.SalesPersonId != 0 { mapped := userDTO.ToUserRelationDTO(mdp.MarketingProduct.Marketing.SalesPerson) item.Sales = &mapped } - // Map vehicle number item.VehicleNumber = mdp.VehicleNumber - // Map product using ProductRelationDTO if mdp.MarketingProduct.ProductWarehouse.ProductId != 0 { mapped := productDTO.ToProductRelationDTO(mdp.MarketingProduct.ProductWarehouse.Product) item.Product = &mapped @@ -102,7 +104,6 @@ func ToRepportMarketingItemDTO(mdp entity.MarketingDeliveryProduct, hppPricePerK return item } -// ToRepportMarketingItemDTOs maps array of delivery products to report items with HPP calculation func ToRepportMarketingItemDTOs(mdps []entity.MarketingDeliveryProduct, hppPricePerKg float64) []RepportMarketingItemDTO { items := make([]RepportMarketingItemDTO, 0, len(mdps)) for _, mdp := range mdps { @@ -111,11 +112,46 @@ func ToRepportMarketingItemDTOs(mdps []entity.MarketingDeliveryProduct, hppPrice return items } -// ToRepportMarketingResponseDTO creates complete marketing report response +func ToSummary(mdps []entity.MarketingDeliveryProduct, hppPricePerKg float64) *Summary { + if len(mdps) == 0 { + return nil + } + + totalQty := 0 + totalWeightKg := 0.0 + totalSalesAmount := int64(0) + totalHppAmount := int64(0) + + for _, mdp := range mdps { + calculatedTotalWeight := mdp.Qty * mdp.AvgWeight + totalQty += int(mdp.Qty) + totalWeightKg += calculatedTotalWeight + totalSalesAmount += int64(calculatedTotalWeight * mdp.UnitPrice) + totalHppAmount += int64(calculatedTotalWeight * hppPricePerKg) + } + + return &Summary{ + TotalQty: totalQty, + TotalWeightKg: totalWeightKg, + TotalSalesAmount: totalSalesAmount, + TotalHppAmount: totalHppAmount, + } +} + +func generateDoNumber(soNumber string, deliveryDate *time.Time, warehouseId uint) string { + dateStr := "" + if deliveryDate != nil { + dateStr = deliveryDate.Format("20060102") + } + return fmt.Sprintf("%s-%s-%d", soNumber, dateStr, warehouseId) +} + func ToRepportMarketingResponseDTO(mdps []entity.MarketingDeliveryProduct, hppPricePerKg float64) RepportMarketingResponseDTO { items := ToRepportMarketingItemDTOs(mdps, hppPricePerKg) + total := ToSummary(mdps, hppPricePerKg) return RepportMarketingResponseDTO{ Items: items, + Total: total, } } diff --git a/internal/modules/repports/services/repport.service.go b/internal/modules/repports/services/repport.service.go index 4db200ab..553fc7af 100644 --- a/internal/modules/repports/services/repport.service.go +++ b/internal/modules/repports/services/repport.service.go @@ -152,12 +152,22 @@ func (s *repportService) calculateHppPricePerKg(ctx context.Context, projectFloc return 0 } - totalActualCost := float64(0) + costBop := float64(0) + for _, realization := range realizations { cost := realization.Price * realization.Qty - totalActualCost += cost + category := "" + if realization.ExpenseNonstock != nil && realization.ExpenseNonstock.Expense != nil { + category = realization.ExpenseNonstock.Expense.Category + } + + if category == "BOP" { + costBop += cost + } } + totalActualCost := costBop + if totalActualCost == 0 { return 0 } diff --git a/internal/modules/repports/validations/repport.validation.go b/internal/modules/repports/validations/repport.validation.go index e568952d..6b3bd71e 100644 --- a/internal/modules/repports/validations/repport.validation.go +++ b/internal/modules/repports/validations/repport.validation.go @@ -23,9 +23,9 @@ type MarketingQuery struct { ProductId int64 `query:"product_id" validate:"omitempty"` WarehouseId int64 `query:"warehouse_id" validate:"omitempty"` SalesPersonId int64 `query:"sales_person_id" validate:"omitempty"` - FilterBy string `query:"filter_by" validate:"omitempty,oneof=realization_date delivery_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=delivery_date customer warehouse product sales_person vehicle_number sales_amount hpp_amount qty average_weight total_weight sales_price hpp_price aging_days"` + 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"` SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"` }