Files
pos-system/microservices/services/order-service-net/SERVICE_DOCS.md
Ho Ngoc Hai 76d75c753b Migrate
2026-05-23 18:37:02 +07:00

29 KiB

Order Service - Service Documentation

Auto-generated from source code audit. Last updated: 2026-03-13.

Overview

Order Service is the central order processing microservice for the GoodGo POS platform. It orchestrates order creation, payment, fulfillment, returns, exchanges, and reporting across multiple business verticals (Restaurant/F&B, Retail, Spa/Services).

  • Framework: .NET 10.0, C# 14
  • Architecture: Clean Architecture + CQRS (MediatR 12.4.1)
  • Database: PostgreSQL (Neon) via EF Core 10 (writes) + Dapper 2.1 (reads)
  • Real-time: SignalR with Redis backplane + MessagePack protocol
  • Port: 5017 (Development)
  • Base Route: api/v1/orders, api/v1/admin/orders, api/v1/reports
  • SignalR Hub: /hubs/pos
  • Health Checks: /health, /health/live, /health/ready

Key Features

  • Multi-vertical order processing via Strategy pattern (Physical, PreparedFood, Service)
  • Multi-payment support (cash, card, VNPay, Momo, QR, bank transfer)
  • Real-time POS/KDS notifications via SignalR
  • Multi-tenant row-level security (EF Core global query filters + PostgreSQL RLS)
  • Return and exchange workflows
  • Revenue analytics, staff performance, and End-of-Day reports
  • Idempotency support for duplicate request detection

API Endpoints

OrdersController (api/v1/orders)

Method Route Description Request Response
POST /api/v1/orders Create a new order CreateOrderCommand (body) 201 Created - CreateOrderResult
GET /api/v1/orders/{id} Get order by ID id (path), shopId (query) 200 OK - OrderDto / 404
GET /api/v1/orders List orders by shop (paginated) shopId, status?, fromDate?, toDate?, page, pageSize (query) 200 OK - PagedResult<OrderSummaryDto>
POST /api/v1/orders/{id}/pay Process payment id (path), shopId (query), PayOrderRequest (body) 200 OK - PayOrderResult / 400
POST /api/v1/orders/{id}/payment-callback Payment gateway callback id (path), PaymentCallbackRequest (body) 200 OK - CompleteOrderPaymentResult / 400
POST /api/v1/orders/{id}/cancel Cancel an order id (path), shopId (query), CancelOrderRequest (body) 200 OK - CancelOrderResult
POST /api/v1/orders/{id}/complete Complete an order id (path), shopId (query) 200 OK - CompleteOrderResult
GET /api/v1/orders/dashboard POS dashboard stats shopId, period? ("today"/"7d"/"30d") (query) 200 OK - PosDashboardDto
GET /api/v1/orders/active-by-table Active orders grouped by table shopId (query) 200 OK - List<ActiveTableOrderDto>
POST /api/v1/orders/returns Create a return CreateReturnCommand (body) 201 Created - CreateReturnResult / 400
POST /api/v1/orders/exchanges Create an exchange CreateExchangeCommand (body) 201 Created - CreateExchangeResult / 400
GET /api/v1/orders/{orderId}/returns Get return history orderId (path) 200 OK - List<ReturnOrderDto>
GET /api/v1/orders/customer/{customerId} Get orders by customer customerId (path), page, pageSize (query) 200 OK - PagedResult<OrderSummaryDto>

AdminOrdersController (api/v1/admin/orders)

Method Route Description Request Response
GET /api/v1/admin/orders List all orders (admin) shopId?, customerId?, status?, fromDate?, toDate?, minAmount?, maxAmount?, page, pageSize (query) 200 OK - PagedResult<OrderSummaryDto>
GET /api/v1/admin/orders/stats Order statistics shopId?, fromDate?, toDate? (query) 200 OK - OrderStatsDto
GET /api/v1/admin/orders/export Export orders as CSV shopId?, fromDate?, toDate? (query) 200 OK - CSV file download

ReportsController (api/v1/reports)

