Sinh từ OnModelCreating (FK đã kiểm chứng). Quan hệ: cha ||--o{ con = con giữ khóa ngoại trỏ cha. Đã ẩn khóa phân vùng SubsidiaryId & audit để làm rõ quan hệ domain. FK* = khóa tổng hợp.
Kho – Sản phẩm – BOM – Kiểm kê – Vị trí lưu trữ — 20 entity, 30 quan hệ domain · ~58 entity tổng trong DB
📋 Flow chi tiết (16 bước)
Mô-đun Kho quản lý toàn bộ vòng đời sản phẩm từ nhập, xuất, tồn kho cho đến kiểm kê định kỳ. Dữ liệu chảy qua các phiếu giao dịch (Inventory) để thay đổi số lượng trong kiện hàng (Package), sau đó cập nhật lịch sử tồn kho theo thời gian (InvBalance) và hỗ trợ các yêu cầu xuất kho từ sản xuất (InventoryRequirement). Mô-đun liên kết chặt với Sản xuất, QC, và Kế toán để đảm bảo dữ liệu chi phí và trạng thái hoạt động luôn đồng bộ.
1. Khi nhân viên tạo phiếu giao dịch, hệ thống tạo bản ghi Inventory với InventoryCode duy nhất, InventoryTypeId (1=Nhập/2=Xuất), và StockId để xác định kho lưu trữ.
2. Cho mỗi dòng sản phẩm, tạo InventoryDetail với ProductId, ProductUnitConversionId (đơn vị tính), PrimaryQuantity, và FromPackageId/ToPackageId để theo dõi nguồn/đích kiện hàng.
3. Hệ thống xác thực Package (từ bảng Package) tồn tại và có số lượng hợp lệ qua FromPackageId, kiểm tra số dư trong kiện trước khi cho phép xuất.
4. Người dùng hoặc QC có thể gửi Inventory tới kiểm tra bằng SentToCensor, lúc này CensorByUserId và CensorDatetimeUtc được ghi lại cho phép QC xử lý.
5. Khi phiếu được phê duyệt (IsApproved=true), hệ thống cập nhật ToPackageId nếu là nhập kho, hoặc giảm FromPackageId nếu là xuất kho; TotalMoney tính từ tổng Money của InventoryDetail.
6. Đối với mỗi Package bị ảnh hưởng, hệ thống gọi RecalcPackageQuantityOnInvDetail để tái tính số dư dựa trên tất cả InventoryDetail liên quan.
7. Sau khi cập nhật Package, hệ thống đánh dấu InvBalanceDirty để chỉ ra rằng InvBalance cần tái tính, đảm bảo không phải tải lại toàn bộ lịch sử mà chỉ từ điểm bẩn.
8. Hệ thống tái tính InvBalance bằng cách lấy InvBalance gần nhất trước ngày thay đổi, sau đó phát lại tất cả InventoryDetail theo thứ tự (Date, InventoryTypeId, InventoryDetailId) để tích lũy số dư cho InvBalanceDetail.
9. Khi sản xuất yêu cầu nguyên liệu, hệ thống tạo InventoryRequirement với InventoryRequirementCode, InventoryTypeId=2 (xuất), Date, và DepartmentId của bộ phận yêu cầu.
10. Mỗi dòng InventoryRequirementDetail chứa ProductId, AssignStockId (kho cấp hàng), ProductUnitConversionId, tương ứng với ProductBom của sản phẩm hoàn thành để tính tỷ lệ cần hàng.
11. Khi InventoryRequirementDetail được phê duyệt, hệ thống tạo Inventory xuất kho với FromPackageId từ kho AssignStockId, liên kết qua InventoryRequirementDetailId và ProductionOrderCode để theo dõi nguồn gốc.
12. Một Inventory có thể có RefInventoryId để liên kết các phiếu liên quan (ví dụ: từ phiếu kho đầu vào thành phiếu kho xuất khi chuyển kho hoặc nhập lại), tạo chuỗi giao dịch theo dõi được.
13. Mỗi tháng/kỳ, quản lý tạo StockTakePeriod với StockTakePeriodCode, Date, StockId, và Status để khởi tạo phiếu kiểm kê định kỳ cho một kho cụ thể.
14. Nhân viên kho điền StockTake (chi tiết kiểm kê thực tế) với StockTakeId, StockTakeDetail chứa ProductId, số lượng kiểm được thực tế, so sánh với InvBalanceDetail để phát hiện chênh lệch (IsDifference).
15. Sau khi hoàn thành kiểm kê, quản lý tạo StockTakeAcceptanceCertificate với StockTakePeriodId, ghi nhận kết luận (ConclusionContent), xác nhận hoặc điều chỉnh số dư nếu có mất mát/lỗi.
16. Mô-đun thông báo tới Manufacturing để cập nhật trạng thái ProductionOrderStatus khi có biến động Inventory, và tới Accountancy để ghi chi phí dựa trên UnitPrice × Quantity trong InventoryDetail, đảm bảo báo cáo tài chính chính xác.
Sản xuất – Lệnh SX – Công đoạn – Bàn giao – Gia công ngoài – Kế hoạch — 21 entity, 33 quan hệ domain · ~67 entity tổng trong DB
📋 Flow chi tiết (16 bước)
Mô-đun Manufacturing quản lý toàn bộ vòng đời sản xuất từ lập kế hoạch tháng đến hoàn thành bàn giao sản phẩm. Dữ liệu chảy từ MonthPlan → ProductionOrder → ProductionOrderDetail → ProductionAssignment (giao công đoạn cho bộ phận) → ProductionHandover (bàn giao giữa công đoạn) → hoàn thành. Mô-đun hỗ trợ gia công ngoài (OutsourcePartRequest), quản lý vật tư tiêu hao (ProductionConsumMaterial), và cập nhật tự động với Kho hàng thông qua InventoryDetail.
1. Tạo MonthPlan: Lập kế hoạch tháng sản xuất với StartDate, EndDate để xác định khoảng thời gian kế hoạch (dùng làm container cho các ProductionOrder).
2. Tạo ProductionOrder: Nhân viên tạo lệnh sản xuất từ đơn hàng bán (Sales), ghi ProductionOrderCode, StartDate, EndDate, MonthPlanId (liên kết kế hoạch tháng). Ban đầu IsDraft=true, ProductionOrderStatus=Draft.
3. Thêm ProductionOrderDetail: Mỗi lệnh sản xuất chứa chi tiết sản phẩm cần sản xuất, ghi ProductId, Quantity (số lượng), ReserveQuantity (bù hao), OrderDetailId (tham chiếu từ bán hàng), InvInpQuantity (nhập kho dự kiến).
4. Định nghĩa ProductionStep: Hệ thống tạo quy trình sản xuất bao gồm các công đoạn (Step) cần thực hiện. Mỗi ProductionStep có StepId, Title, ContainerId (liên kết ProductionOrderId), IsStartStep (công đoạn bắt đầu), IsEndStep (công đoạn cuối), CoordinateX/Y (vị trí trên bản đồ quy trình), OutsourceStepRequestId (nếu gia công ngoài).
5. Tạo ProductionStepLinkData: Dữ liệu liên kết chi tiết mỗi công đoạn (chi tiết kỹ thuật, bản vẽ, tham số). Hỗ trợ ProductionStepLinkDataRole để định nghĩa vai trò dữ liệu (kiểm tra, gia công, vân vân).
6. Gán phân công sản xuất (ProductionAssignment): Với mỗi cặp (ProductionOrder, ProductionStep, DepartmentId), tạo assignment ghi AssignmentQuantity, AssignmentWorkload, AssignmentHours, StartDate, EndDate, RateInPercent (mức độ hoàn thành 0-100%). Mỗi assignment liên kết ProductionStepLinkDataId để theo dõi kỹ thuật chi tiết.
7. Tạo ProductionOrderMaterials: Tính toán vật tư cần thiết cho mỗi ProductionOrder, ghi ProductId (vật tư), Quantity, ConversionRate, StepId, DepartmentId, ParentId (tạo cấu trúc BOM). Tự động tạo yêu cầu nhập kho với InventoryRequirementStatusId (liên kết với Stock module).
8. Gia công ngoài (nếu cần): Nhận diện công đoạn hoặc chi tiết sản phẩm cần gia công. Tạo OutsourcePartRequest từ ProductionOrderDetail, ghi OutsourcePartRequestCode, Status, tìm hoặc tạo PurchaseOrder (liên kết với Purchase module). Tạo OutsourceStepRequest từ ProductionOrder với OutsourceStepRequestData chứa các ProductionStepId cần gia công.
9. Cập nhật ProductionAssignmentDetail: Ghi nhận chi tiết thực hiện công đoạn tại bộ phận (lao động, thiết bị), liên kết ProductionAssignmentDetailLinkData với ProductionStepLinkDataId để theo dõi kỹ thuật thực tế.
10. Theo dõi tiêu hao vật tư (ProductionConsumMaterial): Khi công đoạn thực hiện, ghi vật tư tiêu hao thực tế (từ ProductionStep, ProductionAssignment), tính toán chi phí tiêu hao để so sánh dự toán.
11. Bàn giao giữa công đoạn (ProductionHandover): Khi công đoạn 1 hoàn thành, tạo ProductionHandover từ FromProductionStep (bộ phận phát hàng) đến ToProductionStep (bộ phận nhận), ghi HandoverQuantity, HandoverDatetime, Status=Pending. Liên kết InventoryDetailId (tự động tạo phiếu xuất/nhập kho trong Stock module). HandoverQuantity phải ≤ AssignmentQuantity.
12. Xác nhận bàn giao: Bộ phận tiếp nhận kiểm tra và chấp nhận handover (Status=Accepted), tạo ProductionHandoverReceipt (biên bản bàn giao). Cập nhật tự động vị trí vật tư trong Kho thông qua InventoryDetail.
13. Ghi lịch sử sản xuất (ProductionHistory): Mỗi lần bàn giao được chấp nhận, tạo ProductionHistory ghi FromProductionStep, ToProductionStep, ProductionHandoverReceiptId, theo dõi lịch sử di chuyển vật tư qua các công đoạn.
14. Cập nhật tiến độ Assignment: Theo dõi tiến độ công đoạn bằng RateInPercent (0-100%), AssignedProgressStatus (Pending/InProgress/Complete). Khi hoàn thành, AssignedProgressStatus=Complete, IsManualFinish có thể được đặt = true.
15. Hoàn thành ProductionOrder: Khi tất cả công đoạn xong (ProductionStep đều có status=Complete) và vật tư đã bàn giao về cuối (IsEndStep=true), cập nhật IsFinished=true, EstimateFinishDate → MaxInvOrHandoverDate (kiểm tra cân đối với Kho), MaxAccountingHandoverDate (kiểm tra với Kế toán).
16. Đồng bộ với các mô-đun khác: Cập nhật Sales (hoàn thành đơn hàng), Stock (tự động tạo phiếu nhập cuối sản xuất), Accountancy (ghi nhận chi phí sản xuất dựa MinAccountingHandoverDate/MaxAccountingHandoverDate). Kết thúc vòng đời khi tất cả điều kiện: đã xuất kho, đã hạch toán, Accountancy confirm = true.
100%
erDiagram
MonthPlan["MonthPlan (Kế hoạch tháng)"] {
}
OutsourcePartRequest["OutsourcePartRequest (Yêu cầu gia công chi tiết)"] {
}
OutsourcePartRequestDetail["OutsourcePartRequestDetail (Chi tiết yêu cầu gia công)"] {
}
OutsourceStepRequest["OutsourceStepRequest (Yêu cầu gia công công đoạn)"] {
}
OutsourceStepRequestData["OutsourceStepRequestData (Dữ liệu gia công công đoạn)"] {
}
ProductionAssignment["ProductionAssignment (Phân công sản xuất)"] {
}
ProductionAssignmentDetail["ProductionAssignmentDetail (Chi tiết phân công sản xuất)"] {
}
ProductionAssignmentDetailLinkData["ProductionAssignmentDetailLinkData (Liên kết chi tiết phân công)"] {
}
ProductionConsumMaterial["ProductionConsumMaterial (Vật tư tiêu hao)"] {
}
ProductionHandover["ProductionHandover (Bàn giao sản xuất)"] {
}
ProductionHandoverReceipt["ProductionHandoverReceipt (Biên bản bàn giao)"] {
}
ProductionHistory["ProductionHistory (Lịch sử sản xuất)"] {
}
ProductionOrder["ProductionOrder (Lệnh sản xuất)"] {
}
ProductionOrderDetail["ProductionOrderDetail (Chi tiết lệnh sản xuất)"] {
}
ProductionOrderMaterials["ProductionOrderMaterials (Vật tư lệnh sản xuất)"] {
}
ProductionStep["ProductionStep (Công đoạn sản xuất)"] {
}
ProductionStepLinkData["ProductionStepLinkData (Dữ liệu liên kết công đoạn)"] {
}
ProductionStepLinkDataRole["ProductionStepLinkDataRole (Vai trò dữ liệu liên kết)"] {
}
Step["Step (Bước quy trình)"] {
}
StepGroup["StepGroup (Nhóm bước quy trình)"] {
}
WeekPlan["WeekPlan (Kế hoạch tuần)"] {
}
MonthPlan ||--o{ ProductionOrder : "MonthPlanId"
WeekPlan ||--o{ ProductionOrder : "FromWeekPlanId"
WeekPlan ||--o{ ProductionOrder : "ToWeekPlanId"
ProductionOrder ||--o{ ProductionOrderDetail : "ProductionOrderId"
ProductionOrderDetail ||--o{ OutsourcePartRequest : "ProductionOrderDetailId"
OutsourcePartRequest ||--o{ OutsourcePartRequestDetail : "OutsourcePartRequestId"
OutsourcePartRequestDetail ||--o{ ProductionStepLinkData : "OutsourceRequestDetailId"
ProductionOrder ||--o{ OutsourceStepRequest : "ProductionOrderId"
OutsourceStepRequest ||--o{ OutsourceStepRequestData : "OutsourceStepRequestId"
ProductionStep ||--o{ OutsourceStepRequestData : "ProductionStepId"
ProductionStepLinkData ||--o{ OutsourceStepRequestData : "ProductionStepLinkDataId"
OutsourceStepRequest ||--o{ ProductionStep : "OutsourceStepRequestId"
Step ||--o{ ProductionStep : "StepId"
StepGroup ||--o{ Step : "StepGroupId"
ProductionOrder ||--o{ ProductionAssignment : "ProductionOrderId"
ProductionStep ||--o{ ProductionAssignment : "ProductionStepId"
ProductionStepLinkData ||--o{ ProductionAssignment : "ProductionStepLinkDataId"
ProductionAssignment ||--o{ ProductionAssignmentDetail : "FK*"
ProductionAssignmentDetail ||--o{ ProductionAssignmentDetailLinkData : "FK*"
ProductionStepLinkData ||--o{ ProductionAssignmentDetailLinkData : "ProductionStepLinkDataId"
ProductionStep ||--o{ ProductionStepLinkDataRole : "ProductionStepId"
ProductionStepLinkData ||--o{ ProductionStepLinkDataRole : "ProductionStepLinkDataId"
ProductionOrder ||--o{ ProductionHandover : "ProductionOrderId"
ProductionStep ||--o{ ProductionHandover : "FromProductionStepId"
ProductionStep ||--o{ ProductionHandover : "ToProductionStepId"
ProductionHandoverReceipt ||--o{ ProductionHandover : "ProductionHandoverReceiptId"
ProductionHandoverReceipt ||--o{ ProductionHistory : "ProductionHandoverReceiptId"
ProductionStep ||--o{ ProductionHistory : "ProductionStepId"
ProductionStep ||--o{ ProductionConsumMaterial : "ProductionStepId"
ProductionAssignment ||--o{ ProductionConsumMaterial : "FK*"
ProductionOrder ||--o{ ProductionOrderMaterials : "ProductionOrderId"
ProductionStepLinkData ||--o{ ProductionOrderMaterials : "ProductionStepLinkDataId"
ProductionOrderMaterials ||--o{ ProductionOrderMaterials : "ParentId"
03. PurchaseOrder (PurchaseOrderDBContext)
Mua hàng (Procurement) + Bán hàng/Voucher (Ordering) — 37 entity, 33 quan hệ domain · ~89 entity tổng trong DB
📋 Flow chi tiết (16 bước)
Module PurchaseOrder quản lý toàn bộ quy trình mua hàng từ nhà cung cấp, bao gồm tạo yêu cầu mua, nhập giá từ nhà cung cấp, phân công đặt hàng cho nhân viên, và cuối cùng phát hành đơn đặt hàng chính thức. Dữ liệu chuyển động qua các tầng: từ PurchasingRequest (nhu cầu) → PurchasingSuggest (tổng hợp nhà cung cấp) → PoAssignment (phân công người mua) → PoProviderPricing (giá nhà cung cấp) → PurchaseOrder (đơn hàng chính thức), với các bảng phụ theo dõi vật liệu, chi phí vượt quá, và tình trạng giao hàng.
Bước 1: Tạo PurchasingRequest (yêu cầu mua hàng) - Hệ thống hoặc người dùng khởi tạo PurchasingRequest với PurchasingRequestCode, Date, NeedDate và PurchasingRequestTypeId (loại: Normal/OrderMaterial/MaterialCalc/ProductionOrder). Nếu từ MaterialCalc, trỏ đến MaterialCalcId; nếu từ SaleOrder, trỏ đến OrderDetailId; nếu từ ProductionOrder, trỏ đến ProductionOrderId. Chi tiết sản phẩm được lưu trong PurchasingRequestDetail.
Bước 2: Tạo PurchasingSuggestDetail từ PurchasingRequestDetail - Khi người dùng chọn tạo gợi ý mua hàng, hệ thống tạo PurchasingSuggest mới với PurchasingSuggestCode, Date, CurrencyId, ExchangeRate, và TotalMoney (được tính từ chi tiết). Các dòng chi tiết được tạo trong PurchasingSuggestDetail, tham chiếu trở lại PurchasingRequestDetail qua PurchasingRequestDetailId.
Bước 3: Thêm PurchasingSuggestDetailSubCalculation cho các chi tiết phức tạp - Nếu sản phẩm yêu cầu tính toán con (e.g., sản phẩm cắt, hỗn hợp), hệ thống tạo PurchasingSuggestDetailSubCalculation cho mỗi chi tiết, lưu dữ liệu chi tiết của phép tính con như hệ số chuyển đổi, giá chi tiết.
Bước 4: Tạo PoAssignment (phân công đặt hàng) từ PurchasingSuggest - Người quản lý tạo PoAssignment với PoAssignmentCode, AssigneeUserId (người mua được giao), PoAssignmentStatusId, và tham chiếu PurchasingSuggestId. Các dòng chi tiết từ PurchasingSuggest được copy vào PoAssignmentDetail, sở dĩ cần PurchasingSuggestDetailId để link ngược.
Bước 5: Người mua xác nhận PoAssignment - Người được giao (AssigneeUserId) xem danh sách PoAssignment, kiểm tra các sản phẩm cần mua, đặt IsConfirmed = true hoặc false để xác nhận hoặc từ chối phân công.
Bước 6: Tạo PoProviderPricing (báo giá từ nhà cung cấp) - Khi nhà cung cấp gửi báo giá hoặc hệ thống nhận dữ liệu, tạo PoProviderPricing với PoProviderPricingCode, CustomerId (ID nhà cung cấp), Date, DeliveryDate, CurrencyId, ExchangeRate, TotalMoney. Chi tiết giá được lưu trong PoProviderPricingDetail (tham chiếu ProductId, giá chuyển đổi, etc.).
Bước 7: Kiểm tra PoProviderPricing (IsChecked) - Người kiểm tra (CheckedByUserId) xem xét báo giá, kiểm tra giá vs thị trường, đặt IsChecked = true, ghi lại CheckedDatetimeUtc. Nếu báo giá không hợp lệ, có thể reject hoặc yêu cầu sửa.
Bước 8: Phê duyệt PoProviderPricing (IsApproved) - Cấp quản lý cao hơn (CensorByUserId) xem xét báo giá đã kiểm tra, đặt IsApproved = true và CensorDatetimeUtc. Báo giá được duyệt là cơ sở để tạo PurchaseOrder.
Bước 9: Tạo PurchaseOrder từ báo giá đã duyệt - Khi PoProviderPricing được phê duyệt, hệ thống tạo PurchaseOrder mới với PurchaseOrderCode (sinh từ CustomGenCode), CustomerId (nhà cung cấp từ PoProviderPricing), Date, DeliveryDate, DeliveryDestination, CurrencyId, ExchangeRate, TotalMoney. Chi tiết PurchaseOrder được tạo từ PoProviderPricingDetail, lưu vào PurchaseOrderDetail và trỏ RefPoAssignmentId, RefPurchasingSuggestId để khoanh vùng dữ liệu.
Bước 10: Thêm PurchaseOrderDetailSubCalculation - Nếu chi tiết PurchaseOrder có sub-calculation (e.g., từ property calc hoặc material calc), hệ thống tạo PurchaseOrderDetailSubCalculation với dữ liệu tính toán chi tiết (phí, chiều dài, độ rộng, etc.).
Bước 11: Liên kết với PropertyCalc hoặc MaterialCalc nếu có - Nếu PurchaseOrder được tạo từ PropertyCalc (cắt dặt) hoặc MaterialCalc (tính vật liệu), đặt PropertyCalcId hoặc tham chiếu đến MaterialCalc thông qua detail. Dữ liệu dimension/excess được lưu trong PurchaseOrderExcess nếu có chi phí vượt quá.
Bước 12: Ghi nhận vật liệu trong PurchaseOrderMaterials - Nếu PurchaseOrder cung cấp vật liệu (e.g., từ ngoài, giao kèm), ghi nhận trong PurchaseOrderMaterials với ProductId, Quantity để theo dõi tích hợp với Stock module.
Bước 13: Gửi PurchaseOrder để kiểm tra (SendToCensor) - Người tạo gửi PurchaseOrder cho người kiểm tra, đặt PoProcessStatusId = InProgress, CensorByUserId, CensorDatetimeUtc. Người kiểm tra xem toàn bộ chi tiết (PurchaseOrderDetail), tính toán (PurchaseOrderDetailSubCalculation).
Bước 14: Kiểm tra PurchaseOrder - Người kiểm tra (CheckedByUserId) xác minh PurchaseOrder, đặt IsChecked = true, CheckedDatetimeUtc. Nếu có lỗi, có thể gọi RejectCheck để quay lại Draft.
Bước 15: Phê duyệt PurchaseOrder cuối cùng - Cấp quản lý (CensorByUserId) phê duyệt PurchaseOrder, đặt IsApproved = true, PoProcessStatusId = Complete. Lúc này PurchaseOrder được coi là chính thức, kích hoạt sự kiện gửi email SendMailNotify, và chuẩn bị dữ liệu để Stock module tạo InventoryInput khi hàng đến.
Bước 16: Theo dõi giao hàng trong PurchaseOrderTracked - Khi hàng được giao từng phần hoặc toàn bộ, hệ thống ghi nhận trong PurchaseOrderTracked (lưu ReceivedQuantity, ReceivedDate, delivery status) để quản lý tình trạng giao hàng. Đồng thời, nếu là outsource part, PurchaseOrderOutsourceMapping liên kết với OutsourcePart request từ Manufacturing module để cập nhật tình trạng sản xuất ngoài.
100%
erDiagram
MaterialCalc["MaterialCalc (Tính toán vật liệu)"] {
}
MaterialCalcConsumptionGroup["MaterialCalcConsumptionGroup (Nhóm tiêu thụ vật liệu)"] {
}
MaterialCalcProduct["MaterialCalcProduct (Sản phẩm tính vật liệu)"] {
}
MaterialCalcProductDetail["MaterialCalcProductDetail (Chi tiết sản phẩm tính vật liệu)"] {
}
MaterialCalcProductOrder["MaterialCalcProductOrder (Đơn hàng sản phẩm tính toán)"] {
}
MaterialCalcSummary["MaterialCalcSummary (Tóm tắt vật liệu tính toán)"] {
}
MaterialCalcSummarySubCalculation["MaterialCalcSummarySubCalculation (Phép tính con tóm tắt vật liệu)"] {
}
PoAssignment["PoAssignment (Phân công đặt hàng)"] {
}
PoAssignmentDetail["PoAssignmentDetail (Chi tiết phân công đặt hàng)"] {
}
PoProviderPricing["PoProviderPricing (Giá nhà cung cấp)"] {
}
PoProviderPricingDetail["PoProviderPricingDetail (Chi tiết giá nhà cung cấp)"] {
}
PropertyCalc["PropertyCalc (Tính toán thuộc tính)"] {
}
PropertyCalcProduct["PropertyCalcProduct (Sản phẩm tính thuộc tính)"] {
}
PropertyCalcProductDetail["PropertyCalcProductDetail (Chi tiết sản phẩm tính thuộc tính)"] {
}
PropertyCalcProductOrder["PropertyCalcProductOrder (Đơn hàng sản phẩm tính thuộc tính)"] {
}
PropertyCalcProperty["PropertyCalcProperty (Thuộc tính tính toán)"] {
}
PropertyCalcSummary["PropertyCalcSummary (Tóm tắt thuộc tính tính toán)"] {
}
ProviderProductInfo["ProviderProductInfo (Thông tin sản phẩm nhà cung cấp)"] {
}
PurchaseOrder["PurchaseOrder (Đơn đặt hàng mua)"] {
}
PurchaseOrderDetail["PurchaseOrderDetail (Chi tiết đơn đặt hàng)"] {
}
PurchaseOrderDetailSubCalculation["PurchaseOrderDetailSubCalculation (Phép tính con chi tiết đơn hàng)"] {
}
PurchaseOrderExcess["PurchaseOrderExcess (Chi phí vượt quá đơn hàng)"] {
}
PurchaseOrderMaterials["PurchaseOrderMaterials (Vật liệu đơn đặt hàng)"] {
}
PurchaseOrderTracked["PurchaseOrderTracked (Theo dõi đơn đặt hàng)"] {
}
PurchasingRequest["PurchasingRequest (Yêu cầu mua hàng)"] {
}
PurchasingRequestDetail["PurchasingRequestDetail (Chi tiết yêu cầu mua)"] {
}
PurchasingSuggest["PurchasingSuggest (Gợi ý mua hàng)"] {
}
PurchasingSuggestDetail["PurchasingSuggestDetail (Chi tiết gợi ý mua hàng)"] {
}
PurchasingSuggestDetailSubCalculation["PurchasingSuggestDetailSubCalculation (Phép tính con chi tiết gợi ý)"] {
}
VoucherArea["VoucherArea (Khu vực chứng từ)"] {
}
VoucherAreaField["VoucherAreaField (Trường khu vực chứng từ)"] {
}
VoucherBill["VoucherBill (Hóa đơn chứng từ)"] {
}
VoucherField["VoucherField (Trường chứng từ)"] {
}
VoucherType["VoucherType (Loại chứng từ)"] {
}
VoucherTypeGroup["VoucherTypeGroup (Nhóm loại chứng từ)"] {
}
VoucherTypeView["VoucherTypeView (Lược đồ xem chứng từ)"] {
}
VoucherTypeViewField["VoucherTypeViewField (Trường lược đồ xem chứng từ)"] {
}
PurchasingRequest ||--o{ PurchasingRequestDetail : "PurchasingRequestId"
MaterialCalc ||--o{ PurchasingRequest : "MaterialCalcId"
PropertyCalc ||--o{ PurchasingRequest : "PropertyCalcId"
MaterialCalc ||--o{ MaterialCalcConsumptionGroup : "MaterialCalcId"
MaterialCalc ||--o{ MaterialCalcProduct : "MaterialCalcId"
MaterialCalcProduct ||--o{ MaterialCalcProductDetail : "MaterialCalcProductId"
MaterialCalcProduct ||--o{ MaterialCalcProductOrder : "MaterialCalcProductId"
MaterialCalc ||--o{ MaterialCalcSummary : "MaterialCalcId"
MaterialCalcSummary ||--o{ MaterialCalcSummarySubCalculation : "MaterialCalcSummaryId"
PropertyCalc ||--o{ PropertyCalcProduct : "PropertyCalcId"
PropertyCalcProduct ||--o{ PropertyCalcProductDetail : "PropertyCalcProductId"
PropertyCalcProduct ||--o{ PropertyCalcProductOrder : "PropertyCalcProductId"
PropertyCalc ||--o{ PropertyCalcProperty : "PropertyCalcId"
PropertyCalc ||--o{ PropertyCalcSummary : "PropertyCalcId"
PurchasingSuggest ||--o{ PoAssignment : "PurchasingSuggestId"
PoAssignment ||--o{ PoAssignmentDetail : "PoAssignmentId"
PurchasingSuggestDetail ||--o{ PoAssignmentDetail : "PurchasingSuggestDetailId"
PoProviderPricing ||--o{ PoProviderPricingDetail : "PoProviderPricingId"
PurchasingSuggest ||--o{ PurchasingSuggestDetail : "PurchasingSuggestId"
PurchasingSuggestDetail ||--o{ PurchasingSuggestDetailSubCalculation : "PurchasingSuggestDetailId"
PurchaseOrder ||--o{ PurchaseOrderDetail : "PurchaseOrderId"
PurchaseOrderDetail ||--o{ PurchaseOrderDetailSubCalculation : "PurchaseOrderDetailId"
PurchaseOrder ||--o{ PurchaseOrderExcess : "PurchaseOrderId"
PurchaseOrder ||--o{ PurchaseOrderMaterials : "PurchaseOrderId"
PurchaseOrder ||--o{ PurchaseOrderTracked : "PurchaseOrderId"
VoucherTypeGroup ||--o{ VoucherType : "VoucherTypeGroupId"
VoucherType ||--o{ VoucherArea : "VoucherTypeId"
VoucherArea ||--o{ VoucherAreaField : "VoucherAreaId"
VoucherField ||--o{ VoucherAreaField : "VoucherFieldId"
VoucherType ||--o{ VoucherAreaField : "VoucherTypeId"
VoucherType ||--o{ VoucherBill : "VoucherTypeId"
VoucherType ||--o{ VoucherTypeView : "VoucherTypeId"
VoucherTypeView ||--o{ VoucherTypeViewField : "VoucherTypeViewId"
04. Accountancy (AccountancyDBContext)
Kế toán – Phiếu nhập liệu động – Tính giá/phí – P&L – GL – Phân bổ hóa đơn — 16 entity, 10 quan hệ domain · ~63 entity tổng trong DB
📋 Flow chi tiết (18 bước)
Module Accountancy quản lý toàn bộ quy trình phân loại, ghi chép, tính toán chi phí và lợi nhuận trong kinh doanh. Dòng chảy chính bao gồm: (1) tạo và xử lý phiếu nhập liệu (InputBill) theo loại cấu hình linh hoạt với các hooks xử lý; (2) tính toán chi phí sản phẩm (CalcFeeProduct) từ chi phí vật liệu, nhân công, chi phí chung; (3) báo cáo lợi nhuận thua lỗ (CalcProfitAndLost); (4) phân bổ hóa đơn thủ công với duyệt phê chuẩn (InvoiceManualAllocate); (5) quyết toán hóa đơn thanh toán (InvoiceClearing); (6) phân bổ hóa đơn tự động qua JobRequest bất đồng bộ; (7) tạo bút toán kế toán (InputValueRowBt) với tài khoản nợ-có và tiền tệ; (8) cấu hình GL entry (TransactionEntryConfig) và công thức tính toán (ProgramingFunction) tùy chỉnh.
Bước 1: Quản trị viên tạo InputTypeGroup để phân loại các loại phiếu nhập, sau đó tạo InputType với code, tiêu đề, hooks xử lý (PreLoadAction, AfterLoadAction, BeforeSubmitAction, BeforeSaveAction, AfterSaveAction) và cấu hình phân bổ (DataAllowcationInputTypeIds, ResultAllowcationInputTypeId, CalcResultAllowcationSqlQuery).
Bước 2: Quản trị viên cấu hình InputArea và InputAreaField theo InputType để định nghĩa bố cục form nhập liệu động, xác định các trường dữ liệu (InputField) sẽ hiển thị.
Bước 3: Kế toán tạo InputBill mới bằng cách chọn InputType, hệ thống tự động thực thi PreLoadAction hook để tải dữ liệu phụ thuộc.
Bước 4: Kế toán nhập dữ liệu vào các InputArea theo cấu hình, hệ thống thực thi AfterLoadAction hook sau khi tải form.
Bước 5: Trước khi lưu, hệ thống thực thi BeforeSubmitAction hook để xác thực dữ liệu, kiểm tra các InputBill liên quan (CheckExisted/CheckDeleted checks).
Bước 6: Kế toán submit InputBill, hệ thống thực thi BeforeSaveAction hook, sau đó lưu InputBill với version tracking (LatestBillVersion) và tạo bản ghi InfoRowFId.
Bước 7: Sau khi lưu, hệ thống tạo InputValueRowBt (dòng bút toán) theo TransactionEntryConfig, ghi nhận tài khoản nợ (TkNo), tài khoản có (TkCo), số tiền VND (VndNo/VndCo), ngoại tệ (NgoaiTeNo/NgoaiTeCo), tỷ giá, và thông tin hóa đơn gốc (InvoiceId, InvoiceDate, SeriHd).
Bước 8: Hệ thống thực thi AfterSaveAction hook để xử lý sau khi ghi sổ, có thể kích hoạt các tác vụ bổ sung.
Bước 9: Nếu InputType có cấu hình DataAllowcationInputTypeIds, hệ thống tạo JobRequest với JobName, SubsidiaryId, UnitType, UnitValue để chạy bất đồng bộ phân bổ hóa đơn tự động.
Bước 10: JobRequest được xử lý bất đồng bộ, JobRequestEvent theo dõi trạng thái (Queued → Running → Completed/Failed), hệ thống thực thi CalcResultAllowcationSqlQuery để tạo InvoiceAutoAllocation records với OriginBillInvoiceUnitId, PayBillInvoiceUnitId, Amount.
Bước 11: Hàng tháng, kế toán chạy CalcFeeProduct để tính toán chi phí sản phẩm: ghi nhận FromDate, ToDate, Year, Month, ProductId, FactoryDepartmentId, tính ProducingSumTotal từ chi phí vật liệu (SumTotalMaterial), nhân công (SumTotalLabor), chi phí chung (SumTotalGeneral), áp dụng ProducingProductPriceFeeFactor.
Bước 12: Sau khi tính CalcFeeProduct, hệ thống gọi UpdateBillPrices để đồng bộ các giá phí tính được vào các InputBill tương ứng, đánh dấu IsUpdatedToInputBills=true, lưu UpdatedToInputBillsDateUtc.
Bước 13: Kế toán tạo CalcProfitAndLost báo cáo với FromDate, ToDate, ProductId, Filter, Options để tính doanh thu từ đơn hàng (OrderCode), trừ đi chi phí vật liệu (CalcFeeProductDoneDirect), chi phí nhân công (CalcFeeProductProducingDirect), chi phí chung (CalcFeeProductProducingInDirect) theo sản phẩm.
Bước 14: Kế toán thủ công tạo InvoiceManualAllocate để phân bổ hóa đơn thanh toán, nhập Code, Date, Description, liên kết InputBillFId, hệ thống khởi tạo InvoiceManualAllocateStatus=Pending.
Bước 15: Kế toán tạo InvoiceManualAllocateDetail cho mỗi hóa đơn, ghi nhận tài khoản nợ (DebtAccountNumber), tài khoản có (CreditAccountNumber), số tiền theo TransactionEntryConfig (IsByCustomer, IsByDepartment, IsByProduct, IsByProductionOrder).
Bước 16: Kiểm soát viên kiểm tra InvoiceManualAllocate, đánh dấu IsChecked=true, CheckedByUserId, CheckedDatetimeUtc, trạng thái chuyển Pending → Checked. Giám đốc phê chuẩn cuối cùng, đánh dấu IsApproved=true, CensorByUserId, CensorDatetimeUtc, trạng thái Checked → Approved, các bút toán GL Entry được khóa.
Bước 17: Kế toán tạo InvoiceClearing để quyết toán hóa đơn thanh toán, nhập InvoiceClearingCode, Date, DiffInputBillFId (chênh lệch), ClearingInputBillFId (thanh toán), khởi tạo InvoiceManualAllocateStatus=Pending.
Bước 18: Kế toán tạo InvoiceClearingDetail ghi nhận từng đơn hóa đơn được quyết toán, khớp số tiền thanh toán với hóa đơn gốc, chuyển trạng thái Pending → Matched, sau đó kiểm soát viên xác nhận Matched → Cleared, hệ thống tạo GL entry: Nợ Tài khoản Phải trả (DR Payable) / Có Tiền mặt hoặc Ngân hàng (CR Cash), đồng bộ với Stock hoặc Master Data để cập nhật tình trạng thanh toán.
100%
erDiagram
CalcFeeProduct["CalcFeeProduct (Tính chi phí sản phẩm)"] {
}
CalcFeeProductDoneRow["CalcFeeProductDoneRow (Chi tiết chi phí sản phẩm)"] {
}
CalcProfitAndLost["CalcProfitAndLost (Tính lợi nhuận thua lỗ)"] {
}
InputArea["InputArea (Khu vực phiếu nhập)"] {
}
InputAreaField["InputAreaField (Trường khu vực phiếu)"] {
}
InputBill["InputBill (Phiếu nhập liệu)"] {
}
InputField["InputField (Trường dữ liệu cơ bản)"] {
}
InputType["InputType (Loại phiếu nhập)"] {
}
InputTypeGroup["InputTypeGroup (Nhóm loại phiếu)"] {
}
InputValueRowBt["InputValueRowBt (Dòng giá trị bút toán)"] {
}
InvoiceAutoAllocation["InvoiceAutoAllocation (Phân bổ hóa đơn tự động)"] {
}
InvoiceClearing["InvoiceClearing (Quyết toán hóa đơn)"] {
}
InvoiceClearingDetail["InvoiceClearingDetail (Chi tiết quyết toán)"] {
}
InvoiceManualAllocate["InvoiceManualAllocate (Phân bổ hóa đơn thủ công)"] {
}
InvoiceManualInfo["InvoiceManualInfo (Thông tin hóa đơn thủ công)"] {
}
TransactionEntryConfig["TransactionEntryConfig (Cấu hình bút toán)"] {
}
InputTypeGroup ||--o{ InputType : "InputTypeGroupId"
InputType ||--o{ InputArea : "InputTypeId"
InputType ||--o{ InputAreaField : "InputTypeId"
InputType ||--o{ InputBill : "InputTypeId"
InputType ||--o{ InputType : "ResultAllowcationInputTypeId"
InputArea ||--o{ InputAreaField : "InputAreaId"
InputField ||--o{ InputAreaField : "InputFieldId"
InputBill ||--o{ InputBill : "ParentInputBillFId"
CalcFeeProduct ||--o{ CalcFeeProductDoneRow : "CalcFeeProductId"
InvoiceClearing ||--o{ InvoiceClearingDetail : "InvoiceClearingId"
05. QcManagement (QcManagementDBContext)
Quản lý chất lượng – Phiếu kiểm – Tiêu chí/Tiêu chuẩn – Lấy mẫu AQL – Lỗi – CAPA — 24 entity, 31 quan hệ domain · ~64 entity tổng trong DB
📋 Flow chi tiết (16 bước)
Module QcManagement quản lý quy trình kiểm tra chất lượng (QC) cho hàng nhập hoặc sản xuất. Dữ liệu trải qua vòng đời từ tạo phiếu kiểm tra, khởi tạo tiêu chí theo sản phẩm, thực hiện kiểm tra từng tiêu chí, tính toán kết quả, đến xử lý sản phẩm không đạt thông qua Corrective Action Plan (CAP).
1. Người dùng tạo InspectionSheet (phiếu kiểm tra) với thông tin cơ bản: mã phiếu, ngày kiểm tra, loại kiểm tra QcFlowTypeId (PO nhập/công đoạn/thành phẩm/xuất bán), phương pháp lấy mẫu SamplingMethodId, kho liên quan (PurchaseOrderId hoặc ProductionOrderId hoặc StepId), bộ phận kiểm tra DepartmentId, bộ phận nhà máy FactoryDepartmentId, mức AQL (AqlLevelId), trạng thái QcProgressStateId=NotStarted.
2. Dữ liệu InspectionSheet tự động nhân bản từ tài liệu nguồn (PO, lệnh sản xuất, hoặc phản hồi khách hàng): tạo InspectionSheetItem cho mỗi mặt hàng với ProductId (nullable - khi không trong danh mục thì lưu ProductName), số lượng nguyên liệu PrimaryQuantity & PuQuantity, xác định đơn vị QC IsPrimaryUnitQc, tính lô QC QcLotQuantity, trạng thái ban đầu QcProgressStateId=NotStarted, IsPassed=false, PassedQuantity=0, FailedQuantity=0.
3. Truy vấn lịch sử tiêu chuẩn áp dụng: hệ thống tìm kiếm QualityStandard theo bộ lọc (ProductId/ProductCategoryId/ProductLineId/StepId + QcFlowTypeId) thông qua QualityStandardQcFlowType, QualityStandardAssignment. Lấy SamplingStandard được gán cho QualityStandard đó (đối với mỗi tiêu chí: áp dụng AQL rate + InspectionLevel).
4. Người dùng click khởi tạo QC cho item: gọi initQC → hệ thống xoá toàn bộ dữ liệu cũ (InspectionSheetItemCriteria + InspectionSheetItemTestRun + InspectionSheetItemCriteriaTestRun + InspectionSheetItemCriteriaAql + InspectionSheetItemCriteriaTestRunError), reset QcState (PassedCriteriasCount=0, FailedCriteriasCount=0, PassedQuantity=0, FailedQuantity=0, IsPassed=false, InspectionQcDecisionId=null, AcceptedQuantity=null).
5. Khởi tạo tiêu chí: tạo InspectionSheetItemCriteria cho mỗi QualityCriteria trong QualityStandard được chọn, gán InspectionLevelId từ QualityStandardCriteria, tính MaxSamplingQuantity dựa trên bảng SamplingStandardSampleSize (lot size × inspection level), gán NominalValue từ biểu thức QualityCriteria.NominalValueExpression.
6. Thiết lập ngưỡng AQL: tạo InspectionSheetItemCriteriaAql (một bản ghi cho mỗi mức lỗi: Critical/Major/Minor) với Aqlid, SamplingQuantity, Accept (ngưỡng chấp nhận lỗi), Reject (ngưỡng từ chối lỗi), IsPassed=false ban đầu.
7. Thực hiện kiểm tra lần 1 (test run): người dùng nhập dữ liệu kiểm tra một mẫu → tạo InspectionSheetItemTestRun (coordinator, lưu Quantity tổng lượng kiểm tra, PackageNo, PalletQuantity) + tạo InspectionSheetItemCriteriaTestRun cho mỗi tiêu chí (ghi Quantity kiểm tra, PassedQuantity, FailedQuantity).
8. Ghi nhận lỗi chi tiết: người dùng nhập danh sách lỗi → tạo InspectionSheetItemCriteriaTestRunError (gán InspectionErrorId từ danh sách mã lỗi được liên kết với tiêu chí qua QualityCriteria), mỗi lỗi được phân loại theo ErrorLevelTypeId (Critical/Major/Minor).
9. Tự động tính toán kết quả tiêu chí: sau mỗi lần thêm/sửa/xoá test run, hệ thống gọi RecalculateAllStatuses() → cộng dồn PassedQuantity & FailedQuantity từ tất cả InspectionSheetItemCriteriaTestRun, so sánh với ngưỡng InspectionSheetItemCriteriaAql (FailedQuantity > Reject → IsPassed=false, ≤ Accept → IsPassed=true).
10. Aggregation tại mức sản phẩm: từ tất cả InspectionSheetItemCriteria (các tiêu chí đã bắt đầu kiểm tra), tính PassedQuantity = Min(tiêu chí.PassedQuantity) [bottleneck: mẫu phải đạt TẤT CẢ], FailedQuantity = Max(tiêu chí.FailedQuantity) [worst case], cập nhật PassedCriteriasCount (số tiêu chí IsPassed=true), FailedCriteriasCount (số tiêu chí IsPassed=false), IsPassed = (FailedCriteriasCount == 0).
11. Aggregation tại mức phiếu: từ tất cả InspectionSheetItem, tính QcProgressState (NotStarted: tất cả chưa bắt đầu, InProgress: có tiêu chí đang/hoàn thành, Completed: tất cả hoàn thành), xác định InspectionResultId tổng hợp (Pass: tất cả item IsPassed=true, Fail: có item IsPassed=false, Conditional: có item ManuallyEnded).
12. Xử lý update thủ công: người dùng có thể ManualUpdateStatus để override kết quả item (do yếu tố ngoài tiêu chí) → gọi RecalcSheetAfterItemManualUpdate() chỉ tính toán lại QcProgressState mà KHÔNG recalculate từ test run, giữ nguyên quantities đã set thủ công.
13. Xác định hành động tiếp theo: dựa trên InspectionResultId + QcFlowTypeId → tạo InspectionSheetNextAction (PO: chấp nhận nhập kho hoặc từ chối trả vendor; FinishedGoods: tiến hành xuất bán hoặc loại bỏ; ProductionProcess: tiếp tục công đoạn hoặc rework).
14. Tạo Corrective Action Plan (CAP) khi phát hiện lỗi: hệ thống tạo CorrectiveActionPlan từ InspectionSheet (chỉ khi có linked ProductId - unlinked items bị bỏ qua), tự động tập hợp lỗi từ InspectionSheetItemCriteriaTestRunError vào CapErrorItem (gán InspectionErrorId, nhóm theo ProductId + ErrorId).
15. Phân tích nguyên nhân CAP: người dùng điền InitialCause, thực hiện Error Simulation (CapErrorSimulation), Why Analysis (CapCauseWhy), xác định Root Cause (CauseTraceability) và hành động khắc phục (CapDispositionAction).
16. Hoàn thành CAP: người dùng chọn FinalCapDispositionAction (hành động cuối cùng sau khi xác minh), lưu ConclusionNote, cập nhật QcCapSectionStatusId & QaCapSectionStatusId (trạng thái phê duyệt), sau đó phiếu kiểm tra có thể được censor (đánh dấu hoàn thành chính thức và không được phép sửa test run nữa).
Nhân sự – Phòng ban – Lương – Ca làm – Khách hàng – Chi nhánh — 38 entity, 31 quan hệ domain · đã ẩn 2 quan hệ phân vùng/audit · ~120 entity tổng trong DB
📋 Flow chi tiết (16 bước)
Module Organization là hệ thống quản lý nhân sự tập trung, xử lý quản lý phòng ban, lương thưởng, công tác nhân viên, quản lý phép nghỉ, quản lý khách hàng và lịch tổ chức. Dữ liệu sinh ra từ cấu hình tổ chức cơ bản (Subsidiary, Department) chảy qua quá trình cấp phát nhân viên (EmployeeDepartmentMapping), xử lý chấm công thô từ thiết bị (TimeSheetRaw) để tạo bảng công chi tiết (TimeSheet → TimeSheetDetail → TimeSheetAggregate), song song đó quản lý phép nghỉ (Leave, LeaveConfig), cấu hình ca làm việc (ShiftConfiguration, ShiftSchedule), tính toán lương theo kỳ (SalaryPeriod, SalaryGroup, SalaryField), và cuối cùng kết nối với khách hàng (Customer, CustomerCate).
Bước 1: Tạo cơ cấu tổ chức cơ sở - Subsidiary (công ty con) được tạo với ParentSubsidiaryId để hình thành cây cơ cấu công ty; Department được gán vào Subsidiary cụ thể, xây dựng cây phòng ban qua ParentId (với denormalization: PathCodes, PathNames, ParentIdLevel1-10) cho truy vấn nhanh.
Bước 2: Cấp phát nhân viên vào phòng ban - EmployeeDepartmentMapping liên kết UserId tới DepartmentId theo chu kỳ thời gian (EffectiveDate, ExpirationDate) để theo dõi quân số phòng ban (NumberOfPerson, WorkingHoursPerDay ở Department).
Bước 3: Cấu hình chính sách phép và tăng ca - LeaveConfig được tạo với chính sách (MonthRate, MaxAyear, SeniorityMonthsStart) cộng AbsenceTypeSymbol định nghĩa loại vắng mặt; OvertimeConfiguration liên kết ShiftConfiguration để cấu hình tăng ca.
Bước 4: Cấu hình ca làm việc - ShiftConfiguration được tạo định nghĩa loại ca (giờ làm, kiểu ca); ShiftSchedule gán ca cho các phòng ban từ FromDate tới ToDate; ShiftScheduleDetail chỉ định ca cho từng phòng ban cụ thể.
Bước 5: Thu thập dữ liệu chấm công thô từ thiết bị - TimeSheetRaw lưu dữ liệu thô từ VTAS terminal (Date, Time, TerminalId) cho mỗi EmployeeId cùng TimeKeepingMethod.
Bước 6: Xử lý và kiểm chứng chấm công - Dữ liệu từ TimeSheetRaw được xử lý, liên kết với ShiftSchedule để xác định ca làm việc hợp lệ; kiểm tra lỗi muộn/sớm qua MinsLate, MinsEarly; bước này chuẩn bị dữ liệu cho bảng công chi tiết.
Bước 7: Tạo bảng công và chi tiết chấm công - TimeSheet được tạo cho một tháng cụ thể (Month, Year, BeginDate, EndDate); TimeSheetDetail ghi chi tiết từng ngày làm việc; TimeSheetDetailShift ghi chi tiết từng ca làm việc cùng các loại tính toán (Counted).
Bước 8: Tính toán vắng mặt và phép nghỉ - Nhân viên gửi Leave (DateStart, DateEnd, DateStartIsHalf, DateEndIsHalf, TotalDays) với LeaveConfigId tương ứng; Leave được gán AbsenceTypeSymbolId; quy trình phê duyệt: CheckedByUserId (kiểm tra) → CensoredByUserId (phê chuẩn).
Bước 9: Tính tổng hợp công nhân viên - TimeSheetAggregate được tạo gộp dữ liệu từ TimeSheetDetail (CountedWeekday, CountedWeekend, CountedHoliday, CountedWeekdayHour, CountedWeekendHour, CountedHolidayHour) cho mỗi EmployeeId trong TimeSheet.
Bước 10: Ghi nhận vắng mặt và tăng ca vào tổng hợp - TimeSheetAggregateAbsence (liên kết AbsenceTypeSymbolId) và TimeSheetAggregateOvertime được tạo để ghi vào TimeSheetAggregate các ngày vắng mặt, tăng ca từ Leave và ShiftRequestLetter.
Bước 11: Định cấu hình kỳ lương và nhóm lương - SalaryPeriod được tạo cho tháng cụ thể (Month, Year, FromDate, ToDate, SalaryPeriodCensorStatusId) với trạng thái chưa kiểm tra; SalaryGroup được tạo với EmployeeFilter (JSON tiêu chí) để chọn nhân viên tham gia; SalaryField định nghĩa các trường lương (lương cơ bản, phụ cấp, v.v.).
Bước 12: Gắn nhóm lương vào kỳ lương - SalaryPeriodGroup liên kết SalaryPeriodId tới SalaryGroupId và SalaryFieldId để xác định những trường lương áp dụng cho nhóm nào trong kỳ nào; SalaryGroupField chỉ định trường lương nào thuộc nhóm lương nào.
Bước 13: Nhập hóa đơn cộng thêm (khoản cộng/trừ) - SalaryPeriodAdditionBill được tạo với BillCode, Content, Date và SalaryPeriodAdditionTypeId để ghi nhận các khoản phụ cấp, thưởng, tiền phạt cho kỳ lương; SalaryPeriodAdditionBillEmployee liên kết tới nhân viên cụ thể.
Bước 14: Tính toán giá trị lương từng nhân viên - SalaryPeriodAdditionBillEmployeeValue lưu giá trị cộng/trừ cụ thể cho mỗi trường lương (FieldId) của mỗi nhân viên, tính toán dựa trên SalaryPeriodAdditionField và TimeSheetAggregate (công đã làm, giá trị giờ làm).
Bước 15: Kiểm chứng dữ liệu kỳ lương - SalaryPeriod được check (CheckedByUserId, CheckedDatetimeUtc) để kiểm tra tính toàn vẹn: tổng công, tổng lương, kiểm tra nhân viên đã rời khỏi (ExpirationDate) có cần xóa khỏi lương không; SalaryPeriodCensorStatusId chuyển trạng thái.
Bước 16: Phê chuẩn kỳ lương - SalaryPeriod được Censor (CensorByUserId, CensorDatetimeUtc, SalaryPeriodCensorStatusId thành trạng thái phê chuẩn); lúc này dữ liệu lương được khóa và có thể xuất sang Accountancy module để tạo hạch toán, đạo thải hoặc liên kết với Customer để ghi nhận chi phí nhân công cho từng dự án/khách hàng.
100%
erDiagram
AbsenceTypeSymbol["AbsenceTypeSymbol (Ký hiệu loại vắng mặt)"] {
}
BusinessInfo["BusinessInfo (Thông tin kinh doanh)"] {
}
Customer["Customer (Khách hàng)"] {
}
CustomerCate["CustomerCate (Danh mục khách hàng)"] {
}
Department["Department (Phòng ban)"] {
}
DepartmentCapacityBalance["DepartmentCapacityBalance (Cân đối năng lực phòng ban)"] {
}
EmployeeDepartmentMapping["EmployeeDepartmentMapping (Ánh xạ nhân viên phòng ban)"] {
}
HrArea["HrArea (Khu vực hồ sơ)"] {
}
HrAreaField["HrAreaField (Trường khu vực hồ sơ)"] {
}
HrBill["HrBill (Hóa đơn hồ sơ)"] {
}
HrType["HrType (Loại hồ sơ nhân sự)"] {
}
HrTypeGroup["HrTypeGroup (Nhóm loại hồ sơ)"] {
}
HrTypeView["HrTypeView (View hiển thị loại hồ sơ)"] {
}
Leave["Leave (Đơn xin nghỉ phép)"] {
}
LeaveConfig["LeaveConfig (Cấu hình chính sách phép)"] {
}
LeaveConfigRole["LeaveConfigRole (Vai trò cấu hình phép)"] {
}
LeaveConfigValidation["LeaveConfigValidation (Validate cấu hình phép)"] {
}
OvertimeConfiguration["OvertimeConfiguration (Cấu hình tăng ca)"] {
}
OvertimeConfigurationMapping["OvertimeConfigurationMapping (Ánh xạ level tăng ca)"] {
}
OvertimeConfigurationTimeFrame["OvertimeConfigurationTimeFrame (Khung giờ tăng ca)"] {
}
RefUser["RefUser (Tham chiếu người dùng)"] {
}
SalaryField["SalaryField (Trường lương)"] {
}
SalaryGroup["SalaryGroup (Nhóm lương)"] {
}
SalaryGroupField["SalaryGroupField (Trường nhóm lương)"] {
}
SalaryPeriod["SalaryPeriod (Kỳ lương)"] {
}
SalaryPeriodGroup["SalaryPeriodGroup (Kỳ lương nhóm)"] {
}
ShiftConfiguration["ShiftConfiguration (Cấu hình ca làm việc)"] {
}
ShiftRequestLetterAbsence["ShiftRequestLetterAbsence (Chi tiết vắng mặt trong đơn shift)"] {
}
ShiftRequestLetterOvertime["ShiftRequestLetterOvertime (Chi tiết tăng ca trong đơn shift)"] {
}
ShiftSchedule["ShiftSchedule (Lịch ca làm việc)"] {
}
ShiftScheduleConfiguration["ShiftScheduleConfiguration (Cấu hình lịch ca)"] {
}
ShiftScheduleDetail["ShiftScheduleDetail (Chi tiết lịch ca)"] {
}
Subsidiary["Subsidiary (Công ty con)"] {
}
TimeSheet["TimeSheet (Bảng công nhân viên)"] {
}
TimeSheetAggregate["TimeSheetAggregate (Tổng hợp công nhân viên)"] {
}
TimeSheetDepartment["TimeSheetDepartment (Phòng ban trong bảng công)"] {
}
TimeSheetDetail["TimeSheetDetail (Chi tiết bảng công)"] {
}
UserCustomerManager["UserCustomerManager (Quản lý khách hàng của user)"] {
}
HrTypeGroup ||--o{ HrType : "HrTypeGroupId"
Department ||--o{ Department : "ParentId"
Subsidiary ||--o{ Subsidiary : "ParentSubsidiaryId"
OvertimeConfiguration ||--o{ ShiftConfiguration : "OvertimeConfigurationId"
AbsenceTypeSymbol ||--o{ Leave : "AbsenceTypeSymbolId"
LeaveConfig ||--o{ Leave : "LeaveConfigId"
HrType ||--o{ HrArea : "HrTypeId"
HrType ||--o{ HrAreaField : "HrTypeId"
HrArea ||--o{ HrAreaField : "HrAreaId"
HrType ||--o{ HrBill : "HrTypeId"
HrType ||--o{ HrTypeView : "HrTypeId"
SalaryField ||--o{ SalaryGroupField : "SalaryFieldId"
SalaryGroup ||--o{ SalaryGroupField : "SalaryGroupId"
SalaryPeriod ||--o{ SalaryPeriodGroup : "SalaryPeriodId"
SalaryGroup ||--o{ SalaryPeriodGroup : "SalaryGroupId"
OvertimeConfiguration ||--o{ OvertimeConfigurationMapping : "OvertimeConfigurationId"
OvertimeConfiguration ||--o{ OvertimeConfigurationTimeFrame : "OvertimeConfigurationId"
AbsenceTypeSymbol ||--o{ ShiftRequestLetterAbsence : "AbsenceTypeSymbolId"
ShiftConfiguration ||--o{ ShiftRequestLetterAbsence : "ShiftConfigurationId"
ShiftConfiguration ||--o{ ShiftRequestLetterOvertime : "ShiftConfigurationId"
LeaveConfig ||--o{ LeaveConfigRole : "LeaveConfigId"
LeaveConfig ||--o{ LeaveConfigValidation : "LeaveConfigId"
CustomerCate ||--o{ Customer : "CustomerCateId"
Customer ||--o{ UserCustomerManager : "CustomerId"
Department ||--o{ DepartmentCapacityBalance : "DepartmentId"
Department ||--o{ EmployeeDepartmentMapping : "DepartmentId"
TimeSheet ||--o{ TimeSheetAggregate : "TimeSheetId"
TimeSheet ||--o{ TimeSheetDepartment : "TimeSheetId"
TimeSheet ||--o{ TimeSheetDetail : "TimeSheetId"
ShiftSchedule ||--o{ ShiftScheduleConfiguration : "ShiftScheduleId"
ShiftSchedule ||--o{ ShiftScheduleDetail : "ShiftScheduleId"
07. Master (MasterDBContext)
Master data dùng chung – User/Role/Phân quyền – Danh mục động – Gen code – Tiền tệ – Menu/Module — 24 entity, 14 quan hệ domain · ~76 entity tổng trong DB
📋 Flow chi tiết (16 bước)
Module Master quản lý cơ sở hạ tầng toàn hệ thống bao gồm RBAC (Role-Based Access Control), quản lý dữ liệu tham chiếu động (Category), sinh mã tài liệu (CustomGenCode), cấu hình in ấn, và ghi nhật ký kiểm toán. Mọi module khác phụ thuộc vào Master để kiểm soát quyền hạn, sinh mã tài liệu, và truy vấn dữ liệu danh mục tham chiếu.
Bước 1: User được tạo trong bảng User (UserId, UserName, SubsidiaryId) và liên kết tới Role thông qua trường RoleId trong User.
Bước 2: Role được thiết lập trong bảng Role (RoleId, RoleName, SubsidiaryId, ParentRoleId) hỗ trợ phân cấp vai trò; trường IsModulePermissionInherit xác định quyền có được kế thừa từ ParentRole hay không.
Bước 3: Module được đăng ký trong bảng Module (ModuleId, ModuleName, ModuleGroupId) và nhóm thông qua ModuleGroup để tổ chức giao diện điều hướng.
Bước 4: RolePermission liên kết Role → Module thông qua (RoleId, ModuleId) với bitmask Permission xác định quyền hạn (View, Create, Update, Delete); nếu IsModulePermissionInherit=true thì quyền được kế thừa từ Role cha.
Bước 5: RoleDataPermission giới hạn quyền ở cấp dữ liệu thông qua (RoleId, ObjectTypeId, ObjectId); chẳng hạn Role có thể chỉ xem dữ liệu của một Subsidiary cụ thể hoặc Branch cụ thể.
Bước 6: Category được tạo trong bảng Category (CategoryId, CategoryCode, Title, CategoryGroupId); hỗ trợ cấu trúc cây qua ParentKey và IsTreeView=true (ví dụ: loại nhân viên, loại ca làm việc).
Bước 7: CategoryField định nghĩa schema động cho mỗi Category (FieldName, DataTypeId, FormTypeId, IsRequired, IsUnique); không cần migration DB khi thêm danh mục mới.
Bước 8: CategoryView (liên kết từ Category) cung cấp các giao diện lọc tùy chỉnh; CategoryViewField chỉ định các trường nào xuất hiện trong từng view.
Bước 9: SQL hooks được cấu hình trong Category (PreLoadAction, PostLoadAction, BeforeSaveAction, AfterSaveAction) cho phép logic nghiệp vụ runtime; JoinSqlRaw/SearchSqlRaw cho phép lọc phức tạp.
Bước 10: Dữ liệu danh mục được nhập vào động bằng CategoryDataService thông qua API POST /api/Category/{code}/Data; mỗi dòng được lưu vào bảng tương ứng với CategoryCode.
Bước 11: CustomGenCode được cấu hình trong bảng CustomGenCode (CustomGenCodeId, CodeFormat, BaseFormat, CodeLength, ResetDate) để sinh mã tài liệu; ví dụ: INV-{yyyy}-{###} sinh ra INV-2026-1001, INV-2026-1002.
Bước 12: Khi yêu cầu sinh mã, GenerateCode API truy vấn CustomGenCode, tăng giá trị LastCode, tạo bản ghi trong CustomGenCodeValue với trạng thái pending; mã được trả về ứng dụng.
Bước 13: ConfirmCode API xác nhận mã được sử dụng; cập nhật CustomGenCodeValue thành confirmed; nếu không xác nhận, mã bị rollback và có thể tái sử dụng.
Bước 14: Module khác (Stock, Voucher, PurchaseOrder) gọi CustomGenCode API để sinh mã cho các tài liệu (phiếu kho, hoá đơn, đơn mua hàng) và lưu mã vào bảng tương ứng.
Bước 15: Menu được xây dựng từ bảng Menu và đạo hàm từ ModuleApiEndpointMapping (liên kết Module → ApiEndpoint → Method); IModuleAccessService lọc Menu dựa trên RolePermission của User hiện tại.
Bước 16: Mọi thay đổi dữ liệu (Create/Update/Delete) kích hoạt UserActivityLog entry (Module khác gọi IUserLogActionService); ghi UserId, ObjectTypeId, ObjectId, trạng thái trước/sau trong ObjectJson; lưu vào ActivityLogDB để tách biệt audit trail khỏi dữ liệu kinh tế.
100%
erDiagram
Action["Action (Hành động)"] {
}
ApiEndpoint["ApiEndpoint (Điểm cuối API)"] {
}
Category["Category (Danh mục dữ liệu)"] {
}
CategoryField["CategoryField (Trường danh mục)"] {
}
CategoryGroup["CategoryGroup (Nhóm danh mục)"] {
}
CategoryView["CategoryView (Giao diện danh mục)"] {
}
CategoryViewField["CategoryViewField (Trường giao diện danh mục)"] {
}
Config["Config (Cấu hình hệ thống)"] {
}
CurrencyConvert["CurrencyConvert (Tỷ giá tiền tệ)"] {
}
CustomGenCode["CustomGenCode (Cấu hình sinh mã tùy chỉnh)"] {
}
CustomGenCodeValue["CustomGenCodeValue (Giá trị sinh mã tùy chỉnh)"] {
}
EmailConfiguration["EmailConfiguration (Cấu hình email)"] {
}
I18nLanguage["I18nLanguage (Dịch đa ngôn ngữ)"] {
}
MailTemplate["MailTemplate (Mẫu email)"] {
}
Menu["Menu (Mục menu ứng dụng)"] {
}
Method["Method (Phương thức HTTP)"] {
}
Module["Module (Module ứng dụng)"] {
}
ModuleApiEndpointMapping["ModuleApiEndpointMapping (Ánh xạ module-endpoint)"] {
}
ModuleGroup["ModuleGroup (Nhóm module)"] {
}
PrintConfigStandard["PrintConfigStandard (Cấu hình in chuẩn)"] {
}
Role["Role (Vai trò người dùng)"] {
}
RoleDataPermission["RoleDataPermission (Quyền dữ liệu vai trò)"] {
}
RolePermission["RolePermission (Quyền module vai trò)"] {
}
User["User (Người dùng hệ thống)"] {
}
Action ||--o{ ApiEndpoint : "ActionId"
Method ||--o{ ApiEndpoint : "MethodId"
Category ||--o{ CategoryField : "CategoryId"
CategoryGroup ||--o{ Category : "CategoryGroupId"
Category ||--o{ CategoryView : "CategoryId"
CategoryView ||--o{ CategoryViewField : "CategoryViewId"
ModuleGroup ||--o{ Module : "ModuleGroupId"
Module ||--o{ ModuleApiEndpointMapping : "ModuleId"
ApiEndpoint ||--o{ ModuleApiEndpointMapping : "ApiEndpointId"
Role ||--o{ RolePermission : "RoleId"
Module ||--o{ RolePermission : "ModuleId"
Role ||--o{ RoleDataPermission : "RoleId"
Role ||--o{ Role : "ParentRoleId"
CustomGenCode ||--o{ CustomGenCodeValue : "CustomGenCodeId"
08. ReportConfig (ReportConfigDBContext)
Cấu hình báo cáo – Loại BC/View/Cột/Bộ lọc – Dashboard – Scripting SQL/JS – Template xuất — 13 entity, 9 quan hệ domain · ~29 entity tổng trong DB
📋 Flow chi tiết (16 bước)
Module ReportConfig cung cấp hệ thống cấu hình bảng điều khiển và báo cáo linh hoạt, cho phép quản trị viên định nghĩa các mẫu báo cáo và dashboard mà không cần thay đổi code. Dữ liệu di chuyển từ cấu hình mẫu (ReportType, DashboardType) → định nghĩa view và trường (ReportTypeView, DashboardTypeView) → thực thi query (BodySql) → lưu trữ kết quả dữ liệu (ReportSavedData) → render giao diện cho người dùng.
Bước 1: Người dùng (DBA/Admin) tạo ReportTypeGroup hoặc DashboardTypeGroup để tổ chức các báo cáo/dashboard theo nhóm lôgic (ví dụ: Bán hàng, Kiểm chất, Quản lý kho).
Bước 2: Tạo ReportType (với ReportTypeGroupId, ReportTypeName, Columns JSON schema) hoặc DashboardType (với DashboardTypeGroupId, ModuleTypeId, DashboardTypeName, DashboardLocationId: Home=1 hoặc Report=2) làm mẫu chính.
Bước 3: Định nghĩa BodySql trong ReportType hoặc DashboardType chứa các truy vấn SQL tham số hóa (có điều kiện WHERE động, JOIN, GROUP BY) để lấy dữ liệu từ các bảng khác trong hệ thống (ví dụ: InspectionSheet từ QC, Stock từ Stock Module).
Bước 4: Cấu hình Columns (JSON) trong ReportType/DashboardType để định nghĩa các trường dữ liệu: tên trường, kiểu dữ liệu (decimal, datetime, string), định dạng hiển thị (currency, date format).
Bước 5: Tạo ReportTypeView hoặc DashboardTypeView đặt tên cho các biến thể view (ví dụ: Summary, Detail, Trend) và đánh dấu IsDefault=true cho view mặc định.
Bước 6: Gán các trường cụ thể cho mỗi view bằng ReportTypeViewField hoặc DashboardTypeViewField với SortOrder để xác định thứ tự hiển thị cột (ví dụ: trường Location, OnHand, TotalValue).
Bước 7: Cấu hình biểu đồ trong DashboardType thông qua JsProcessedChart, MenuContextChart, WizardChart để định nghĩa loại biểu đồ, trục X/Y, chuyên sâu phân tích.
Bước 8: Khi người dùng cuối truy cập dashboard/báo cáo, hệ thống tải DashboardType hoặc ReportType có DashboardLocationId hoặc ReportPath tương ứng.
Bước 9: Hệ thống áp dụng bộ lọc người dùng (ReportFilterDataModel: khoảng ngày, phòng ban, sản phẩm) vào BodySql dưới dạng tham số hóa để tránh SQL injection.
Bước 10: Thực thi BodySql (có thể có HeadSql, FooterSql trong ReportType) trên cơ sở dữ liệu và nhận kết quả dưới dạng NonCamelCaseDictionary (schema linh hoạt, không cần migration).
Bước 11: Áp dụng metadata từ Columns JSON để chuyển đổi kiểu dữ liệu, định dạng giá trị (ví dụ: Decimal(5,2) → định dạng tiền tệ), sắp xếp theo SortOrder từ ReportTypeViewField.
Bước 12: Nếu là báo cáo in/xuất, lấy ReportExcelTemplateFile hoặc ReportXmlTemplateFile (được tạo từ ReportType via TemplateExcelId) để áp dụng layout mẫu.
Bước 13: Áp dụng cấu hình HTML (HtmlTemplate từ ReportType) hoặc JavaScript (BodyJs, FooterJs, AfterLoadDataJsCode) để xử lý dữ liệu phía client, tính toán thêm, ẩn/hiện cột.
Bước 14: Render kết quả dữ liệu vào UI với cấu hình layout từ DashboardTypeView hoặc ReportTypeView đã chọn, hiển thị biểu đồ theo JsProcessedChart/WizardChart.
Bước 15: Nếu người dùng lưu báo cáo, tạo ReportSavedData (ReportTypeId, Title, Description, Data dạng JSON, Filter điều kiện) với CreatedByUserId, CreatedDatetimeUtc để lưu trữ snapshot.
Bước 16: Theo dõi ETL refresh status qua JobProcessDataInfo (lưu LastExecutionDate), dùng để cập nhật dữ liệu báo cáo định kỳ từ các modules khác (StatisticReportData, Manufacturing, Accountancy) và đảm bảo dữ liệu dashboard luôn mới nhất.
100%
erDiagram
DashboardType["DashboardType (Loại bảng điều khiển)"] {
}
DashboardTypeGroup["DashboardTypeGroup (Nhóm loại bảng điều khiển)"] {
}
DashboardTypeView["DashboardTypeView (Chế độ xem bảng điều khiển)"] {
}
DashboardTypeViewField["DashboardTypeViewField (Trường lọc chế độ xem bảng điều khiển)"] {
}
ReportExcelTemplateFile["ReportExcelTemplateFile (Tệp template Excel báo cáo)"] {
}
ReportSavedData["ReportSavedData (Dữ liệu báo cáo lưu trữ)"] {
}
ReportType["ReportType (Loại báo cáo)"] {
}
ReportTypeCustom["ReportTypeCustom (Báo cáo tùy chỉnh)"] {
}
ReportTypeGroup["ReportTypeGroup (Nhóm loại báo cáo)"] {
}
ReportTypeView["ReportTypeView (Chế độ xem báo cáo)"] {
}
ReportTypeViewField["ReportTypeViewField (Trường lọc chế độ xem)"] {
}
ReportTypeViewFieldValue["ReportTypeViewFieldValue (Giá trị trường chế độ xem báo cáo)"] {
}
ReportXmlTemplateFile["ReportXmlTemplateFile (Tệp template XML báo cáo)"] {
}
ReportTypeGroup ||--o{ ReportType : "ReportTypeGroupId"
ReportType ||--o{ ReportTypeView : "ReportTypeId"
ReportTypeView ||--o{ ReportTypeViewField : "ReportTypeViewId"
ReportType ||--o{ ReportSavedData : "ReportTypeId"
ReportType ||--o{ ReportExcelTemplateFile : "ReportTypeId"
ReportType ||--o{ ReportXmlTemplateFile : "ReportTypeId"
DashboardTypeGroup ||--o{ DashboardType : "DashboardTypeGroupId"
DashboardType ||--o{ DashboardTypeView : "DashboardTypeId"
DashboardTypeView ||--o{ DashboardTypeViewField : "DashboardTypeViewId"
Dữ liệu báo cáo thống kê – Lương ngày – Cache dashboard – Bảng tạm báo cáo — 7 entity, 2 quan hệ domain · ~13 entity tổng trong DB
📋 Flow chi tiết (22 bước)
Module thực hiện tính toán và lưu trữ dữ liệu lương hàng ngày cho nhân viên. Quy trình chính gồm: (1) lấy dữ liệu chấm công từ module Organization, (2) tính toán giá trị lương theo nhóm lương cho mỗi nhân viên mỗi ngày, (3) lưu dữ liệu vào bảng nóng (CurrentMonth) để truy vấn nhanh tháng hiện tại, (4) lưu trữ (archival) dữ liệu cũ sang bảng lạnh (Historical) khi tháng kết thúc.
Công việc được kích hoạt hàng ngày vào thời điểm cố định (23:00 UTC): hệ thống gọi EvalDaily(zoneDate) với ngày hiện tại.
Lấy khóa phân tán từ DistributedLockProvider để đảm bảo chỉ một node xử lý: ghi IP địa chỉ vào JobProcessDataInfo.LocalIpAddress, ngăn chặn tính toán trùng lặp.
Đọc JobProcessDataInfo.SalaryDailyLastDateZone để xác định điểm khôi phục: nếu chưa có, mặc định từ 60 ngày trước, tạo phạm vi [minDateZone, zoneDate] để tính toán lại.
Truy vấn OrganizationDBContext.TimeSheet để lấy dữ liệu chấm công trong phạm vi ngày: kết nối với TimeSheetDepartment để xác định các bộ phận, quyết định dữ liệu nào cần xử lý.
Tích hợp với ISalaryEmployeeService để sinh ra CalcTimeSheetTable: biến đổi dữ liệu chấm công thành bảng tính toán, gộp các chi tiết chấm công theo ngày và nhân viên.
Lặp qua từng SalaryGroup: với mỗi nhóm lương từ OrganizationDBContext, chuẩn bị công thức tính toán lương cùng danh sách nhân viên thuộc nhóm đó.
Với mỗi ngày trong phạm vi [minDateZone, zoneDate], lấy danh sách nhân viên được gán công việc: gọi IShiftScheduleService để xác định ai làm việc hôm đó, lọc từ dữ liệu SalaryGroup.
Với mỗi nhân viên hôm đó, gọi ISalaryEmployeeService.EvalSalaryEmployeeByGroup(): tính toán ngày công (attendance), giờ tăng ca (overtime), thưởng (bonuses), khấu trừ (deductions).
ISalaryEmployeeService trả về danh sách giá trị lương: mỗi phần tử chứa SalaryFieldId, SalaryFieldName, Value (decimal), IsConstant (cố định hay biến động).
Xây dựng record cho SalaryDailyEmployeeCurrentMonth: ghi nhận (DateZone, EmployeeId, SubsidiaryId, SalaryPeriodId, SalaryGroupId, DepartmentId, WorkPositionId, IsPeriodCompleted, IsAssigned, ToUtcDate, ToZoneDate, LocalIpAddress).
Xây dựng record cho SalaryDailyEmployeeValueCurrentMonth: với mỗi giá trị lương trả về, tạo bản ghi (DateZone, EmployeeId, SubsidiaryId, SalaryFieldId, SalaryFieldName, Value, IsConstant).
Tích lũy các record vào batch (5.000 bản ghi mỗi batch): khi đạt ngưỡng, thực hiện BULK INSERT đến SalaryDailyEmployeeCurrentMonth và SalaryDailyEmployeeValueCurrentMonth.
Cập nhật JobProcessDataInfo.SalaryDailyLastDateZone = zoneDate: ghi nhận điểm tiến trình xử lý để lần tiếp theo biết bắt đầu từ đâu.
Kiểm tra xem tháng hiện tại có kết thúc chưa: so sánh ngày xử lý với ngày đầu tiên của tháng tiếp theo.
Nếu tháng đã kết thúc, khởi động quy trình lưu trữ (Archival): SELECT dữ liệu từ SalaryDailyEmployeeCurrentMonth có DateZone < ngày đầu tháng tiếp theo.
BULK INSERT những record được SELECT vào SalaryDailyEmployee (bảng lạnh/cold): giữ lại dữ liệu lịch sử toàn bộ.
Thực hiện quy trình tương tự với SalaryDailyEmployeeValueCurrentMonth: SELECT, BULK INSERT vào SalaryDailyEmployeeValue, sau đó xóa khỏi bảng nóng.
DELETE dữ liệu đã lưu trữ từ SalaryDailyEmployeeCurrentMonth và SalaryDailyEmployeeValueCurrentMonth: giải phóng không gian bộ nhớ, duy trì kích thước bảng nóng.
Cập nhật JobProcessDataInfo.SalaryDailyLastDateZone lần nữa: khôi phục giá trị cho tháng mới để tính toán từ ngày đầu tháng tiếp theo.
Dashboard/báo cáo (Report Config module) truy vấn dữ liệu: sử dụng SalaryDailyEmployeeCurrentMonth (hiện tại) hoặc SalaryDailyEmployee (lịch sử) thông qua DashboardJobData cache để render bảng điều khiển lương.
Trường hợp báo cáo tạm thời: ReportTempTable lưu tên logic và vật lý của bảng tạm, cho phép ReportTypeId tham chiếu dữ liệu lương để sinh báo cáo động.
Dữ liệu hết hạn cache trong DashboardJobData được xóa định kỳ (CleanupCache): giữ cache tươi mới, loại bỏ FilterSnapshot và DataJson cũ hơn 24 giờ để tiết kiệm dung lượng.
100%
erDiagram
DashboardJobData["DashboardJobData (Dữ liệu công việc bảng điều khiển)"] {
}
JobProcessDataInfo["JobProcessDataInfo (Thông tin quá trình công việc)"] {
}
ReportTempTable["ReportTempTable (Bảng tạm thời báo cáo)"] {
}
SalaryDailyEmployee["SalaryDailyEmployee (Nhân viên lương hàng ngày)"] {
}
SalaryDailyEmployeeCurrentMonth["SalaryDailyEmployeeCurrentMonth (Nhân viên lương tháng hiện tại)"] {
}
SalaryDailyEmployeeValue["SalaryDailyEmployeeValue (Giá trị lương nhân viên hàng ngày)"] {
}
SalaryDailyEmployeeValueCurrentMonth["SalaryDailyEmployeeValueCurrentMonth (Giá trị lương tháng hiện tại)"] {
}
SalaryDailyEmployee ||--o{ SalaryDailyEmployeeValue : "FK*"
SalaryDailyEmployeeCurrentMonth ||--o{ SalaryDailyEmployeeValueCurrentMonth : "FK*"