[CLAUDE] Domain+App+Infra+Api+FE-Admin+FE-User: S37 Mig 37 enum + Plan G-O3 Đề xuất full-stack
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m53s

Phase 10.3 G-O3 Đề xuất (Proposal) — Mig 37 enum extend +5 values + Mig 38
Proposal schema + BE CQRS 8 endpoint + FE 2 app SHA256 IDENTICAL.

Mig 37 (em main solo): extend ApprovalWorkflowApplicableType enum +5 values
  ProposalGeneral=4 / LeaveRequest=5 / OtRequest=6 / VehicleBooking=7 / ItTicket=8
  cookie-cutter Mig 22 pattern (Up/Down empty — enum mức Domain).

Mig 38 (em main solo): 4 entity Proposal (Code DX/YYYY/NNN) + ProposalAttachment
  + ProposalLevelOpinion (UNIQUE composite PEId+LevelId mirror PE Mig 26) +
  ProposalCodeSequence (Prefix PK atomic seq). 4 EF Config + 2 DbContext mod.

BE CQRS (em main solo ~700 LOC ProposalFeatures.cs sau Implementer truncate phase
exploration gotcha #53 5th + 529 Overload):
  - 4 Header handler (List paged + GetById detail + Create + UpdateDraft owner-OR-admin)
  - 4 Workflow handler (Submit gen MaDeXuat atomic + Approve UPSERT LevelOpinion advance + Reject + Return)
  - SERIALIZABLE transaction CodeGen
  - DTOs nested LevelOpinion với Step+Level metadata JOIN

ProposalsController 8 endpoint /api/proposals (List/GetById/Create/Update/Submit/Approve/Reject/Return)
class-level [Authorize] + handler-level owner-OR-admin guard.

DbInitializer: SeedSampleProposalWorkflowV2Async ~40 LOC seed QT-DX-V2-001 IsUserSelectable=true
NOT gated DemoSeed per gotcha #51. SeedMenuTreeAsync +4 row (Off_DeXuat sub-group + 3 leaf).

FE 2 app (em main solo + Implementer 529 fail fallback):
  - types/proposal.ts × 2 SHA256 IDENTICAL 95607052ff1138f2
  - ProposalsListPage.tsx × 2 IDENTICAL 603f0d9cf74cd09a — table 6 cột + Status badge + filter
  - ProposalCreatePage.tsx × 2 IDENTICAL 6aed3a76563dd576 — Form Header card
  - ProposalDetailPage.tsx × 2 IDENTICAL 3dc229ea8dcc9bc0 — 3 Section + WorkflowActions
  - Pattern 16-bis 8× cumulative (App.tsx + menuKeys + Layout staticMap 3 entry)

Verify:
- dotnet build PASS 0 error 2 warning pre-existing DocxRenderer
- dotnet test 130/130 PASS baseline preserve
- npm build × 2 PASS (fe-admin 14.72s + fe-user 6.40s)
- SHA256 verify 4 file × 2 app all IDENTICAL

Pattern reinforced cumulative S37:
- Pattern 12-bis cross-module mirror 11× (PE V2 → Proposal V2 ApproveV2)
- Pattern 16-bis 4-place mirror cross-app 8×
- gotcha #53 5th occurrence Implementer mid-exploration truncation + 529 Overload 1× — em main solo fallback proven

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-28 15:51:14 +07:00
parent 37593f95b5
commit de1c378279
35 changed files with 13650 additions and 0 deletions

View File

@ -47,6 +47,14 @@ public enum ApprovalWorkflowApplicableType
DuyetNcc = 1, // PE module — Duyệt NCC (default test target)
DuyetNccPhuongAn = 2, // PE — Duyệt NCC + Giải pháp
Contract = 3, // HĐ general (any ContractType)
// Phase 10.3 Workflow Apps (S37 — Mig 37 enum extend). Cookie-cutter Mig 22 V2 pattern.
// Mỗi module có 1 workflow per ApplicableType, admin Designer config + pin per draft.
ProposalGeneral = 4, // G-O3 — Đề xuất (Proposal)
LeaveRequest = 5, // G-O4 — Đơn nghỉ phép
OtRequest = 6, // G-O4 — Đơn OT
VehicleBooking = 7, // G-O5 — Đặt xe công
ItTicket = 8, // G-O6 — Ticket CNTT
}
// Bước = Phòng. 1 quy trình có nhiều bước theo Order.

