mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 09:33:13 +00:00
feat(vendor-portal): report work-order media counts on the dispatch detail
Adds MediaCounts (limits, current photos/videos, and the counts a new completion version would see) so the portal can refuse an 11th photo or 4th video before uploading it. Uses the same count and replacement rule the upload check enforces.
This commit is contained in:
parent
fd59a3da13
commit
eecc2a61bd
4 changed files with 106 additions and 4 deletions
|
|
@ -553,6 +553,46 @@ public sealed class VendorPortalDocumentTests : IDisposable
|
|||
context.VendorCompletionDocuments.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDispatchDetail_ReportsWorkOrderMediaCountsForThePortalPreCheck()
|
||||
{
|
||||
using var context = NewContext();
|
||||
var (vendor, dispatch) = await SeedDispatch(context);
|
||||
foreach (var name in new[] { "IMG_0001.MOV", "IMG_0002.MOV", "IMG_0003.jpg" })
|
||||
{
|
||||
context.workOrderAttachments.Add(new WorkOrderAttachments
|
||||
{
|
||||
WorkorderId = dispatch.WorkOrderId!.Value,
|
||||
Attachments = $"https://api.example.com/Assets/Documents/{Guid.NewGuid()}_{name}",
|
||||
});
|
||||
}
|
||||
context.VendorCompletionDocuments.Add(new VendorCompletionDocument
|
||||
{
|
||||
VendorId = vendor.Id,
|
||||
DispatchId = dispatch.Id,
|
||||
WorkOrderId = dispatch.WorkOrderId!.Value,
|
||||
ContentType = "video/mp4",
|
||||
StoredFileName = "v1.mp4",
|
||||
Version = 1,
|
||||
Purpose = "Completion"
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = NewService(context);
|
||||
var session = await service.ResolveSessionAsync(Token, CancellationToken.None);
|
||||
var detail = await service.GetDispatchDetailAsync(session!, dispatch.Id, CancellationToken.None);
|
||||
|
||||
detail!.MediaCounts.Should().BeEquivalentTo(new SeaHaven.Services.DTOs.PortalMediaCountsDTO
|
||||
{
|
||||
MaxPhotos = 10,
|
||||
MaxVideos = 3,
|
||||
Photos = 1,
|
||||
Videos = 3,
|
||||
CompletionPhotos = 1,
|
||||
CompletionVideos = 2,
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UploadCompletionDocument_RejectsVideoOverHundredMegabytes()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -134,6 +134,26 @@ namespace SeaHaven.Services.DTOs
|
|||
public IEnumerable<PortalCommentDTO> Comments { get; set; } = Enumerable.Empty<PortalCommentDTO>();
|
||||
public IEnumerable<PortalUpliftDTO> UpliftRequests { get; set; } = Enumerable.Empty<PortalUpliftDTO>();
|
||||
public IEnumerable<VendorPortalDocumentDTO> Documents { get; set; } = Enumerable.Empty<VendorPortalDocumentDTO>();
|
||||
public PortalMediaCountsDTO? MediaCounts { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SH-116 per-work-order photo/video usage, so the portal can pre-check an upload
|
||||
/// before sending it. The server still enforces the limit on upload.
|
||||
/// </summary>
|
||||
public class PortalMediaCountsDTO
|
||||
{
|
||||
public int MaxPhotos { get; set; }
|
||||
public int MaxVideos { get; set; }
|
||||
/// <summary>Photos/videos on the work order, counted for evidence uploads.</summary>
|
||||
public int Photos { get; set; }
|
||||
public int Videos { get; set; }
|
||||
/// <summary>
|
||||
/// Photos/videos counted for a new completion version, which replaces the dispatch's
|
||||
/// latest document and so does not count it.
|
||||
/// </summary>
|
||||
public int CompletionPhotos { get; set; }
|
||||
public int CompletionVideos { get; set; }
|
||||
}
|
||||
|
||||
public class VendorPortalDocumentDTO
|
||||
|
|
|
|||
|
|
@ -93,16 +93,29 @@ namespace SeaHaven.Services.Helpers
|
|||
if (kind != UploadKind.Photo && kind != UploadKind.Video)
|
||||
return null;
|
||||
|
||||
var count = attachmentUrls.Count(url => ResolveKind(null, url) == kind)
|
||||
+ vendorContentTypes.Count(type => ResolveKind(type, null) == kind);
|
||||
var (photos, videos) = CountKinds(attachmentUrls, vendorContentTypes);
|
||||
|
||||
if (kind == UploadKind.Photo && count >= MaxPhotosPerWorkOrder)
|
||||
if (kind == UploadKind.Photo && photos >= MaxPhotosPerWorkOrder)
|
||||
return "A work order can have at most 10 photos.";
|
||||
if (kind == UploadKind.Video && count >= MaxVideosPerWorkOrder)
|
||||
if (kind == UploadKind.Video && videos >= MaxVideosPerWorkOrder)
|
||||
return "A work order can have at most 3 videos.";
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Photos and videos on a work order: dispatcher attachments (kind from the stored URL)
|
||||
/// plus current vendor-portal documents (kind from the validated content type).
|
||||
/// </summary>
|
||||
public static (int Photos, int Videos) CountKinds(
|
||||
IEnumerable<string> attachmentUrls,
|
||||
IEnumerable<string> vendorContentTypes)
|
||||
{
|
||||
var kinds = attachmentUrls.Select(url => ResolveKind(null, url))
|
||||
.Concat(vendorContentTypes.Select(type => ResolveKind(type, null)))
|
||||
.ToList();
|
||||
return (kinds.Count(k => k == UploadKind.Photo), kinds.Count(k => k == UploadKind.Video));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 90-second video limit, read from the MP4/QuickTime movie header. A video whose
|
||||
/// duration cannot be read is not blocked. Returns the stable message or null.
|
||||
|
|
|
|||
|
|
@ -206,8 +206,37 @@ namespace SeaHaven.Services.Implementation
|
|||
}
|
||||
}
|
||||
|
||||
PortalMediaCountsDTO? mediaCounts = null;
|
||||
if (dispatch.WorkOrderId is int workOrderId)
|
||||
{
|
||||
// Same basis as the upload check: a new completion version replaces the
|
||||
// dispatch's latest document, so that one is not counted for it.
|
||||
var attachmentUrls = await _documentData.ListActiveWorkOrderAttachmentUrlsAsync(
|
||||
workOrderId, cancellationToken);
|
||||
var vendorTypes = await _documentData.ListActiveContentTypesForWorkOrderAsync(
|
||||
workOrderId, null, cancellationToken);
|
||||
var latest = await _documentData.GetLatestForDispatchAsync(id, cancellationToken);
|
||||
var completionTypes = latest == null
|
||||
? vendorTypes
|
||||
: await _documentData.ListActiveContentTypesForWorkOrderAsync(
|
||||
workOrderId, latest.Id, cancellationToken);
|
||||
var (photos, videos) = WorkOrderMediaContract.CountKinds(attachmentUrls, vendorTypes);
|
||||
var (completionPhotos, completionVideos) =
|
||||
WorkOrderMediaContract.CountKinds(attachmentUrls, completionTypes);
|
||||
mediaCounts = new PortalMediaCountsDTO
|
||||
{
|
||||
MaxPhotos = WorkOrderMediaContract.MaxPhotosPerWorkOrder,
|
||||
MaxVideos = WorkOrderMediaContract.MaxVideosPerWorkOrder,
|
||||
Photos = photos,
|
||||
Videos = videos,
|
||||
CompletionPhotos = completionPhotos,
|
||||
CompletionVideos = completionVideos
|
||||
};
|
||||
}
|
||||
|
||||
return new VendorDispatchDetailDTO
|
||||
{
|
||||
MediaCounts = mediaCounts,
|
||||
Id = dispatch.Id,
|
||||
DispatchNumber = dispatch.DispatchNumber,
|
||||
PONumber = dispatch.PONumber,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue