102 lines
2.7 KiB
Go
102 lines
2.7 KiB
Go
package entity
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/genex/trading-service/internal/domain/vo"
|
|
)
|
|
|
|
// OrderStatus represents the lifecycle status of an order.
|
|
type OrderStatus string
|
|
|
|
const (
|
|
OrderPending OrderStatus = "pending"
|
|
OrderPartial OrderStatus = "partial"
|
|
OrderFilled OrderStatus = "filled"
|
|
OrderCancelled OrderStatus = "cancelled"
|
|
)
|
|
|
|
// Order is the core domain entity representing a trading order.
|
|
type Order struct {
|
|
ID string `json:"id"`
|
|
UserID string `json:"userId"`
|
|
CouponID string `json:"couponId"`
|
|
Side vo.OrderSide `json:"side"`
|
|
Type vo.OrderType `json:"type"`
|
|
Price vo.Price `json:"price"`
|
|
Quantity vo.Quantity `json:"quantity"`
|
|
FilledQty vo.Quantity `json:"filledQty"`
|
|
RemainingQty vo.Quantity `json:"remainingQty"`
|
|
Status OrderStatus `json:"status"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
// NewOrder creates a new Order with validated inputs.
|
|
func NewOrder(id, userID, couponID string, side vo.OrderSide, orderType vo.OrderType, price vo.Price, quantity vo.Quantity) (*Order, error) {
|
|
if id == "" {
|
|
return nil, fmt.Errorf("order ID cannot be empty")
|
|
}
|
|
if userID == "" {
|
|
return nil, fmt.Errorf("user ID cannot be empty")
|
|
}
|
|
if couponID == "" {
|
|
return nil, fmt.Errorf("coupon ID cannot be empty")
|
|
}
|
|
if !side.IsValid() {
|
|
return nil, fmt.Errorf("invalid order side: %s", side)
|
|
}
|
|
if !orderType.IsValid() {
|
|
return nil, fmt.Errorf("invalid order type: %s", orderType)
|
|
}
|
|
if orderType == vo.Limit && !price.IsPositive() {
|
|
return nil, fmt.Errorf("limit order must have a positive price")
|
|
}
|
|
|
|
now := time.Now()
|
|
return &Order{
|
|
ID: id,
|
|
UserID: userID,
|
|
CouponID: couponID,
|
|
Side: side,
|
|
Type: orderType,
|
|
Price: price,
|
|
Quantity: quantity,
|
|
FilledQty: vo.ZeroQuantity(),
|
|
RemainingQty: quantity,
|
|
Status: OrderPending,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}, nil
|
|
}
|
|
|
|
// Fill records a partial or full fill of the given quantity.
|
|
func (o *Order) Fill(qty vo.Quantity) {
|
|
o.FilledQty = o.FilledQty.Add(qty)
|
|
o.RemainingQty = o.RemainingQty.Subtract(qty)
|
|
o.UpdatedAt = time.Now()
|
|
|
|
if o.RemainingQty.IsZero() {
|
|
o.Status = OrderFilled
|
|
} else {
|
|
o.Status = OrderPartial
|
|
}
|
|
}
|
|
|
|
// Cancel marks the order as cancelled.
|
|
func (o *Order) Cancel() {
|
|
o.Status = OrderCancelled
|
|
o.UpdatedAt = time.Now()
|
|
}
|
|
|
|
// IsActive returns true if the order can still participate in matching.
|
|
func (o *Order) IsActive() bool {
|
|
return o.Status == OrderPending || o.Status == OrderPartial
|
|
}
|
|
|
|
// IsFilled returns true if the order is fully filled.
|
|
func (o *Order) IsFilled() bool {
|
|
return o.Status == OrderFilled
|
|
}
|