View File

@ -105,6 +105,12 @@ public static class MenuKeys
public const string OffPhongHopManage = "Off_PhongHop_Manage"; // Quản lý phòng (Admin CRUD MeetingRoom)
public const string OffPhongHopBook = "Off_PhongHop_Book"; // Đặt phòng (Create/Update/Cancel Booking)
// Phase 10.3 G-O3 (Mig 38 — S37 2026-05-28) — Đề xuất (Proposal) workflow V2.
public const string OffDeXuat = "Off_DeXuat"; // sub-group Đề xuất
public const string OffDeXuatList = "Off_DeXuat_List"; // Danh sách đề xuất
public const string OffDeXuatCreate = "Off_DeXuat_Create"; // Tạo đề xuất mới
public const string OffDeXuatInbox = "Off_DeXuat_Inbox"; // Inbox phê duyệt
public static readonly string[] PurchaseEvaluationTypeCodes =
["DuyetNcc", "DuyetNccPhuongAn"];
@ -133,6 +139,7 @@ public static class MenuKeys
HrmConfig, HrmConfigLeaveTypes, HrmConfigHolidays, HrmConfigShifts, HrmConfigOtPolicies, // Mig 35 — Phase 10.2 G-H2
Off, OffDanhBa, // Phase 10.2 G-O1 — Văn phòng số
OffPhongHop, OffPhongHopView, OffPhongHopManage, OffPhongHopBook, // Phase 10.2 G-O2 — Phòng họp
OffDeXuat, OffDeXuatList, OffDeXuatCreate, OffDeXuatInbox, // Phase 10.3 G-O3 — Đề xuất
System, Users, Roles, Permissions, MenuVisibility, Workflows, PeWorkflows,
ApprovalWorkflowsV2, ApprovalWorkflowDuyetNccV2, ApprovalWorkflowDuyetNccPhuongAnV2, // Mig 22
];

View File

@ -10,3 +10,15 @@ public enum MeetingBookingStatus
Cancelled = 2, // Đã huỷ
Completed = 3, // Đã kết thúc (auto-set khi EndAt < Now via job/manual)
}
// Phase 10.3 G-O3 (Mig 38 — S37 2026-05-28) — Trạng thái Đề xuất (Proposal).
// 5-state mirror PE/Contract pattern (Nháp / Đã gửi duyệt / Trả lại / Từ chối / Đã duyệt).
// Workflow V2 dynamic theo ApprovalWorkflow.ApplicableType=ProposalGeneral=4.
public enum ProposalStatus
{
Nhap = 1, // Drafter đang soạn, chưa gửi duyệt
DaGuiDuyet = 2, // Trong workflow approve flow (CurrentApprovalLevelOrder)
TraLai = 3, // Approver trả về Drafter sửa → resubmit lại từ Cấp 1
TuChoi = 4, // Terminal — không thể edit/resubmit
DaDuyet = 5, // Terminal — workflow complete tất cả Cấp
}

View File

@ -0,0 +1,38 @@
using SolutionErp.Domain.Common;
namespace SolutionErp.Domain.Office;
// Phase 10.3 G-O3 (Mig 38 — S37 2026-05-28) — Đề xuất (Proposal) aggregate root.
// Mirror PE Plan B Mig 22-26 pattern cookie-cutter: Workflow V2 dynamic (ApplicableType=4) +
// LevelOpinions UPSERT auto via ApproveV2Async + atomic CodeGen DX/YYYY/NNN.
// Reference: PurchaseEvaluation.cs (PE module flagship V2 mirror source).
public class Proposal : AuditableEntity
{
public string? MaDeXuat { get; set; } // Auto-gen "DX/YYYY/NNN" qua ProposalCodeSequence
public string Title { get; set; } = string.Empty; // "Đề xuất mua máy tính", "Đề xuất tăng ngân sách"
public string? Description { get; set; } // Nội dung chi tiết (max 5000)
// Tổng số tiền dự kiến (đ). Optional — có đề xuất không số tiền (vd policy).
public decimal? AmountEstimate { get; set; }
public ProposalStatus Status { get; set; } = ProposalStatus.Nhap;
public Guid? DepartmentId { get; set; } // Phòng ban đề xuất (org filter)
public Guid DrafterUserId { get; set; } // User tạo đề xuất
// Workflow V2 pin (cookie-cutter PE Mig 22-23-24).
// ApplicableType phải = ProposalGeneral=4 (Mig 37 enum extend).
public Guid? ApprovalWorkflowId { get; set; }
public int? CurrentApprovalLevelOrder { get; set; } // Cấp đang chờ (1-based) khi DaGuiDuyet
// SLA (mirror PE)
public DateTime? SlaDeadline { get; set; }
public bool SlaWarningSent { get; set; }
// Smart reject (mirror PE Mig 16)
public ProposalStatus? RejectedFromStatus { get; set; }
public List<ProposalAttachment> Attachments { get; set; } = new();
public List<ProposalLevelOpinion> LevelOpinions { get; set; } = new();
}

