using DXPBLL.Common; using DXPBLL.VendorPo; using DXPDAL.Common; using DXPDAL.Kyc; using DXPDAL.VendorPo; using GB5Shared.Telemetry; namespace DXPBLL.VendorInvoice; public class VendorInvoiceBLL : IVendorInvoiceBLL { private readonly IVendorPoDAL _VendorPoDAL; private readonly IPartyKycDAL _KycDAL; private readonly IMmIntegrationService _MmIntegration; private readonly IDXPSystemContext _SystemContext; private readonly IDXPHubNotifier _HubNotifier; public VendorInvoiceBLL( IVendorPoDAL vendorPoDAL, IPartyKycDAL kycDAL, IMmIntegrationService mmIntegration, IDXPSystemContext systemContext, IDXPHubNotifier hubNotifier) { _VendorPoDAL = vendorPoDAL; _KycDAL = kycDAL; _MmIntegration = mmIntegration; _SystemContext = systemContext; _HubNotifier = hubNotifier; } public async Task<(string Message, VendorPoDTO? Invoice)> SubmitInvoiceAsync( int dxpPartyId, int poMMHeadId, int localPartyId, int ouId, int invoiceBizTransactionTypeId, int dxpPartyBranchLinkId, string? invoiceRemarks, 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."); // GST invoicing requires a verified GSTIN — reject unverified/rejected/missing KYC rather // than let an unverified vendor submit a document Compliance would later have to untangle. var systemLogin = _SystemContext.GetSystemLogin(actingDxpUserId); var kyc = await _KycDAL.GetActiveByBranchLinkAsync(dxpPartyBranchLinkId, systemLogin, ct).ConfigureAwait(false); if (kyc is null || kyc.VerificationStatus != 2) throw new InvalidOperationException("KYC for this branch must be verified before submitting an invoice."); 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 invoice."); GB5Trace.Step("submit-dxp-invoice", new { poMMHeadId, invoiceBizTransactionTypeId }); // Same generic MM document-save pattern as AsnBLL — see its comments for the full // rationale (metadata-driven BizTransactionType, response-is-a-message-not-an-id, etc.). // Phase 1 invoices the full PO-ordered quantity/rate per line; partial/adjusted invoicing // is a Phase 2 enhancement. var request = new MmDocumentSaveRequestDTO { BizTransactionTypeId = invoiceBizTransactionTypeId, OUId = ouId, PartyId = localPartyId, MMHeadReferenceNumber = po.MMHeadNumber, MMHeadReferenceDate = po.MMHeadDate, MMHeadRemarks = invoiceRemarks, MMDetailArray = poDetails.Select(d => new MmDocumentDetailSaveRequestDTO { MMDetailSlNo = d.MMDetailSlNo, ItemId = d.ItemId, MMDetailTransactionQuantity = d.MMDetailTransactionQuantity, TransactionUOMId = d.TransactionUOMId, MMDetailRate = d.MMDetailRate }).ToList() }; var outcome = await _MmIntegration.SaveMmDocumentAsync(request, tenantLogin, ct).ConfigureAwait(false); if (!outcome.Success) { GB5Trace.MarkFailed("submit-dxp-invoice-mm-save-failed", new InvalidOperationException(outcome.Error)); return ($"Invoice submission failed: {outcome.Error}", null); } var invoice = await _VendorPoDAL.FindNewlyCreatedDocumentAsync( po.MMHeadNumber, invoiceBizTransactionTypeId, localPartyId, tenantLogin, ct).ConfigureAwait(false); if (invoice != null) { await _HubNotifier.NotifyAsync(new DXPNotificationDTO { EventType = DXPNotificationEventTypes.InvoiceSubmitted, DxpPartyId = dxpPartyId, MMHeadId = invoice.MMHeadId, ReferenceNumber = invoice.MMHeadNumber, Message = $"Invoice {invoice.MMHeadNumber} submitted for PO {po.MMHeadNumber}.", OccurredOn = DateTime.UtcNow }, ct).ConfigureAwait(false); } return ($"Invoice submitted successfully{(invoice != null ? $" with number {invoice.MMHeadNumber}" : string.Empty)}.", invoice); } }