feat(web-client-tpos): integrate StorageService API and adapt client data handling while removing multi-vertical architecture plan documentation.
This commit is contained in:
@@ -1,408 +0,0 @@
|
||||
Multi-vertical Architecture Implementation Plan
|
||||
Problem Statement / Vấn Đề
|
||||
Hệ thống hiện tại có Merchant Service đang làm tốt vai trò quản trị/định danh, nhưng cần mở rộng để hỗ trợ đa ngành hàng (Multi-vertical) bao gồm:
|
||||
|
||||
Retail: Bán lẻ hàng hóa vật lý (Áo, Mũ, Điện thoại...)
|
||||
F&B: Food & Beverage (Quán cafe, Nhà hàng, Quán ăn...)
|
||||
Services: Dịch vụ (Spa, Salon, Phòng khám...)
|
||||
Hybrid: Kết hợp nhiều ngành (Quán cafe bán kèm merchandise)
|
||||
Nguyên tắc thiết kế:
|
||||
|
||||
Merchant Service chỉ quản lý "Ai" (Identity) và "Luật chơi" (Configuration), còn "Chơi thế nào" (Execution) phải đẩy ra các Service vệ tinh.
|
||||
|
||||
Architecture Overview / Tổng Quan Kiến Trúc
|
||||
🔵 Vertical Engines - Logic Ngành
|
||||
🔴 Orchestration Layer
|
||||
🟡 Core Services - Configuration & Identity
|
||||
Client
|
||||
Retail Item
|
||||
Service Item
|
||||
F&B Item
|
||||
Ref
|
||||
Get Config
|
||||
⚡ Traefik Gateway
|
||||
💳 Smart POS / Web
|
||||
🔐 IAM Service(User/Role)
|
||||
🏪 Merchant Service(Shop DNA, Settings, Staff)
|
||||
💰 Wallet Service(Payment)
|
||||
📦 Catalog Service(Polymorphic Products)
|
||||
📝 Order Service(The Brain - Strategy Pattern)
|
||||
🏭 Inventory Service(Retail Logic)
|
||||
📅 Booking Service(Spa/Service Logic)
|
||||
🍳 F&B Engine(Table/Session/KDS)
|
||||
User Review Required
|
||||
IMPORTANT
|
||||
|
||||
Quyết định Scope: Plan này bao gồm 4 phases. User cần xác nhận muốn triển khai tất cả hay chỉ một số phases cụ thể.
|
||||
|
||||
WARNING
|
||||
|
||||
Breaking Changes trong Phase 1: Thay đổi database schema của Shop entity sẽ yêu cầu migration và có thể ảnh hưởng đến các service đang sử dụng Merchant Service API.
|
||||
|
||||
Phase 1: Foundation Refactoring
|
||||
Mục tiêu
|
||||
Chuẩn bị Merchant Service làm nền tảng cho multi-vertical, không triển khai logic nghiệp vụ ngành.
|
||||
|
||||
1.1 Database Changes
|
||||
[MODIFY] Add JSONB columns to shops table
|
||||
Migration SQL:
|
||||
|
||||
-- Migration: Add multi-vertical support columns
|
||||
ALTER TABLE shops
|
||||
ADD COLUMN features_config JSONB DEFAULT '{}',
|
||||
ADD COLUMN vertical_settings JSONB DEFAULT '{}',
|
||||
ADD COLUMN business_category VARCHAR(50) DEFAULT 'Other';
|
||||
-- Index for performance
|
||||
CREATE INDEX idx_shops_business_category ON shops(business_category);
|
||||
CREATE INDEX idx_shops_features_config ON shops USING GIN (features_config);
|
||||
-- Comment documentation
|
||||
COMMENT ON COLUMN shops.features_config IS 'Feature flags: HasInventory, HasBooking, HasTables, HasKitchen...';
|
||||
COMMENT ON COLUMN shops.vertical_settings IS 'Business-specific settings as JSON document';
|
||||
COMMENT ON COLUMN shops.business_category IS 'Primary business type: Retail, FoodBeverage, Services, Other';
|
||||
1.2 Domain Model Changes
|
||||
[MODIFY]
|
||||
|
||||
Shop.cs
|
||||
New Properties:
|
||||
|
||||
public class Shop : Entity, IAggregateRoot
|
||||
{
|
||||
// ... existing properties ...
|
||||
|
||||
// NEW: Business category enum
|
||||
public BusinessCategory Category { get; private set; }
|
||||
|
||||
// NEW: Feature configuration (maps to JSONB)
|
||||
public ShopFeatures Features { get; private set; }
|
||||
|
||||
// NEW: Dynamic settings (maps to JSONB)
|
||||
public JsonDocument? Settings { get; private set; }
|
||||
|
||||
// Factory method with category-based defaults
|
||||
public static Shop Create(Guid merchantId, string name, BusinessCategory category)
|
||||
{
|
||||
var shop = new Shop(merchantId, name, category);
|
||||
shop.Features = GetDefaultFeatures(category);
|
||||
return shop;
|
||||
}
|
||||
|
||||
private static ShopFeatures GetDefaultFeatures(BusinessCategory category) => category switch
|
||||
{
|
||||
BusinessCategory.Retail => new ShopFeatures(HasInventory: true, HasShipping: true),
|
||||
BusinessCategory.FoodBeverage => new ShopFeatures(HasTables: true, HasKitchen: true, HasInventory: true),
|
||||
BusinessCategory.Services => new ShopFeatures(HasBooking: true),
|
||||
_ => new ShopFeatures(HasInventory: true, HasBooking: true) // Hybrid
|
||||
};
|
||||
}
|
||||
[NEW] BusinessCategory.cs
|
||||
// Path: MerchantService.Domain/AggregatesModel/ShopAggregate/BusinessCategory.cs
|
||||
public enum BusinessCategory
|
||||
{
|
||||
Retail = 1, // Bán lẻ
|
||||
FoodBeverage = 2, // F&B
|
||||
Services = 3, // Dịch vụ (Spa, Salon)
|
||||
Other = 99 // Hybrid/Other
|
||||
}
|
||||
[NEW] ShopFeatures.cs (Value Object)
|
||||
// Path: MerchantService.Domain/AggregatesModel/ShopAggregate/ShopFeatures.cs
|
||||
public record ShopFeatures(
|
||||
bool HasInventory = false,
|
||||
bool HasBooking = false,
|
||||
bool HasTables = false,
|
||||
bool HasKitchen = false,
|
||||
bool HasShipping = false,
|
||||
bool HasDelivery = false
|
||||
);
|
||||
1.3 API Changes
|
||||
[MODIFY] Shop DTOs
|
||||
CreateShopRequest:
|
||||
|
||||
public record CreateShopRequest(
|
||||
string Name,
|
||||
string? Description,
|
||||
+ BusinessCategory Category = BusinessCategory.Other,
|
||||
+ ShopFeaturesDto? Features = null
|
||||
);
|
||||
ShopResponse:
|
||||
|
||||
public record ShopResponse(
|
||||
Guid Id,
|
||||
string Name,
|
||||
string? Description,
|
||||
+ BusinessCategory Category,
|
||||
+ ShopFeaturesDto Features,
|
||||
...
|
||||
);
|
||||
[NEW] ShopFeaturesDto
|
||||
public record ShopFeaturesDto(
|
||||
bool HasInventory = false,
|
||||
bool HasBooking = false,
|
||||
bool HasTables = false,
|
||||
bool HasKitchen = false,
|
||||
bool HasShipping = false,
|
||||
bool HasDelivery = false
|
||||
);
|
||||
Phase 2: Retail MVP
|
||||
Mục tiêu
|
||||
Xây dựng luồng bán hàng cơ bản cho ngành Retail.
|
||||
|
||||
2.1 Catalog Service (NEW)
|
||||
Trách nhiệm: Quản lý sản phẩm đa hình (Polymorphic Products)
|
||||
|
||||
Database Schema
|
||||
CREATE TABLE products (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
shop_id UUID NOT NULL REFERENCES shops(id),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
price DECIMAL(18,2) NOT NULL,
|
||||
product_type VARCHAR(50) NOT NULL, -- Physical, Service, PreparedFood
|
||||
attributes JSONB DEFAULT '{}', -- Type-specific attributes
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_products_shop ON products(shop_id);
|
||||
CREATE INDEX idx_products_type ON products(product_type);
|
||||
Core Entities
|
||||
Product (Aggregate Root)
|
||||
ProductType (Enum: Physical, Service, PreparedFood)
|
||||
ProductVariant (Size, Color...)
|
||||
ProductCategory
|
||||
2.2 Inventory Service (NEW)
|
||||
Trách nhiệm: Quản lý tồn kho cho Retail
|
||||
|
||||
Database Schema
|
||||
CREATE TABLE inventory_items (
|
||||
id UUID PRIMARY KEY,
|
||||
product_id UUID NOT NULL,
|
||||
shop_id UUID NOT NULL,
|
||||
quantity INT NOT NULL DEFAULT 0,
|
||||
reserved_quantity INT NOT NULL DEFAULT 0,
|
||||
reorder_level INT DEFAULT 10,
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
CREATE TABLE inventory_transactions (
|
||||
id UUID PRIMARY KEY,
|
||||
inventory_item_id UUID NOT NULL,
|
||||
transaction_type VARCHAR(50) NOT NULL, -- IN, OUT, ADJUSTMENT
|
||||
quantity INT NOT NULL,
|
||||
reference_id UUID, -- Order ID, PO ID...
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
Core Features
|
||||
Stock in/out operations
|
||||
Stock reservation for orders
|
||||
Low stock alerts
|
||||
Inventory audit trail
|
||||
2.3 Order Service (NEW) - The Brain
|
||||
Trách nhiệm: Điều phối đơn hàng, sử dụng Strategy Pattern
|
||||
|
||||
Strategy Pattern Design
|
||||
// Interface for line item processing
|
||||
public interface ILineItemStrategy
|
||||
{
|
||||
ProductType SupportedType { get; }
|
||||
Task ValidateAsync(OrderItem item, Guid shopId);
|
||||
Task ExecuteAsync(OrderItem item, Guid shopId);
|
||||
}
|
||||
// Strategy implementations
|
||||
public class RetailStrategy : ILineItemStrategy
|
||||
{
|
||||
public ProductType SupportedType => ProductType.Physical;
|
||||
|
||||
public async Task ValidateAsync(OrderItem item, Guid shopId)
|
||||
{
|
||||
// Call Inventory Service: Check stock availability
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(OrderItem item, Guid shopId)
|
||||
{
|
||||
// Call Inventory Service: Reserve/Deduct stock
|
||||
}
|
||||
}
|
||||
Order Flow
|
||||
WalletService
|
||||
InventoryService
|
||||
MerchantService
|
||||
OrderService
|
||||
POS
|
||||
WalletService
|
||||
InventoryService
|
||||
MerchantService
|
||||
OrderService
|
||||
POS
|
||||
loop
|
||||
[For each LineItem]
|
||||
loop
|
||||
[For each LineItem]
|
||||
Create Order
|
||||
Get Shop Features
|
||||
ShopFeatures
|
||||
Select Strategy by ProductType
|
||||
Validate (Check Stock)
|
||||
OK/Error
|
||||
Process Payment
|
||||
Payment OK
|
||||
Execute (Deduct Stock)
|
||||
Order Completed
|
||||
Phase 3: F&B Vertical
|
||||
Mục tiêu
|
||||
Mở rộng hỗ trợ ngành F&B (Food & Beverage)
|
||||
|
||||
3.1 F&B Engine (NEW)
|
||||
Trách nhiệm: Quản lý phiên ăn uống và bếp
|
||||
|
||||
Core Entities
|
||||
Table - Sơ đồ bàn
|
||||
Session - Phiên ăn (khách ngồi tại bàn)
|
||||
KitchenTicket - Phiếu bếp
|
||||
Recipe - Định lượng nguyên liệu
|
||||
Database Schema
|
||||
CREATE TABLE tables (
|
||||
id UUID PRIMARY KEY,
|
||||
shop_id UUID NOT NULL,
|
||||
table_number VARCHAR(50) NOT NULL,
|
||||
capacity INT DEFAULT 4,
|
||||
zone VARCHAR(100),
|
||||
status VARCHAR(50) DEFAULT 'Available'
|
||||
);
|
||||
CREATE TABLE sessions (
|
||||
id UUID PRIMARY KEY,
|
||||
table_id UUID NOT NULL,
|
||||
started_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
closed_at TIMESTAMPTZ,
|
||||
guest_count INT DEFAULT 1,
|
||||
status VARCHAR(50) DEFAULT 'Active'
|
||||
);
|
||||
CREATE TABLE kitchen_tickets (
|
||||
id UUID PRIMARY KEY,
|
||||
session_id UUID NOT NULL,
|
||||
order_item_id UUID NOT NULL,
|
||||
station VARCHAR(50), -- Bar, Kitchen, Grill...
|
||||
priority INT DEFAULT 0,
|
||||
status VARCHAR(50) DEFAULT 'Pending',
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
3.2 F&B Strategy
|
||||
public class FnbStrategy : ILineItemStrategy
|
||||
{
|
||||
public ProductType SupportedType => ProductType.PreparedFood;
|
||||
|
||||
public async Task ExecuteAsync(OrderItem item, Guid shopId)
|
||||
{
|
||||
// 1. Publish KitchenTicketCreated event
|
||||
// 2. F&B Engine receives and displays on KDS
|
||||
// 3. Optionally: Deduct ingredients from Inventory
|
||||
}
|
||||
}
|
||||
Phase 4: Services Vertical (Spa/Salon)
|
||||
Mục tiêu
|
||||
Mở rộng hỗ trợ ngành Dịch vụ (Spa, Salon, Clinic)
|
||||
|
||||
4.1 Booking Service (NEW)
|
||||
Trách nhiệm: Quản lý "tồn kho thời gian"
|
||||
|
||||
Core Entities
|
||||
StaffSchedule - Lịch làm việc nhân viên
|
||||
Resource - Tài nguyên (Giường, Phòng)
|
||||
Appointment - Cuộc hẹn
|
||||
TimeSlot - Slot thời gian khả dụng
|
||||
Database Schema
|
||||
CREATE TABLE staff_schedules (
|
||||
id UUID PRIMARY KEY,
|
||||
staff_id UUID NOT NULL,
|
||||
shop_id UUID NOT NULL,
|
||||
day_of_week INT NOT NULL,
|
||||
start_time TIME NOT NULL,
|
||||
end_time TIME NOT NULL
|
||||
);
|
||||
CREATE TABLE resources (
|
||||
id UUID PRIMARY KEY,
|
||||
shop_id UUID NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
resource_type VARCHAR(50) NOT NULL, -- Room, Bed, Equipment
|
||||
capacity INT DEFAULT 1
|
||||
);
|
||||
CREATE TABLE appointments (
|
||||
id UUID PRIMARY KEY,
|
||||
shop_id UUID NOT NULL,
|
||||
customer_id UUID,
|
||||
staff_id UUID,
|
||||
resource_id UUID,
|
||||
service_id UUID NOT NULL,
|
||||
start_time TIMESTAMPTZ NOT NULL,
|
||||
end_time TIMESTAMPTZ NOT NULL,
|
||||
status VARCHAR(50) DEFAULT 'Scheduled'
|
||||
);
|
||||
4.2 Booking Strategy
|
||||
public class ServiceStrategy : ILineItemStrategy
|
||||
{
|
||||
public ProductType SupportedType => ProductType.Service;
|
||||
|
||||
public async Task ValidateAsync(OrderItem item, Guid shopId)
|
||||
{
|
||||
// Call Booking Service: Check slot availability
|
||||
// Check staff + resource availability intersection
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(OrderItem item, Guid shopId)
|
||||
{
|
||||
// Call Booking Service: Create Appointment
|
||||
}
|
||||
}
|
||||
New Microservices Summary
|
||||
Service Trách nhiệm Phase
|
||||
Catalog Service Quản lý sản phẩm đa hình Phase 2
|
||||
Inventory Service Quản lý tồn kho Retail + F&B ingredients Phase 2
|
||||
Order Service Orchestrator với Strategy Pattern Phase 2
|
||||
F&B Engine Table management, KDS, Sessions Phase 3
|
||||
Booking Service Appointment scheduling Phase 4
|
||||
File Changes Summary
|
||||
Phase 1 (Merchant Service Updates)
|
||||
Action File
|
||||
MODIFY MerchantService.Domain/AggregatesModel/ShopAggregate/Shop.cs
|
||||
NEW MerchantService.Domain/AggregatesModel/ShopAggregate/BusinessCategory.cs
|
||||
NEW MerchantService.Domain/AggregatesModel/ShopAggregate/ShopFeatures.cs
|
||||
MODIFY MerchantService.Infrastructure/EntityConfigurations/ShopConfiguration.cs
|
||||
NEW Migration file for JSONB columns
|
||||
MODIFY API DTOs (CreateShopRequest, ShopResponse)
|
||||
Phase 2-4 (New Services)
|
||||
Action Project
|
||||
NEW catalog-service-net/
|
||||
NEW inventory-service-net/
|
||||
NEW order-service-net/
|
||||
NEW fnb-engine-net/
|
||||
NEW booking-service-net/
|
||||
Verification Plan
|
||||
Documentation Review
|
||||
Technical Documentation reviewed by team
|
||||
Architecture diagrams validated
|
||||
API specifications approved
|
||||
Phase 1 Verification
|
||||
Migration runs successfully on test database
|
||||
Existing Merchant Service APIs unchanged (backward compatible)
|
||||
New ShopFeatures returned in API responses
|
||||
Unit tests for new Value Objects pass
|
||||
Phase 2+ Verification
|
||||
Integration tests between services pass
|
||||
End-to-end order flow works for Retail
|
||||
Strategy Pattern correctly routes line items
|
||||
Notes
|
||||
NOTE
|
||||
|
||||
Scope: User yêu cầu chỉ tạo documentation và planning, KHÔNG triển khai code trong phase này.
|
||||
|
||||
TIP
|
||||
|
||||
Phụ thuộc Skills: Khi triển khai code, tham khảo các skills:
|
||||
|
||||
|
||||
domain-driven-design
|
||||
|
||||
repository-pattern
|
||||
|
||||
cqrs-mediatr
|
||||
|
||||
inter-service-communication
|
||||
@@ -891,12 +891,17 @@ public class PosDataService
|
||||
public record StorageFolderInfo(Guid Id, Guid? ParentId, string Name, string? Path, DateTime CreatedAt);
|
||||
public record CreateFolderRequest(string Name, Guid? ParentId);
|
||||
|
||||
// Storage API wraps responses in { success, data, error }
|
||||
private record StorageApiResponse<T>(bool Success, T? Data, string? Error);
|
||||
private record UserFilesResult(List<StorageFileInfo> Files, int TotalCount);
|
||||
|
||||
public async Task<List<StorageFileInfo>> GetStorageFilesAsync(int skip = 0, int take = 50, string? search = null)
|
||||
{
|
||||
AttachToken();
|
||||
var qs = $"?skip={skip}&take={take}";
|
||||
if (!string.IsNullOrEmpty(search)) qs += $"&search={Uri.EscapeDataString(search)}";
|
||||
return await _http.GetFromJsonAsync<List<StorageFileInfo>>($"api/bff/files{qs}", _jsonOptions) ?? new();
|
||||
var wrapper = await _http.GetFromJsonAsync<StorageApiResponse<UserFilesResult>>($"api/bff/files{qs}", _jsonOptions);
|
||||
return wrapper?.Data?.Files ?? new();
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteStorageFileAsync(Guid fileId)
|
||||
@@ -907,15 +912,22 @@ public class PosDataService
|
||||
AttachToken();
|
||||
var resp = await _http.GetAsync($"api/bff/files/{fileId}/download-url");
|
||||
if (!resp.IsSuccessStatusCode) return null;
|
||||
var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
return json.TryGetProperty("url", out var url) ? url.GetString() : null;
|
||||
var json = await resp.Content.ReadFromJsonAsync<JsonElement>(_jsonOptions);
|
||||
if (json.TryGetProperty("data", out var data))
|
||||
{
|
||||
if (data.TryGetProperty("url", out var url)) return url.GetString();
|
||||
if (data.TryGetProperty("downloadUrl", out var dl)) return dl.GetString();
|
||||
}
|
||||
if (json.TryGetProperty("url", out var directUrl)) return directUrl.GetString();
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<List<StorageFolderInfo>> GetFoldersAsync(Guid? parentId = null)
|
||||
{
|
||||
AttachToken();
|
||||
var qs = parentId.HasValue ? $"?parentId={parentId}" : "";
|
||||
return await _http.GetFromJsonAsync<List<StorageFolderInfo>>($"api/bff/folders{qs}", _jsonOptions) ?? new();
|
||||
var wrapper = await _http.GetFromJsonAsync<StorageApiResponse<IEnumerable<StorageFolderInfo>>>($"api/bff/folders{qs}", _jsonOptions);
|
||||
return wrapper?.Data?.ToList() ?? new();
|
||||
}
|
||||
|
||||
public async Task<(bool Ok, string? Error)> CreateFolderAsync(CreateFolderRequest req)
|
||||
|
||||
@@ -1304,6 +1304,7 @@ services:
|
||||
- PromotionService__BaseUrl=http://promotion-service-net:8080
|
||||
- BookingService__BaseUrl=http://booking-service-net:8080
|
||||
- FnbEngine__BaseUrl=http://fnb-engine-net:8080
|
||||
- StorageService__BaseUrl=http://storage-service:8080
|
||||
ports:
|
||||
- "3001:8080"
|
||||
depends_on:
|
||||
|
||||
Reference in New Issue
Block a user