feat(purchase

This commit is contained in:
Efril
2026-08-11 22:41:10 +07:00
parent dbc143954c
commit e6078e3c0b
19 changed files with 532 additions and 17 deletions
+48 -3
View File
@@ -1,8 +1,12 @@
package models
import (
"fmt"
"time"
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/entities"
"github.com/google/uuid"
)
@@ -93,9 +97,34 @@ type SalesAnalyticsData struct {
type PurchasingAnalyticsRequest struct {
OrganizationID uuid.UUID `validate:"required"`
OutletID *uuid.UUID `validate:"omitempty"`
DateFrom time.Time `validate:"required"`
DateTo time.Time `validate:"required"`
GroupBy string `validate:"omitempty,oneof=day hour week month"`
// Team is the raw value the team picker sends: a parent category id,
// "central" for Pusat, "none" for purchases with no team, or empty for all.
Team string
DateFrom time.Time `validate:"required"`
DateTo time.Time `validate:"required"`
GroupBy string `validate:"omitempty,oneof=day hour week month"`
}
// ParsePurchaseTeamFilter turns the team value the picker sends into the scope and
// category the purchasing queries filter on. An empty value spans every team; an
// unknown one is an error rather than a report that quietly ignores the filter.
func ParsePurchaseTeamFilter(team string) (*entities.PurchaseTeamFilter, error) {
switch team {
case "":
return nil, nil
case constants.PurchaseTeamScopeCentral, constants.PurchaseTeamNone:
return &entities.PurchaseTeamFilter{Scope: team}, nil
}
categoryID, err := uuid.Parse(team)
if err != nil || categoryID == uuid.Nil {
return nil, fmt.Errorf("team must be one of: central, none, or a category id")
}
return &entities.PurchaseTeamFilter{
Scope: constants.PurchaseTeamScopeCategory,
CategoryID: &categoryID,
}, nil
}
// PurchasingAnalyticsResponse represents the response for purchasing analytics
@@ -103,6 +132,7 @@ type PurchasingAnalyticsResponse struct {
OrganizationID uuid.UUID `json:"organization_id"`
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
OutletName *string `json:"outlet_name,omitempty"`
Team string `json:"team,omitempty"`
DateFrom time.Time `json:"date_from"`
DateTo time.Time `json:"date_to"`
GroupBy string `json:"group_by"`
@@ -110,6 +140,20 @@ type PurchasingAnalyticsResponse struct {
Data []PurchasingAnalyticsData `json:"data"`
IngredientData []PurchasingIngredientData `json:"ingredient_data"`
VendorData []PurchasingVendorData `json:"vendor_data"`
TeamData []PurchasingTeamData `json:"team_data"`
}
// PurchasingTeamData represents purchasing analytics for a single team
type PurchasingTeamData struct {
Scope string `json:"scope"`
CategoryID *uuid.UUID `json:"category_id"`
Name string `json:"name"`
TotalPurchases float64 `json:"total_purchases"`
RawMaterialPurchases float64 `json:"raw_material_purchases"`
ExpensePurchases float64 `json:"expense_purchases"`
PurchaseOrderCount int64 `json:"purchase_order_count"`
Quantity float64 `json:"quantity"`
Percentage float64 `json:"percentage"`
}
// PurchasingSummary represents the summary of purchasing analytics
@@ -124,6 +168,7 @@ type PurchasingSummary struct {
AveragePurchaseOrderValue float64 `json:"average_purchase_order_value"`
TotalIngredients int64 `json:"total_ingredients"`
TotalVendors int64 `json:"total_vendors"`
TotalTeams int64 `json:"total_teams"`
}
// PurchasingAnalyticsData represents purchasing analytics by time period
+1
View File
@@ -152,6 +152,7 @@ type ListPurchaseOrdersRequest struct {
Search string `json:"search,omitempty"`
Status string `json:"status,omitempty"`
VendorID *uuid.UUID `json:"vendor_id,omitempty"`
Team string `json:"team,omitempty"`
TeamScope string `json:"team_scope,omitempty"`
TeamCategoryID *uuid.UUID `json:"team_category_id,omitempty"`
StartDate *time.Time `json:"start_date,omitempty"`