package dto import ( "encoding/json" "time" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" marketingDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/dto" customerDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/dto" productCategoryDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/product-categories/dto" productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto" supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto" uomDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/dto" warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto" userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" "gitlab.com/mbugroup/lti-api.git/internal/utils" ) type RepportMarketingItemDTO struct { 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"` DoNumber string `json:"do_number"` Sales *userDTO.UserRelationDTO `json:"sales,omitempty"` VehicleNumber string `json:"vehicle_number"` Product *ProductRelationDTOFixed `json:"product,omitempty"` MarketingType string `json:"marketing_type"` Qty float64 `json:"qty"` AverageWeightKg float64 `json:"average_weight_kg"` TotalWeightKg float64 `json:"total_weight_kg"` SalesPricePerKg float64 `json:"sales_price_per_kg"` HppPricePerKg float64 `json:"hpp_price_per_kg"` SalesAmount float64 `json:"sales_amount"` HppAmount float64 `json:"hpp_amount"` } 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"` 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 { 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 { hppPerKg := float64(0) category := "" if projectFlockKandang := mdp.MarketingProduct.ProductWarehouse.ProjectFlockKandang; projectFlockKandang != nil { if hpp, exists := hppMap[projectFlockKandang.ProjectFlockId]; exists { hppPerKg = hpp } category = projectFlockKandang.ProjectFlock.Category } item := ToRepportMarketingItemDTO(mdp, hppPerKg, category) 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 } totalQty := 0 totalWeightKg := 0.0 totalSalesAmount := int64(0) totalHppAmount := int64(0) for _, item := range items { totalQty += int(item.Qty) totalWeightKg += item.TotalWeightKg totalSalesAmount += int64(item.SalesAmount) totalHppAmount += int64(item.HppAmount) } totalHppPricePerKg := float64(0) if totalWeightKg > 0 { totalHppPricePerKg = float64(totalHppAmount) / totalWeightKg } return &Summary{ TotalQty: totalQty, TotalWeightKg: totalWeightKg, 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 } fixed := ProductRelationDTOFixed{ ProductRelationDTO: *original, ProductPrice: original.ProductPrice, SellingPrice: original.SellingPrice, } return &fixed } func (p ProductRelationDTOFixed) MarshalJSON() ([]byte, error) { type Alias struct { Id uint `json:"id"` Name string `json:"name"` ProductPrice float64 `json:"product_price"` SellingPrice *float64 `json:"selling_price"` Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"` Flags *[]string `json:"flags,omitempty"` ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"` Suppliers []supplierDTO.SupplierRelationDTO `json:"suppliers"` } suppliers := make([]supplierDTO.SupplierRelationDTO, len(p.ProductRelationDTO.Suppliers)) for i, ps := range p.ProductRelationDTO.Suppliers { suppliers[i] = supplierDTO.SupplierRelationDTO{ Id: ps.Id, Name: ps.Name, Alias: ps.Alias, Category: ps.Category, } } return json.Marshal(&Alias{ Id: p.ProductRelationDTO.Id, Name: p.ProductRelationDTO.Name, ProductPrice: p.ProductPrice, SellingPrice: p.SellingPrice, Uom: p.ProductRelationDTO.Uom, Flags: p.ProductRelationDTO.Flags, ProductCategory: p.ProductRelationDTO.ProductCategory, Suppliers: suppliers, }) }