Method Route Description Request Response
GET /api/v1/reports/revenue Revenue report (daily/weekly/monthly) shopId, period, fromDate?, toDate? (query) 200 OK - RevenueReportDto
GET /api/v1/reports/top-products Top selling products shopId, limit?, fromDate?, toDate? (query) 200 OK - List<TopProductDto>
GET /api/v1/reports/revenue-analytics Advanced revenue analytics shopId, startDate, endDate, period? (query) 200 OK - RevenueAnalyticsDto
GET /api/v1/reports/staff-performance Staff performance metrics shopId, startDate, endDate (query) 200 OK - StaffPerformanceDto
GET /api/v1/reports/eod End-of-Day report shopId, date? (query) 200 OK - EodReportDto
POST /api/v1/reports/close-day Close business day CloseDayRequest (body) 200 OK - CloseDayResult / 400

SignalR Hub (/hubs/pos)

Method Direction Description
JoinShop(shopId) Client -> Server Join shop group for all POS updates
JoinKds(shopId) Client -> Server Join KDS group for kitchen updates
JoinPos(shopId) Client -> Server Join POS terminal group
LeaveShop(shopId) Client -> Server Leave shop group
LeaveKds(shopId) Client -> Server Leave KDS group
LeavePos(shopId) Client -> Server Leave POS terminal group
OrderCreated Server -> Client New order created notification
OrderUpdated Server -> Client Order updated notification
OrderStatusChanged Server -> Client Order status change notification
KitchenTicketCreated Server -> Client New kitchen ticket (to KDS)
KitchenTicketUpdated Server -> Client Kitchen ticket update (to KDS)
PaymentCompleted Server -> Client Payment completed notification
TableStatusChanged Server -> Client Table status change notification

Groups: shop:{shopId}, kds:{shopId}, pos:{shopId} Auth: JWT required (token via query string access_token for WebSocket) Shop Access: Validated via shop_id JWT claim (prevents cross-tenant access)


Commands

CreateOrderCommand

  • Input: ShopId (Guid), CustomerId? (Guid?), Items (List<OrderItemRequest>), DiscountAmount?, DiscountType?, DiscountReference?, TableId?
  • OrderItemRequest: ProductId, ProductName, ProductType ("Physical"|"Service"|"PreparedFood"), Quantity, UnitPrice, TrackInventory
  • Logic: Creates Order aggregate -> adds items -> validates each item via strategy (RetailStrategy/ServiceStrategy/FnbStrategy) -> executes each item via strategy (inventory deduction / kitchen ticket / booking) -> applies discount -> marks as Validated -> saves -> sends SignalR notification
  • Result: CreateOrderResult(OrderId, TotalAmount, Status)
  • Validator: ShopId required, Items non-empty, each item validated (ProductId, ProductName, ProductType in ["Physical","Service","PreparedFood"], Quantity > 0, UnitPrice >= 0)

PayOrderCommand

  • Input: OrderId, ShopId, PaymentMethod ("cash"|"card"|"vnpay"|"momo"|"qr"|"transfer"), AmountTendered?, ReturnUrl?, IpAddress?
  • Logic: Loads order -> verifies shop ownership -> routes to payment flow:
    • Cash: Validates sufficient amount -> generates CASH-* transaction ID -> calculates change -> marks Paid+Processing
    • Card/QR/Transfer: Generates CARD-* transaction ID -> marks Paid+Processing (POS terminal confirmed)
    • VNPay/Momo: Calls wallet-service CreatePaymentAsync -> marks PaymentPending -> returns payment URL for redirect
  • Result: PayOrderResult(Success, Status, PaymentUrl?, ChangeAmount?, TransactionId?, ErrorMessage?)
  • Validator: OrderId/ShopId required, PaymentMethod in supported list, AmountTendered required for cash, ReturnUrl required for vnpay/momo

CompleteOrderPaymentCommand

  • Input: OrderId, GatewayTransactionId, IsSuccess, GatewayResponseCode?
  • Logic: Called by wallet-service callback after online payment. If success -> CompletePayment + MarkAsProcessing. If failure -> Cancel order.
  • Result: CompleteOrderPaymentResult(Success, Status, ErrorMessage?)
  • Validator: OrderId required, GatewayTransactionId required (max 200), GatewayResponseCode max 50