View File

@ -0,0 +1,20 @@
using SolutionErp.Domain.Common;
namespace SolutionErp.Domain.Office;
// Phase 10.3 G-O3 (Mig 38 — S37) — Attachment cho Đề xuất.
// FK Cascade Proposal (xoá Đề xuất → wipe Attachments).
// Mirror PurchaseEvaluationAttachment pattern.
public class ProposalAttachment : AuditableEntity
{
public Guid ProposalId { get; set; }
public Proposal Proposal { get; set; } = null!;
public string FileName { get; set; } = string.Empty; // Original file name display
public string FilePath { get; set; } = string.Empty; // Server file path / blob URL
public long FileSize { get; set; } // Bytes
public string? MimeType { get; set; } // "application/pdf", "image/png"
public Guid UploadedByUserId { get; set; } // Drafter or approver tải lên
public string UploadedByFullName { get; set; } = string.Empty; // denorm
}

View File

@ -0,0 +1,13 @@
namespace SolutionErp.Domain.Office;
// Phase 10.3 G-O3 (Mig 38 — S37) — Sequence generator cho mã Đề xuất (MaDeXuat).
// Mirror PurchaseEvaluationCodeSequence pattern (Prefix string PK + LastSeq atomic).
// Format: "DX/YYYY/NNN" — vd "DX/2026/001" → "DX/2026/002" → ...
// Prefix = "DX/{YYYY}" cho từng năm. LastSeq reset đầu năm tự nhiên (key mới).
// Update atomic qua SERIALIZABLE transaction trong CodeGen service.
public class ProposalCodeSequence
{
public string Prefix { get; set; } = string.Empty; // PK — "DX/2026"
public int LastSeq { get; set; }
public DateTime UpdatedAt { get; set; }
}

View File

@ -0,0 +1,28 @@
using SolutionErp.Domain.ApprovalWorkflowsV2;
using SolutionErp.Domain.Common;
namespace SolutionErp.Domain.Office;
// Phase 10.3 G-O3 (Mig 38 — S37) — Ý kiến cấp duyệt V2 dynamic cho Proposal.
// Cookie-cutter mirror PurchaseEvaluationLevelOpinion (Mig 26).
//
// Mỗi row = 1 (Proposal × ApprovalWorkflowLevel). Service ApproveV2Async sau
// khi approve thành công Cấp hiện tại sẽ UPSERT row này (latest-write-wins).
// Reject (TraLai/TuChoi) KHÔNG sync.
//
// UNIQUE composite (ProposalId, LevelId) — 1 row / level / proposal.
// FK Cascade Proposal (wipe khi xoá) + Restrict Level (admin xoá Level chặn).
// SignedByUserId track actor thật (có thể Admin override) + denorm FullName.
public class ProposalLevelOpinion : AuditableEntity
{
public Guid ProposalId { get; set; }
public Guid ApprovalWorkflowLevelId { get; set; }
public string? Comment { get; set; } // max 2000 hoặc placeholder
public DateTime SignedAt { get; set; }
public Guid SignedByUserId { get; set; } // người ký thực sự (có thể Admin thay)
public string SignedByFullName { get; set; } = string.Empty; // snapshot denorm
public Proposal? Proposal { get; set; }
public ApprovalWorkflowLevel? Level { get; set; }
}