Files
efrilmandClaude Opus 5.5 fc97c78300 feat(loyalty): show what an order earned on the order and receipt
Adds points_earned and coins_earned to the order response
(docs/prd-point-coin.md F3, PC-205). The POS prints the receipt from this
response, so the receipt gets them too.

The values are the sums of the order's EARN rows, read in one query for a
list of orders. They are filled for create, add items, update, detail and
list, and are 0 for an order that earned nothing. UpdateOrder earns before
building its response, so a payment completed there already shows the
earning. A failure to read them is logged and leaves them at 0 rather than
failing the order read. The self-order session listing reads orders
directly from the repository and still shows 0.

The two order hooks are merged into one OrderLoyalty interface (paid,
refunded, earned by orders) with a single SetLoyalty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:50:37 +07:00

313 lines
9.2 KiB
Go

package models
import (
"apskel-pos-be/internal/constants"
"time"
"github.com/google/uuid"
)
type Order struct {
ID uuid.UUID
OrganizationID uuid.UUID
OutletID uuid.UUID
UserID uuid.UUID
CustomerID *uuid.UUID
OrderNumber string
TableNumber *string
OrderType constants.OrderType
Status constants.OrderStatus
Subtotal float64
TaxAmount float64
DiscountAmount float64
TotalAmount float64
TotalCost float64
RemainingAmount float64
PaymentStatus constants.PaymentStatus
RefundAmount float64
IsVoid bool
IsRefund bool
VoidReason *string
VoidedAt *time.Time
VoidedBy *uuid.UUID
RefundReason *string
RefundedAt *time.Time
RefundedBy *uuid.UUID
Metadata map[string]interface{}
CreatedAt time.Time
UpdatedAt time.Time
}
type OrderItem struct {
ID uuid.UUID
OrderID uuid.UUID
ProductID uuid.UUID
ProductVariantID *uuid.UUID
Quantity int
Weight *float64
UnitID *uuid.UUID
UnitPrice float64
TotalPrice float64
UnitCost float64
TotalCost float64
RefundAmount float64
RefundQuantity int
IsPartiallyRefunded bool
IsFullyRefunded bool
RefundReason *string
RefundedAt *time.Time
RefundedBy *uuid.UUID
Modifiers []map[string]interface{}
Status constants.OrderItemStatus
CreatedAt time.Time
UpdatedAt time.Time
}
type PaymentOrderItem struct {
ID uuid.UUID
PaymentID uuid.UUID
OrderItemID uuid.UUID
Quantity int // Quantity paid for this specific payment
Amount float64
CreatedAt time.Time
UpdatedAt time.Time
}
type OrderSequence struct {
ID uuid.UUID
OrganizationID uuid.UUID
OutletID uuid.UUID
Year int
Month int
SequenceNumber int
CreatedAt time.Time
UpdatedAt time.Time
}
type CreateOrderRequest struct {
OutletID uuid.UUID `validate:"required"`
UserID uuid.UUID `validate:"required"`
CustomerID *uuid.UUID `validate:"omitempty"`
TableID *uuid.UUID `validate:"omitempty"`
TableNumber *string `validate:"omitempty,max=50"`
OrderType constants.OrderType `validate:"required"`
OrderItems []CreateOrderItemRequest `validate:"required,min=1,dive"`
Notes *string `validate:"omitempty,max=1000"`
Metadata map[string]interface{}
CustomerName *string `validate:"omitempty,max=255"`
}
type CreateOrderItemRequest struct {
ProductID uuid.UUID `validate:"required"`
ProductVariantID *uuid.UUID `validate:"omitempty"`
Quantity int `validate:"required,min=1"`
Weight *float64 `validate:"omitempty,gt=0"`
UnitPrice *float64 `validate:"omitempty,min=0"` // Ignored: price always comes from the database
Modifiers []map[string]interface{} `validate:"omitempty"`
Notes *string `validate:"omitempty,max=500"`
Metadata map[string]interface{} `validate:"omitempty"`
}
type UpdateOrderRequest struct {
TableNumber *string `validate:"omitempty,max=50"`
Status *constants.OrderStatus `validate:"omitempty"`
DiscountAmount *float64 `validate:"omitempty,min=0"`
Notes *string `validate:"omitempty,max=1000"`
Metadata map[string]interface{}
}
type CreatePaymentOrderItemRequest struct {
OrderItemID uuid.UUID `validate:"required"`
Quantity int `validate:"required,min=1"` // Quantity being paid for
Amount float64 `validate:"required,min=0"`
}
type VoidOrderRequest struct {
OrderID uuid.UUID `validate:"required"`
Reason string `validate:"required"`
Type string `validate:"required,oneof=ALL ITEM"`
Items []VoidItemRequest `validate:"required_if=Type ITEM,dive"`
}
type VoidItemRequest struct {
OrderItemID uuid.UUID `validate:"required"`
Quantity int `validate:"required,min=1"`
}
type RefundOrderRequest struct {
Reason *string `validate:"omitempty,max=255"`
RefundAmount *float64 `validate:"omitempty,min=0"`
OrderItems []RefundOrderItemRequest `validate:"omitempty,dive"`
}
type RefundOrderItemRequest struct {
OrderItemID uuid.UUID `validate:"required"`
RefundQuantity int `validate:"omitempty,min=1"`
RefundAmount *float64 `validate:"omitempty,min=0"`
Reason *string `validate:"omitempty,max=255"`
}
type OrderResponse struct {
ID uuid.UUID
OrganizationID uuid.UUID
OutletID uuid.UUID
UserID uuid.UUID
CustomerID *uuid.UUID
OrderNumber string
TableNumber *string
OrderType constants.OrderType
Status constants.OrderStatus
Subtotal float64
TaxAmount float64
DiscountAmount float64
TotalAmount float64
TotalCost float64
RemainingAmount float64
PaymentStatus constants.PaymentStatus
RefundAmount float64
IsVoid bool
IsRefund bool
VoidReason *string
VoidedAt *time.Time
VoidedBy *uuid.UUID
RefundReason *string
RefundedAt *time.Time
RefundedBy *uuid.UUID
Notes *string
Metadata map[string]interface{}
CreatedAt time.Time
UpdatedAt time.Time
OrderItems []OrderItemResponse
Payments []PaymentResponse
TotalPaid float64
PaymentCount int
SplitType *string
// What the order earned (docs/prd-point-coin.md F3), from its EARN rows.
PointsEarned int64
CoinsEarned int64
}
type OrderItemResponse struct {
ID uuid.UUID
OrderID uuid.UUID
ProductID uuid.UUID
ProductName string
ProductVariantID *uuid.UUID
ProductVariantName *string
CategoryID *uuid.UUID
CategoryName *string
Quantity int
Weight *float64
UnitID *uuid.UUID
UnitName *string
UnitAbbreviation *string
UnitPrice float64
TotalPrice float64
UnitCost float64
TotalCost float64
RefundAmount float64
RefundQuantity int
IsPartiallyRefunded bool
IsFullyRefunded bool
RefundReason *string
RefundedAt *time.Time
RefundedBy *uuid.UUID
Modifiers []map[string]interface{}
Notes *string
Metadata map[string]interface{}
Status constants.OrderItemStatus
CreatedAt time.Time
UpdatedAt time.Time
PrinterType string
PrintToChecker bool
PaidQuantity int
}
type PaymentOrderItemResponse struct {
ID uuid.UUID
PaymentID uuid.UUID
OrderItemID uuid.UUID
Quantity int // Quantity paid for this specific payment
Amount float64
CreatedAt time.Time
UpdatedAt time.Time
}
type ListOrdersRequest struct {
OrganizationID *uuid.UUID
OutletID *uuid.UUID
UserID *uuid.UUID
CustomerID *uuid.UUID
OrderType *constants.OrderType
Status *constants.OrderStatus
PaymentStatus *constants.PaymentStatus
IsVoid *bool
IsRefund *bool
DateFrom *time.Time
DateTo *time.Time
Search string
Page int `validate:"required,min=1"`
Limit int `validate:"required,min=1,max=100"`
}
type ListOrdersResponse struct {
Orders []OrderResponse
Payments []PaymentResponse
TotalCount int
Page int
Limit int
TotalPages int
}
func (o *Order) CanBeModified() bool {
return o.Status == constants.OrderStatusPending || o.Status == constants.OrderStatusPreparing
}
func (o *Order) CanBeCancelled() bool {
return o.Status == constants.OrderStatusPending || o.Status == constants.OrderStatusPreparing
}
func (o *Order) CalculateTotalAmount(taxRate float64) float64 {
taxAmount := o.Subtotal * taxRate
return o.Subtotal + taxAmount - o.DiscountAmount
}
func (o *Order) IsComplete() bool {
return o.Status == constants.OrderStatusCompleted
}
func (o *Order) IsPaid() bool {
return o.Status == constants.OrderStatusPaid
}
func (r *CreateOrderRequest) Validate() bool {
return r.OrderType.IsValidOrderType()
}
// AddToOrderRequest represents the request to add items to an existing order
type AddToOrderRequest struct {
OrderItems []CreateOrderItemRequest `validate:"required,min=1,dive"`
Notes *string `validate:"omitempty,max=1000"`
Metadata map[string]interface{} `validate:"omitempty"`
}
// AddToOrderResponse represents the response when adding items to an existing order
type AddToOrderResponse struct {
OrderID uuid.UUID
OrderNumber string
AddedItems []OrderItemResponse
UpdatedOrder OrderResponse
}
// SetOrderCustomerRequest represents the request to set customer for an order
type SetOrderCustomerRequest struct {
CustomerID uuid.UUID `validate:"required"`
}
// SetOrderCustomerResponse represents the response when setting customer for an order
type SetOrderCustomerResponse struct {
OrderID uuid.UUID
CustomerID uuid.UUID
Message string
}