CancelOrderCommand

  • Input: OrderId, ShopId, Reason
  • Logic: Loads order -> verifies shop ownership -> calls Order.Cancel(reason) (domain validates not Completed/already Cancelled) -> saves -> sends SignalR notification
  • Result: CancelOrderResult(Success, Status)
  • Validator: OrderId/ShopId required, Reason required (max 500)

CompleteOrderCommand

  • Input: OrderId, ShopId
  • Logic: Loads order -> verifies shop ownership -> calls Order.MarkAsCompleted() (domain validates must be Processing) -> saves -> sends SignalR notification
  • Result: CompleteOrderResult(Success, Status)
  • Validator: OrderId/ShopId required

CreateReturnCommand

  • Input: ShopId, OriginalOrderId, Items (List<ReturnItemDto>), Reason
  • ReturnItemDto: OrderItemId, Quantity, Reason?
  • Logic: Validates original order (must be Completed/Paid, same shop) -> creates new return order with items from original -> marks Validated -> ProcessReturn (sets isReturn, returnReason, originalOrderId) -> saves
  • Result: CreateReturnResult(Success, ReturnOrderId?, RefundAmount?, ErrorMessage?)
  • Validator: ShopId/OriginalOrderId required, Reason required (max 1000), Items non-empty, each item: OrderItemId required, Quantity > 0

CreateExchangeCommand

  • Input: ShopId, OriginalOrderId, ReturnItems (List<ReturnItemDto>), NewItems (List<ExchangeItemDto>), Reason
  • ExchangeItemDto: ProductId, Quantity, UnitPrice
  • Logic: Validates original order -> creates return order (step 1) -> creates new order with replacement items (step 2) -> calculates price difference -> saves both in same transaction -> raises OrderExchangedDomainEvent
  • Result: CreateExchangeResult(Success, ReturnOrderId?, NewOrderId?, PriceDifference?, ErrorMessage?)
  • Validator: ShopId/OriginalOrderId required, Reason required, ReturnItems/NewItems non-empty with item-level validation

CloseDayCommand

  • Input: ShopId, CloseDate
  • Logic: Checks for pending/in-progress orders (status 1,2,3,4,7) via Dapper -> generates EOD report via GetEodReportQuery -> returns report with warning if pending orders exist
  • Result: CloseDayResult(Success, Report?, Message?, PendingOrderCount)
  • Validator: ShopId required, CloseDate required and not in future

Queries

GetOrderByIdQuery

  • Input: OrderId, ShopId
  • Logic: Dapper query joining orders + order_statuses + order_items, filtered by OrderId and ShopId
  • Result: OrderDto (with items) or null

ListOrdersByShopQuery

  • Input: ShopId, Status?, FromDate?, ToDate?, Page, PageSize
  • Logic: Dapper query with dynamic WHERE clause, paginated (LIMIT/OFFSET), ordered by created_at DESC
  • Result: PagedResult<OrderSummaryDto>

AdminListOrdersQuery

  • Input: ShopId?, CustomerId?, Status?, FromDate?, ToDate?, MinAmount?, MaxAmount?, Page, PageSize
  • Logic: Dapper query with all optional filters, no shop restriction (admin access)
  • Result: PagedResult<OrderSummaryDto>

GetOrdersByCustomerQuery

  • Input: CustomerId, Page, PageSize
  • Logic: Dapper query filtered by customer_id, paginated
  • Result: PagedResult<OrderSummaryDto>

GetActiveTableOrdersQuery

  • Input: ShopId
  • Logic: Gets orders with status_id=2 (Validated) that have table_id, then batch-loads items via ANY(@OrderIds)
  • Result: List<ActiveTableOrderDto> (each with items)

GetOrderReturnsQuery

  • Input: OrderId
  • Logic: Uses EF Core repository GetReturnsByOriginalOrderIdAsync to find return orders
  • Result: List<ReturnOrderDto>

GetOrderStatsQuery

  • Input: ShopId?, FromDate?, ToDate?
  • Logic: Dapper aggregate (COUNT, SUM, AVG) + GROUP BY status name
  • Result: OrderStatsDto(TotalOrders, TotalRevenue, AverageOrderValue, OrdersByStatus)

