Feat[BE]:: adjust marketing report API

This commit is contained in:
aguhh18
2025-12-17 11:30:49 +07:00
parent afe4b2ffe3
commit 40f192660d
5 changed files with 131 additions and 48 deletions
@@ -135,7 +135,19 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C
} }
if filters.FilterBy != "" && (filters.StartDate != "" || filters.EndDate != "") { 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 filters.StartDate != "" {
if startDate, err := utils.ParseDateString(filters.StartDate); err == nil { if startDate, err := utils.ParseDateString(filters.StartDate); err == nil {
db = db.Where("marketing_delivery_products.delivery_date >= ?", startDate) 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) 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 != "" { if filters.SortBy != "" {
switch filters.SortBy { switch filters.SortBy {
case "delivery_date": case "so_date":
sortColumn = "marketings.so_date"
case "realization_date":
sortColumn = "marketing_delivery_products.delivery_date" sortColumn = "marketing_delivery_products.delivery_date"
case "customer": case "customer":
sortColumn = "customers.name" sortColumn = "customers.name"
@@ -11,6 +11,17 @@ import (
"github.com/gofiber/fiber/v2" "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 { type RepportController struct {
RepportService service.RepportService RepportService service.RepportService
} }
@@ -85,8 +96,31 @@ func (c *RepportController) GetMarketing(ctx *fiber.Ctx) error {
return err 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). return ctx.Status(fiber.StatusOK).
JSON(response.SuccessWithPaginate[dto.RepportMarketingItemDTO]{ JSON(MarketingReportResponse{
Code: fiber.StatusOK, Code: fiber.StatusOK,
Status: "success", Status: "success",
Message: "Get marketing report successfully", 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))), TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
TotalResults: totalResults, TotalResults: totalResults,
}, },
Data: result, Data: result,
Total: total,
}) })
} }
@@ -1,6 +1,9 @@
package dto package dto
import ( import (
"fmt"
"time"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities" entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
customerDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/dto" customerDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/dto"
productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/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" userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
) )
// === Main Report Item DTO ===
type RepportMarketingItemDTO struct { type RepportMarketingItemDTO struct {
DoDate string `json:"do_date"` ID int `json:"id"`
RealizationDate string `json:"realization_date"` SoDate time.Time `json:"so_date"`
RealizationDate time.Time `json:"realization_date"`
AgingDays int `json:"aging_days"` AgingDays int `json:"aging_days"`
Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"` Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"`
Customer *customerDTO.CustomerRelationDTO `json:"customer,omitempty"` Customer *customerDTO.CustomerRelationDTO `json:"customer,omitempty"`
@@ -30,70 +32,70 @@ type RepportMarketingItemDTO struct {
HppAmount float64 `json:"hpp_amount"` 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 { type RepportMarketingResponseDTO struct {
Items []RepportMarketingItemDTO `json:"items"` 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 { func ToRepportMarketingItemDTO(mdp entity.MarketingDeliveryProduct, hppPricePerKg float64) RepportMarketingItemDTO {
soDate := time.Time{}
agingDays := 0 agingDays := 0
if mdp.MarketingProduct.Marketing.SoDate.Year() > 1 {
doDate := "" soDate = mdp.MarketingProduct.Marketing.SoDate
if mdp.DeliveryDate != nil { agingDays = int(time.Now().Sub(soDate).Hours() / 24)
doDate = mdp.DeliveryDate.Format("02-Jan-2006")
} }
realizationDate := "" realizationDate := time.Time{}
if mdp.DeliveryDate != nil { if mdp.DeliveryDate != nil {
realizationDate = mdp.DeliveryDate.Format("02-Jan-2006") realizationDate = *mdp.DeliveryDate
} }
// Calculate sales_amount = total_weight_kg * sales_price_per_kg doNumber := generateDoNumber(mdp.MarketingProduct.Marketing.SoNumber, mdp.DeliveryDate, mdp.MarketingProduct.ProductWarehouse.WarehouseId)
salesAmount := mdp.TotalWeight * mdp.UnitPrice
// Calculate hpp_amount = total_weight_kg * hpp_price_per_kg totalWeightKg := mdp.Qty * mdp.AvgWeight
hppAmount := mdp.TotalWeight * hppPricePerKg salesAmount := totalWeightKg * mdp.UnitPrice
hppAmount := totalWeightKg * hppPricePerKg
item := RepportMarketingItemDTO{ item := RepportMarketingItemDTO{
DoDate: doDate, ID: int(mdp.Id),
SoDate: soDate,
RealizationDate: realizationDate, RealizationDate: realizationDate,
AgingDays: agingDays, AgingDays: agingDays,
DoNumber: mdp.MarketingProduct.Marketing.SoNumber, DoNumber: doNumber,
MarketingType: "ayam", MarketingType: "ayam",
Qty: mdp.Qty, Qty: mdp.Qty,
AverageWeightKg: mdp.AvgWeight, AverageWeightKg: mdp.AvgWeight,
TotalWeightKg: mdp.TotalWeight, TotalWeightKg: totalWeightKg,
SalesPricePerKg: mdp.UnitPrice, SalesPricePerKg: mdp.UnitPrice,
HppPricePerKg: hppPricePerKg, HppPricePerKg: hppPricePerKg,
SalesAmount: salesAmount, SalesAmount: salesAmount,
HppAmount: hppAmount, HppAmount: hppAmount,
} }
// Map warehouse with full details
if mdp.MarketingProduct.ProductWarehouse.WarehouseId != 0 { if mdp.MarketingProduct.ProductWarehouse.WarehouseId != 0 {
mapped := warehouseDTO.ToWarehouseRelationDTO(mdp.MarketingProduct.ProductWarehouse.Warehouse) mapped := warehouseDTO.ToWarehouseRelationDTO(mdp.MarketingProduct.ProductWarehouse.Warehouse)
item.Warehouse = &mapped item.Warehouse = &mapped
} }
// Map customer using CustomerRelationDTO
if mdp.MarketingProduct.Marketing.CustomerId != 0 { if mdp.MarketingProduct.Marketing.CustomerId != 0 {
mapped := customerDTO.ToCustomerRelationDTO(mdp.MarketingProduct.Marketing.Customer) mapped := customerDTO.ToCustomerRelationDTO(mdp.MarketingProduct.Marketing.Customer)
item.Customer = &mapped item.Customer = &mapped
} }
// Map sales person
if mdp.MarketingProduct.Marketing.SalesPersonId != 0 { if mdp.MarketingProduct.Marketing.SalesPersonId != 0 {
mapped := userDTO.ToUserRelationDTO(mdp.MarketingProduct.Marketing.SalesPerson) mapped := userDTO.ToUserRelationDTO(mdp.MarketingProduct.Marketing.SalesPerson)
item.Sales = &mapped item.Sales = &mapped
} }
// Map vehicle number
item.VehicleNumber = mdp.VehicleNumber item.VehicleNumber = mdp.VehicleNumber
// Map product using ProductRelationDTO
if mdp.MarketingProduct.ProductWarehouse.ProductId != 0 { if mdp.MarketingProduct.ProductWarehouse.ProductId != 0 {
mapped := productDTO.ToProductRelationDTO(mdp.MarketingProduct.ProductWarehouse.Product) mapped := productDTO.ToProductRelationDTO(mdp.MarketingProduct.ProductWarehouse.Product)
item.Product = &mapped item.Product = &mapped
@@ -102,7 +104,6 @@ func ToRepportMarketingItemDTO(mdp entity.MarketingDeliveryProduct, hppPricePerK
return item return item
} }
// ToRepportMarketingItemDTOs maps array of delivery products to report items with HPP calculation
func ToRepportMarketingItemDTOs(mdps []entity.MarketingDeliveryProduct, hppPricePerKg float64) []RepportMarketingItemDTO { func ToRepportMarketingItemDTOs(mdps []entity.MarketingDeliveryProduct, hppPricePerKg float64) []RepportMarketingItemDTO {
items := make([]RepportMarketingItemDTO, 0, len(mdps)) items := make([]RepportMarketingItemDTO, 0, len(mdps))
for _, mdp := range mdps { for _, mdp := range mdps {
@@ -111,11 +112,46 @@ func ToRepportMarketingItemDTOs(mdps []entity.MarketingDeliveryProduct, hppPrice
return items 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 { func ToRepportMarketingResponseDTO(mdps []entity.MarketingDeliveryProduct, hppPricePerKg float64) RepportMarketingResponseDTO {
items := ToRepportMarketingItemDTOs(mdps, hppPricePerKg) items := ToRepportMarketingItemDTOs(mdps, hppPricePerKg)
total := ToSummary(mdps, hppPricePerKg)
return RepportMarketingResponseDTO{ return RepportMarketingResponseDTO{
Items: items, Items: items,
Total: total,
} }
} }
@@ -152,12 +152,22 @@ func (s *repportService) calculateHppPricePerKg(ctx context.Context, projectFloc
return 0 return 0
} }
totalActualCost := float64(0) costBop := float64(0)
for _, realization := range realizations { for _, realization := range realizations {
cost := realization.Price * realization.Qty 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 { if totalActualCost == 0 {
return 0 return 0
} }
@@ -23,9 +23,9 @@ type MarketingQuery struct {
ProductId int64 `query:"product_id" validate:"omitempty"` ProductId int64 `query:"product_id" validate:"omitempty"`
WarehouseId int64 `query:"warehouse_id" validate:"omitempty"` WarehouseId int64 `query:"warehouse_id" validate:"omitempty"`
SalesPersonId int64 `query:"sales_person_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"` StartDate string `query:"start_date" validate:"omitempty,datetime=2006-01-02"`
EndDate string `query:"end_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"` SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"`
} }