[CLAUDE] PurchaseEvaluation: demo seed 4 phieu + MaPhieu atomic sequence + Pe_* perm defaults

Polish session tiep cua PE module skeleton (commit 2c6f0ca..3990066):
3 task A (MISSING in MVP) khac STATUS.md In Progress:

1. Demo PE data seed (SeedDemoPurchaseEvaluationsAsync)
   - 4 phieu varied A/B x phase: A-001 DangSoanThao (mo), A-002
     ChoCEODuyetNCC (winner+9 quotes), A-003 DaDuyet (chua tao HD,
     PaymentTerms JSON), B-001 ChoDuAn (5-step giua chung).
   - Idempotent: skip-if-[DEMO]-exists.
   - Approval history dung policy A (3-step) hoac B (5-step).

2. MaPhieu atomic sequence — Migration 13
   - Format PE/{YYYY}/{TypeLetter}/{Seq:D3} (vd PE/2026/A/001).
   - PurchaseEvaluationCodeSequence entity (Prefix PK).
   - IPurchaseEvaluationCodeGenerator + impl SERIALIZABLE
     transaction (mirror ContractCodeGenerator 1:1).
   - Replace Random.Shared trong CreatePurchaseEvaluationCommandHandler.
   - Migration AddPurchaseEvaluationCodeSequences (1 bang).

3. Pe_* permission defaults
   - SeedPurchaseEvaluationPermissionDefaultsAsync — 7 role business x 9 menu key.
   - Drafter/DeptManager/Procurement: R+C+U; CostControl/PM/Director/AuthorizedSigner: R+U.
   - DeptManager them Delete (xoa nhap).
   - Idempotent per-(roleId x menuKey).

Build: 0 error, 2 warning (pre-existing DocxRenderer).

Files: 4 new + 8 modified (1 migration + entity + generator + DI + 2 ctx + 2 features).

Resolves: STATUS.md In Progress §A — 3 item PE MISSING.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-04-24 10:41:17 +07:00
parent 0048a2e83a
commit c48ac2116d
16 changed files with 3621 additions and 24 deletions

View File

@ -0,0 +1,55 @@
using System.Data;
using Microsoft.EntityFrameworkCore;
using SolutionErp.Application.Common.Interfaces;
using SolutionErp.Application.PurchaseEvaluations.Services;
using SolutionErp.Domain.PurchaseEvaluations;
namespace SolutionErp.Infrastructure.Services;
// Mirror ContractCodeGenerator pattern — atomic sequence per (year × type).
// Prefix = "PE/{YYYY}/{TypeLetter}".
public class PurchaseEvaluationCodeGenerator(IApplicationDbContext db, IDateTime dateTime)
: IPurchaseEvaluationCodeGenerator
{
public async Task<string> GenerateAsync(PurchaseEvaluation evaluation, CancellationToken ct = default)
{
var typeLetter = evaluation.Type switch
{
PurchaseEvaluationType.DuyetNcc => "A",
PurchaseEvaluationType.DuyetNccPhuongAn => "B",
_ => "X",
};
var year = dateTime.UtcNow.Year;
var prefix = $"PE/{year}/{typeLetter}";
var context = (DbContext)db;
await using var tx = await context.Database
.BeginTransactionAsync(IsolationLevel.Serializable, ct);
try
{
var seq = await db.PurchaseEvaluationCodeSequences
.FirstOrDefaultAsync(s => s.Prefix == prefix, ct);
if (seq is null)
{
seq = new PurchaseEvaluationCodeSequence
{
Prefix = prefix, LastSeq = 1, UpdatedAt = dateTime.UtcNow,
};
db.PurchaseEvaluationCodeSequences.Add(seq);
}
else
{
seq.LastSeq += 1;
seq.UpdatedAt = dateTime.UtcNow;
}
await db.SaveChangesAsync(ct);
await tx.CommitAsync(ct);
return $"{prefix}/{seq.LastSeq:D3}";
}
catch
{
await tx.RollbackAsync(ct);
throw;
}
}
}