ExportOrdersQuery

  • Input: ShopId?, FromDate?, ToDate?
  • Logic: Dapper query all matching orders -> generates CSV string
  • Result: ExportOrdersResult(FileName, CsvContent) (served as file download)

GetPosDashboardQuery

  • Input: ShopId, Period? ("today"|"7d"|"30d")
  • Logic: Multiple Dapper queries: aggregate stats, top 5 products, payment breakdown by status, hourly revenue, last 10 orders
  • Result: PosDashboardDto(Revenue, OrderCount, ItemsSold, AvgOrderValue, PopularItems, PaymentBreakdown, HourlyRevenue, RecentOrders)

GetRevenueReportQuery

  • Input: Period ("daily"|"weekly"|"monthly"), ShopId, FromDate?, ToDate?
  • Logic: Dapper with DATE_TRUNC grouping
  • Result: RevenueReportDto(Period, ShopId, TotalRevenue, TotalOrders, Data[])

GetTopProductsQuery

  • Input: ShopId, Limit, FromDate?, ToDate?
  • Logic: Dapper GROUP BY product_id, product_name, ORDER BY quantity DESC
  • Result: List<TopProductDto>

GetRevenueAnalyticsQuery

  • Input: ShopId, StartDate, EndDate, Period
  • Logic: 6 Dapper queries: current aggregate, previous period revenue, trends, payment method breakdown, top 10 products, vertical breakdown. Calculates growth percentage.
  • Result: RevenueAnalyticsDto(TotalRevenue, PreviousPeriodRevenue, GrowthPercentage, TotalOrders, AverageOrderValue, Trends, PaymentMethods, TopProducts, VerticalBreakdown)
  • Validator: ShopId required, StartDate < EndDate, EndDate not future, Period in ["daily","weekly","monthly"]

GetStaffPerformanceQuery

  • Input: ShopId, StartDate, EndDate
  • Logic: Dapper GROUP BY staff_id, staff_name with FILTER for completed/cancelled, calculates completion rate and avg handling time
  • Result: StaffPerformanceDto(Staff[], ShopAverage)
  • Validator: ShopId required, StartDate < EndDate, EndDate not future

GetEodReportQuery

  • Input: ShopId, ReportDate
  • Logic: 4 Dapper queries: aggregate (total/completed/cancelled/revenue by payment type/discounts), payment breakdown, top 10 items, hourly revenue
  • Result: EodReportDto(ReportDate, ShopId, TotalOrders, CompletedOrders, CancelledOrders, TotalRevenue, CashRevenue, CardRevenue, OnlineRevenue, DiscountTotal, PaymentBreakdown, TopItems, HourlyRevenue)
  • Validator: ShopId required, ReportDate required and not future

Domain Model

Order (Aggregate Root)

Table: orders

Private Fields / Properties:

Field Type Description
_shopId / ShopId Guid Shop that owns the order
_customerId / CustomerId Guid? Customer (null for walk-in)
_tableId / TableId Guid? Table for dine-in orders
StatusId int FK to order_statuses
_status / Status OrderStatus Resolved from StatusId
_totalAmount / TotalAmount decimal Calculated: sum(items) - discount
_discountAmount / DiscountAmount decimal Discount applied
_discountType / DiscountType string? Discount type (e.g. "percentage", "fixed")
_discountReference / DiscountReference string? Promotion/coupon reference
_paymentMethod / PaymentMethod string? "cash", "card", "vnpay", "momo", "qr", "transfer"
_transactionId / TransactionId string? External transaction ID
_amountTendered / AmountTendered decimal? Amount customer paid (cash)
_changeAmount / ChangeAmount decimal? Change returned (cash)
_isReturn / IsReturn bool Whether this is a return order
_returnReason / ReturnReason string? Reason for return
_returnedAt / ReturnedAt DateTime? When return was processed
_originalOrderId / OriginalOrderId Guid? Original order (for returns/exchanges)
_createdAt / CreatedAt DateTime Creation timestamp (UTC)
_updatedAt / UpdatedAt DateTime? Last update timestamp (UTC)
_items / Items List<OrderItem> Line items (owned collection)

