package dto import ( "fmt" "math" "sort" "strings" "time" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" 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" ) type MarketingRelationDTO struct { Id uint `json:"id"` SoNumber string `json:"so_number"` DoNumber *string `json:"do_number"` SoDate time.Time `json:"so_date"` Notes string `json:"notes,omitempty"` } type MarketingListDTO struct { MarketingRelationDTO Customer customerDTO.CustomerRelationDTO `json:"customer"` SalesPerson userDTO.UserRelationDTO `json:"sales_person"` SoDocs string `json:"so_docs"` SalesOrder []DeliveryMarketingProductDTO `json:"sales_order"` CreatedUser userDTO.UserRelationDTO `json:"created_user"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` LatestApproval approvalDTO.ApprovalRelationDTO `json:"latest_approval"` } type MarketingDetailDTO struct { MarketingRelationDTO Customer customerDTO.CustomerRelationDTO `json:"customer"` SalesPerson userDTO.UserRelationDTO `json:"sales_person"` SoDocs string `json:"so_docs"` SalesOrder []DeliveryMarketingProductDTO `json:"sales_order"` DeliveryOrder []DeliveryGroupDTO `json:"delivery_order"` CreatedUser userDTO.UserRelationDTO `json:"created_user"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` LatestApproval approvalDTO.ApprovalRelationDTO `json:"latest_approval"` } type MarketingDeliveryProductDTO struct { Id uint `json:"id"` MarketingProductId uint `json:"marketing_product_id"` Qty float64 `json:"qty"` UnitPrice float64 `json:"unit_price"` TotalWeight float64 `json:"total_weight"` AvgWeight float64 `json:"avg_weight"` TotalPrice float64 `json:"total_price"` DeliveryDate *time.Time `json:"delivery_date"` VehicleNumber string `json:"vehicle_number"` ConvertionUnit *string `json:"-"` WeightPerConvertion *float64 `json:"-"` ProductWarehouse *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse,omitempty"` } type DeliveryItemDTO struct { MarketingProductId uint `json:"marketing_product_id"` ProductWarehouse *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse"` Qty float64 `json:"qty"` UnitPrice float64 `json:"unit_price"` TotalWeight float64 `json:"total_weight"` AvgWeight float64 `json:"avg_weight"` WeightPerConvertion *float64 `json:"weight_per_convertion"` TotalPeti *float64 `json:"total_peti"` TotalPrice float64 `json:"total_price"` VehicleNumber string `json:"vehicle_number"` } type DeliveryGroupDTO struct { DoNumber string `json:"do_number"` DeliveryDate *time.Time `json:"delivery_date"` Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"` Deliveries []DeliveryItemDTO `json:"deliveries"` } type DeliveryMarketingProductDTO struct { Id uint `json:"id"` MarketingId uint `json:"marketing_id"` ProductWarehouseId uint `json:"product_warehouse_id"` MarketingType string `json:"marketing_type"` Qty float64 `json:"qty"` UnitPrice float64 `json:"unit_price"` AvgWeight float64 `json:"avg_weight"` TotalWeight float64 `json:"total_weight"` TotalPrice float64 `json:"total_price"` ConvertionUnit *string `json:"convertion_unit,omitempty"` WeightPerConvertion *float64 `json:"weight_per_convertion,omitempty"` TotalPeti *float64 `json:"total_peti,omitempty"` Week *int `json:"week,omitempty"` ProductWarehouse *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse,omitempty"` VehicleNumber string `json:"vehicle_number,omitempty"` } func ToMarketingRelationDTO(marketing *entity.Marketing) MarketingRelationDTO { var doNumber *string if doNumbers := collectDoNumbers(marketing); len(doNumbers) > 0 { value := doNumbers[0] doNumber = &value } return MarketingRelationDTO{ Id: marketing.Id, SoNumber: marketing.SoNumber, DoNumber: doNumber, SoDate: marketing.SoDate, Notes: marketing.Notes, } } func ToDeliveryMarketingProductDTO(e entity.MarketingProduct, marketingType string) DeliveryMarketingProductDTO { var productWarehouse *productwarehouseDTO.ProductWarehousNestedDTO if e.ProductWarehouse.Id != 0 { mapped := productwarehouseDTO.ToProductWarehouseNestedDTO(e.ProductWarehouse) productWarehouse = &mapped } // Calculate total_peti only for TELUR marketing type var totalPeti *float64 if marketingType == "TELUR" && e.ConvertionUnit != nil && *e.ConvertionUnit == "PETI" && e.WeightPerConvertion != nil && *e.WeightPerConvertion > 0 { calculated := math.Floor(e.TotalWeight / *e.WeightPerConvertion) totalPeti = &calculated } return DeliveryMarketingProductDTO{ Id: e.Id, MarketingId: e.MarketingId, ProductWarehouseId: e.ProductWarehouseId, MarketingType: marketingType, Qty: e.Qty, UnitPrice: e.UnitPrice, AvgWeight: e.AvgWeight, TotalWeight: e.TotalWeight, TotalPrice: e.TotalPrice, ConvertionUnit: e.ConvertionUnit, WeightPerConvertion: e.WeightPerConvertion, TotalPeti: totalPeti, Week: e.Week, ProductWarehouse: productWarehouse, VehicleNumber: getVehicleNumber(e), } } func ToMarketingDeliveryProductDTO(e entity.MarketingDeliveryProduct) MarketingDeliveryProductDTO { return MarketingDeliveryProductDTO{ Id: e.Id, MarketingProductId: e.MarketingProductId, Qty: e.UsageQty + e.PendingQty, UnitPrice: e.UnitPrice, TotalWeight: e.TotalWeight, AvgWeight: e.AvgWeight, TotalPrice: e.TotalPrice, DeliveryDate: e.DeliveryDate, VehicleNumber: e.VehicleNumber, WeightPerConvertion: e.WeightPerConvertion, } } func ToMarketingListDTO(marketing *entity.Marketing, deliveryProducts []entity.MarketingDeliveryProduct) MarketingListDTO { var createdUser userDTO.UserRelationDTO if marketing.CreatedUser.Id != 0 { mapped := userDTO.ToUserRelationDTO(marketing.CreatedUser) createdUser = mapped } var customer customerDTO.CustomerRelationDTO if marketing.Customer.Id != 0 { mapped := customerDTO.ToCustomerRelationDTO(marketing.Customer) customer = mapped } var salesPerson userDTO.UserRelationDTO if marketing.SalesPerson.Id != 0 { mapped := userDTO.ToUserRelationDTO(marketing.SalesPerson) salesPerson = mapped } var latestApproval approvalDTO.ApprovalRelationDTO if marketing.LatestApproval != nil { mapped := approvalDTO.ToApprovalDTO(*marketing.LatestApproval) latestApproval = mapped } var salesOrderProducts []DeliveryMarketingProductDTO if len(marketing.Products) > 0 { salesOrderProducts = make([]DeliveryMarketingProductDTO, len(marketing.Products)) for i, product := range marketing.Products { salesOrderProducts[i] = ToDeliveryMarketingProductDTO(product, marketing.MarketingType) } } return MarketingListDTO{ MarketingRelationDTO: ToMarketingRelationDTO(marketing), Customer: customer, SalesPerson: salesPerson, SoDocs: marketing.SoDocs, SalesOrder: salesOrderProducts, CreatedUser: createdUser, CreatedAt: marketing.CreatedAt, UpdatedAt: marketing.UpdatedAt, LatestApproval: latestApproval, } } func ToMarketingDetailDTO(marketing *entity.Marketing, deliveryProducts []entity.MarketingDeliveryProduct) MarketingDetailDTO { var createdUser userDTO.UserRelationDTO if marketing.CreatedUser.Id != 0 { mapped := userDTO.ToUserRelationDTO(marketing.CreatedUser) createdUser = mapped } var customer customerDTO.CustomerRelationDTO if marketing.Customer.Id != 0 { mapped := customerDTO.ToCustomerRelationDTO(marketing.Customer) customer = mapped } var salesPerson userDTO.UserRelationDTO if marketing.SalesPerson.Id != 0 { mapped := userDTO.ToUserRelationDTO(marketing.SalesPerson) salesPerson = mapped } var salesOrderProducts []DeliveryMarketingProductDTO if len(marketing.Products) > 0 { salesOrderProducts = make([]DeliveryMarketingProductDTO, len(marketing.Products)) for i, product := range marketing.Products { salesOrderProducts[i] = ToDeliveryMarketingProductDTO(product, marketing.MarketingType) } } var deliveryProductsDTOs []MarketingDeliveryProductDTO if len(deliveryProducts) > 0 { deliveryProductsDTOs = make([]MarketingDeliveryProductDTO, len(deliveryProducts)) for i, dp := range deliveryProducts { deliveryProductsDTOs[i] = ToMarketingDeliveryProductDTO(dp) } deliveryProductsDTOs = enrichDeliveryProductDTOsWithWarehouse(deliveryProductsDTOs, marketing) } deliveryGroups := groupDeliveryProducts(deliveryProductsDTOs, marketing.SoNumber) var latestApproval approvalDTO.ApprovalRelationDTO if marketing.LatestApproval != nil { mapped := approvalDTO.ToApprovalDTO(*marketing.LatestApproval) latestApproval = mapped } return MarketingDetailDTO{ MarketingRelationDTO: ToMarketingRelationDTO(marketing), SoDocs: marketing.SoDocs, Customer: customer, SalesPerson: salesPerson, SalesOrder: salesOrderProducts, DeliveryOrder: deliveryGroups, CreatedUser: createdUser, CreatedAt: marketing.CreatedAt, UpdatedAt: marketing.UpdatedAt, LatestApproval: latestApproval, } } func ToMarketingListDTOs(marketings []entity.Marketing) []MarketingListDTO { result := make([]MarketingListDTO, len(marketings)) for i, m := range marketings { result[i] = ToMarketingListDTO(&m, []entity.MarketingDeliveryProduct{}) } return result } func enrichDeliveryProductDTOsWithWarehouse(deliveryProductDTOs []MarketingDeliveryProductDTO, marketing *entity.Marketing) []MarketingDeliveryProductDTO { if len(deliveryProductDTOs) == 0 || marketing == nil || len(marketing.Products) == 0 { return deliveryProductDTOs } productMap := make(map[uint]*entity.MarketingProduct) for i := range marketing.Products { productMap[marketing.Products[i].Id] = &marketing.Products[i] } for i := range deliveryProductDTOs { if product, exists := productMap[deliveryProductDTOs[i].MarketingProductId]; exists { if product.ProductWarehouse.Id != 0 { mapped := productwarehouseDTO.ToProductWarehouseNestedDTO(product.ProductWarehouse) deliveryProductDTOs[i].ProductWarehouse = &mapped } deliveryProductDTOs[i].ConvertionUnit = product.ConvertionUnit } } return deliveryProductDTOs } func groupDeliveryProducts(products []MarketingDeliveryProductDTO, soNumber string) []DeliveryGroupDTO { groupMap := make(map[string]*DeliveryGroupDTO) for _, product := range products { if product.DeliveryDate == nil { continue } var warehouseId uint var warehouseName string if product.ProductWarehouse != nil { warehouseId = product.ProductWarehouse.Warehouse.Id warehouseName = product.ProductWarehouse.Warehouse.Name } key := fmt.Sprintf("%d_%s", warehouseId, product.DeliveryDate.Format("2006-01-02")) group, exists := groupMap[key] if !exists { group = &DeliveryGroupDTO{ DeliveryDate: product.DeliveryDate, Warehouse: &warehouseDTO.WarehouseRelationDTO{ Id: warehouseId, Name: warehouseName, }, Deliveries: []DeliveryItemDTO{}, } groupMap[key] = group } deliveryItem := DeliveryItemDTO{ MarketingProductId: product.MarketingProductId, ProductWarehouse: product.ProductWarehouse, Qty: product.Qty, UnitPrice: product.UnitPrice, TotalWeight: product.TotalWeight, AvgWeight: product.AvgWeight, WeightPerConvertion: product.WeightPerConvertion, TotalPrice: product.TotalPrice, VehicleNumber: product.VehicleNumber, } if product.ConvertionUnit != nil && strings.EqualFold(*product.ConvertionUnit, "PETI") && product.WeightPerConvertion != nil && *product.WeightPerConvertion > 0 { totalPeti := product.TotalWeight / *product.WeightPerConvertion deliveryItem.TotalPeti = &totalPeti } group.Deliveries = append(group.Deliveries, deliveryItem) } var groups []DeliveryGroupDTO for _, group := range groupMap { groups = append(groups, *group) } 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) }) for i := range groups { groups[i].DoNumber = GenerateDeliveryOrderNumber(soNumber, groups[i].DeliveryDate, groups[i].Warehouse.Id) } return groups } func GenerateDeliveryOrderNumber(soNumber string, deliveryDate *time.Time, warehouseId uint) string { numberPrefix := soNumber if strings.HasPrefix(strings.ToUpper(strings.TrimSpace(soNumber)), "SO-") { numberPrefix = "DO-" + soNumber[3:] } return numberPrefix } func collectDoNumbers(marketing *entity.Marketing) []string { if marketing == nil || len(marketing.Products) == 0 { return nil } seen := make(map[string]struct{}) for _, product := range marketing.Products { if product.DeliveryProduct == nil || product.DeliveryProduct.DeliveryDate == nil { continue } warehouseID := product.ProductWarehouse.WarehouseId if warehouseID == 0 && product.ProductWarehouse.Warehouse.Id != 0 { warehouseID = product.ProductWarehouse.Warehouse.Id } if warehouseID == 0 { continue } doNumber := GenerateDeliveryOrderNumber(marketing.SoNumber, product.DeliveryProduct.DeliveryDate, warehouseID) if doNumber != "" { seen[doNumber] = struct{}{} } } if len(seen) == 0 { return nil } result := make([]string, 0, len(seen)) for value := range seen { result = append(result, value) } sort.Strings(result) return result } func getVehicleNumber(e entity.MarketingProduct) string { if e.DeliveryProduct != nil && e.DeliveryProduct.VehicleNumber != "" { return e.DeliveryProduct.VehicleNumber } return "" }