| Line 886... |
Line 886... |
| 886 |
float closingBalance = walletService.getOpeningTill(fofoId, endDate);
|
886 |
float closingBalance = walletService.getOpeningTill(fofoId, endDate);
|
| 887 |
UserWallet uw = walletService.getUserWallet(fofoId);
|
887 |
UserWallet uw = walletService.getUserWallet(fofoId);
|
| 888 |
LOGGER.info("Start date - {}, end Date - {}", startDate, endDate);
|
888 |
LOGGER.info("Start date - {}, end Date - {}", startDate, endDate);
|
| 889 |
List<UserWalletHistory> history = userWalletHistoryRepository.selectPaginatedByWalletId(uw.getId(), startDate, endDate, 0, 0);
|
889 |
List<UserWalletHistory> history = userWalletHistoryRepository.selectPaginatedByWalletId(uw.getId(), startDate, endDate, 0, 0);
|
| 890 |
//LOGGER.info("Data - {}", history.stream().filter(x -> x.getReferenceType().equals(WalletReferenceType.OTHERS)).collect(Collectors.groupingBy(x -> x.getBusinessTimestamp().toLocalDate())));
|
890 |
//LOGGER.info("Data - {}", history.stream().filter(x -> x.getReferenceType().equals(WalletReferenceType.OTHERS)).collect(Collectors.groupingBy(x -> x.getBusinessTimestamp().toLocalDate())));
|
| - |
|
891 |
// Raw scheme entries (CN_WALLET_REFERENCES) are normally shown as the month-end MARGINS credit-note line; but for a
|
| - |
|
892 |
// statement that does NOT end on a month-end the trailing month's CN isn't issued yet, so we fall back to these
|
| - |
|
893 |
// raw entries for that partial month (see populateData).
|
| - |
|
894 |
List<UserWalletHistory> schemeEntries = history.stream()
|
| - |
|
895 |
.filter(x -> WalletService.CN_WALLET_REFERENCES.contains(x.getReferenceType()))
|
| - |
|
896 |
.sorted(Comparator.comparing(UserWalletHistory::getId)).collect(Collectors.toList());
|
| 891 |
// Body shows genuine money movements. Exclude: PURCHASE (rebuilt via billing); the loan cash legs
|
897 |
// Body shows genuine money movements. Exclude: PURCHASE (rebuilt via billing); the loan cash legs
|
| 892 |
// CREDIT_LIMIT/CREDIT_UTILIZED (represented by the Interest Accrued line + opening/closing loan); and the
|
898 |
// CREDIT_LIMIT/CREDIT_UTILIZED (represented by the Interest Accrued line + opening/closing loan); and the
|
| 893 |
// scheme set CN_WALLET_REFERENCES (shown instead as the month-end MARGINS credit-note line).
|
899 |
// scheme set CN_WALLET_REFERENCES (shown instead as the month-end MARGINS credit-note line / partial fallback).
|
| 894 |
history = history.stream().filter(x -> !x.getReferenceType().equals(WalletReferenceType.PURCHASE))
|
900 |
history = history.stream().filter(x -> !x.getReferenceType().equals(WalletReferenceType.PURCHASE))
|
| 895 |
.filter(x -> !x.getReferenceType().equals(WalletReferenceType.CREDIT_LIMIT))
|
901 |
.filter(x -> !x.getReferenceType().equals(WalletReferenceType.CREDIT_LIMIT))
|
| 896 |
.filter(x -> !x.getReferenceType().equals(WalletReferenceType.CREDIT_UTILIZED))
|
902 |
.filter(x -> !x.getReferenceType().equals(WalletReferenceType.CREDIT_UTILIZED))
|
| 897 |
.filter(x -> !WalletService.CN_WALLET_REFERENCES.contains(x.getReferenceType()))
|
903 |
.filter(x -> !WalletService.CN_WALLET_REFERENCES.contains(x.getReferenceType()))
|
| 898 |
.sorted(Comparator.comparing(UserWalletHistory::getId)).collect(Collectors.toList());
|
904 |
.sorted(Comparator.comparing(UserWalletHistory::getId)).collect(Collectors.toList());
|
| Line 909... |
Line 915... |
| 909 |
float openingLoan = loanStatementRepository.getLoanPrincipalAtDate(fofoId, startDate) + loanStatementRepository.getLoanInterestAtDate(fofoId, startDate);
|
915 |
float openingLoan = loanStatementRepository.getLoanPrincipalAtDate(fofoId, startDate) + loanStatementRepository.getLoanInterestAtDate(fofoId, startDate);
|
| 910 |
float closingLoan = loanStatementRepository.getLoanPrincipalAtDate(fofoId, endDate) + loanStatementRepository.getLoanInterestAtDate(fofoId, endDate);
|
916 |
float closingLoan = loanStatementRepository.getLoanPrincipalAtDate(fofoId, endDate) + loanStatementRepository.getLoanInterestAtDate(fofoId, endDate);
|
| 911 |
float interestAccruedCost = loanStatementRepository.getInterestAccruedBetween(fofoId, startDate, endDate);
|
917 |
float interestAccruedCost = loanStatementRepository.getInterestAccruedBetween(fofoId, startDate, endDate);
|
| 912 |
LOGGER.info("OpeningPI - {}, ClosingPI - {}, OpeningLoan - {}, ClosingLoan - {}, InterestAccrued - {}", openingPendingAmount, closingPendingAmount, openingLoan, closingLoan, interestAccruedCost);
|
918 |
LOGGER.info("OpeningPI - {}, ClosingPI - {}, OpeningLoan - {}, ClosingLoan - {}, InterestAccrued - {}", openingPendingAmount, closingPendingAmount, openingLoan, closingLoan, interestAccruedCost);
|
| 913 |
ByteArrayOutputStream byteArrayOutputStream = this.populateData(is, openingBalance + openingPendingAmount - openingLoan, closingBalance + closingPendingAmount - closingLoan,
|
919 |
ByteArrayOutputStream byteArrayOutputStream = this.populateData(is, openingBalance + openingPendingAmount - openingLoan, closingBalance + closingPendingAmount - closingLoan,
|
| 914 |
customRetailer, history, startDate, endDate, billedStatementDetails, returnDetails, interestAccruedCost);
|
920 |
customRetailer, history, startDate, endDate, billedStatementDetails, returnDetails, schemeEntries, interestAccruedCost);
|
| 915 |
|
921 |
|
| 916 |
final HttpHeaders headers = new HttpHeaders();
|
922 |
final HttpHeaders headers = new HttpHeaders();
|
| 917 |
headers.set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
923 |
headers.set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
| 918 |
headers.set("Content-disposition", "inline; filename=account-statement." + StringUtils.toHyphenatedString(startDate.toLocalDate()) + "-" + StringUtils.toHyphenatedString(endDate.toLocalDate()) + ".xlsx");
|
924 |
headers.set("Content-disposition", "inline; filename=account-statement." + StringUtils.toHyphenatedString(startDate.toLocalDate()) + "-" + StringUtils.toHyphenatedString(endDate.toLocalDate()) + ".xlsx");
|
| 919 |
headers.setContentLength(byteArrayOutputStream.toByteArray().length);
|
925 |
headers.setContentLength(byteArrayOutputStream.toByteArray().length);
|
| Line 955... |
Line 961... |
| 955 |
}
|
961 |
}
|
| 956 |
}
|
962 |
}
|
| 957 |
|
963 |
|
| 958 |
private ByteArrayOutputStream populateData(InputStream is, float openingBalance, float closingBalance, CustomRetailer customRetailer, List<UserWalletHistory> history,
|
964 |
private ByteArrayOutputStream populateData(InputStream is, float openingBalance, float closingBalance, CustomRetailer customRetailer, List<UserWalletHistory> history,
|
| 959 |
LocalDateTime startLocalDateTime, LocalDateTime endLocalDateTime, List<StatementDetailModel> invoiceDetails,
|
965 |
LocalDateTime startLocalDateTime, LocalDateTime endLocalDateTime, List<StatementDetailModel> invoiceDetails,
|
| 960 |
List<StatementDetailModel> returnDetails, float interestAccruedCost) throws Exception {
|
966 |
List<StatementDetailModel> returnDetails, List<UserWalletHistory> schemeEntries, float interestAccruedCost) throws Exception {
|
| 961 |
Map<LocalDate, List<StatementDetailModel>> dateInvoiceMap = invoiceDetails.stream().collect(Collectors.groupingBy(x -> x.getOnDate().toLocalDate()));
|
967 |
Map<LocalDate, List<StatementDetailModel>> dateInvoiceMap = invoiceDetails.stream().collect(Collectors.groupingBy(x -> x.getOnDate().toLocalDate()));
|
| 962 |
Map<LocalDate, List<StatementDetailModel>> dateReturnMap = returnDetails == null ? new HashMap<>()
|
968 |
Map<LocalDate, List<StatementDetailModel>> dateReturnMap = returnDetails == null ? new HashMap<>()
|
| 963 |
: returnDetails.stream().collect(Collectors.groupingBy(x -> x.getOnDate().toLocalDate()));
|
969 |
: returnDetails.stream().collect(Collectors.groupingBy(x -> x.getOnDate().toLocalDate()));
|
| 964 |
//LOGGER.info("dateInvoiceMap - {}", dateInvoiceMap);
|
970 |
//LOGGER.info("dateInvoiceMap - {}", dateInvoiceMap);
|
| 965 |
Map<LocalDate, List<UserWalletHistory>> dateWalletMap = history.stream().collect(Collectors.groupingBy(x -> x.getTimestamp().toLocalDate()));
|
971 |
Map<LocalDate, List<UserWalletHistory>> dateWalletMap = history.stream().collect(Collectors.groupingBy(x -> x.getTimestamp().toLocalDate()));
|
| Line 1069... |
Line 1075... |
| 1069 |
sheet.getRow(row).createCell(5).setCellValue(currentOpening);
|
1075 |
sheet.getRow(row).createCell(5).setCellValue(currentOpening);
|
| 1070 |
sheet.getRow(row).createCell(6).setCellValue("Return (Against Invoice " + ret.getInvoiceNumber() + ")");
|
1076 |
sheet.getRow(row).createCell(6).setCellValue("Return (Against Invoice " + ret.getInvoiceNumber() + ")");
|
| 1071 |
row += 1;
|
1077 |
row += 1;
|
| 1072 |
}
|
1078 |
}
|
| 1073 |
}
|
1079 |
}
|
| 1074 |
// Month-end credit notes (shown before the interest line): all CN types EXCEPT RETURNS (returns come from
|
1080 |
// Month-end scheme/margin: one "Margin / Scheme Payout" line PER credit note (MARGINS credit; CN_CANCELLATION
|
| 1075 |
// returnorderinfo via the billing query). MARGINS = scheme/margin credit; CN_CANCELLATION = debit (reverses a
|
1081 |
// debit, reverses a MARGINS CN), keyed by margin_month. RETURNS are sourced from returnorderinfo and skipped.
|
| 1076 |
// MARGINS CN). Any CN→raw-scheme or cross-period timing difference lands in the reconciling diff line below.
|
1082 |
// Per CN: net >=0 => credit (CN); net <0 => debit (DN), reducing the balance.
|
| 1077 |
if (YearMonth.from(date).atEndOfMonth().equals(date)) {
|
1083 |
if (YearMonth.from(date).atEndOfMonth().equals(date)) {
|
| 1078 |
List<CreditNote> creditNotes = creditNoteRepository.selectByMarginMonth(customRetailer.getPartnerId(), YearMonth.from(date));
|
1084 |
List<CreditNote> creditNotes = creditNoteRepository.selectByMarginMonth(customRetailer.getPartnerId(), YearMonth.from(date));
|
| 1079 |
if (creditNotes != null) {
|
1085 |
if (creditNotes != null) {
|
| 1080 |
for (CreditNote creditNote : creditNotes) {
|
1086 |
for (CreditNote creditNote : creditNotes) {
|
| 1081 |
// RETURNS are sourced from returnorderinfo (billing query), not from the CN; skip them here.
|
- |
|
| 1082 |
if (creditNote.getType() == CreditNoteType.RETURNS) {
|
1087 |
if (creditNote.getType() == CreditNoteType.RETURNS) {
|
| 1083 |
continue;
|
1088 |
continue;
|
| 1084 |
}
|
1089 |
}
|
| 1085 |
double cnAmount = creditNoteLineRepository.selectAllByCreditNote(creditNote.getId()).stream().collect(Collectors.summingDouble(x -> x.getAmount()));
|
1090 |
double cnAmount = creditNoteLineRepository.selectAllByCreditNote(creditNote.getId()).stream().collect(Collectors.summingDouble(x -> x.getAmount()));
|
| 1086 |
// CN_CANCELLATION reverses a previously-issued MARGINS credit note; line amount is stored positive but must debit.
|
- |
|
| 1087 |
if (creditNote.getType() == CreditNoteType.CN_CANCELLATION) {
|
1091 |
if (creditNote.getType() == CreditNoteType.CN_CANCELLATION) {
|
| 1088 |
cnAmount = -cnAmount;
|
1092 |
cnAmount = -cnAmount;
|
| 1089 |
}
|
1093 |
}
|
| - |
|
1094 |
if (Math.abs(cnAmount) <= 0.01) {
|
| - |
|
1095 |
continue;
|
| - |
|
1096 |
}
|
| - |
|
1097 |
boolean isCredit = cnAmount >= 0;
|
| 1090 |
Cell dateCell = sheet.createRow(row).createCell(0);
|
1098 |
Cell dateCell = sheet.createRow(row).createCell(0);
|
| 1091 |
dateCell.setCellValue(new Date(date.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()));
|
1099 |
dateCell.setCellValue(new Date(date.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()));
|
| 1092 |
dateCell.setCellStyle(dateStyle);
|
1100 |
dateCell.setCellStyle(dateStyle);
|
| 1093 |
// Net -ve (e.g. negative net margin, or a cancellation) is a debit note.
|
- |
|
| 1094 |
sheet.getRow(row).createCell(1).setCellValue(cnAmount >= 0 ? "CREDIT NOTE" : "DEBIT NOTE");
|
1101 |
sheet.getRow(row).createCell(1).setCellValue("Margin / Scheme Payout");
|
| 1095 |
sheet.getRow(row).createCell(2).setCellValue(creditNote.getCreditNoteNumber());
|
1102 |
sheet.getRow(row).createCell(2).setCellValue(creditNote.getCreditNoteNumber());
|
| 1096 |
if (cnAmount > 0) {
|
1103 |
if (isCredit) {
|
| 1097 |
sheet.getRow(row).createCell(3).setCellValue(0);
|
1104 |
sheet.getRow(row).createCell(3).setCellValue(0);
|
| 1098 |
sheet.getRow(row).createCell(4).setCellValue(cnAmount);
|
1105 |
sheet.getRow(row).createCell(4).setCellValue(cnAmount);
|
| 1099 |
grandTotalCredit += cnAmount;
|
1106 |
grandTotalCredit += (long) cnAmount;
|
| 1100 |
} else {
|
1107 |
} else {
|
| 1101 |
sheet.getRow(row).createCell(3).setCellValue(-cnAmount);
|
1108 |
sheet.getRow(row).createCell(3).setCellValue(-cnAmount);
|
| 1102 |
sheet.getRow(row).createCell(4).setCellValue(0);
|
1109 |
sheet.getRow(row).createCell(4).setCellValue(0);
|
| 1103 |
grandTotalDebit += -cnAmount;
|
1110 |
grandTotalDebit += (long) -cnAmount;
|
| 1104 |
}
|
1111 |
}
|
| 1105 |
currentOpening += cnAmount;
|
1112 |
currentOpening += cnAmount;
|
| 1106 |
sheet.getRow(row).createCell(5).setCellValue(currentOpening);
|
1113 |
sheet.getRow(row).createCell(5).setCellValue(currentOpening);
|
| 1107 |
sheet.getRow(row).createCell(6).setCellValue(creditNote.getType() == CreditNoteType.CN_CANCELLATION
|
1114 |
sheet.getRow(row).createCell(6).setCellValue(isCredit ? "Credit Note (CN)" : "Debit Note (DN)");
|
| 1108 |
? "Margin Cancellation" : "Scheme / Margin");
|
- |
|
| 1109 |
row += 1;
|
1115 |
row += 1;
|
| 1110 |
}
|
1116 |
}
|
| 1111 |
}
|
1117 |
}
|
| 1112 |
}
|
1118 |
}
|
| 1113 |
}
|
1119 |
}
|
| 1114 |
|
1120 |
|
| - |
|
1121 |
// Partial-month fallback (net computed here, rendered as ONE line just before the FLOAT DIFF below): if the
|
| - |
|
1122 |
// statement does NOT end on a month-end, the trailing month's MARGINS CN isn't issued yet, so consolidate that
|
| - |
|
1123 |
// month's raw scheme entries into a single line. Full months already used their month-end CN above; only the
|
| - |
|
1124 |
// trailing month's entries are summed here.
|
| - |
|
1125 |
double schemeFallbackNet = 0d;
|
| - |
|
1126 |
if (!YearMonth.from(endDate).atEndOfMonth().equals(endDate) && schemeEntries != null) {
|
| - |
|
1127 |
LocalDate partialStart = YearMonth.from(endDate).atDay(1);
|
| - |
|
1128 |
for (UserWalletHistory s : schemeEntries) {
|
| - |
|
1129 |
if (!s.getTimestamp().toLocalDate().isBefore(partialStart)) {
|
| - |
|
1130 |
schemeFallbackNet += s.getAmount();
|
| - |
|
1131 |
}
|
| - |
|
1132 |
}
|
| - |
|
1133 |
}
|
| - |
|
1134 |
|
| 1115 |
// --- Reconciling lines at period end ---
|
1135 |
// --- Reconciling lines at period end ---
|
| 1116 |
// Interest accrued (charged, non-cash) is the only standing-moving loan piece. Principal movement and interest
|
1136 |
// Interest accrued (charged, non-cash) is the only standing-moving loan piece. Principal movement and interest
|
| 1117 |
// PAID are funded by wallet cash (the excluded CREDIT_LIMIT/CREDIT_UTILIZED legs) and net to ~0, so they are not
|
1137 |
// PAID are funded by wallet cash (the excluded CREDIT_LIMIT/CREDIT_UTILIZED legs) and net to ~0, so they are not
|
| 1118 |
// shown as balance lines. Accrued interest is a debit (the partner's liability grows).
|
1138 |
// shown as balance lines. Accrued interest is a debit (the partner's liability grows).
|
| 1119 |
if (Math.abs(interestAccruedCost) > 0.01f) {
|
1139 |
if (Math.abs(interestAccruedCost) > 0.01f) {
|
| Line 1128... |
Line 1148... |
| 1128 |
currentOpening -= interestAccruedCost;
|
1148 |
currentOpening -= interestAccruedCost;
|
| 1129 |
sheet.getRow(row).createCell(5).setCellValue(currentOpening);
|
1149 |
sheet.getRow(row).createCell(5).setCellValue(currentOpening);
|
| 1130 |
sheet.getRow(row).createCell(6).setCellValue("SD Credit — Interest accrued (financing cost)");
|
1150 |
sheet.getRow(row).createCell(6).setCellValue("SD Credit — Interest accrued (financing cost)");
|
| 1131 |
row++;
|
1151 |
row++;
|
| 1132 |
}
|
1152 |
}
|
| - |
|
1153 |
// Single consolidated scheme/margin line for a partial (non-month-end) trailing month (computed above). Placed
|
| - |
|
1154 |
// here, at the end of the statement just before the float adjustment. Net >=0 => credit (CN); net <0 => debit (DN).
|
| - |
|
1155 |
if (Math.abs(schemeFallbackNet) > 0.01) {
|
| - |
|
1156 |
boolean isCredit = schemeFallbackNet >= 0;
|
| - |
|
1157 |
Cell dateCell = sheet.createRow(row).createCell(0);
|
| - |
|
1158 |
dateCell.setCellValue(new Date(endLocalDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()));
|
| - |
|
1159 |
dateCell.setCellStyle(dateStyle);
|
| - |
|
1160 |
sheet.getRow(row).createCell(1).setCellValue("Margin / Scheme Payout");
|
| - |
|
1161 |
sheet.getRow(row).createCell(2).setCellValue("");
|
| - |
|
1162 |
if (isCredit) {
|
| - |
|
1163 |
sheet.getRow(row).createCell(3).setCellValue(0);
|
| - |
|
1164 |
sheet.getRow(row).createCell(4).setCellValue(schemeFallbackNet);
|
| - |
|
1165 |
grandTotalCredit += (long) schemeFallbackNet;
|
| - |
|
1166 |
} else {
|
| - |
|
1167 |
sheet.getRow(row).createCell(3).setCellValue(-schemeFallbackNet);
|
| - |
|
1168 |
sheet.getRow(row).createCell(4).setCellValue(0);
|
| - |
|
1169 |
grandTotalDebit += (long) -schemeFallbackNet;
|
| - |
|
1170 |
}
|
| - |
|
1171 |
currentOpening += schemeFallbackNet;
|
| - |
|
1172 |
sheet.getRow(row).createCell(5).setCellValue(currentOpening);
|
| - |
|
1173 |
sheet.getRow(row).createCell(6).setCellValue(isCredit ? "Credit Note (CN)" : "Debit Note (DN)");
|
| - |
|
1174 |
row++;
|
| - |
|
1175 |
}
|
| 1133 |
// Float-difference line: lands the running balance exactly on the formula closing. With clean data this is only
|
1176 |
// Float-difference line: lands the running balance exactly on the formula closing. With clean data this is only
|
| 1134 |
// a few rupees of rounding/float; a large value signals a data issue (e.g. MARGINS CN not yet rolled out) and is
|
1177 |
// a few rupees of rounding/float; a large value signals a data issue (e.g. MARGINS CN not yet rolled out) and is
|
| 1135 |
// surfaced by the reconciliation warning below rather than being a real ledger movement.
|
1178 |
// surfaced by the reconciliation warning below rather than being a real ledger movement.
|
| 1136 |
double floatDiff = closingBalance - currentOpening;
|
1179 |
double floatDiff = closingBalance - currentOpening;
|
| 1137 |
if (Math.abs(floatDiff) > 0.01) {
|
1180 |
if (Math.abs(floatDiff) > 0.01) {
|