Behavior Methods:

Method Transitions Domain Event Validation
Order(shopId, customerId?, tableId?) -> Draft OrderCreatedDomainEvent ShopId != empty
AddItem(item) (Draft only) - Status must be Draft
MarkAsValidated() Draft -> Validated - Must be Draft, must have items
MarkAsPaid(method, txnId, amountTendered?) Validated/PaymentPending -> Paid OrderPaidDomainEvent Must be Validated or PaymentPending
MarkAsPaymentPending(method, txnId) Validated -> PaymentPending OrderPaymentPendingDomainEvent Must be Validated
CompletePayment(gatewayTxnId) PaymentPending -> Paid OrderPaidDomainEvent Must be PaymentPending
MarkAsProcessing() Paid -> Processing - Must be Paid
MarkAsCompleted() Processing -> Completed OrderCompletedDomainEvent Must be Processing
Cancel(reason) Any (except Completed/Cancelled) -> Cancelled OrderCancelledDomainEvent Not Completed, not already Cancelled
ProcessReturn(reason, originalOrderId?) -> Returned OrderReturnedDomainEvent Reason required
ApplyDiscount(amount, type?, reference?) - - Amount >= 0

OrderItem (Entity, Owned by Order)

Table: order_items

Field Type Description
Id Guid Primary key
_productId / ProductId Guid Product from Catalog Service
_productName / ProductName string Snapshot of product name
_productType / ProductType string "Physical", "Service", "PreparedFood"
_quantity / Quantity int Quantity ordered
_unitPrice / UnitPrice decimal Unit price at order time
TotalPrice decimal Computed: Quantity * UnitPrice
_status / Status string "Pending", "Completed", "Failed"
_trackInventory / TrackInventory bool Auto-deduct inventory flag (default true)
_metadata / Metadata string? JSON metadata (e.g. appointment details)

Methods: MarkAsCompleted(), MarkAsFailed()

OrderStatus (Enumeration)

Id Name Description
1 Draft Created but not validated
2 Validated All items validated and available
3 Paid Payment processed successfully
4 Processing Being fulfilled
5 Completed Successfully completed
6 Cancelled Cancelled
7 PaymentPending Waiting for online payment gateway
8 Returned Items returned (defined in code, not seeded)

Domain Events

Event Raised By Payload
OrderCreatedDomainEvent Order constructor Order
OrderPaidDomainEvent MarkAsPaid, CompletePayment Order
OrderCompletedDomainEvent MarkAsCompleted Order
OrderPaymentPendingDomainEvent MarkAsPaymentPending Order
OrderCancelledDomainEvent Cancel Order, Reason
OrderReturnedDomainEvent ProcessReturn Order
OrderExchangedDomainEvent CreateExchangeCommandHandler ReturnOrder, NewOrder

Database Schema

Table: orders

Column Type Nullable Description
id uuid NOT NULL (PK) Order ID (generated in code)
shop_id uuid NOT NULL Shop tenant ID
customer_id uuid nullable Customer ID
table_id uuid nullable Table ID (dine-in)
status_id int NOT NULL FK to order_statuses
total_amount decimal(18,2) NOT NULL Order total
notes varchar(2000) nullable Notes
discount_amount decimal(18,2) NOT NULL Discount amount
discount_type varchar(50) nullable Discount type
discount_reference varchar(255) nullable Promotion/coupon ref
payment_method varchar(50) nullable Payment method used
transaction_id varchar(255) nullable External transaction ID
amount_tendered decimal(18,2) nullable Amount customer paid
change_amount decimal(18,2) nullable Change returned
is_return boolean NOT NULL (default false) Return order flag
return_reason varchar(1000) nullable Return reason
returned_at timestamp with time zone nullable Return timestamp
original_order_id uuid nullable Original order (returns)
created_at timestamp with time zone NOT NULL Creation time
updated_at timestamp with time zone nullable Last update time

Indexes:

  • ix_orders_shop_id on shop_id
  • ix_orders_customer_id on customer_id
  • ix_orders_status_id on status_id
  • ix_orders_created_at on created_at
  • ix_orders_original_order_id on original_order_id

