tango-app-api-payment-subscription 3.5.1 → 3.5.2
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
|
@@ -481,25 +481,33 @@ export async function latestDailyPricing( req, res ) {
|
|
|
481
481
|
const products = store.products || [];
|
|
482
482
|
if ( products.length > 0 ) {
|
|
483
483
|
products.forEach( ( product ) => {
|
|
484
|
+
// Per-product zeroing: tangoTraffic rows only carry the traffic
|
|
485
|
+
// camera count; tangoZone rows only carry zone camera + zone count.
|
|
486
|
+
// Previously every row repeated all three values, which made
|
|
487
|
+
// tangoTraffic and tangoZone rows look identical in the export.
|
|
488
|
+
const isTraffic = product.productName === 'tangoTraffic';
|
|
489
|
+
const isZone = product.productName === 'tangoZone';
|
|
484
490
|
exportdata.push( {
|
|
485
491
|
'Store Name': store.storeName,
|
|
486
492
|
'Store ID': store.storeId,
|
|
487
493
|
'Product': product.productName,
|
|
488
|
-
'Traffic Camera Count': store.trafficCameraCount || 0,
|
|
489
|
-
'Zone Camera Count': store.zoneCameraCount || 0,
|
|
490
|
-
'Zone Count': store.zoneCount || 0,
|
|
494
|
+
'Traffic Camera Count': isTraffic ? ( store.trafficCameraCount || 0 ) : 0,
|
|
495
|
+
'Zone Camera Count': isZone ? ( store.zoneCameraCount || 0 ) : 0,
|
|
496
|
+
'Zone Count': isZone ? ( store.zoneCount || 0 ) : 0,
|
|
491
497
|
'Working Days': product.workingdays || 0,
|
|
492
498
|
'Status': store.status || '',
|
|
493
499
|
} );
|
|
494
500
|
} );
|
|
495
501
|
} else {
|
|
502
|
+
// No product attached — leave all camera/zone columns at 0 so an
|
|
503
|
+
// unattributed row never carries product-specific counts.
|
|
496
504
|
exportdata.push( {
|
|
497
505
|
'Store Name': store.storeName,
|
|
498
506
|
'Store ID': store.storeId,
|
|
499
507
|
'Product': '',
|
|
500
|
-
'Traffic Camera Count':
|
|
501
|
-
'Zone Camera Count':
|
|
502
|
-
'Zone Count':
|
|
508
|
+
'Traffic Camera Count': 0,
|
|
509
|
+
'Zone Camera Count': 0,
|
|
510
|
+
'Zone Count': 0,
|
|
503
511
|
'Working Days': 0,
|
|
504
512
|
'Status': store.status || '',
|
|
505
513
|
} );
|
|
@@ -1014,7 +1022,11 @@ export async function bulkDownloadBillingGroups( req, res ) {
|
|
|
1014
1022
|
|
|
1015
1023
|
export async function bulkUpdateBillingGroups( req, res ) {
|
|
1016
1024
|
try {
|
|
1017
|
-
|
|
1025
|
+
// The frontend sends the XLSX as base64 in JSON (see billing.service.ts).
|
|
1026
|
+
// Switching off multipart sidestepped multer's "Unexpected end of form"
|
|
1027
|
+
// failures we were seeing in production.
|
|
1028
|
+
const fileBase64 = req.body?.fileBase64;
|
|
1029
|
+
if ( !fileBase64 || typeof fileBase64 !== 'string' ) {
|
|
1018
1030
|
return res.sendError( 'No file uploaded', 400 );
|
|
1019
1031
|
}
|
|
1020
1032
|
|
|
@@ -1023,9 +1035,22 @@ export async function bulkUpdateBillingGroups( req, res ) {
|
|
|
1023
1035
|
return res.sendError( 'clientId form field is required', 400 );
|
|
1024
1036
|
}
|
|
1025
1037
|
|
|
1038
|
+
let fileBuffer;
|
|
1039
|
+
try {
|
|
1040
|
+
fileBuffer = Buffer.from( fileBase64, 'base64' );
|
|
1041
|
+
} catch ( decodeErr ) {
|
|
1042
|
+
logger.error( { error: decodeErr, function: 'bulkUpdateBillingGroups.decode' } );
|
|
1043
|
+
return res.sendError( 'Invalid file payload', 400 );
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// Match the 5MB cap multer was enforcing.
|
|
1047
|
+
if ( fileBuffer.length > 5 * 1024 * 1024 ) {
|
|
1048
|
+
return res.sendError( 'File too large (max 5MB)', 413 );
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1026
1051
|
let rawRows;
|
|
1027
1052
|
try {
|
|
1028
|
-
const wb = XLSX.read(
|
|
1053
|
+
const wb = XLSX.read( fileBuffer, { type: 'buffer' } );
|
|
1029
1054
|
const sheet = wb.Sheets[wb.SheetNames[0]];
|
|
1030
1055
|
rawRows = XLSX.utils.sheet_to_json( sheet, { defval: '' } );
|
|
1031
1056
|
} catch ( parseErr ) {
|
|
@@ -174,6 +174,7 @@ export async function createInvoice( req, res ) {
|
|
|
174
174
|
let invoicedate = req.body.invoiceId ? dayjs( findInvoice.billingDate ).format( 'YYYY-MM-DD' ) : baseDate.format( 'YYYY-MM-DD' );
|
|
175
175
|
let daysExtend = group?.paymentTerm ? group?.paymentTerm : 30;
|
|
176
176
|
let dueDate = baseDate.add( daysExtend, 'days' );
|
|
177
|
+
console.log( 'group.currencygroup.currency', group.currency );
|
|
177
178
|
let data = {
|
|
178
179
|
groupName: group.groupName,
|
|
179
180
|
groupId: group._id,
|
|
@@ -265,6 +266,11 @@ export async function invoiceDownload( req, res ) {
|
|
|
265
266
|
let invoiceInfo = await invoiceService.findOne( { _id: req.params.invoiceId } );
|
|
266
267
|
if ( invoiceInfo ) {
|
|
267
268
|
let clientDetails = await clientService.findOne( { clientId: invoiceInfo.clientId } );
|
|
269
|
+
// The invoice records its own currency at creation time (from the
|
|
270
|
+
// billing group). That's the source of truth for the PDF — using
|
|
271
|
+
// client.paymentInvoice or virtualAccount.currency causes historical
|
|
272
|
+
// invoices to re-render in the wrong currency if those settings change.
|
|
273
|
+
const invoiceCurrency = symbolFor( invoiceInfo.currency );
|
|
268
274
|
invoiceInfo.products.forEach( ( item, index ) => {
|
|
269
275
|
item.index = index + 1;
|
|
270
276
|
let [ firstWord, secondWord ] = item.productName.replace( /([a-z])([A-Z])/g, '$1 $2' ).split( ' ' );
|
|
@@ -272,7 +278,7 @@ export async function invoiceDownload( req, res ) {
|
|
|
272
278
|
item.productName = firstWord + ' ' + secondWord;
|
|
273
279
|
item.price = Math.round( item.price ).toLocaleString( 'en-IN' );
|
|
274
280
|
item.amount = item.amount.toLocaleString( 'en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 } );
|
|
275
|
-
item.currency =
|
|
281
|
+
item.currency = invoiceCurrency;
|
|
276
282
|
} );
|
|
277
283
|
|
|
278
284
|
|
|
@@ -304,7 +310,7 @@ export async function invoiceDownload( req, res ) {
|
|
|
304
310
|
PoNum: getgroup?.po,
|
|
305
311
|
amountwords: AmountinWords,
|
|
306
312
|
Terms: `Term ${getgroup?.paymentTerm ? getgroup?.paymentTerm : '30'}`,
|
|
307
|
-
currencyType:
|
|
313
|
+
currencyType: invoiceCurrency,
|
|
308
314
|
totalAmount: invoiceInfo.totalAmount.toLocaleString( 'en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 } ),
|
|
309
315
|
invoiceDate,
|
|
310
316
|
dueDate,
|
|
@@ -375,7 +381,7 @@ export async function invoiceDownload( req, res ) {
|
|
|
375
381
|
storeName: '$stores.storeName',
|
|
376
382
|
edgefirstFileDate: '$stores.edgefirstFileDate',
|
|
377
383
|
workingdays: '$stores.products.workingdays',
|
|
378
|
-
currencyType: { $literal:
|
|
384
|
+
currencyType: { $literal: invoiceCurrency },
|
|
379
385
|
},
|
|
380
386
|
},
|
|
381
387
|
{
|
|
@@ -580,6 +586,10 @@ async function buildInvoicePdfBuffer( invoiceId ) {
|
|
|
580
586
|
}
|
|
581
587
|
|
|
582
588
|
let clientDetails = await clientService.findOne( { clientId: invoiceInfo.clientId } );
|
|
589
|
+
// Source of truth for the PDF currency is the invoice's own `currency`
|
|
590
|
+
// field, recorded at creation from the billing group. See invoiceDownload
|
|
591
|
+
// above for the same pattern.
|
|
592
|
+
const invoiceCurrency = symbolFor( invoiceInfo.currency );
|
|
583
593
|
invoiceInfo.products.forEach( ( item, index ) => {
|
|
584
594
|
item.index = index + 1;
|
|
585
595
|
let [ firstWord, secondWord ] = item.productName.replace( /([a-z])([A-Z])/g, '$1 $2' ).split( ' ' );
|
|
@@ -587,7 +597,7 @@ async function buildInvoicePdfBuffer( invoiceId ) {
|
|
|
587
597
|
item.productName = firstWord + ' ' + secondWord;
|
|
588
598
|
item.price = Math.round( item.price ).toLocaleString( 'en-IN' );
|
|
589
599
|
item.amount = item.amount.toLocaleString( 'en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 } );
|
|
590
|
-
item.currency =
|
|
600
|
+
item.currency = invoiceCurrency;
|
|
591
601
|
} );
|
|
592
602
|
|
|
593
603
|
let invoiceDate = dayjs( invoiceInfo.billingDate ).format( 'DD/MM/YYYY' );
|
|
@@ -616,7 +626,7 @@ async function buildInvoicePdfBuffer( invoiceId ) {
|
|
|
616
626
|
PoNum: getgroup?.po,
|
|
617
627
|
amountwords: AmountinWords,
|
|
618
628
|
Terms: `Term ${getgroup?.paymentTerm ? getgroup?.paymentTerm : '30'}`,
|
|
619
|
-
currencyType:
|
|
629
|
+
currencyType: invoiceCurrency,
|
|
620
630
|
totalAmount: invoiceInfo.totalAmount.toLocaleString( 'en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 } ),
|
|
621
631
|
invoiceDate,
|
|
622
632
|
dueDate,
|
|
@@ -661,7 +671,7 @@ async function buildInvoicePdfBuffer( invoiceId ) {
|
|
|
661
671
|
storeName: '$stores.storeName',
|
|
662
672
|
edgefirstFileDate: '$stores.edgefirstFileDate',
|
|
663
673
|
workingdays: '$stores.products.workingdays',
|
|
664
|
-
currencyType: { $literal:
|
|
674
|
+
currencyType: { $literal: invoiceCurrency },
|
|
665
675
|
} },
|
|
666
676
|
{ $sort: { productName: 1, workingdays: -1 } },
|
|
667
677
|
{ $match: { workingdays: { $gt: 0 } } },
|
|
@@ -816,8 +826,10 @@ export async function invoiceAnnexure( req, res ) {
|
|
|
816
826
|
return res.sendSuccess( { data: [] } );
|
|
817
827
|
}
|
|
818
828
|
|
|
819
|
-
const clientDetails = await clientService.findOne( { clientId: invoiceInfo.clientId } );
|
|
820
829
|
const currentMonthDays = dayjs().daysInMonth();
|
|
830
|
+
// Annexure must show the same currency as the invoice it accompanies —
|
|
831
|
+
// see invoiceDownload / buildInvoicePdfBuffer for the same pattern.
|
|
832
|
+
const invoiceCurrency = symbolFor( invoiceInfo.currency );
|
|
821
833
|
|
|
822
834
|
const annexureData = await dailyPricingService.aggregate( [
|
|
823
835
|
{ $match: { clientId: invoiceInfo.clientId } },
|
|
@@ -832,7 +844,7 @@ export async function invoiceAnnexure( req, res ) {
|
|
|
832
844
|
storeName: '$stores.storeName',
|
|
833
845
|
edgefirstFileDate: '$stores.edgefirstFileDate',
|
|
834
846
|
workingdays: '$stores.products.workingdays',
|
|
835
|
-
currencyType: { $literal:
|
|
847
|
+
currencyType: { $literal: invoiceCurrency },
|
|
836
848
|
} },
|
|
837
849
|
{ $sort: { productName: 1, workingdays: -1 } },
|
|
838
850
|
{ $match: { workingdays: { $gt: 0 } } },
|
|
@@ -1,21 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
import express from 'express';
|
|
3
|
-
import multer from 'multer';
|
|
4
3
|
import { brandsBillingList, brandInvoiceList, latestDailyPricing, brandBillingGroups, updateDailyPricingWorkingDays, updateDailyPricingStoreField, getClientBillingInfo, bulkDownloadBillingGroups, bulkUpdateBillingGroups } from '../controllers/brandsBilling.controller.js';
|
|
5
4
|
import { isAllowedSessionHandler, accessVerification } from 'tango-app-api-middleware';
|
|
6
5
|
|
|
7
|
-
const bulkUpload = multer( {
|
|
8
|
-
storage: multer.memoryStorage(),
|
|
9
|
-
limits: { fileSize: 5 * 1024 * 1024 },
|
|
10
|
-
fileFilter: ( _req, file, cb ) => {
|
|
11
|
-
const ok = [
|
|
12
|
-
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
13
|
-
'application/vnd.ms-excel',
|
|
14
|
-
'application/octet-stream',
|
|
15
|
-
].includes( file.mimetype );
|
|
16
|
-
cb( ok ? null : new Error( 'Invalid file format — use XLSX' ), ok );
|
|
17
|
-
},
|
|
18
|
-
} );
|
|
19
6
|
export const brandsBillingRouter = express.Router();
|
|
20
7
|
|
|
21
8
|
brandsBillingRouter.post( '/brandsBillingList', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), brandsBillingList );
|
|
@@ -26,4 +13,4 @@ brandsBillingRouter.put( '/updateDailyPricingWorkingDays', isAllowedSessionHandl
|
|
|
26
13
|
brandsBillingRouter.put( '/updateDailyPricingStoreField', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), updateDailyPricingStoreField );
|
|
27
14
|
brandsBillingRouter.post( '/getClientBillingInfo', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), getClientBillingInfo );
|
|
28
15
|
brandsBillingRouter.get( '/bulk-download-billing-groups', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), bulkDownloadBillingGroups );
|
|
29
|
-
brandsBillingRouter.post( '/bulk-update-billing-groups', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ),
|
|
16
|
+
brandsBillingRouter.post( '/bulk-update-billing-groups', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), bulkUpdateBillingGroups );
|