[CLAUDE] PE: upload file dinh kem per-NCC (doi chieu bao gia)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m9s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m9s
User request: 'cho cac NCC 1,2 va 3 thi cho them cho upload file dinh
kem cho tung NCC de co the doi chieu'.
Entity PurchaseEvaluationAttachment + PurchaseEvaluationSupplierId nullable
da thiet ke san tu migration 12 — gio wire up BE + FE.
BE (Application/Api):
- PurchaseEvaluationAttachmentFeatures: Upload (multipart + supplierRowId
optional) + Download + Delete. Reuse IFileStorage + LocalFileStorage.
Validator 20MB + MIME whitelist (pdf/doc/docx/xls/xlsx/png/jpg/webp).
- Upload log vao PurchaseEvaluationChangelogs (Attachment + Insert).
- PurchaseEvaluationAttachmentDto + them field Attachments vao bundle.
- GetPurchaseEvaluationQueryHandler Include(x => x.Attachments) +
OrderByDescending(a => a.CreatedAt) projection.
- PurchaseEvaluationsController 3 endpoint:
POST /attachments (IFormFile + [FromForm] supplierRowId/purpose/note)
GET /attachments/{attId}/download (File stream)
DELETE /attachments/{attId}
- Storage path: wwwroot/uploads/purchase-evaluations/{id}/{attId}_{safeName}
FE (fe-admin + fe-user):
- Type PeAttachment + PeAttachmentPurpose/Label (QuoteDocument default)
- PeDetailBundle.attachments: PeAttachment[]
- SuppliersTab thay column Hien thi + Ghi chu bang column File dinh kem
(per-NCC upload + list N attachments + download + delete).
- SupplierAttachmentsCell component: <input type=file> hidden + [+ Them
file] button + inline list attachments voi Paperclip icon + filename
(click tai ve) + size + purpose chip + Trash2 delete.
This commit is contained in:
@ -150,6 +150,42 @@ public class PurchaseEvaluationsController(IMediator mediator) : ControllerBase
|
||||
public async Task<List<PurchaseEvaluationChangelogDto>> GetChangelogs(Guid id, CancellationToken ct)
|
||||
=> await mediator.Send(new ListPurchaseEvaluationChangelogsQuery(id), ct);
|
||||
|
||||
// ========== Attachments (per-supplier hoặc general) ==========
|
||||
|
||||
// Upload file đính kèm — gắn với NCC cụ thể (supplierRowId) hoặc phiếu tổng.
|
||||
[HttpPost("{id:guid}/attachments")]
|
||||
[RequestSizeLimit(25_000_000)]
|
||||
public async Task<ActionResult<PurchaseEvaluationAttachmentDto>> UploadAttachment(
|
||||
Guid id,
|
||||
IFormFile file,
|
||||
[FromForm] Guid? supplierRowId = null,
|
||||
[FromForm] PurchaseEvaluationAttachmentPurpose purpose = PurchaseEvaluationAttachmentPurpose.QuoteDocument,
|
||||
[FromForm] string? note = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (file is null || file.Length == 0)
|
||||
return BadRequest(new { detail = "Chưa chọn file." });
|
||||
|
||||
await using var stream = file.OpenReadStream();
|
||||
var dto = await mediator.Send(new UploadPurchaseEvaluationAttachmentCommand(
|
||||
id, supplierRowId, file.FileName, file.ContentType, file.Length, stream, purpose, note), ct);
|
||||
return Ok(dto);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/attachments/{attId:guid}/download")]
|
||||
public async Task<IActionResult> DownloadAttachment(Guid id, Guid attId, CancellationToken ct)
|
||||
{
|
||||
var f = await mediator.Send(new DownloadPurchaseEvaluationAttachmentQuery(id, attId), ct);
|
||||
return File(f.Content, f.ContentType, f.FileName);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}/attachments/{attId:guid}")]
|
||||
public async Task<IActionResult> DeleteAttachment(Guid id, Guid attId, CancellationToken ct)
|
||||
{
|
||||
await mediator.Send(new DeletePurchaseEvaluationAttachmentCommand(id, attId), ct);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// ========== Kế thừa HĐ ==========
|
||||
|
||||
// List phiếu đã DaDuyet chưa gen HĐ — dùng cho modal "Tạo HĐ từ phiếu"
|
||||
|
||||
Reference in New Issue
Block a user