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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tango-app-api-payment-subscription",
3
- "version": "3.5.31",
3
+ "version": "3.5.32",
4
4
  "description": "paymentSubscription",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -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
- dueDate: req.body.dueDate ? new Date( req.body.dueDate ) : baseDate.add( 30, 'days' ).toDate(),
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
- tempIndex: req.body.invoiceId ? undefined : tempSeqForThis,
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: virtualAccount?.currency,
902
- virtualaccountNumber: virtualAccount ? virtualAccount?.accountNumber : '',
903
- virtualifsc: virtualAccount ? virtualAccount?.ifsc : '',
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: virtualAccount?.currency,
1102
- virtualaccountNumber: virtualAccount ? virtualAccount?.accountNumber : '',
1103
- virtualifsc: virtualAccount ? virtualAccount?.ifsc : '',
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
- virtualaccountNumber: virtualAccount ? virtualAccount.accountNumber : '',
1402
- virtualifsc: virtualAccount ? virtualAccount.ifsc : '',
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
- { paymentStatus: { $regex: req.body.searchValue, $options: 'i' } },
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
- if ( invoice.currency === 'inr' ) {
2753
- if ( invoice.tax.length ) {
2754
- for ( let i = 0; i < invoice.tax.length; i++ ) {
2755
- invoice.tax[i].taxAmount = String( roundAmount( ( invoice.amount * invoice.tax[i].value ) / 100, invoice.currency ) );
2756
- invoice.totalAmount = roundAmount( Number( invoice.amount ) + Number( invoice.tax[i].taxAmount ), invoice.currency );
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
- res.sendSuccess( invoice );
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>Invoices from Tango Eye</title></head>
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
- Invoices from Tango Eye — attached (ZIP)
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;">Virtual Account Details</div>
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;">Virtual Account Details </span></div>
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>