d29de83116
Adds POST/GET payee-1099/{id}/w9, mirroring the expense-receipt upload:
IFileStorage saves to finance/w9/{id}{ext}, content-type derived from the
blob extension. Frontend dialog (edit mode) gains a W-9 file input and an
auth-correct blob "View W-9" link. Payee1099Service ctor now takes
IFileStorage; tests updated with an in-memory FakeStorage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
72 lines
2.8 KiB
C#
72 lines
2.8 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using ROLAC.API.Authorization;
|
|
using ROLAC.API.DTOs.Payee;
|
|
using ROLAC.API.Services;
|
|
|
|
namespace ROLAC.API.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/payee-1099")]
|
|
[HasPermission(Modules.Form1099, PermissionActions.Read)]
|
|
public class Payee1099Controller : ControllerBase
|
|
{
|
|
private readonly IPayee1099Service _svc;
|
|
public Payee1099Controller(IPayee1099Service svc) => _svc = svc;
|
|
|
|
[HttpGet]
|
|
public async Task<IActionResult> GetAll([FromQuery] bool includeInactive = false)
|
|
=> Ok(await _svc.GetAllAsync(includeInactive));
|
|
|
|
[HttpGet("{id:int}")]
|
|
public async Task<IActionResult> GetById(int id)
|
|
=> await _svc.GetByIdAsync(id) is { } dto ? Ok(dto) : NotFound();
|
|
|
|
[HttpPost]
|
|
[HasPermission(Modules.Form1099, PermissionActions.Write)]
|
|
public async Task<IActionResult> Create([FromBody] SavePayee1099Request r)
|
|
=> Ok(new { id = await _svc.CreateAsync(r) });
|
|
|
|
[HttpPut("{id:int}")]
|
|
[HasPermission(Modules.Form1099, PermissionActions.Write)]
|
|
public async Task<IActionResult> Update(int id, [FromBody] SavePayee1099Request r)
|
|
{ await _svc.UpdateAsync(id, r); return NoContent(); }
|
|
|
|
[HttpDelete("{id:int}")]
|
|
[HasPermission(Modules.Form1099, PermissionActions.Delete)]
|
|
public async Task<IActionResult> Delete(int id)
|
|
{ await _svc.DeleteAsync(id); return NoContent(); }
|
|
|
|
// Full TIN reveal is gated on Write (a stronger right than Read).
|
|
[HttpGet("{id:int}/tin")]
|
|
[HasPermission(Modules.Form1099, PermissionActions.Write)]
|
|
public async Task<IActionResult> RevealTin(int id)
|
|
=> Ok(new { tin = await _svc.RevealTinAsync(id) });
|
|
|
|
// Mirrors the expense-receipt upload: multipart form file, size-limited, type-checked.
|
|
[HttpPost("{id:int}/w9")]
|
|
[HasPermission(Modules.Form1099, PermissionActions.Write)]
|
|
[RequestSizeLimit(10_485_760)]
|
|
public async Task<IActionResult> UploadW9(int id, IFormFile file)
|
|
{
|
|
if (file is null || file.Length == 0) return BadRequest(new { message = "No file." });
|
|
var allowed = new[] { "image/jpeg", "image/png", "image/webp", "application/pdf" };
|
|
if (!allowed.Contains(file.ContentType)) return BadRequest(new { message = "Unsupported file type." });
|
|
try
|
|
{
|
|
await using var stream = file.OpenReadStream();
|
|
await _svc.SaveW9Async(id, stream, file.FileName);
|
|
return NoContent();
|
|
}
|
|
catch (KeyNotFoundException) { return NotFound(); }
|
|
}
|
|
|
|
// Class-level Read gate covers viewing the stored W-9 (mirrors the receipt GET).
|
|
[HttpGet("{id:int}/w9")]
|
|
public async Task<IActionResult> GetW9(int id)
|
|
{
|
|
var result = await _svc.OpenW9Async(id);
|
|
if (result is null) return NotFound();
|
|
return File(result.Value.stream, result.Value.contentType);
|
|
}
|
|
}
|