tango-app-api-payment-subscription 3.5.2 → 3.5.3

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.2",
3
+ "version": "3.5.3",
4
4
  "description": "paymentSubscription",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -2,6 +2,7 @@ import * as invoiceService from '../services/invoice.service.js';
2
2
  import * as dailyPricingService from '../services/dailyPrice.service.js';
3
3
  import * as clientService from '../services/clientPayment.services.js';
4
4
  import * as billingService from '../services/billing.service.js';
5
+ import mongoose from 'mongoose';
5
6
  import dayjs from 'dayjs';
6
7
  import { logger, checkFileExist, signedUrl, download, sendEmailWithSES, insertOpenSearchData } from 'tango-app-api-middleware';
7
8
  // import Handlebars from 'handlebars';
@@ -65,6 +66,25 @@ async function getInvoiceCcEmails( clientId ) {
65
66
  export async function createInvoice( req, res ) {
66
67
  try {
67
68
  let invoiceGroupList = [];
69
+ // Optional groupIds filter — when present, only those billing groups will
70
+ // be processed. Lets the UI generate an invoice for a specific subset of
71
+ // groups instead of every group on the client. Strings are matched against
72
+ // billing _id.
73
+ const groupIdsFilter = Array.isArray( req.body.groupIds ) && req.body.groupIds.length > 0 ?
74
+ req.body.groupIds :
75
+ null;
76
+ const groupObjectIdFilter = groupIdsFilter ?
77
+ groupIdsFilter
78
+ .map( ( id ) => {
79
+ try {
80
+ return new mongoose.Types.ObjectId( id );
81
+ } catch {
82
+ return null;
83
+ }
84
+ } )
85
+ .filter( ( id ) => id !== null ) :
86
+ null;
87
+
68
88
  if ( req.body.allClient ) {
69
89
  if ( req.body.clientList && req.body.clientList.length > 0 ) {
70
90
  req.body.clientList = req.body.clientList;
@@ -74,12 +94,12 @@ export async function createInvoice( req, res ) {
74
94
  }
75
95
 
76
96
  for ( let client of req.body.clientList ) {
97
+ const matchStage = { clientId: client };
98
+ if ( groupObjectIdFilter && groupObjectIdFilter.length ) {
99
+ matchStage._id = { $in: groupObjectIdFilter };
100
+ }
77
101
  let invoiceGroup = await billingService.aggregatebilling( [
78
- {
79
- $match: {
80
- clientId: client,
81
- },
82
- },
102
+ { $match: matchStage },
83
103
  ] );
84
104
  for ( let invGrp of invoiceGroup ) {
85
105
  invoiceGroupList.push( invGrp );
@@ -95,7 +115,14 @@ export async function createInvoice( req, res ) {
95
115
 
96
116
  for ( let group of invoiceGroupList ) {
97
117
  let Finacialyear = getCurrentFinancialYear();
98
- let previousinvoice = await invoiceService.findandsort( {}, {}, { invoiceIndex: -1 } );
118
+ // Scope the highest-index lookup to invoices created in the current FY
119
+ // (invoice IDs are `INV-${FY}-${index}`). Without this scope the new FY
120
+ // would continue the previous year's sequence instead of resetting.
121
+ let previousinvoice = await invoiceService.findandsort(
122
+ { invoice: { $regex: `^INV-${Finacialyear}-` } },
123
+ {},
124
+ { invoiceIndex: -1 },
125
+ );
99
126
  let invoiceNo = '00001';
100
127
  if ( previousinvoice && previousinvoice.length > 0 ) {
101
128
  invoiceNo = Number( previousinvoice[0].invoiceIndex ) + 1;
@@ -2110,6 +2137,13 @@ export async function updateInvoice( req, res ) {
2110
2137
  return res.sendError( 'Invoice not found', 404 );
2111
2138
  }
2112
2139
 
2140
+ // Lock once final-approved. The UI hides the Edit icon for these rows,
2141
+ // but a direct API call would otherwise still let someone mutate a
2142
+ // finalised invoice. Match the frontend's isInvoiceLocked() check.
2143
+ if ( invoice.status === 'approved' ) {
2144
+ return res.sendError( 'Cannot edit a final-approved invoice.', 409 );
2145
+ }
2146
+
2113
2147
  let updateData = {};
2114
2148
  const allowedFields = [
2115
2149
  'companyName', 'companyAddress', 'GSTNumber', 'PlaceOfSupply',
@@ -2220,6 +2254,12 @@ export async function deleteInvoice( req, res ) {
2220
2254
  return res.sendError( 'Invoice not found', 404 );
2221
2255
  }
2222
2256
 
2257
+ // Same lock the UI enforces — a final-approved invoice must not be
2258
+ // deletable, even by a power user hitting the API directly.
2259
+ if ( invoice.status === 'approved' ) {
2260
+ return res.sendError( 'Cannot delete a final-approved invoice.', 409 );
2261
+ }
2262
+
2223
2263
  await invoiceService.deleteRecord( { _id: invoiceId } );
2224
2264
 
2225
2265
  const logObj = {
@@ -3772,7 +3772,15 @@ export const invoiceGenerate = async ( req, res ) => {
3772
3772
  }
3773
3773
 
3774
3774
 
3775
- let previousinvoice = await invoiceService.findandsort( {}, {}, { _id: -1 } );
3775
+ // Scope the highest-index lookup to the current financial year
3776
+ // (invoice IDs are `INV-${FY}-${index}`). Sort by invoiceIndex (not
3777
+ // _id) so the largest sequence number wins even if records were
3778
+ // back-dated or imported out of order.
3779
+ let previousinvoice = await invoiceService.findandsort(
3780
+ { invoice: { $regex: `^INV-${Finacialyear}-` } },
3781
+ {},
3782
+ { invoiceIndex: -1 },
3783
+ );
3776
3784
  let invoiceNo = '00001';
3777
3785
  if ( previousinvoice && previousinvoice.length > 0 ) {
3778
3786
  invoiceNo = Number( previousinvoice[0].invoiceIndex ) + 1;
@@ -1643,7 +1643,7 @@
1643
1643
  </div>
1644
1644
  <div class="frame-54">
1645
1645
  <div class="ifsc-code">IFSC Code</div>
1646
- <div class="hdfc-0000269">HDFC0000269</div>
1646
+ <div class="hdfc-0000269">HDFC0000386</div>
1647
1647
  </div>
1648
1648
  <div class="frame-532">
1649
1649
  <div class="payment-type">Payment Type</div>