using System.Text.Json; using ComplianceBLL.Adapter; using ComplianceDAL.CustomCode.AdapterConfig; using ComplianceDAL.CustomCode.ComplianceArtifact; using ComplianceDAL.CustomCode.GSTReturn; using ComplianceDAL.DTO.ComplianceArtifact; using ComplianceDAL.DTO.GSTReturn; using GB5Shared.DTO.Framework.Login; using GB5Shared.Resource.GB5Solution; using GB5Shared.Validation; using Microsoft.Extensions.Logging; namespace ComplianceBLL.GSTReturn; public class GSTReturnBLL : IGSTReturnBLL { private readonly IGSTReturnDAL _dal; private readonly IComplianceArtifactDAL _artifactDal; private readonly IAdapterConfigDAL _adapterConfigDal; private readonly IComplianceAdapterFactory _adapterFactory; private readonly IValidation _validation; private readonly ILogger _logger; public GSTReturnBLL( IGSTReturnDAL dal, IComplianceArtifactDAL artifactDal, IAdapterConfigDAL adapterConfigDal, IComplianceAdapterFactory adapterFactory, IValidation validation, ILogger logger) { _dal = dal; _artifactDal = artifactDal; _adapterConfigDal = adapterConfigDal; _adapterFactory = adapterFactory; _validation = validation; _logger = logger; } public async Task GetReturnStatusAsync(GetReturnStatusRequestDTO request, LoginDTO login, CancellationToken ct) { await _validation.NotEmpty(request.GSTIN, nameof(request.GSTIN)); await _validation.NotEmpty(request.ReturnPeriod, nameof(request.ReturnPeriod)); var (year, month) = ParseReturnPeriodYearMonth(request.ReturnPeriod); var period = await _dal.GetCompliancePeriodAsync( request.Framework, request.GSTIN, request.ReturnType, year, month, login, ct).ConfigureAwait(false); return JsonSerializer.Serialize(period); } public async Task GetReturnStatusListAsync(ReturnStatusListFilterDTO filter, LoginDTO login, CancellationToken ct) { var list = await _dal.GetPeriodListAsync(filter, login, ct).ConfigureAwait(false); return JsonSerializer.Serialize(list); } public async Task SyncReturnStatusFromPortalAsync(SyncReturnStatusRequestDTO request, GSTHeaderDTO header, LoginDTO login, CancellationToken ct) { await _validation.NotEmpty(request.GSTIN, nameof(request.GSTIN)); await _validation.NotEmpty(request.ReturnPeriod, nameof(request.ReturnPeriod)); if (string.IsNullOrEmpty(header?.AuthToken) || string.IsNullOrEmpty(header.Sek)) return JsonSerializer.Serialize(new { success = false, message = "Please request OTP and authenticate before syncing return status." }); header.ReturnPeriod = request.ReturnPeriod; var adapterConfig = await _adapterConfigDal.GetAdapterConfigByGSTIN(request.GSTIN, "GST_INDIA", login, ct).ConfigureAwait(false); if (adapterConfig is null) return JsonSerializer.Serialize(new { success = false, message = ComplianceResource.AdapterConfigNotFound }); var adapter = _adapterFactory.GetAdapter(adapterConfig); var result = await adapter.GetReturnStatusAsync( string.Empty, header, adapterConfig, ct).ConfigureAwait(false); if (!result.IsSuccess) { _logger.LogWarning("SyncReturnStatus failed for GSTIN {GSTIN} Period {Period}: {Error}", request.GSTIN, request.ReturnPeriod, result.ErrorMessage); return JsonSerializer.Serialize(new { success = false, message = ComplianceResource.PortalError, errorMessage = result.ErrorMessage }); } // Parse portal response and update local period status var (year, month) = ParseReturnPeriodYearMonth(request.ReturnPeriod); var portalStatus = ExtractStatusFromPortalResponse(result.RawResponse, request.ReturnType); await _dal.UpdatePeriodStatusAsync( "GST_INDIA", request.GSTIN, request.ReturnType, year, month, portalStatus.status, portalStatus.ackNumber, portalStatus.confirmationNumber, login.UserId, login, ct).ConfigureAwait(false); _logger.LogInformation("Synced return status: GSTIN={GSTIN} Period={Period} Status={Status}", request.GSTIN, request.ReturnPeriod, portalStatus.status); return JsonSerializer.Serialize(new { success = true, status = portalStatus.status, ackNumber = portalStatus.ackNumber }); } public async Task GetGSTR3BDataAsync(string gstin, string returnPeriod, LoginDTO login, CancellationToken ct) { await _validation.NotEmpty(gstin, nameof(gstin)); await _validation.NotEmpty(returnPeriod, nameof(returnPeriod)); var raw = await _dal.GetGSTR3BOutwardSectionAsync(gstin, returnPeriod, login, ct).ConfigureAwait(false); if (raw is null) return JsonSerializer.Serialize(new { success = false, message = "No outward data found for this period. Run SyncGSTOutward first." }); var section31 = new GSTR3BSection31DTO { TaxableValueA = raw.TaxableValueA, IGSTAmountA = raw.IGSTAmountA - raw.CDNRIGSTAmount, CGSTAmountA = raw.CGSTAmountA - raw.CDNRCGSTAmount, SGSTAmountA = raw.SGSTAmountA - raw.CDNRSGSTAmount, CessAmountA = raw.CessAmountA, TaxableValueB = raw.TaxableValueB, IGSTAmountB = raw.IGSTAmountB, CessAmountB = raw.CessAmountB, TaxableValueC = raw.TaxableValueC, CDNRIGSTAmount = raw.CDNRIGSTAmount, CDNRCGSTAmount = raw.CDNRCGSTAmount, CDNRSGSTAmount = raw.CDNRSGSTAmount }; var netIGST = section31.IGSTAmountA + section31.IGSTAmountB; var netCGST = section31.CGSTAmountA; var netSGST = section31.SGSTAmountA; var netCess = section31.CessAmountA + section31.CessAmountB; var gstr3b = new GSTR3BDTO { GSTIN = gstin, ReturnPeriod = returnPeriod, Section31 = section31, Summary = new GSTR3BSummaryDTO { NetIGST = netIGST, NetCGST = netCGST, NetSGST = netSGST, NetCess = netCess, TotalTaxLiability = netIGST + netCGST + netSGST + netCess } }; return JsonSerializer.Serialize(gstr3b); } public async Task SubmitGSTR3BAsync(SubmitGSTR3BRequestDTO request, GSTHeaderDTO header, LoginDTO login, CancellationToken ct) { await _validation.NotEmpty(request.GSTIN, nameof(request.GSTIN)); await _validation.NotEmpty(request.ReturnPeriod, nameof(request.ReturnPeriod)); await _validation.NotEmpty(request.EVCOtp, nameof(request.EVCOtp)); if (string.IsNullOrEmpty(header?.AuthToken) || string.IsNullOrEmpty(header.Sek)) return JsonSerializer.Serialize(new { success = false, message = "Please request OTP and authenticate before submitting this return." }); header.ReturnPeriod = request.ReturnPeriod; var gstr3bJson = await GetGSTR3BDataAsync(request.GSTIN, request.ReturnPeriod, login, ct).ConfigureAwait(false); var adapterConfig = await _adapterConfigDal.GetAdapterConfigByGSTIN(request.GSTIN, "GST_INDIA", login, ct).ConfigureAwait(false); if (adapterConfig is null) return JsonSerializer.Serialize(new { success = false, message = ComplianceResource.AdapterConfigNotFound }); var adapter = _adapterFactory.GetAdapter(adapterConfig); var saveResult = await adapter.SaveReturnAsync( "GSTR3B", gstr3bJson, header, adapterConfig, ct).ConfigureAwait(false); if (!saveResult.IsSuccess) return JsonSerializer.Serialize(new { success = false, message = ComplianceResource.PortalError, errorMessage = saveResult.ErrorMessage }); header.EvcOTP = request.EVCOtp; var fileResult = await adapter.FileReturnAsync( "GSTR3B", header, adapterConfig, ct).ConfigureAwait(false); await _artifactDal.SaveAudit(new ComplianceAuditDTO { Action = "API_CALL", RequestPayload = gstr3bJson, ResponsePayload = fileResult.RawResponse, PreviousState = "NotStarted", NewState = fileResult.IsSuccess ? "Filed" : "NotStarted", ProviderCode = "GSTCafe", HttpStatusCode = fileResult.HttpStatusCode, ReferenceNumber = fileResult.ReferenceId, ErrorMessage = fileResult.ErrorMessage, AttemptNumber = 1, UserId = login.UserId }, login, ct).ConfigureAwait(false); if (!fileResult.IsSuccess) { _logger.LogWarning("SubmitGSTR3B filing failed for GSTIN {GSTIN} Period {Period}: {Error}", request.GSTIN, request.ReturnPeriod, fileResult.ErrorMessage); return JsonSerializer.Serialize(new { success = false, message = ComplianceResource.PortalError, errorMessage = fileResult.ErrorMessage }); } var (year, month) = ParseReturnPeriodYearMonth(request.ReturnPeriod); await _dal.UpdatePeriodStatusAsync( "GST_INDIA", request.GSTIN, "GSTR3B", year, month, "Filed", string.Empty, fileResult.ReferenceId, login.UserId, login, ct).ConfigureAwait(false); _logger.LogInformation("GSTR-3B filed: GSTIN={GSTIN} Period={Period} AckNumber={AckNumber}", request.GSTIN, request.ReturnPeriod, fileResult.ReferenceId); return JsonSerializer.Serialize(new { success = true, message = ComplianceResource.ReturnFiled, ackNumber = fileResult.ReferenceId }); } // ── Private helpers ─────────────────────────────────────────────────────── private static (int year, int month) ParseReturnPeriodYearMonth(string returnPeriod) { var month = int.Parse(returnPeriod[..2]); var year = int.Parse(returnPeriod[2..]); return (year, month); } private static (string status, string ackNumber, string confirmationNumber) ExtractStatusFromPortalResponse( string rawResponse, string returnType) { try { using var doc = JsonDocument.Parse(rawResponse); var root = doc.RootElement; var status = root.TryGetProperty("status_cd", out var sc) ? sc.GetString() ?? "NotStarted" : "NotStarted"; var ackNumber = root.TryGetProperty("arn", out var arn) ? arn.GetString() ?? string.Empty : string.Empty; var confirmNum = root.TryGetProperty("confirm_no", out var cn) ? cn.GetString() ?? string.Empty : string.Empty; var localStatus = status switch { "P" or "Filed" => "Filed", "S" or "Submitted" => "Submitted", _ => "NotStarted" }; return (localStatus, ackNumber, confirmNum); } catch { return ("NotStarted", string.Empty, string.Empty); } } }