using DXPBLL.Common; using DXPBLL.VendorPo; using DXPDAL.Common; using DXPDAL.VendorPo; using GB5Shared.Attachment; using GB5Shared.DTO.ECM; using GB5Shared.DTO.Framework.Login; using GB5Shared.Telemetry; using static GB5Shared.GB5Constant.Constant; namespace DXPBLL.Asn; public class AsnBLL : IAsnBLL { private readonly IVendorPoDAL _VendorPoDAL; private readonly IMmIntegrationService _MmIntegration; private readonly IDXPSystemContext _SystemContext; private readonly IAttachmentUploadService _AttachmentUploadService; private readonly IAttachmentResolveDAL _AttachmentResolveDAL; private readonly IDXPHubNotifier _HubNotifier; public AsnBLL( IVendorPoDAL vendorPoDAL, IMmIntegrationService mmIntegration, IDXPSystemContext systemContext, IAttachmentUploadService attachmentUploadService, IAttachmentResolveDAL attachmentResolveDAL, IDXPHubNotifier hubNotifier) { _VendorPoDAL = vendorPoDAL; _MmIntegration = mmIntegration; _SystemContext = systemContext; _AttachmentUploadService = attachmentUploadService; _AttachmentResolveDAL = attachmentResolveDAL; _HubNotifier = hubNotifier; } public async Task<(string Message, VendorPoDTO? Asn)> CreateAsnAsync( int dxpPartyId, int poMMHeadId, int localPartyId, int ouId, int asnBizTransactionTypeId, string? dispatchRemarks, Guid rowGuid, int tenantId, string databaseName, byte databaseType, int actingDxpUserId, CancellationToken ct) { var tenantLogin = _SystemContext.GetTenantLogin(tenantId, databaseName, databaseType, localPartyId, actingDxpUserId); var links = await _VendorPoDAL.GetPartyOuLinksAsync(localPartyId, tenantLogin, ct).ConfigureAwait(false); if (!links.Any(l => l.OUId == ouId)) throw new InvalidOperationException("This party is not linked to the requested Organization Unit."); var po = await _VendorPoDAL.GetPoHeaderAsync(poMMHeadId, localPartyId, tenantLogin, ct).ConfigureAwait(false) ?? throw new InvalidOperationException("Purchase Order not found for this party."); var poDetails = (await _VendorPoDAL.GetPoDetailsAsync(poMMHeadId, tenantLogin, ct).ConfigureAwait(false)).ToList(); if (poDetails.Count == 0) throw new InvalidOperationException("Purchase Order has no line items to ship."); GB5Trace.Step("create-dxp-asn", new { poMMHeadId, asnBizTransactionTypeId }); var request = new MmDocumentSaveRequestDTO { BizTransactionTypeId = asnBizTransactionTypeId, OUId = ouId, PartyId = localPartyId, MMHeadReferenceNumber = po.MMHeadNumber, MMHeadReferenceDate = po.MMHeadDate, MMHeadRemarks = dispatchRemarks, // Phase 1 ships the full PO-ordered quantity per line — partial/split shipment is a // Phase 2 enhancement (would need a caller-supplied dispatched-qty per line instead // of reusing the PO's own ordered quantity verbatim). MMDetailArray = poDetails.Select(d => new MmDocumentDetailSaveRequestDTO { MMDetailSlNo = d.MMDetailSlNo, ItemId = d.ItemId, MMDetailTransactionQuantity = d.MMDetailTransactionQuantity, TransactionUOMId = d.TransactionUOMId, MMDetailRate = d.MMDetailRate // StoreId intentionally left at its -1 default — Phase 1 does not resolve a // dispatch store from the party's linked OU; a real deployment would source this // from PartyOU configuration or a caller-supplied store selection. }).ToList() }; var outcome = await _MmIntegration.SaveMmDocumentAsync(request, tenantLogin, ct).ConfigureAwait(false); if (!outcome.Success) { GB5Trace.MarkFailed("create-dxp-asn-mm-save-failed", new InvalidOperationException(outcome.Error)); return ($"ASN creation failed: {outcome.Error}", null); } // MM's response is a message string, not the new DocumentId — look the row up ourselves // (see VendorPoQB.FIND_NEWLY_CREATED_DOCUMENT for why this is a defensible approach). var asn = await _VendorPoDAL.FindNewlyCreatedDocumentAsync( po.MMHeadNumber, asnBizTransactionTypeId, localPartyId, tenantLogin, ct).ConfigureAwait(false); if (asn != null && rowGuid != Guid.Empty) { // Backfill any Delivery Challan uploaded pre-save (ObjectId=0, tagged with rowGuid) // to the now-known MMHeadId — same UAS two-phase pattern as KYC (Phase 1.2), but // resolved in the TENANT's database this time, since that's where the ASN row lives. await _AttachmentResolveDAL.ResolveAsync( EntityConstant.OBJECTDXPASN, rowGuid, asn.MMHeadId, tenantLogin, ct).ConfigureAwait(false); } if (asn != null) { await _HubNotifier.NotifyAsync(new DXPNotificationDTO { EventType = DXPNotificationEventTypes.AsnCreated, DxpPartyId = dxpPartyId, MMHeadId = asn.MMHeadId, ReferenceNumber = asn.MMHeadNumber, Message = $"ASN {asn.MMHeadNumber} created for PO {po.MMHeadNumber}.", OccurredOn = DateTime.UtcNow }, ct).ConfigureAwait(false); } return ($"ASN created successfully{(asn != null ? $" with number {asn.MMHeadNumber}" : string.Empty)}.", asn); } public async Task UploadDeliveryChallanAsync( AttachmentUploadRequest request, Stream fileStream, string originalFileName, string contentType, int tenantId, string databaseName, byte databaseType, int localPartyId, int actingDxpUserId, CancellationToken ct) { var tenantLogin = _SystemContext.GetTenantLogin(tenantId, databaseName, databaseType, localPartyId, actingDxpUserId); return await _AttachmentUploadService .UploadAsync(request, fileStream, originalFileName, contentType, tenantLogin, ct) .ConfigureAwait(false); } }