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);
}
}
}