[CLAUDE] Contract: W7 KHKK GD4 "Hop dong cung" 1-moc — HardCopiesPage x2 + ?type filter + hasSealedCopy badge + 3 test PIN (suite 590/0)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m50s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m50s
- BE: ListContractsQuery +ContractType? type (filter server-side F-C1/F-05) + DTO HasSealedCopy derive Attachments.Any(purpose=SealedCopy=3) — append-cuoi default false, 0 policy moi, 0 migration, attachment endpoints 0-diff (O-A giu) - FE x2 SHA-PAIR: trang /hard-copies (list phase=9 hardcode + badge + panel upload multipart purpose=3 + Y kien cap duyet readonly) + 7 leaf Hdc_* re-point ?type=1..7 (coming-soon 0/0, menu 142 giu) - Tests: ContractHardCopyTests 3 PIN (phase-only + type-filter Total-server-side + badge derive purpose=3 no-IsDeleted) — probe vi-phan 5/5 phan biet ban hong; suite 587->590/0 - Review: PASS-co-dieu-kien (FLAG-1 untracked da gate; FLAG-2/3 comment da va, SHA-pair C7DCC20B giu; +31/-4 errata BE self-report) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@ -20,8 +20,11 @@ public class ContractsController(IMediator mediator) : ControllerBase
|
||||
[FromQuery] ContractPhase? phase = null,
|
||||
[FromQuery] Guid? supplierId = null,
|
||||
[FromQuery] Guid? projectId = null,
|
||||
// [W7 S161] Lọc theo loại HĐ cho 7 leaf bản-cứng `Hdc_*`: `?type={1..7}&phase=9`.
|
||||
// Nullable ⇒ bỏ trống = giữ nguyên hành vi cũ (0 breaking cho caller hiện có).
|
||||
[FromQuery] ContractType? type = null,
|
||||
CancellationToken ct = default)
|
||||
=> Ok(await mediator.Send(new ListContractsQuery(phase, supplierId, projectId) { Page = page, PageSize = pageSize, Search = search, SortDesc = sortDesc }, ct));
|
||||
=> Ok(await mediator.Send(new ListContractsQuery(phase, supplierId, projectId, type) { Page = page, PageSize = pageSize, Search = search, SortDesc = sortDesc }, ct));
|
||||
|
||||
// [S159-đợt5] Màn "Đã xóa" HĐ (chỉ-xem) — mirror PE /deleted (S155). Read-only,
|
||||
// policy root Contracts.Read (chặt hơn [Authorize] trần của class — không phá gì).
|
||||
|
||||
@ -279,10 +279,14 @@ public class AddCommentCommandHandler(
|
||||
|
||||
// ========== LIST contracts (admin view) ==========
|
||||
|
||||
// [W7 S161] `Type` append-CUỐI (positional) — call-site controller dựng bằng vị-trí
|
||||
// `new ListContractsQuery(phase, supplierId, projectId, type)`, chèn giữa là đổi nghĩa
|
||||
// 3 tham số cũ. Khuôn lấy nguyên từ `ListDeletedContractsQuery(:408)` đã có sẵn.
|
||||
public record ListContractsQuery(
|
||||
ContractPhase? Phase = null,
|
||||
Guid? SupplierId = null,
|
||||
Guid? ProjectId = null) : PagedRequest, IRequest<PagedResult<ContractListItemDto>>;
|
||||
Guid? ProjectId = null,
|
||||
ContractType? Type = null) : PagedRequest, IRequest<PagedResult<ContractListItemDto>>;
|
||||
|
||||
public class ListContractsQueryHandler(IApplicationDbContext db, ICurrentUser currentUser)
|
||||
: IRequestHandler<ListContractsQuery, PagedResult<ContractListItemDto>>
|
||||
@ -317,6 +321,10 @@ public class ListContractsQueryHandler(IApplicationDbContext db, ICurrentUser cu
|
||||
if (request.Phase is not null) q = q.Where(x => x.c.Phase == request.Phase);
|
||||
if (request.SupplierId is not null) q = q.Where(x => x.c.SupplierId == request.SupplierId);
|
||||
if (request.ProjectId is not null) q = q.Where(x => x.c.ProjectId == request.ProjectId);
|
||||
// [W7 S161] Lọc theo loại HĐ — server-side, ĐẶT TRONG khối filter (SAU khối IDOR
|
||||
// `:298-315`) ⇒ thu hẹp tập đã-được-phép, KHÔNG mở thêm gì. FE 7 leaf `Hdc_*` gọi
|
||||
// `?type={1..7}&phase=9`. Mirror `ListDeletedContractsQueryHandler:434`.
|
||||
if (request.Type is not null) q = q.Where(x => x.c.Type == request.Type);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.Search))
|
||||
{
|
||||
@ -336,7 +344,15 @@ public class ListContractsQueryHandler(IApplicationDbContext db, ICurrentUser cu
|
||||
x.c.Id, x.c.MaHopDong, x.c.TenHopDong, x.c.Type, x.c.Phase,
|
||||
x.c.SupplierId, x.s.Name,
|
||||
x.c.ProjectId, x.p.Name,
|
||||
x.c.GiaTri, x.c.SlaDeadline, x.c.CreatedAt))
|
||||
x.c.GiaTri, x.c.SlaDeadline, x.c.CreatedAt,
|
||||
// [W7 S161] Cờ "đã có bản cứng" — dịch thành EXISTS(...) trên
|
||||
// `ContractAttachments`, 0 round-trip thêm.
|
||||
// 🔴 CỐ Ý KHÔNG có vế `!a.IsDeleted`: `ContractAttachment : BaseEntity`
|
||||
// (ContractAttachment.cs:13) — BaseEntity KHÔNG có `IsDeleted`, và
|
||||
// `ContractAttachmentConfiguration` (ContractConfiguration.cs:78-93) KHÔNG
|
||||
// có `HasQueryFilter`. Xoá đính kèm là HARD delete
|
||||
// (`ContractAttachmentFeatures.cs:143` `.Remove(att)`) ⇒ row còn = file còn.
|
||||
x.c.Attachments.Any(a => a.Purpose == AttachmentPurpose.SealedCopy)))
|
||||
.ToListAsync(ct);
|
||||
|
||||
return new PagedResult<ContractListItemDto>(items, total, request.Page, request.PageSize);
|
||||
|
||||
@ -14,7 +14,15 @@ public record ContractListItemDto(
|
||||
string ProjectName,
|
||||
decimal GiaTri,
|
||||
DateTime? SlaDeadline,
|
||||
DateTime CreatedAt);
|
||||
DateTime CreatedAt,
|
||||
// [W7 S161] Cờ "đã có bản cứng" — append-CUỐI + CÓ DEFAULT có chủ đích.
|
||||
// 🔴 Default BẮT BUỘC: DTO này còn 2 call-site dựng bằng 12 arg
|
||||
// (`ContractFeatures.cs` ListDeleted + Inbox) — bỏ default là vỡ compile 2 chỗ đó.
|
||||
// Hệ quả KHAI THẲNG: cờ CHỈ được derive ở `ListContractsQueryHandler` (màn danh sách
|
||||
// HĐ, nơi FE gắn badge); ở màn "Đã xóa" + "Hộp thư" nó luôn `false` vì KHÔNG derive
|
||||
// — cố ý giữ scope W7 tối thiểu, KHÔNG phải bỏ sót.
|
||||
// Nguồn sự-thật của cờ = có ≥1 `ContractAttachment.Purpose == SealedCopy(3)`.
|
||||
bool HasSealedCopy = false);
|
||||
|
||||
public record ContractDetailDto(
|
||||
Guid Id,
|
||||
|
||||
Reference in New Issue
Block a user