package transformer import ( "apskel-pos-be/internal/contract" "apskel-pos-be/internal/models" "apskel-pos-be/internal/util" "fmt" "time" "github.com/google/uuid" ) // parseOutletID converts a *string outlet ID to *uuid.UUID, returning nil for invalid/empty values. func parseOutletID(s *string) *uuid.UUID { if s == nil { return nil } id, err := uuid.Parse(*s) if err != nil { return nil } return &id } // PaymentMethodAnalyticsContractToModel converts contract request to model func PaymentMethodAnalyticsContractToModel(req *contract.PaymentMethodAnalyticsRequest) *models.PaymentMethodAnalyticsRequest { var dateFrom, dateTo time.Time if fromTime, toTime, err := util.ParseDateRangeToJakartaTime(req.DateFrom, req.DateTo); err == nil { if fromTime != nil { dateFrom = *fromTime } if toTime != nil { dateTo = *toTime } } return &models.PaymentMethodAnalyticsRequest{ OrganizationID: req.OrganizationID, OutletID: parseOutletID(req.OutletID), DateFrom: dateFrom, DateTo: dateTo, GroupBy: req.GroupBy, } } // PaymentMethodAnalyticsModelToContract converts model response to contract func PaymentMethodAnalyticsModelToContract(resp *models.PaymentMethodAnalyticsResponse) *contract.PaymentMethodAnalyticsResponse { if resp == nil { return nil } var data []contract.PaymentMethodAnalyticsData for _, item := range resp.Data { data = append(data, contract.PaymentMethodAnalyticsData{ PaymentMethodID: item.PaymentMethodID, PaymentMethodName: item.PaymentMethodName, PaymentMethodType: item.PaymentMethodType, TotalAmount: item.TotalAmount, OrderCount: item.OrderCount, PaymentCount: item.PaymentCount, Percentage: item.Percentage, }) } return &contract.PaymentMethodAnalyticsResponse{ OrganizationID: resp.OrganizationID, OutletID: resp.OutletID, DateFrom: resp.DateFrom, DateTo: resp.DateTo, GroupBy: resp.GroupBy, Summary: contract.PaymentMethodSummary{ TotalAmount: resp.Summary.TotalAmount, TotalOrders: resp.Summary.TotalOrders, TotalPayments: resp.Summary.TotalPayments, AverageOrderValue: resp.Summary.AverageOrderValue, }, Data: data, } } func SalesAnalyticsContractToModel(req *contract.SalesAnalyticsRequest) *models.SalesAnalyticsRequest { var dateFrom, dateTo time.Time if fromTime, toTime, err := util.ParseDateRangeToJakartaTime(req.DateFrom, req.DateTo); err == nil { if fromTime != nil { dateFrom = *fromTime } if toTime != nil { dateTo = *toTime } } return &models.SalesAnalyticsRequest{ OrganizationID: req.OrganizationID, OutletID: parseOutletID(req.OutletID), DateFrom: dateFrom, DateTo: dateTo, GroupBy: req.GroupBy, } } // SalesAnalyticsModelToContract converts model response to contract func SalesAnalyticsModelToContract(resp *models.SalesAnalyticsResponse) *contract.SalesAnalyticsResponse { if resp == nil { return nil } var data []contract.SalesAnalyticsData for _, item := range resp.Data { data = append(data, contract.SalesAnalyticsData{ Date: item.Date, Sales: item.Sales, Orders: item.Orders, Items: item.Items, Tax: item.Tax, Discount: item.Discount, NetSales: item.NetSales, }) } return &contract.SalesAnalyticsResponse{ OrganizationID: resp.OrganizationID, OutletID: resp.OutletID, DateFrom: resp.DateFrom, DateTo: resp.DateTo, GroupBy: resp.GroupBy, Summary: contract.SalesSummary{ TotalSales: resp.Summary.TotalSales, TotalOrders: resp.Summary.TotalOrders, TotalItems: resp.Summary.TotalItems, AverageOrderValue: resp.Summary.AverageOrderValue, TotalTax: resp.Summary.TotalTax, TotalDiscount: resp.Summary.TotalDiscount, NetSales: resp.Summary.NetSales, }, Data: data, } } // PurchasingAnalyticsContractToModel converts contract request to model func PurchasingAnalyticsContractToModel(req *contract.PurchasingAnalyticsRequest) *models.PurchasingAnalyticsRequest { var dateFrom, dateTo time.Time if fromTime, toTime, err := util.ParseDateRangeToJakartaTime(req.DateFrom, req.DateTo); err == nil { if fromTime != nil { dateFrom = *fromTime } if toTime != nil { dateTo = *toTime } } return &models.PurchasingAnalyticsRequest{ OrganizationID: req.OrganizationID, OutletID: parseOutletID(req.OutletID), DateFrom: dateFrom, DateTo: dateTo, GroupBy: req.GroupBy, } } // PurchasingAnalyticsModelToContract converts model response to contract func PurchasingAnalyticsModelToContract(resp *models.PurchasingAnalyticsResponse) *contract.PurchasingAnalyticsResponse { if resp == nil { return nil } data := make([]contract.PurchasingAnalyticsData, len(resp.Data)) for i, item := range resp.Data { data[i] = contract.PurchasingAnalyticsData{ Date: item.Date, Purchases: item.Purchases, PurchaseOrders: item.PurchaseOrders, Quantity: item.Quantity, Ingredients: item.Ingredients, Vendors: item.Vendors, } } ingredientData := make([]contract.PurchasingIngredientData, len(resp.IngredientData)) for i, item := range resp.IngredientData { ingredientData[i] = contract.PurchasingIngredientData{ IngredientID: item.IngredientID, IngredientName: item.IngredientName, Quantity: item.Quantity, TotalCost: item.TotalCost, AverageUnitCost: item.AverageUnitCost, PurchaseOrderCount: item.PurchaseOrderCount, } } vendorData := make([]contract.PurchasingVendorData, len(resp.VendorData)) for i, item := range resp.VendorData { vendorData[i] = contract.PurchasingVendorData{ VendorID: item.VendorID, VendorName: item.VendorName, TotalCost: item.TotalCost, PurchaseOrderCount: item.PurchaseOrderCount, IngredientCount: item.IngredientCount, Quantity: item.Quantity, } } return &contract.PurchasingAnalyticsResponse{ OrganizationID: resp.OrganizationID, OutletID: resp.OutletID, OutletName: resp.OutletName, DateFrom: resp.DateFrom, DateTo: resp.DateTo, GroupBy: resp.GroupBy, Summary: contract.PurchasingSummary{ TotalPurchases: resp.Summary.TotalPurchases, TotalPurchaseOrders: resp.Summary.TotalPurchaseOrders, TotalQuantity: resp.Summary.TotalQuantity, AveragePurchaseOrderValue: resp.Summary.AveragePurchaseOrderValue, TotalIngredients: resp.Summary.TotalIngredients, TotalVendors: resp.Summary.TotalVendors, }, Data: data, IngredientData: ingredientData, VendorData: vendorData, } } // ProductAnalyticsContractToModel converts contract request to model func ProductAnalyticsContractToModel(req *contract.ProductAnalyticsRequest) *models.ProductAnalyticsRequest { var dateFrom, dateTo time.Time if fromTime, toTime, err := util.ParseDateRangeToJakartaTime(req.DateFrom, req.DateTo); err == nil { if fromTime != nil { dateFrom = *fromTime } if toTime != nil { dateTo = *toTime } } return &models.ProductAnalyticsRequest{ OrganizationID: req.OrganizationID, OutletID: parseOutletID(req.OutletID), DateFrom: dateFrom, DateTo: dateTo, Limit: req.Limit, } } // ProductAnalyticsModelToContract converts model response to contract func ProductAnalyticsModelToContract(resp *models.ProductAnalyticsResponse) *contract.ProductAnalyticsResponse { if resp == nil { return nil } var data []contract.ProductAnalyticsData for _, item := range resp.Data { data = append(data, contract.ProductAnalyticsData{ ProductID: item.ProductID, ProductName: item.ProductName, ProductSku: item.ProductSku, ProductPrice: item.ProductPrice, CategoryID: item.CategoryID, CategoryName: item.CategoryName, CategoryOrder: item.CategoryOrder, QuantitySold: item.QuantitySold, Revenue: item.Revenue, AveragePrice: item.AveragePrice, OrderCount: item.OrderCount, StandardHppPerUnit: item.StandardHppPerUnit, StandardHppTotal: item.StandardHppTotal, FifoHppPerUnit: item.FifoHppPerUnit, FifoHppTotal: item.FifoHppTotal, MovingAverageHppPerUnit: item.MovingAverageHppPerUnit, MovingAverageHppTotal: item.MovingAverageHppTotal, }) } return &contract.ProductAnalyticsResponse{ OrganizationID: resp.OrganizationID, OutletID: resp.OutletID, DateFrom: resp.DateFrom, DateTo: resp.DateTo, Data: data, } } // ProductAnalyticsPerCategoryContractToModel converts contract request to model func ProductAnalyticsPerCategoryContractToModel(req *contract.ProductAnalyticsPerCategoryRequest) *models.ProductAnalyticsPerCategoryRequest { var dateFrom, dateTo time.Time // Parse date range using utility function if fromTime, toTime, err := util.ParseDateRangeToJakartaTime(req.DateFrom, req.DateTo); err == nil { if fromTime != nil { dateFrom = *fromTime } if toTime != nil { dateTo = *toTime } } return &models.ProductAnalyticsPerCategoryRequest{ OrganizationID: req.OrganizationID, OutletID: parseOutletID(req.OutletID), DateFrom: dateFrom, DateTo: dateTo, } } // ProductAnalyticsPerCategoryModelToContract converts model response to contract func ProductAnalyticsPerCategoryModelToContract(resp *models.ProductAnalyticsPerCategoryResponse) *contract.ProductAnalyticsPerCategoryResponse { if resp == nil { return nil } var data []contract.ProductAnalyticsPerCategoryData for _, item := range resp.Data { data = append(data, contract.ProductAnalyticsPerCategoryData{ CategoryID: item.CategoryID, CategoryName: item.CategoryName, TotalRevenue: item.TotalRevenue, TotalQuantity: item.TotalQuantity, ProductCount: item.ProductCount, OrderCount: item.OrderCount, TotalStandardHpp: item.TotalStandardHpp, TotalFifoHpp: item.TotalFifoHpp, TotalMovingAverageHpp: item.TotalMovingAverageHpp, }) } return &contract.ProductAnalyticsPerCategoryResponse{ OrganizationID: resp.OrganizationID, OutletID: resp.OutletID, DateFrom: resp.DateFrom, DateTo: resp.DateTo, Data: data, } } // DashboardAnalyticsContractToModel converts contract request to model func DashboardAnalyticsContractToModel(req *contract.DashboardAnalyticsRequest) *models.DashboardAnalyticsRequest { var dateFrom, dateTo time.Time // Parse date range using utility function if fromTime, toTime, err := util.ParseDateRangeToJakartaTime(req.DateFrom, req.DateTo); err == nil { if fromTime != nil { dateFrom = *fromTime } if toTime != nil { dateTo = *toTime } } return &models.DashboardAnalyticsRequest{ OrganizationID: req.OrganizationID, OutletID: parseOutletID(req.OutletID), DateFrom: dateFrom, DateTo: dateTo, } } // DashboardAnalyticsModelToContract converts model response to contract func DashboardAnalyticsModelToContract(resp *models.DashboardAnalyticsResponse) *contract.DashboardAnalyticsResponse { if resp == nil { return nil } var topProducts []contract.ProductAnalyticsData for _, item := range resp.TopProducts { topProducts = append(topProducts, contract.ProductAnalyticsData{ ProductID: item.ProductID, ProductName: item.ProductName, ProductPrice: item.ProductPrice, CategoryID: item.CategoryID, CategoryName: item.CategoryName, QuantitySold: item.QuantitySold, Revenue: item.Revenue, AveragePrice: item.AveragePrice, OrderCount: item.OrderCount, StandardHppPerUnit: item.StandardHppPerUnit, StandardHppTotal: item.StandardHppTotal, FifoHppPerUnit: item.FifoHppPerUnit, FifoHppTotal: item.FifoHppTotal, MovingAverageHppPerUnit: item.MovingAverageHppPerUnit, MovingAverageHppTotal: item.MovingAverageHppTotal, }) } var paymentMethods []contract.PaymentMethodAnalyticsData for _, item := range resp.PaymentMethods { paymentMethods = append(paymentMethods, contract.PaymentMethodAnalyticsData{ PaymentMethodID: item.PaymentMethodID, PaymentMethodName: item.PaymentMethodName, PaymentMethodType: item.PaymentMethodType, TotalAmount: item.TotalAmount, OrderCount: item.OrderCount, PaymentCount: item.PaymentCount, Percentage: item.Percentage, }) } var recentSales []contract.SalesAnalyticsData for _, item := range resp.RecentSales { recentSales = append(recentSales, contract.SalesAnalyticsData{ Date: item.Date, Sales: item.Sales, Orders: item.Orders, Items: item.Items, Tax: item.Tax, Discount: item.Discount, NetSales: item.NetSales, }) } return &contract.DashboardAnalyticsResponse{ OrganizationID: resp.OrganizationID, OutletID: resp.OutletID, DateFrom: resp.DateFrom, DateTo: resp.DateTo, Overview: contract.DashboardOverview{ TotalSales: resp.Overview.TotalSales, TotalOrders: resp.Overview.TotalOrders, AverageOrderValue: resp.Overview.AverageOrderValue, TotalCustomers: resp.Overview.TotalCustomers, VoidedOrders: resp.Overview.VoidedOrders, RefundedOrders: resp.Overview.RefundedOrders, }, TopProducts: topProducts, PaymentMethods: paymentMethods, RecentSales: recentSales, } } func ProfitLossAnalyticsContractToModel(req *contract.ProfitLossAnalyticsRequest) (*models.ProfitLossAnalyticsRequest, error) { if req == nil { return nil, fmt.Errorf("request cannot be nil") } dateFrom, dateTo, err := util.ParseDateRangeToJakartaTime(req.DateFrom, req.DateTo) if err != nil { return nil, fmt.Errorf("invalid date range: %w", err) } if dateFrom == nil { return nil, fmt.Errorf("date_from is required") } if dateTo == nil { return nil, fmt.Errorf("date_to is required") } return &models.ProfitLossAnalyticsRequest{ OrganizationID: req.OrganizationID, OutletID: parseOutletID(req.OutletID), DateFrom: *dateFrom, DateTo: *dateTo, GroupBy: req.GroupBy, }, nil } func ProfitLossAnalyticsModelToContract(resp *models.ProfitLossAnalyticsResponse) *contract.ProfitLossAnalyticsResponse { if resp == nil { return nil } mainSummary := make([]contract.ProfitLossSummaryRow, len(resp.MainSummary)) for i, row := range resp.MainSummary { mainSummary[i] = profitLossSummaryRowModelToContract(row) } data := make([]contract.ProfitLossData, len(resp.Data)) for i, item := range resp.Data { data[i] = contract.ProfitLossData{ Date: item.Date, Revenue: item.Revenue, Cost: item.Cost, GrossProfit: item.GrossProfit, GrossProfitMargin: item.GrossProfitMargin, Tax: item.Tax, Discount: item.Discount, NetProfit: item.NetProfit, NetProfitMargin: item.NetProfitMargin, Orders: item.Orders, } } productData := make([]contract.ProductProfitData, len(resp.ProductData)) for i, item := range resp.ProductData { productData[i] = contract.ProductProfitData{ ProductID: item.ProductID, ProductName: item.ProductName, CategoryID: item.CategoryID, CategoryName: item.CategoryName, QuantitySold: item.QuantitySold, Revenue: item.Revenue, Cost: item.Cost, GrossProfit: item.GrossProfit, GrossProfitMargin: item.GrossProfitMargin, AveragePrice: item.AveragePrice, AverageCost: item.AverageCost, ProfitPerUnit: item.ProfitPerUnit, } } opsItems := make([]contract.OperationalExpenseItem, len(resp.OperationalExpenses)) for i, item := range resp.OperationalExpenses { opsItems[i] = contract.OperationalExpenseItem{ Item: item.Item, Nominal: item.Nominal, } } return &contract.ProfitLossAnalyticsResponse{ OrganizationID: resp.OrganizationID, OutletID: resp.OutletID, DateFrom: resp.DateFrom, DateTo: resp.DateTo, GroupBy: resp.GroupBy, Summary: contract.ProfitLossSummary{ TotalRevenue: resp.Summary.TotalRevenue, TotalCost: resp.Summary.TotalCost, GrossProfit: resp.Summary.GrossProfit, GrossProfitMargin: resp.Summary.GrossProfitMargin, TotalTax: resp.Summary.TotalTax, TotalDiscount: resp.Summary.TotalDiscount, NetProfit: resp.Summary.NetProfit, NetProfitMargin: resp.Summary.NetProfitMargin, TotalOrders: resp.Summary.TotalOrders, AverageProfit: resp.Summary.AverageProfit, ProfitabilityRatio: resp.Summary.ProfitabilityRatio, }, Data: data, ProductData: productData, MainSummary: mainSummary, OperationalExpenses: opsItems, OperationalExpensesTotal: resp.OperationalExpensesTotal, } } func profitLossSummaryRowModelToContract(row models.ProfitLossSummaryRow) contract.ProfitLossSummaryRow { subItems := make([]contract.ProfitLossSummaryRow, len(row.SubItems)) for i, sub := range row.SubItems { subItems[i] = profitLossSummaryRowModelToContract(sub) } return contract.ProfitLossSummaryRow{ ID: row.ID, Label: row.Label, IsBold: row.IsBold, TodayNominal: row.TodayNominal, TodayPct: row.TodayPct, MtdNominal: row.MtdNominal, MtdPct: row.MtdPct, SubItems: subItems, } }