tango-app-api-payment-subscription 3.5.31 → 3.5.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -460,6 +460,8 @@ export async function brandInvoiceList( req, res ) {
|
|
|
460
460
|
dateFrom = now.startOf( 'month' ).toDate();
|
|
461
461
|
} else if ( req.body.durationFilter === 'prev' ) {
|
|
462
462
|
dateFrom = now.subtract( 1, 'month' ).startOf( 'month' ).toDate();
|
|
463
|
+
} else if ( req.body.durationFilter === 'last3' ) {
|
|
464
|
+
dateFrom = now.subtract( 3, 'month' ).startOf( 'month' ).toDate();
|
|
463
465
|
} else if ( req.body.durationFilter === 'last6' ) {
|
|
464
466
|
dateFrom = now.subtract( 6, 'month' ).startOf( 'month' ).toDate();
|
|
465
467
|
} else if ( req.body.durationFilter === 'last12' ) {
|
|
@@ -183,7 +183,11 @@ export async function createInvoice( req, res ) {
|
|
|
183
183
|
stores: Number( req.body.stores ) || 0,
|
|
184
184
|
currency: req.body.currency || 'inr',
|
|
185
185
|
billingDate: baseDate.toDate(),
|
|
186
|
-
|
|
186
|
+
// Payment term (days) chosen at creation. Persisted on the invoice so
|
|
187
|
+
// the term + due date are read from the invoice everywhere, not the
|
|
188
|
+
// billing group's default. Falls back to 30.
|
|
189
|
+
paymentTerm: Number( req.body.paymentTerm ) > 0 ? Number( req.body.paymentTerm ) : 30,
|
|
190
|
+
dueDate: req.body.dueDate ? new Date( req.body.dueDate ) : baseDate.add( Number( req.body.paymentTerm ) > 0 ? Number( req.body.paymentTerm ) : 30, 'days' ).toDate(),
|
|
187
191
|
monthOfbilling: baseDate.format( 'MM' ),
|
|
188
192
|
paymentMethod: 'Online',
|
|
189
193
|
status: 'pendingCsm',
|
|
@@ -558,8 +562,20 @@ export async function createInvoice( req, res ) {
|
|
|
558
562
|
products: products,
|
|
559
563
|
status: 'pendingCsm',
|
|
560
564
|
amount: roundAmount( amount, group.currency ),
|
|
565
|
+
// Regenerate reuses the SAME invoice number, so it must also carry the
|
|
566
|
+
// SAME numbering fields the old doc had — otherwise the regenerated
|
|
567
|
+
// invoice loses its identity in the counters. For a real invoice that's
|
|
568
|
+
// invoiceIndex; for a still-draft invoice it's tempIndex. Dropping the
|
|
569
|
+
// draft's tempIndex orphaned its sequence, so the next fresh draft
|
|
570
|
+
// reused that number → duplicate DRAFT-<FY>-NNNNN. Carry both over from
|
|
571
|
+
// findInvoice on regenerate; fresh creates get the running temp seq.
|
|
561
572
|
invoiceIndex: req.body.invoiceId ? findInvoice.invoiceIndex : null,
|
|
562
|
-
|
|
573
|
+
// Regenerate: keep the old draft's tempIndex. Legacy drafts created
|
|
574
|
+
// before tempIndex existed have none — give them the running temp seq so
|
|
575
|
+
// the number is still tracked (never left unindexed, which caused reuse).
|
|
576
|
+
tempIndex: req.body.invoiceId ?
|
|
577
|
+
( findInvoice.tempIndex != null ? findInvoice.tempIndex : tempSeqForThis ) :
|
|
578
|
+
tempSeqForThis,
|
|
563
579
|
tax: taxList,
|
|
564
580
|
companyName: ( group.registeredCompanyName || '' ).toUpperCase(),
|
|
565
581
|
companyAddress: address,
|
|
@@ -864,15 +880,14 @@ export async function invoiceDownload( req, res ) {
|
|
|
864
880
|
if ( invoiceInfo.groupId ) {
|
|
865
881
|
getgroup = await billingService.findOne( { _id: invoiceInfo.groupId } );
|
|
866
882
|
}
|
|
867
|
-
let days = getgroup?.paymentTerm ? getgroup?.paymentTerm : '30';
|
|
883
|
+
let days = invoiceInfo.paymentTerm ? invoiceInfo.paymentTerm : ( getgroup?.paymentTerm ? getgroup?.paymentTerm : '30' );
|
|
868
884
|
let dueDate = invoiceInfo?.dueDate ? dayjs( invoiceInfo?.dueDate ).format( 'DD/MM/YYYY' ) : dayjs().add( days, 'days' ).format( 'DD/MM/YYYY' );
|
|
869
|
-
let virtualAccount = await paymentAccountService.findOneAccount( { clientId: invoiceInfo.clientId } );
|
|
870
885
|
|
|
871
886
|
invoiceData = {
|
|
872
887
|
...invoiceInfo._doc,
|
|
873
888
|
clientName: clientDetails.clientName,
|
|
874
889
|
amount: roundAmount( invoiceInfo.amount, invoiceInfo.currency ).toLocaleString( 'en-IN', moneyFmt ),
|
|
875
|
-
extendDays: getgroup?.paymentTerm ? getgroup?.paymentTerm : '30',
|
|
890
|
+
extendDays: invoiceInfo.paymentTerm ? invoiceInfo.paymentTerm : ( getgroup?.paymentTerm ? getgroup?.paymentTerm : '30' ),
|
|
876
891
|
address: clientDetails.billingDetails.billingAddress,
|
|
877
892
|
subtotal: roundAmount( invoiceInfo.amount, invoiceInfo.currency ).toLocaleString( 'en-IN', moneyFmt ),
|
|
878
893
|
companyName: invoiceInfo.companyName,
|
|
@@ -881,7 +896,7 @@ export async function invoiceDownload( req, res ) {
|
|
|
881
896
|
GSTNumber: invoiceInfo.GSTNumber,
|
|
882
897
|
PoNum: invoiceInfo.purchaseOrderNumber || getgroup?.po || '',
|
|
883
898
|
amountwords: AmountinWords,
|
|
884
|
-
Terms: `Term ${getgroup?.paymentTerm ? getgroup?.paymentTerm : '30'}`,
|
|
899
|
+
Terms: `Term ${invoiceInfo.paymentTerm ? invoiceInfo.paymentTerm : ( getgroup?.paymentTerm ? getgroup?.paymentTerm : '30' )}`,
|
|
885
900
|
currencyType: invoiceCurrency,
|
|
886
901
|
currencyWord: wordFor( invoiceInfo.currency ),
|
|
887
902
|
totalAmount: invoiceInfo.totalAmount.toLocaleString( 'en-IN', moneyFmt ),
|
|
@@ -898,9 +913,12 @@ export async function invoiceDownload( req, res ) {
|
|
|
898
913
|
billingState: getgroup?.state,
|
|
899
914
|
billingCity: getgroup?.city,
|
|
900
915
|
billingPinCode: getgroup?.pinCode,
|
|
901
|
-
billingCurrency:
|
|
902
|
-
|
|
903
|
-
|
|
916
|
+
billingCurrency: getgroup?.currency,
|
|
917
|
+
// Bank account shown on invoice emails is the fixed Tango HDFC account
|
|
918
|
+
// (matches the invoice PDFs and invoiceBankDetails), not the per-client
|
|
919
|
+
// paymentaccounts record.
|
|
920
|
+
virtualaccountNumber: '50200027441433',
|
|
921
|
+
virtualifsc: 'HDFC0000386',
|
|
904
922
|
// GST applies only to domestic (INR) invoices. Gate the tax block on the
|
|
905
923
|
// invoice's OWN currency — not the payment-account currency, which is
|
|
906
924
|
// null/non-inr for many INR invoices and was dropping the GST rows.
|
|
@@ -1063,16 +1081,14 @@ async function buildInvoicePdfBuffer( invoiceId ) {
|
|
|
1063
1081
|
if ( invoiceInfo.groupId ) {
|
|
1064
1082
|
getgroup = await billingService.findOne( { _id: invoiceInfo.groupId } );
|
|
1065
1083
|
}
|
|
1066
|
-
let days = getgroup?.paymentTerm ? getgroup?.paymentTerm : '30';
|
|
1084
|
+
let days = invoiceInfo.paymentTerm ? invoiceInfo.paymentTerm : ( getgroup?.paymentTerm ? getgroup?.paymentTerm : '30' );
|
|
1067
1085
|
let dueDate = invoiceInfo?.dueDate ? dayjs( invoiceInfo?.dueDate ).format( 'DD/MM/YYYY' ) : dayjs().add( days, 'days' ).format( 'DD/MM/YYYY' );
|
|
1068
1086
|
|
|
1069
|
-
let virtualAccount = await paymentAccountService.findOneAccount( { clientId: invoiceInfo.clientId } );
|
|
1070
|
-
|
|
1071
1087
|
let invoiceData = {
|
|
1072
1088
|
...invoiceInfo._doc,
|
|
1073
1089
|
clientName: clientDetails.clientName,
|
|
1074
1090
|
amount: roundAmount( invoiceInfo.amount, invoiceInfo.currency ).toLocaleString( 'en-IN', moneyFmt ),
|
|
1075
|
-
extendDays: getgroup?.paymentTerm ? getgroup?.paymentTerm : '30',
|
|
1091
|
+
extendDays: invoiceInfo.paymentTerm ? invoiceInfo.paymentTerm : ( getgroup?.paymentTerm ? getgroup?.paymentTerm : '30' ),
|
|
1076
1092
|
address: clientDetails.billingDetails.billingAddress,
|
|
1077
1093
|
subtotal: roundAmount( invoiceInfo.amount, invoiceInfo.currency ).toLocaleString( 'en-IN', moneyFmt ),
|
|
1078
1094
|
companyName: invoiceInfo.companyName,
|
|
@@ -1081,7 +1097,7 @@ async function buildInvoicePdfBuffer( invoiceId ) {
|
|
|
1081
1097
|
GSTNumber: invoiceInfo.GSTNumber,
|
|
1082
1098
|
PoNum: invoiceInfo.purchaseOrderNumber || getgroup?.po || '',
|
|
1083
1099
|
amountwords: AmountinWords,
|
|
1084
|
-
Terms: `Term ${getgroup?.paymentTerm ? getgroup?.paymentTerm : '30'}`,
|
|
1100
|
+
Terms: `Term ${invoiceInfo.paymentTerm ? invoiceInfo.paymentTerm : ( getgroup?.paymentTerm ? getgroup?.paymentTerm : '30' )}`,
|
|
1085
1101
|
currencyType: invoiceCurrency,
|
|
1086
1102
|
currencyWord: wordFor( invoiceInfo.currency ),
|
|
1087
1103
|
totalAmount: invoiceInfo.totalAmount.toLocaleString( 'en-IN', moneyFmt ),
|
|
@@ -1098,9 +1114,11 @@ async function buildInvoicePdfBuffer( invoiceId ) {
|
|
|
1098
1114
|
billingState: getgroup?.state,
|
|
1099
1115
|
billingCity: getgroup?.city,
|
|
1100
1116
|
billingPinCode: getgroup?.pinCode,
|
|
1101
|
-
billingCurrency:
|
|
1102
|
-
|
|
1103
|
-
|
|
1117
|
+
billingCurrency: getgroup?.currency,
|
|
1118
|
+
// Fixed Tango HDFC account (matches the invoice PDFs), not the per-client
|
|
1119
|
+
// paymentaccounts record.
|
|
1120
|
+
virtualaccountNumber: '50200027441433',
|
|
1121
|
+
virtualifsc: 'HDFC0000386',
|
|
1104
1122
|
// GST applies only to domestic (INR) invoices; gate on the invoice currency.
|
|
1105
1123
|
gstApplicable: invoiceInfo.currency === 'inr',
|
|
1106
1124
|
};
|
|
@@ -1375,7 +1393,6 @@ export async function invoiceBulkEmailSend( req, res ) {
|
|
|
1375
1393
|
|
|
1376
1394
|
const clientId = approved[0].clientId;
|
|
1377
1395
|
const clientDetails = await clientService.findOne( { clientId } );
|
|
1378
|
-
const virtualAccount = await paymentAccountService.findOneAccount( { clientId } );
|
|
1379
1396
|
|
|
1380
1397
|
// Subject month is derived from the first approved invoice's billing month,
|
|
1381
1398
|
// mirroring the single-invoice email convention (billingDate, else
|
|
@@ -1395,11 +1412,14 @@ export async function invoiceBulkEmailSend( req, res ) {
|
|
|
1395
1412
|
const template = Handlebars.compile( templateHtml );
|
|
1396
1413
|
const mailbody = template( {
|
|
1397
1414
|
clientName: clientDetails?.clientName || '',
|
|
1415
|
+
subjectMonth,
|
|
1398
1416
|
currencyType,
|
|
1399
1417
|
totalExcl: totalExclNum.toLocaleString( 'en-IN', moneyFmt ),
|
|
1400
1418
|
totalIncl: totalInclNum.toLocaleString( 'en-IN', moneyFmt ),
|
|
1401
|
-
|
|
1402
|
-
|
|
1419
|
+
// Fixed Tango HDFC account (matches the invoice PDFs), not the per-client
|
|
1420
|
+
// paymentaccounts record.
|
|
1421
|
+
virtualaccountNumber: '50200027441433',
|
|
1422
|
+
virtualifsc: 'HDFC0000386',
|
|
1403
1423
|
uidomain: `${JSON.parse( process.env.URL ).domain}`,
|
|
1404
1424
|
logo: `${JSON.parse( process.env.URL ).apiDomain}/logo.png`,
|
|
1405
1425
|
rows,
|
|
@@ -2673,12 +2693,15 @@ export async function creditTransactionlist( req, res ) {
|
|
|
2673
2693
|
},
|
|
2674
2694
|
];
|
|
2675
2695
|
if ( req.body.searchValue && req.body.searchValue != '' ) {
|
|
2696
|
+
// This list projects only client identity + balance credit; there are no
|
|
2697
|
+
// paymentStatus/status fields on these rows, so search on the fields that
|
|
2698
|
+
// actually exist (clientName / clientId). Searching paymentStatus/status
|
|
2699
|
+
// here previously matched nothing.
|
|
2676
2700
|
query.push( {
|
|
2677
2701
|
$match: {
|
|
2678
2702
|
$or: [
|
|
2679
2703
|
{ clientName: { $regex: req.body.searchValue, $options: 'i' } },
|
|
2680
|
-
{
|
|
2681
|
-
{ status: { $regex: req.body.searchValue, $options: 'i' } },
|
|
2704
|
+
{ clientId: { $regex: req.body.searchValue, $options: 'i' } },
|
|
2682
2705
|
],
|
|
2683
2706
|
},
|
|
2684
2707
|
} );
|
|
@@ -2749,14 +2772,20 @@ export async function applyDiscount( req, res ) {
|
|
|
2749
2772
|
invoice.discountAmount = roundAmount( ( invoice.amount * req.body.discount ) / 100, invoice.currency );
|
|
2750
2773
|
invoice.amount = roundAmount( invoice.amount - invoice.discountAmount, invoice.currency );
|
|
2751
2774
|
invoice.discountPercentage = req.body.discount;
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2775
|
+
// Recompute every tax line off the discounted amount and set totalAmount =
|
|
2776
|
+
// discounted amount + SUM of ALL taxes. Applies to every currency (a
|
|
2777
|
+
// non-INR discount must also re-total), and accumulates across all tax
|
|
2778
|
+
// rows — a domestic INR invoice has CGST + SGST, so summing only the last
|
|
2779
|
+
// row (the previous bug) dropped one of them from the total.
|
|
2780
|
+
let taxTotal = 0;
|
|
2781
|
+
if ( Array.isArray( invoice.tax ) && invoice.tax.length ) {
|
|
2782
|
+
for ( let i = 0; i < invoice.tax.length; i++ ) {
|
|
2783
|
+
const taxAmt = roundAmount( ( invoice.amount * invoice.tax[i].value ) / 100, invoice.currency );
|
|
2784
|
+
invoice.tax[i].taxAmount = String( taxAmt );
|
|
2785
|
+
taxTotal += taxAmt;
|
|
2758
2786
|
}
|
|
2759
2787
|
}
|
|
2788
|
+
invoice.totalAmount = roundAmount( Number( invoice.amount ) + taxTotal, invoice.currency );
|
|
2760
2789
|
await invoiceService.updateOne( { invoice: req.body.invoice }, invoice );
|
|
2761
2790
|
let logObj = {
|
|
2762
2791
|
userName: req.user?.userName,
|
|
@@ -3035,7 +3064,17 @@ export async function getInvoice( req, res ) {
|
|
|
3035
3064
|
if ( !invoice ) {
|
|
3036
3065
|
return res.sendError( 'Invoice not found', 404 );
|
|
3037
3066
|
}
|
|
3038
|
-
|
|
3067
|
+
// Payment term for the preview: prefer the invoice's own stored paymentTerm
|
|
3068
|
+
// (the value chosen at creation), so preview + download + invoice all agree.
|
|
3069
|
+
// Fall back to the billing group's term (older invoices created before the
|
|
3070
|
+
// field existed), then 30 — mirroring the download.
|
|
3071
|
+
let paymentTerm = invoice.paymentTerm || 30;
|
|
3072
|
+
if ( !invoice.paymentTerm && invoice.groupId ) {
|
|
3073
|
+
const getgroup = await billingService.findOne( { _id: invoice.groupId } );
|
|
3074
|
+
paymentTerm = getgroup?.paymentTerm ? getgroup.paymentTerm : 30;
|
|
3075
|
+
}
|
|
3076
|
+
const invoiceOut = { ...( invoice._doc || invoice ), paymentTerm };
|
|
3077
|
+
res.sendSuccess( invoiceOut );
|
|
3039
3078
|
} catch ( error ) {
|
|
3040
3079
|
logger.error( { error: error, function: 'getInvoice' } );
|
|
3041
3080
|
return res.sendError( error, 500 );
|
|
@@ -3068,7 +3107,7 @@ export async function updateInvoice( req, res ) {
|
|
|
3068
3107
|
let updateData = {};
|
|
3069
3108
|
const allowedFields = [
|
|
3070
3109
|
'companyName', 'companyAddress', 'GSTNumber', 'PlaceOfSupply',
|
|
3071
|
-
'groupName', 'groupId', 'stores', 'billingDate', 'dueDate',
|
|
3110
|
+
'groupName', 'groupId', 'stores', 'billingDate', 'dueDate', 'paymentTerm',
|
|
3072
3111
|
'currency', 'status', 'paymentStatus', 'products', 'tax',
|
|
3073
3112
|
'amount', 'totalAmount', 'paymentMethod', 'purchaseOrderNumber',
|
|
3074
3113
|
];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head><meta charset="utf-8" /><meta content="width=device-width" name="viewport" />
|
|
4
|
-
<title>
|
|
4
|
+
<title>Invoice for {{subjectMonth}} - Tango/{{clientName}}</title></head>
|
|
5
5
|
<body style="margin:0;padding:0;background-color:#b1b1b1;font-family:Inter,Arial,sans-serif;">
|
|
6
6
|
<table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td align="center">
|
|
7
7
|
<table bgcolor="#ffffff" width="640" cellspacing="0" cellpadding="0" border="0"
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<tr><td style="padding:40px 52px;">
|
|
10
10
|
<img src="{{logo}}" width="143" style="display:block;margin-bottom:24px;" />
|
|
11
11
|
<div style="color:#121a26;font-weight:700;font-size:24px;line-height:140%;">
|
|
12
|
-
|
|
12
|
+
Invoice for {{subjectMonth}} - Tango/{{clientName}}
|
|
13
13
|
</div>
|
|
14
14
|
<div style="height:24px;"></div>
|
|
15
15
|
<div style="color:#384860;font-size:16px;line-height:150%;">
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
<div style="height:24px;"></div>
|
|
23
23
|
|
|
24
24
|
<div style="background-color:#f1f5f9;border:1px solid #e2e8f0;border-radius:8px;padding:20px 28px;">
|
|
25
|
-
<div style="color:#121a26;font-weight:700;font-size:18px;">
|
|
25
|
+
<div style="color:#121a26;font-weight:700;font-size:18px;">Account Details</div>
|
|
26
26
|
<div style="height:12px;"></div>
|
|
27
27
|
<div style="color:#121a26;font-weight:500;font-size:16px;">Account Number - {{virtualaccountNumber}}</div>
|
|
28
28
|
<div style="color:#121a26;font-weight:500;font-size:16px;">IFSC Code - {{virtualifsc}}</div>
|
|
@@ -246,7 +246,7 @@ img {
|
|
|
246
246
|
<table cellspacing="0" cellpadding="0" border="0">
|
|
247
247
|
<tbody><tr>
|
|
248
248
|
<td>
|
|
249
|
-
<div style="text-align:left;"><span style="color:#121a26;font-weight:700;font-family:Inter,Arial,sans-serif;font-size:18px;line-height:150%;text-align:left;">
|
|
249
|
+
<div style="text-align:left;"><span style="color:#121a26;font-weight:700;font-family:Inter,Arial,sans-serif;font-size:18px;line-height:150%;text-align:left;">Account Details </span></div>
|
|
250
250
|
</td>
|
|
251
251
|
</tr>
|
|
252
252
|
</tbody></table>
|