using PayRollDAL.DTO.SAP; using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; namespace PayRollBLL.SAP.Builders { /// /// SAP Infotype 584 — House Property. /// /// SAP confirmed two sub-types, each posted to a separate OData service: /// /// Sub-type 1 — Housing Loan (ZPY_ITDEC_HOUSING_LOAN_SRV / ITDecHL_HeaderSet) /// Source: DeclarationDetailType 3 (Rental Income) → Particulars1 block /// DeclarationDetailType 0 (Home Loan) → Particulars2 / Lender block /// Both rows merged into one SAP item per declaration. /// /// Sub-type 2 — Other Income Sources (endpoint TBC — see TODO comments) /// Source: DeclarationDetailType 6 (Other Income) /// SectionDetail keyword matching distributes amounts into the correct SAP buckets. /// /// returns Sub-type 1 only (used by preview/download endpoints). /// returns both sub-types (used by the SAP push flow). /// public class SAP584Builder : ISapInfotypeBuilder { private const byte RentalIncomeType = 3; private const byte LoanType = 0; private const byte OtherIncomeType = 6; // ── Sub-type 1: Housing Loan ────────────────────────────────────────────────────── public int Infotype => 584; public string DisplayName => "House Property"; public string EndpointPath => "/sap/opu/odata/sap/ZPY_ITDEC_HOUSING_LOAN_SRV/ITDecHL_HeaderSet"; public string MsgNavProperty => "Nav2IT_Dec_HL_Msg"; // ── Sub-type 2: Other Income Sources ───────────────────────────────────────────── // TODO: Replace with confirmed SAP service and entity-set name. private const string OtherIncomeEndpoint = "/sap/opu/odata/sap/ZPY_ITDEC_OTHERINCOME_SRV/ITDecOtherIncomeHeaderSet"; // TODO: Replace with confirmed navigation property name from the SAP OData response. private const string OtherIncomeMsgNavProp = "NAV2OTHERINCOME_MSG"; // ───────────────────────────────────────────────────────────────────────────────── // ISapInfotypeBuilder.BuildAll — overrides default to yield BOTH sub-types // ───────────────────────────────────────────────────────────────────────────────── public IEnumerable BuildAll( IEnumerable rows, string date, string remarks) { // Use a list so the data can be iterated twice (Housing Loan + Other Income) var rowList = rows is IList l ? l : rows.ToList(); // Sub-type 1 — Housing Loan string housingLoanJson = Build(rowList, date, remarks); if (!string.IsNullOrEmpty(housingLoanJson)) yield return new SapSubtypePayload( Infotype, "Housing Loan", EndpointPath, MsgNavProperty, housingLoanJson); // Sub-type 2 — Other Income Sources string otherIncomeJson = BuildOtherIncome(rowList, date, remarks); if (!string.IsNullOrEmpty(otherIncomeJson)) yield return new SapSubtypePayload( Infotype, "Other Income Sources", OtherIncomeEndpoint, OtherIncomeMsgNavProp, otherIncomeJson); } // ───────────────────────────────────────────────────────────────────────────────── // Sub-type 1 — Housing Loan (primary Build, also used by preview endpoints) // ───────────────────────────────────────────────────────────────────────────────── public string Build(IEnumerable rows, string date, string remarks) { var relevant = rows .Where(r => r.DeclarationDetailId.HasValue && (r.DeclarationDetailType == RentalIncomeType || r.DeclarationDetailType == LoanType)) .GroupBy(r => r.DeclarationId) .ToList(); if (relevant.Count == 0) return string.Empty; var items = relevant.Select(g => { var header = g.First(); var rental = g.FirstOrDefault(r => r.DeclarationDetailType == RentalIncomeType); var loan = g.FirstOrDefault(r => r.DeclarationDetailType == LoanType); string propertyType = (rental != null, loan != null) switch { (true, true) => "Partly Rented", (true, false) => "Let Out", (false, true) => "Self Occupied", _ => string.Empty }; double rentalIncome = rental?.RentReceived ?? 0; double localTax = rental?.LocalTax ?? 0; double netAnnualValue = rentalIncome - localTax; double standardDeduction = rental?.StandardDeduction ?? 0; double interestOnBorrow = rental?.Interest ?? loan?.Interest ?? 0; double totalDeduction = standardDeduction + interestOnBorrow; double netIncome = rental?.NetIncome ?? (netAnnualValue - totalDeduction); return new Sap584Item { EmployeeID = header.EmployeeCode, FiscalYear = header.FiscalYear, Type = propertyType, Particulars1 = rental?.Particulars ?? string.Empty, AnnualRentalIncome = SapFmt.Amount(rentalIncome), LocalTaxPaid = SapFmt.Amount(localTax), NetAnnualValue = SapFmt.Amount(netAnnualValue), StandardDeduction = SapFmt.Amount(standardDeduction), InterestOnBorrowal = SapFmt.Amount(interestOnBorrow), TotalDeduction = SapFmt.Amount(totalDeduction), NetIncome = SapFmt.Amount(netIncome), EligibleFor80EEA = "No", PossesionObtained = "Yes", Remarks1 = rental?.DetailRemarks ?? string.Empty, Particulars2 = loan?.Particulars ?? string.Empty, PrinciplePaid = SapFmt.Amount(loan?.Principle ?? 0), InterestPaid = SapFmt.Amount(loan?.Interest ?? 0), Remarks2 = loan?.DetailRemarks ?? string.Empty, LenderName = loan?.ContactName ?? string.Empty, LenderPAN = loan?.ContactPAN ?? string.Empty, LenderTAN = loan?.ContactTAN ?? string.Empty, LenderAddressLine1 = loan?.ContactAddress1 ?? string.Empty, LenderAddressLine2 = loan?.ContactAddress2 ?? string.Empty, LenderAddressLine3 = loan?.ContactAddress3 ?? string.Empty, LenderAddressLine4 = loan?.ContactAddress4 ?? string.Empty, LenderAddressLine5 = loan?.ContactAddress5 ?? string.Empty, }; }).ToList(); var payload = new Sap584Payload { Date = date, Remarks = remarks, Nav2IT_Dec_HL_Item = items, }; return JsonSerializer.Serialize(payload, SapJsonOptions.Plain); } // ───────────────────────────────────────────────────────────────────────────────── // Sub-type 2 — Other Income Sources // // Each declaration can have multiple Type-6 detail rows, one per income section. // SectionDetail keyword matching distributes amounts into the SAP income buckets. // Unrecognised sections fall into OtherIncome. // // TODO: Verify keyword mappings against the actual SectionDetail values in MTDSSECTIONDETAIL. // TODO: Verify SAP field names once the SAP team provides the confirmed service spec. // ───────────────────────────────────────────────────────────────────────────────── private string BuildOtherIncome(IEnumerable rows, string date, string remarks) { var relevant = rows .Where(r => r.DeclarationDetailId.HasValue && r.DeclarationDetailType == OtherIncomeType) .GroupBy(r => r.DeclarationId) .ToList(); if (relevant.Count == 0) return string.Empty; var items = relevant.Select(g => { var header = g.First(); double businessProfit = 0; double ltcgNormal = 0; double ltcgSpecial = 0; double stCapitalGains = 0; double dividends = 0; double interest = 0; double digitalIncome = 0; double otherIncome = 0; foreach (var row in g) { string section = (row.SectionDetail ?? string.Empty).ToLowerInvariant(); double amount = row.DeclaredAmount; if (section.Contains("business") || section.Contains("profit")) businessProfit += amount; else if (section.Contains("long term") && (section.Contains("special") || section.Contains("equity") || section.Contains("share") || section.Contains("fund"))) ltcgSpecial += amount; else if (section.Contains("long term")) ltcgNormal += amount; else if (section.Contains("short term")) stCapitalGains += amount; else if (section.Contains("dividend")) dividends += amount; else if (section.Contains("interest") || section.Contains("saving") || section.Contains("fixed deposit") || section.Contains("fd ")) interest += amount; else if (section.Contains("digital") || section.Contains("online") || section.Contains("winning") || section.Contains("lottery") || section.Contains("game")) digitalIncome += amount; else otherIncome += amount; } return new Sap584OtherIncomeItem { EmployeeID = header.EmployeeCode, FiscalYear = header.FiscalYear, BusinessProfit = SapFmt.Amount(businessProfit), LTCGNormal = SapFmt.Amount(ltcgNormal), LTCGSpecial = SapFmt.Amount(ltcgSpecial), STCapitalGains = SapFmt.Amount(stCapitalGains), Dividends = SapFmt.Amount(dividends), Interest = SapFmt.Amount(interest), DigitalIncome = SapFmt.Amount(digitalIncome), OtherIncome = SapFmt.Amount(otherIncome), }; }).ToList(); var payload = new Sap584OtherIncomePayload { Date = date, Remarks = remarks, NAV2OTHERINCOME_ITEM = items, }; return JsonSerializer.Serialize(payload, SapJsonOptions.Plain); } } }