Added an API to get list of delivery challan by purchase invoice ID
This commit is contained in:
parent
41feb58d45
commit
0fe59223e2
@ -40,7 +40,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
/// <param name="cancellationToken">Token to propagate notification that operations should be canceled.</param>
|
||||
/// <returns>A HTTP 200 OK response with a list of purchase invoices or an appropriate HTTP error code.</returns>
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetPurchaseInvoiceListAsync([FromQuery] string? searchString, [FromQuery] string? filter, CancellationToken cancellationToken, [FromQuery] bool isActive = true,
|
||||
public async Task<IActionResult> GetPurchaseInvoiceList([FromQuery] string? searchString, [FromQuery] string? filter, CancellationToken cancellationToken, [FromQuery] bool isActive = true,
|
||||
[FromQuery] int pageSize = 20, [FromQuery] int pageNumber = 1)
|
||||
{
|
||||
// Get the currently logged-in employee
|
||||
@ -54,7 +54,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
}
|
||||
|
||||
[HttpGet("details/{id}")]
|
||||
public async Task<IActionResult> GetPurchaseInvoiceDetailsAsync(Guid id, CancellationToken cancellationToken)
|
||||
public async Task<IActionResult> GetPurchaseInvoiceDetails(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
// Get the currently logged-in employee
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
@ -125,6 +125,14 @@ namespace Marco.Pms.Services.Controllers
|
||||
|
||||
#region =================================================================== Delivery Challan Functions ===================================================================
|
||||
|
||||
[HttpGet("delivery-challan/list/{purchaseInvoiceId}")]
|
||||
public async Task<IActionResult> GetDeliveryChallans(Guid purchaseInvoiceId, CancellationToken cancellationToken)
|
||||
{
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _purchaseInvoiceService.GetDeliveryChallansAsync(purchaseInvoiceId, loggedInEmployee, tenantId, cancellationToken);
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a delivery challan.
|
||||
/// </summary>
|
||||
|
||||
@ -116,7 +116,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
using var context = _dbContextFactory.CreateDbContext();
|
||||
return await context.ProjectAllocations
|
||||
.AsNoTracking()
|
||||
.CountAsync(pa => pa.ProjectId == project.Id && pa.IsActive); // Server-side count is efficient
|
||||
.CountAsync(pa => pa.ProjectId == project.Id && pa.TenantId == project.TenantId && pa.IsActive); // Server-side count is efficient
|
||||
});
|
||||
|
||||
// This task fetches the entire infrastructure hierarchy and performs aggregations in the database.
|
||||
@ -127,26 +127,26 @@ namespace Marco.Pms.Services.Helpers
|
||||
// 1. Fetch all hierarchical data using projections.
|
||||
// This is still a chain, but it's inside one task and much faster due to projections.
|
||||
var buildings = await context.Buildings.AsNoTracking()
|
||||
.Where(b => b.ProjectId == project.Id)
|
||||
.Where(b => b.ProjectId == project.Id && b.TenantId == project.TenantId)
|
||||
.Select(b => new { b.Id, b.ProjectId, b.Name, b.Description })
|
||||
.ToListAsync();
|
||||
var buildingIds = buildings.Select(b => b.Id).ToList();
|
||||
|
||||
var floors = await context.Floor.AsNoTracking()
|
||||
.Where(f => buildingIds.Contains(f.BuildingId))
|
||||
.Where(f => buildingIds.Contains(f.BuildingId) && f.TenantId == project.TenantId)
|
||||
.Select(f => new { f.Id, f.BuildingId, f.FloorName })
|
||||
.ToListAsync();
|
||||
var floorIds = floors.Select(f => f.Id).ToList();
|
||||
|
||||
var workAreas = await context.WorkAreas.AsNoTracking()
|
||||
.Where(wa => floorIds.Contains(wa.FloorId))
|
||||
.Where(wa => floorIds.Contains(wa.FloorId) && wa.TenantId == project.TenantId)
|
||||
.Select(wa => new { wa.Id, wa.FloorId, wa.AreaName })
|
||||
.ToListAsync();
|
||||
var workAreaIds = workAreas.Select(wa => wa.Id).ToList();
|
||||
|
||||
// 2. THE KEY OPTIMIZATION: Aggregate work items in the database.
|
||||
var workSummaries = await context.WorkItems.AsNoTracking()
|
||||
.Where(wi => workAreaIds.Contains(wi.WorkAreaId))
|
||||
.Where(wi => workAreaIds.Contains(wi.WorkAreaId) && wi.TenantId == project.TenantId)
|
||||
.GroupBy(wi => wi.WorkAreaId) // Group by parent on the DB server
|
||||
.Select(g => new // Let the DB do the SUM
|
||||
{
|
||||
@ -281,6 +281,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
var projectStatusIds = projects.Select(p => p.ProjectStatusId).Distinct().ToList();
|
||||
var promotorIds = projects.Select(p => p.PromoterId).Distinct().ToList();
|
||||
var pmcsIds = projects.Select(p => p.PMCId).Distinct().ToList();
|
||||
var tenantIds = projects.Select(p => p.TenantId).Distinct().ToList();
|
||||
|
||||
// --- Step 1: Fetch all required data in maximum parallel ---
|
||||
// Each task uses its own DbContext and selects only the required columns (projection).
|
||||
@ -320,7 +321,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
// Server-side aggregation and projection into a dictionary
|
||||
return await context.ProjectAllocations
|
||||
.AsNoTracking()
|
||||
.Where(pa => projectIds.Contains(pa.ProjectId) && pa.IsActive)
|
||||
.Where(pa => projectIds.Contains(pa.ProjectId) && tenantIds.Contains(pa.TenantId) && pa.IsActive)
|
||||
.GroupBy(pa => pa.ProjectId)
|
||||
.Select(g => new { ProjectId = g.Key, Count = g.Count() })
|
||||
.ToDictionaryAsync(x => x.ProjectId, x => x.Count);
|
||||
@ -331,7 +332,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
using var context = _dbContextFactory.CreateDbContext();
|
||||
return await context.Buildings
|
||||
.AsNoTracking()
|
||||
.Where(b => projectIds.Contains(b.ProjectId))
|
||||
.Where(b => projectIds.Contains(b.ProjectId) && tenantIds.Contains(b.TenantId))
|
||||
.Select(b => new { b.Id, b.ProjectId, b.Name, b.Description }) // Projection
|
||||
.ToListAsync();
|
||||
});
|
||||
@ -345,7 +346,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
using var context = _dbContextFactory.CreateDbContext();
|
||||
return await context.Floor
|
||||
.AsNoTracking()
|
||||
.Where(f => buildingIds.Contains(f.BuildingId))
|
||||
.Where(f => buildingIds.Contains(f.BuildingId) && tenantIds.Contains(f.TenantId))
|
||||
.Select(f => new { f.Id, f.BuildingId, f.FloorName }) // Projection
|
||||
.ToListAsync();
|
||||
});
|
||||
@ -359,7 +360,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
using var context = _dbContextFactory.CreateDbContext();
|
||||
return await context.WorkAreas
|
||||
.AsNoTracking()
|
||||
.Where(wa => floorIds.Contains(wa.FloorId))
|
||||
.Where(wa => floorIds.Contains(wa.FloorId) && tenantIds.Contains(wa.TenantId))
|
||||
.Select(wa => new { wa.Id, wa.FloorId, wa.AreaName }) // Projection
|
||||
.ToListAsync();
|
||||
});
|
||||
@ -376,7 +377,7 @@ namespace Marco.Pms.Services.Helpers
|
||||
// Let the DB do the SUM. This is much faster and transfers less data.
|
||||
return await context.WorkItems
|
||||
.AsNoTracking()
|
||||
.Where(wi => workAreaIds.Contains(wi.WorkAreaId))
|
||||
.Where(wi => workAreaIds.Contains(wi.WorkAreaId) && tenantIds.Contains(wi.TenantId))
|
||||
.GroupBy(wi => wi.WorkAreaId)
|
||||
.Select(g => new
|
||||
{
|
||||
|
||||
@ -886,6 +886,86 @@ namespace Marco.Pms.Services.Service
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Delivery Challan Functions ===================================================================
|
||||
|
||||
public async Task<ApiResponse<List<DeliveryChallanVM>>> GetDeliveryChallansAsync(Guid purchaseInvoiceId, Employee loggedInEmployee, Guid tenantId, CancellationToken ct)
|
||||
{
|
||||
// 1. Setup Context
|
||||
// Using a factory ensures a clean context for this specific unit of work.
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync(ct);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInfo("GetDeliveryChallans: Fetching challans. InvoiceId: {InvoiceId}, Tenant: {TenantId}", purchaseInvoiceId, tenantId);
|
||||
|
||||
// 2. Optimized Validation
|
||||
// Use AnyAsync() instead of FirstOrDefaultAsync().
|
||||
// We only need to know if it *exists*, we don't need to load the data into memory.
|
||||
var isInvoiceValid = await context.PurchaseInvoiceDetails
|
||||
.AsNoTracking()
|
||||
.AnyAsync(pid => pid.Id == purchaseInvoiceId && pid.TenantId == tenantId, ct);
|
||||
|
||||
if (!isInvoiceValid)
|
||||
{
|
||||
_logger.LogWarning("GetDeliveryChallans: Invoice not found. InvoiceId: {InvoiceId}", purchaseInvoiceId);
|
||||
return ApiResponse<List<DeliveryChallanVM>>.ErrorResponse("Invalid Purchase Invoice", "The specified purchase invoice does not exist.", 404);
|
||||
}
|
||||
|
||||
// 3. Data Retrieval
|
||||
// Fetch only valid records with necessary related data.
|
||||
var deliveryChallanEntities = await context.DeliveryChallanDetails
|
||||
.AsNoTracking()
|
||||
.Include(dc => dc.PurchaseInvoice)
|
||||
.Include(dc => dc.Attachment).ThenInclude(pia => pia!.Document)
|
||||
.Where(dc => dc.PurchaseInvoiceId == purchaseInvoiceId
|
||||
&& dc.TenantId == tenantId
|
||||
&& dc.Attachment != null
|
||||
&& dc.Attachment.Document != null) // Ensure strict data integrity
|
||||
.ToListAsync(ct);
|
||||
|
||||
// 4. Early Exit for Empty Lists
|
||||
// Returns an empty list with 200 OK immediately, avoiding unnecessary mapping/looping.
|
||||
if (!deliveryChallanEntities.Any())
|
||||
{
|
||||
_logger.LogInfo("GetDeliveryChallans: No challans found for InvoiceId: {InvoiceId}", purchaseInvoiceId);
|
||||
return ApiResponse<List<DeliveryChallanVM>>.SuccessResponse(new List<DeliveryChallanVM>(), "No delivery challans found.", 200);
|
||||
}
|
||||
|
||||
// 5. Mapping and Transformation
|
||||
// We map the entities to View Models first, then apply business logic (S3 URLs).
|
||||
// Using Map<List<T>> is generally more efficient than mapping inside a Select loop for complex objects.
|
||||
|
||||
// Enhance VMs with Signed URLs
|
||||
// We iterate through the already-mapped list to populate non-database fields.
|
||||
// Zip or standard for-loop could be used, but since we mapped a list, we need to match them up.
|
||||
// Note: Automapper preserves order, so index matching works, but iterating the Source Entity to populate the Dest VM is safer.
|
||||
|
||||
var responseList = deliveryChallanEntities.Select(dc =>
|
||||
{
|
||||
var result = _mapper.Map<DeliveryChallanVM>(dc);
|
||||
if (dc.Attachment?.Document != null)
|
||||
{
|
||||
result.Attachment!.PreSignedUrl = _s3Service.GeneratePreSignedUrl(dc.Attachment.Document.S3Key);
|
||||
|
||||
// Fallback logic for thumbnail
|
||||
var thumbKey = !string.IsNullOrEmpty(dc.Attachment.Document.ThumbS3Key)
|
||||
? dc.Attachment.Document.ThumbS3Key
|
||||
: dc.Attachment.Document.S3Key;
|
||||
|
||||
result.Attachment.ThumbPreSignedUrl = _s3Service.GeneratePreSignedUrl(thumbKey);
|
||||
}
|
||||
return result;
|
||||
}).ToList();
|
||||
|
||||
_logger.LogInfo("GetDeliveryChallans: Successfully returned {Count} items.", responseList.Count);
|
||||
|
||||
return ApiResponse<List<DeliveryChallanVM>>.SuccessResponse(responseList, "List of delivery challans fetched successfully.", 200);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "GetDeliveryChallans: An error occurred. InvoiceId: {InvoiceId}", purchaseInvoiceId);
|
||||
return ApiResponse<List<DeliveryChallanVM>>.ErrorResponse("Internal Server Error", "An unexpected error occurred while fetching delivery challans.", 500);
|
||||
}
|
||||
}
|
||||
public async Task<ApiResponse<DeliveryChallanVM>> AddDeliveryChallanAsync(DeliveryChallanDto model, Employee loggedInEmployee, Guid tenantId, CancellationToken ct)
|
||||
{
|
||||
// 1. Input Validation - Fail Fast
|
||||
@ -923,7 +1003,7 @@ namespace Marco.Pms.Services.Service
|
||||
// Note: We project only what we need or map later to avoid EF translation issues with complex Mappers.
|
||||
var purchaseInvoiceEntity = await context.PurchaseInvoiceDetails
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(pid => pid.Id == model.PurchaseInvoiceId && pid.TenantId == tenantId, ct);
|
||||
.FirstOrDefaultAsync(pid => pid.Id == model.PurchaseInvoiceId && pid.IsActive && pid.TenantId == tenantId, ct);
|
||||
|
||||
if (purchaseInvoiceEntity == null)
|
||||
{
|
||||
|
||||
@ -18,6 +18,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Delivery Challan Functions ===================================================================
|
||||
Task<ApiResponse<List<DeliveryChallanVM>>> GetDeliveryChallansAsync(Guid purchaseInvoiceId, Employee loggedInEmployee, Guid tenantId, CancellationToken ct);
|
||||
Task<ApiResponse<DeliveryChallanVM>> AddDeliveryChallanAsync(DeliveryChallanDto model, Employee loggedInEmployee, Guid tenantId, CancellationToken ct);
|
||||
#endregion
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user