Note: Dapper queries reference o.staff_id, o.staff_name, o.completed_at, o.vertical columns that are NOT defined in the EF entity configuration. These queries will fail at runtime unless the columns exist in the database via manual migration.

Table: order_items (Owned by Order)

Column Type Nullable Description
id uuid NOT NULL (PK) Item ID
order_id uuid NOT NULL (FK) Parent order
product_id uuid NOT NULL Product from catalog
product_name varchar(255) NOT NULL Product name snapshot
product_type varchar(50) NOT NULL "Physical"/"Service"/"PreparedFood"
quantity int NOT NULL Quantity
unit_price decimal(18,2) NOT NULL Unit price
status varchar(50) NOT NULL "Pending"/"Completed"/"Failed"
track_inventory boolean NOT NULL (default true) Auto inventory deduction
metadata jsonb nullable Additional JSON data

Table: order_statuses (Lookup/Seed)

Column Type Nullable Description
id int NOT NULL (PK) Status ID
name varchar(50) NOT NULL Status name

Seeded data: Draft(1), Validated(2), Paid(3), Processing(4), Completed(5), Cancelled(6), PaymentPending(7)

Note: Returned(8) is defined in code but NOT included in seed data.

Migrations

Migration Date Description
20260117175742_InitialOrder 2026-01-17 Initial schema: orders, order_items, order_statuses (1-6)
20260305004928_AddTableIdAndDiscountFields 2026-03-05 Added table_id, discount fields
20260306175520_PhaseTwo 2026-03-06 Added payment fields, return fields, track_inventory, PaymentPending(7) status, original_order_id index

Strategy Pattern (Line Item Processing)

Strategy SupportedType Validate Execute
RetailStrategy "Physical" Checks inventory availability via InventoryServiceClient Deducts stock via InventoryServiceClient
FnbStrategy "PreparedFood" Always returns true (no pre-check) Creates kitchen ticket via FnbEngineClient + deducts recipe ingredients via InventoryServiceClient
ServiceStrategy "Service" Checks availability via BookingServiceClient (parses metadata for StartTime/DurationMinutes) Creates appointment via BookingServiceClient

Interface: ILineItemStrategy (Domain layer) with SupportedType, ValidateAsync, ExecuteAsync Factory: StrategyFactory resolves strategy by product type (case-insensitive)


Multi-Tenant Security

EF Core Global Query Filter

  • Order entity filtered by _shopId == tenantProvider.GetCurrentShopId()
  • Bypassed when: _tenantProvider is null, ShouldBypassTenantFilter() returns true, or GetCurrentShopId() returns null

PostgreSQL RLS (Defense-in-Depth)

  • TenantMiddleware sets SET LOCAL app.current_shop_id and app.current_merchant_id session variables
  • Runs after authentication middleware
  • Skipped for service-to-service calls (X-Service-Call: internal) and admin users

Tenant Provider

  • HttpContextTenantProvider extracts tenant context from:
    1. JWT claim shop_id
    2. HTTP header X-Shop-Id (POS fallback)
  • Admin detection via role claims: "admin", "system", "superadmin"
  • OrderTenantProviderAdapter bridges ITenantProvider (API) to IOrderTenantProvider (Infrastructure)

Dependencies (Cross-Service Communication)

HTTP Clients (with Polly retry + circuit breaker)

Client Target Service Default URL Timeout Usage
CatalogServiceClient Catalog Service http://catalog-service-net:8080 10s Get product details
InventoryServiceClient Inventory Service http://inventory-service-net:8080 10s Check/deduct stock, deduct by ID
FnbEngineClient F&B Engine http://fnb-engine-net:8080 10s Kitchen tickets, recipe lookup
BookingServiceClient Booking Service http://booking-service-net:8080 10s Availability check, create appointment
WalletServiceClient Wallet Service http://wallet-service-net:8080 15s Create online payment (VNPay/Momo)

Polly Policies: Retry 3x with exponential backoff (2^attempt seconds), Circuit breaker (5 failures, 30s break)


MediatR Pipeline

Request -> LoggingBehavior -> ValidatorBehavior -> TransactionBehavior -> Handler
  1. LoggingBehavior: Logs request name and elapsed time (Stopwatch)
  2. ValidatorBehavior: Runs all registered FluentValidation validators, throws ValidationException on failure
  3. TransactionBehavior: Auto-wraps Commands in DB transaction (skips Queries by name convention *Query), uses ExecutionStrategy for retry-safe transactions

Configuration

appsettings.json

Key Description Default
ConnectionStrings:DefaultConnection PostgreSQL connection string Neon PostgreSQL
Redis:ConnectionString Redis for SignalR backplane localhost:6379
SignalR:KeepAliveInterval SignalR keep-alive (seconds) 15
SignalR:ClientTimeoutInterval Client timeout (seconds) 30
SignalR:StatefulReconnectBufferSize Reconnect buffer size 32768
SignalR:EnableMessagePack Enable MessagePack protocol true
Jwt:Authority JWT issuer authority http://localhost:5001
Jwt:Secret JWT secret key (configured)
AllowedOrigins CORS allowed origins localhost:3000, 5173, 5000
Services:CatalogService Catalog service URL http://catalog-service-net:8080
Services:InventoryService Inventory service URL http://inventory-service-net:8080
Services:BookingService Booking service URL http://booking-service-net:8080
Services:FnbEngine F&B engine URL http://fnb-engine-net:8080
Services:WalletService Wallet service URL http://wallet-service-net:8080

NuGet Dependencies (API)

Package Version
MediatR 12.4.1
FluentValidation 11.11.0
FluentValidation.DependencyInjectionExtensions 11.11.0
Microsoft.AspNetCore.Authentication.JwtBearer 10.0.1
Swashbuckle.AspNetCore 7.2.0
AspNetCore.HealthChecks.NpgSql 8.0.2
AspNetCore.HealthChecks.Redis 8.0.1
Hellang.Middleware.ProblemDetails 6.5.1
Serilog.AspNetCore 8.0.3
Microsoft.AspNetCore.SignalR.StackExchangeRedis 9.0.0
Microsoft.AspNetCore.SignalR.Protocols.MessagePack 9.0.0
Microsoft.EntityFrameworkCore.Design 10.0.0

Tests

Unit Tests (tests/OrderService.UnitTests/)

Test Class Tests
OrderAggregateTests CreateOrder_WithValidShopId_ShouldStartInDraftStatus, AddItem_InDraftStatus_ShouldRecalculateTotalAmount, MarkAsValidated_WithoutItems_ShouldThrowDomainException, FullLifecycle_DraftToCompleted_ShouldUpdateStatusSequentially, Cancel_CompletedOrder_ShouldThrowDomainException

Functional Tests (tests/OrderService.FunctionalTests/)

Test Class Description
HealthChecksControllerTests Health endpoint tests
OrdersControllerTests API endpoint integration tests
CustomWebApplicationFactory Test server setup with InMemory DB
TestAuthHandler Fake auth handler for tests

Known Issues / Gaps

  1. Missing DB columns: Dapper queries in GetStaffPerformanceQuery reference o.staff_id, o.staff_name, o.completed_at columns, and GetRevenueAnalyticsQuery references o.vertical column. These are NOT defined in Order entity or EF configurations -- queries will fail at runtime unless columns exist via manual migration.

  2. Returned status not seeded: OrderStatus.Returned (Id=8) is defined in code but NOT included in the OrderStatusEntityTypeConfiguration seed data. Return orders will fail to load if status is queried via JOIN to order_statuses.

  3. Exchange item names: CreateExchangeCommandHandler hardcodes product name as "Exchange Item" and type as "Physical" for new items in exchanges -- these should be resolved from the catalog or passed by the frontend.

  4. No [Authorize] on controllers: OrdersController, AdminOrdersController, and ReportsController do not have [Authorize] attributes. Authentication is configured in Program.cs but not enforced at controller level (only the SignalR hub has [Authorize]).

  5. Template remnants: Build artifacts contain references to "MyService" namespace (from _template_dot_net), indicating the service was scaffolded from the template.