using System.Text.Json; using ComplianceBLL.Common; using ComplianceDAL.CustomCode.GSTR9; using ComplianceDAL.DTO.Analytics; using GB5Shared.DTO.Framework.Login; using Microsoft.Extensions.Logging; namespace ComplianceBLL.GSTR9; public class GSTR9BLL( IGSTR9DAL dal, ILogger logger) : IGSTR9BLL { public async Task GetGSTR9DataAsync( string gstin, string returnPeriod, LoginDTO login, CancellationToken ct) { try { // returnPeriod is MMYYYY (e.g. "062026") — same shape used across the rest of the GST // module. Binding this straight to an int (as PeriodYear used to) silently misparsed it // as the raw number 62026 instead of year 2026; validate the shape and take just the year. if (!ReturnPeriodHelper.IsValidShape(returnPeriod)) return JsonSerializer.Serialize(new { success = false, message = $"ReturnPeriod '{returnPeriod}' must be in MMYYYY format." }); var (periodYear, _) = ReturnPeriodHelper.ParseReturnPeriodYearMonth(returnPeriod); var missingMonths = await dal.GetMissingPeriodMonthsAsync(gstin, periodYear, login, ct); if (missingMonths.Count > 0) logger.LogWarning("GSTR-9 for {GSTIN} year {Year}: missing period summaries for months {Months}", gstin, periodYear, string.Join(", ", missingMonths)); var (rows, hsnRows) = await ( dal.GetAnnualSummaryRowsAsync(gstin, periodYear, returnPeriod, login, ct), dal.GetAnnualHSNSummaryAsync(gstin, periodYear, returnPeriod, login, ct) ).WhenAll(); var data = BuildGSTR9Data(gstin, periodYear, rows, hsnRows); return JsonSerializer.Serialize(data); } catch (Exception ex) { logger.LogError(ex, "GetGSTR9Data failed for GSTIN {GSTIN} ReturnPeriod {ReturnPeriod}", gstin, returnPeriod); throw; } } public async Task GetGSTR9PreviewAsync( string gstin, string returnPeriod, LoginDTO login, CancellationToken ct) // Preview is identical structure to data — only difference is it signals the client // this is draft/unconfirmed. Return the same payload with a Preview flag. => await GetGSTR9DataAsync(gstin, returnPeriod, login, ct); private static GSTR9DataDTO BuildGSTR9Data( string gstin, int periodYear, List rows, List hsnRows) { var gstr1Rows = rows.Where(r => r.SummaryType == "GSTR1").ToList(); var gstr3bRows = rows.Where(r => r.SummaryType == "GSTR3B").ToList(); return new GSTR9DataDTO { GSTIN = gstin, FinancialYear = periodYear, OutwardSupplies = new GSTR9Section4DTO { TotalTaxableValue = gstr1Rows.Sum(r => r.TotalTaxableValue), TotalIGST = gstr1Rows.Sum(r => r.TotalIGST), TotalCGST = gstr1Rows.Sum(r => r.TotalCGST), TotalSGST = gstr1Rows.Sum(r => r.TotalSGST), TotalCess = gstr1Rows.Sum(r => r.TotalCess) }, ITCAvailed = new GSTR9Section6DTO { TotalITCIGST = gstr3bRows.Sum(r => r.TotalITCIGST), TotalITCCGST = gstr3bRows.Sum(r => r.TotalITCCGST), TotalITCSGST = gstr3bRows.Sum(r => r.TotalITCSGST) }, ITCReversals = new GSTR9Section7DTO { // Net ITC = ITC availed (ITCAvailed fields are already net in TCOMPLIANCE_PERIOD_SUMMARY) NetITCIGST = gstr3bRows.Sum(r => r.TotalITCIGST), NetITCCGST = gstr3bRows.Sum(r => r.TotalITCCGST), NetITCSGST = gstr3bRows.Sum(r => r.TotalITCSGST) }, TaxPaid = new GSTR9Section9DTO { TaxPaidIGST = gstr1Rows.Sum(r => r.TotalIGST), TaxPaidCGST = gstr1Rows.Sum(r => r.TotalCGST), TaxPaidSGST = gstr1Rows.Sum(r => r.TotalSGST) }, DifferentialTax = new GSTR9Section14DTO { HSNRows = hsnRows } }; } } file static class TaskExtensions { public static async Task<(T1, T2)> WhenAll(this (Task t1, Task t2) tasks) { await Task.WhenAll(tasks.t1, tasks.t2); // Tasks are already complete after WhenAll — await is free (no blocking) return (await tasks.t1, await tasks.t2); } }