tango-app-api-payment-subscription 3.4.3 → 3.5.0
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/docs/invoice-approval-pipeline.md +44 -0
- package/package.json +8 -3
- package/scripts/grant-tango-approval-permissions.js +84 -0
- package/scripts/migrate-invoice-status-pipeline.js +61 -0
- package/src/controllers/applicationDefault.controllers.js +51 -0
- package/src/controllers/billing.controllers.js +2 -1
- package/src/controllers/brandsBilling.controller.js +387 -6
- package/src/controllers/invoice.controller.js +524 -30
- package/src/controllers/payment.controller.js +4 -3
- package/src/controllers/paymentSubscription.controllers.js +37 -14
- package/src/dtos/validation.dtos.js +55 -0
- package/src/hbs/invoicePdf.hbs +8 -0
- package/src/routes/brandsBilling.routes.js +17 -1
- package/src/routes/invoice.routes.js +12 -2
- package/src/services/applicationDefault.service.js +13 -0
- package/src/utils/currency.js +14 -0
|
@@ -5,6 +5,7 @@ import * as invoiceService from '../services/invoice.service.js';
|
|
|
5
5
|
import { updateOrder, verifySignature, verifyWebhook } from 'tango-app-api-middleware/src/utils/razorPay.js';
|
|
6
6
|
import { aggregateTransaction, createTransaction } from '../services/transaction.service.js';
|
|
7
7
|
import dayjs from 'dayjs';
|
|
8
|
+
import { symbolFor } from '../utils/currency.js';
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
export const getVirtualAccount = async ( req, res ) => {
|
|
@@ -376,9 +377,9 @@ export const transactionList = async ( req, res ) => {
|
|
|
376
377
|
exportResult.push( {
|
|
377
378
|
'Billing date': transaction?.billingDate ? dayjs( transaction.billingDate ).format( 'DD MMM, YYYY' ) : '',
|
|
378
379
|
'Transaction': transaction?.transactionType === 'debt' ? `Consumption for ${dayjs( transaction?.billingDate ).format( 'MMM YYYY' )}- ${transaction?.groupName}` : 'Paid Credit',
|
|
379
|
-
'Debit': transaction?.transactionType === 'debt' ? `${transaction?.currency
|
|
380
|
-
'Credit': transaction?.transactionType === 'credit' ? `${transaction?.currency
|
|
381
|
-
'Balance credit': `${transaction?.balanceCreditCurrency
|
|
380
|
+
'Debit': transaction?.transactionType === 'debt' ? `${symbolFor( transaction?.currency )} ${transaction.amount}` : '',
|
|
381
|
+
'Credit': transaction?.transactionType === 'credit' ? `${symbolFor( transaction?.currency )} ${transaction.amount}` : '',
|
|
382
|
+
'Balance credit': `${symbolFor( transaction?.balanceCreditCurrency )} ${transaction.balanceCredit}` || '',
|
|
382
383
|
|
|
383
384
|
} );
|
|
384
385
|
}
|
|
@@ -13,6 +13,7 @@ import * as cameraService from '../services/camera.service.js';
|
|
|
13
13
|
import * as billingService from '../services/billing.service.js';
|
|
14
14
|
import * as paymentAccountService from '../services/paymentAccount.service.js';
|
|
15
15
|
import * as taggingService from '../services/tagging.service.js';
|
|
16
|
+
import { symbolFor } from '../utils/currency.js';
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
import dayjs from 'dayjs';
|
|
@@ -2417,7 +2418,9 @@ export const pricingListUpdate = async ( req, res ) => {
|
|
|
2417
2418
|
amount = amount + item.price;
|
|
2418
2419
|
subtotal = subtotal+item.originalPrice;
|
|
2419
2420
|
origPrice = origPrice + ( item.basePrice * count );
|
|
2420
|
-
item.price = item.originalPrice
|
|
2421
|
+
item.price = String( Math.round( item.originalPrice ) );
|
|
2422
|
+
item.originalPrice = Math.round( item.originalPrice );
|
|
2423
|
+
item.basePrice = Math.round( item.basePrice );
|
|
2421
2424
|
} );
|
|
2422
2425
|
let discountAmount = origPrice - amount;
|
|
2423
2426
|
let discountPercentage = ( discountAmount / origPrice ) * 100;
|
|
@@ -2425,7 +2428,7 @@ export const pricingListUpdate = async ( req, res ) => {
|
|
|
2425
2428
|
...productList,
|
|
2426
2429
|
amount: amount.toFixed( 2 ),
|
|
2427
2430
|
subtotal: subtotal.toFixed( 2 ),
|
|
2428
|
-
currencyType: req.body.client.paymentInvoice.currencyType
|
|
2431
|
+
currencyType: symbolFor( req.body.client.paymentInvoice.currencyType ),
|
|
2429
2432
|
total: ( parseFloat( subtotal ) + parseFloat( ( amount * GST ) / 100 ) ).toFixed( 2 ),
|
|
2430
2433
|
final: amount.toFixed( 2 ),
|
|
2431
2434
|
discount: `${discountPercentage.toFixed( 2 )}% (-${discountAmount.toFixed( 2 )})`,
|
|
@@ -2666,7 +2669,7 @@ export const updatedRevisedPrice = async ( req, res ) => {
|
|
|
2666
2669
|
extendDays: clientDetails.paymentInvoice.extendPaymentPeriodDays,
|
|
2667
2670
|
address: clientDetails.billingDetails.billingAddress,
|
|
2668
2671
|
amount: amount.toFixed( 2 ),
|
|
2669
|
-
currencyType: clientDetails.paymentInvoice.currencyType
|
|
2672
|
+
currencyType: symbolFor( clientDetails.paymentInvoice.currencyType ),
|
|
2670
2673
|
discount: `${req.body.discount.toFixed( 2 )}% (-${discountAmount.toFixed( 2 )})`,
|
|
2671
2674
|
total: ( parseFloat( amount ) + parseFloat( ( amount * gst ) / 100 ) ).toFixed( 2 ),
|
|
2672
2675
|
final: req.body.revisedAmount.toFixed( 2 ),
|
|
@@ -2975,9 +2978,9 @@ export const invoiceDownload = async ( req, res ) => {
|
|
|
2975
2978
|
|
|
2976
2979
|
amount = amount + item.price;
|
|
2977
2980
|
|
|
2978
|
-
item.basePrice = item.basePrice.toLocaleString( 'en-IN'
|
|
2979
|
-
item.price = item.price.toLocaleString( 'en-IN'
|
|
2980
|
-
item.currency = clientDetails?.paymentInvoice?.currencyType
|
|
2981
|
+
item.basePrice = Math.round( item.basePrice ).toLocaleString( 'en-IN' );
|
|
2982
|
+
item.price = Math.round( item.price ).toLocaleString( 'en-IN' );
|
|
2983
|
+
item.currency = symbolFor( clientDetails?.paymentInvoice?.currencyType );
|
|
2981
2984
|
} );
|
|
2982
2985
|
for ( let tax of invoiceInfo.tax ) {
|
|
2983
2986
|
tax.taxAmount = tax.taxAmount.toLocaleString( 'en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 } );
|
|
@@ -3001,7 +3004,7 @@ export const invoiceDownload = async ( req, res ) => {
|
|
|
3001
3004
|
PoNum: '',
|
|
3002
3005
|
amountwords: AmountinWords,
|
|
3003
3006
|
Terms: `Term ${clientDetails.paymentInvoice.extendPaymentPeriodDays}`,
|
|
3004
|
-
currencyType: clientDetails?.paymentInvoice?.currencyType
|
|
3007
|
+
currencyType: symbolFor( clientDetails?.paymentInvoice?.currencyType ),
|
|
3005
3008
|
totalAmount: invoiceInfo.totalAmount.toLocaleString( 'en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 } ),
|
|
3006
3009
|
invoiceDate,
|
|
3007
3010
|
dueDate,
|
|
@@ -3053,7 +3056,18 @@ export const invoiceDownload = async ( req, res ) => {
|
|
|
3053
3056
|
return res.sendSuccess( result );
|
|
3054
3057
|
}
|
|
3055
3058
|
}
|
|
3056
|
-
|
|
3059
|
+
const invoiceMonth = invoiceInfo.billingDate ?
|
|
3060
|
+
dayjs( invoiceInfo.billingDate ).format( 'MMMM' ) :
|
|
3061
|
+
( invoiceInfo.monthOfbilling ? dayjs( invoiceInfo.monthOfbilling, 'MM' ).format( 'MMMM' ) : '' );
|
|
3062
|
+
let groupForName;
|
|
3063
|
+
if ( invoiceInfo.groupId ) {
|
|
3064
|
+
groupForName = await billingService.findOne( { _id: invoiceInfo.groupId } );
|
|
3065
|
+
}
|
|
3066
|
+
const brandName = groupForName?.registeredCompanyName || invoiceInfo.companyName || '';
|
|
3067
|
+
const safeFilename = `${invoiceInfo.invoice}-${invoiceMonth}-${brandName}.pdf`
|
|
3068
|
+
.replace( /[\r\n"\\]/g, '' )
|
|
3069
|
+
.trim();
|
|
3070
|
+
res.set( 'Content-Disposition', `attachment; filename="${safeFilename}"` );
|
|
3057
3071
|
res.set( 'Content-Type', 'application/pdf' );
|
|
3058
3072
|
res.send( pdfBuffer );
|
|
3059
3073
|
} );
|
|
@@ -3318,15 +3332,23 @@ export const dailyPricingInsert = async ( req, res ) => {
|
|
|
3318
3332
|
];
|
|
3319
3333
|
let dailyData = await dailyPriceService.aggregate( query );
|
|
3320
3334
|
let cameraDetails = await cameraService.find( { storeId: getStore[storeIndex].storeId, clientId: requestClient[clientIndex], isActivated: true, isUp: true }, { streamName: 1, productModule: 1 } );
|
|
3335
|
+
console.log( '🚀 ~ dailyPricingInsert ~ cameraDetails:', cameraDetails );
|
|
3321
3336
|
|
|
3322
|
-
let
|
|
3337
|
+
let trafficCameraCount = cameraDetails.filter( ( cam ) =>
|
|
3323
3338
|
( cam.productModule || [] ).some( ( mod ) =>
|
|
3324
|
-
( mod.
|
|
3339
|
+
( mod.productName === 'tangoTraffic' || mod.productName === 'tangoTracking' ) && mod.checked === true,
|
|
3325
3340
|
),
|
|
3326
3341
|
).length;
|
|
3342
|
+
|
|
3343
|
+
let zoneCameraCount = cameraDetails.filter( ( cam ) =>
|
|
3344
|
+
( cam.productModule || [] ).some( ( mod ) =>
|
|
3345
|
+
mod.productName === 'tangoZone' && mod.checked === true,
|
|
3346
|
+
),
|
|
3347
|
+
).length;
|
|
3348
|
+
|
|
3327
3349
|
let zoneCameraStreamNames = cameraDetails.filter( ( cam ) =>
|
|
3328
3350
|
( cam.productModule || [] ).some( ( mod ) =>
|
|
3329
|
-
mod.
|
|
3351
|
+
mod.productName === 'tangoZone' && mod.checked === true,
|
|
3330
3352
|
),
|
|
3331
3353
|
).map( ( cam ) => cam.streamName );
|
|
3332
3354
|
let taggingDetails = await taggingService.find( { storeId: getStore[storeIndex].storeId, clientId: requestClient[clientIndex], productName: 'tangoZone', coordinates: { $ne: [] }, streamName: { $in: zoneCameraStreamNames } }, { tagName: 1 } );
|
|
@@ -3336,7 +3358,7 @@ export const dailyPricingInsert = async ( req, res ) => {
|
|
|
3336
3358
|
let workingdays;
|
|
3337
3359
|
let workingdaystrax;
|
|
3338
3360
|
const givenDate = dayjs( requestData.date );
|
|
3339
|
-
|
|
3361
|
+
console.log( '🚀 ~ dailyPricingInsert ~ cameraCount:', trafficCameraCount, zoneCameraCount, zoneCount );
|
|
3340
3362
|
const isFirstDayOfMonth = givenDate.isSame( dayjs().startOf( 'month' ), 'day' );
|
|
3341
3363
|
if ( getStore[storeIndex]?.edge.firstFile ) {
|
|
3342
3364
|
if ( firstDate < requestData.date && getStore[storeIndex]?.status == 'active' &&
|
|
@@ -3413,7 +3435,8 @@ export const dailyPricingInsert = async ( req, res ) => {
|
|
|
3413
3435
|
daysDifferenceTrax: workingdaystrax,
|
|
3414
3436
|
products: productList,
|
|
3415
3437
|
camera: cameraDetails.map( ( item ) => item.streamName ),
|
|
3416
|
-
|
|
3438
|
+
trafficCameraCount: trafficCameraCount,
|
|
3439
|
+
zoneCameraCount: zoneCameraCount,
|
|
3417
3440
|
zoneCount: zoneCount,
|
|
3418
3441
|
zoneName: zoneName,
|
|
3419
3442
|
},
|
|
@@ -3843,7 +3866,7 @@ export const invoiceRevised = async ( req, res ) => {
|
|
|
3843
3866
|
...invoiceDetails._doc,
|
|
3844
3867
|
amount: amount,
|
|
3845
3868
|
discount: invoiceDetails.discount,
|
|
3846
|
-
currencyType: clientDetails?.paymentInvoice?.currencyType
|
|
3869
|
+
currencyType: symbolFor( clientDetails?.paymentInvoice?.currencyType ),
|
|
3847
3870
|
total: amount.toFixed( 2 ),
|
|
3848
3871
|
invoiceDate,
|
|
3849
3872
|
dueDate,
|
|
@@ -371,6 +371,31 @@ export const updateBillingGroupsSchema = {
|
|
|
371
371
|
body: updateBillingGroupBody,
|
|
372
372
|
};
|
|
373
373
|
|
|
374
|
+
export const bulkUpdateBillingGroupRowSchema = joi.object( {
|
|
375
|
+
_id: joi.string().length( 24 ).hex().required(),
|
|
376
|
+
groupName: joi.string().required(),
|
|
377
|
+
groupTag: joi.string().required(),
|
|
378
|
+
registeredCompanyName: joi.string().required(),
|
|
379
|
+
gst: joi.string().allow( '' ).optional(),
|
|
380
|
+
addressLineOne: joi.string().allow( '' ).optional(),
|
|
381
|
+
addressLineTwo: joi.string().allow( '' ).optional(),
|
|
382
|
+
city: joi.string().allow( '' ).optional(),
|
|
383
|
+
state: joi.string().allow( '' ).optional(),
|
|
384
|
+
country: joi.string().allow( '' ).optional(),
|
|
385
|
+
pinCode: joi.string().allow( '' ).optional(),
|
|
386
|
+
placeOfSupply: joi.string().allow( '' ).optional(),
|
|
387
|
+
po: joi.string().allow( '' ).optional(),
|
|
388
|
+
proRata: joi.string().allow( '' ).optional(),
|
|
389
|
+
paymentCategory: joi.string().allow( '' ).optional(),
|
|
390
|
+
currency: joi.string().allow( '' ).optional(),
|
|
391
|
+
paymentCycle: joi.string().allow( '' ).optional(),
|
|
392
|
+
paymentTerm: joi.number().integer().min( 0 ).max( 365 ).optional(),
|
|
393
|
+
installationFee: joi.number().min( 0 ).optional(),
|
|
394
|
+
isInstallationOneTime: joi.boolean().optional(),
|
|
395
|
+
attachAnnexure: joi.boolean().optional(),
|
|
396
|
+
advanceInvoice: joi.boolean().optional(),
|
|
397
|
+
} );
|
|
398
|
+
|
|
374
399
|
export const deleteBillingGroupQuery = {
|
|
375
400
|
_id: joi.string().required(),
|
|
376
401
|
};
|
|
@@ -468,3 +493,33 @@ export const clientProductsParam = joi.object().keys( {
|
|
|
468
493
|
export const clientProductsValid = {
|
|
469
494
|
params: clientProductsParam,
|
|
470
495
|
};
|
|
496
|
+
|
|
497
|
+
const INVOICE_STATUS_VALUES = [ 'pendingCsm', 'pendingFinance', 'pendingApproval', 'approved' ];
|
|
498
|
+
|
|
499
|
+
export const invoiceStatusEnum = INVOICE_STATUS_VALUES;
|
|
500
|
+
|
|
501
|
+
export const approveInvoiceTransitionBody = joi.object( {
|
|
502
|
+
invoiceId: joi.string().length( 24 ).hex().required(),
|
|
503
|
+
} );
|
|
504
|
+
|
|
505
|
+
export const approveInvoiceTransitionSchema = {
|
|
506
|
+
body: approveInvoiceTransitionBody,
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
export const updateInvoiceHeadsBody = joi.object( {
|
|
510
|
+
data: joi.array().items(
|
|
511
|
+
joi.object( {
|
|
512
|
+
role: joi.string().valid( 'csm', 'finance' ).required(),
|
|
513
|
+
// email may be a single string (csm) or an array of strings (finance).
|
|
514
|
+
// Empty string / empty array are allowed for un-setting.
|
|
515
|
+
email: joi.alternatives().try(
|
|
516
|
+
joi.string().email().allow( '' ),
|
|
517
|
+
joi.array().items( joi.string().email() ),
|
|
518
|
+
).required(),
|
|
519
|
+
} ),
|
|
520
|
+
).min( 1 ).required(),
|
|
521
|
+
} );
|
|
522
|
+
|
|
523
|
+
export const updateInvoiceHeadsSchema = {
|
|
524
|
+
body: updateInvoiceHeadsBody,
|
|
525
|
+
};
|
package/src/hbs/invoicePdf.hbs
CHANGED
|
@@ -1612,6 +1612,14 @@
|
|
|
1612
1612
|
{{#eq billingCurrency 'dollar'}}
|
|
1613
1613
|
US Dollar
|
|
1614
1614
|
{{/eq}}
|
|
1615
|
+
|
|
1616
|
+
{{#eq billingCurrency 'singaporedollar'}}
|
|
1617
|
+
Singapore Dollar
|
|
1618
|
+
{{/eq}}
|
|
1619
|
+
|
|
1620
|
+
{{#eq billingCurrency 'euro'}}
|
|
1621
|
+
Euro
|
|
1622
|
+
{{/eq}}
|
|
1615
1623
|
{{amountwords}} Only
|
|
1616
1624
|
</div>
|
|
1617
1625
|
</div>
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
|
|
2
2
|
import express from 'express';
|
|
3
|
-
import
|
|
3
|
+
import multer from 'multer';
|
|
4
|
+
import { brandsBillingList, brandInvoiceList, latestDailyPricing, brandBillingGroups, updateDailyPricingWorkingDays, updateDailyPricingStoreField, getClientBillingInfo, bulkDownloadBillingGroups, bulkUpdateBillingGroups } from '../controllers/brandsBilling.controller.js';
|
|
4
5
|
import { isAllowedSessionHandler, accessVerification } from 'tango-app-api-middleware';
|
|
6
|
+
|
|
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
|
+
} );
|
|
5
19
|
export const brandsBillingRouter = express.Router();
|
|
6
20
|
|
|
7
21
|
brandsBillingRouter.post( '/brandsBillingList', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), brandsBillingList );
|
|
@@ -11,3 +25,5 @@ brandsBillingRouter.post( '/brandBillingGroups', isAllowedSessionHandler, access
|
|
|
11
25
|
brandsBillingRouter.put( '/updateDailyPricingWorkingDays', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), updateDailyPricingWorkingDays );
|
|
12
26
|
brandsBillingRouter.put( '/updateDailyPricingStoreField', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), updateDailyPricingStoreField );
|
|
13
27
|
brandsBillingRouter.post( '/getClientBillingInfo', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), getClientBillingInfo );
|
|
28
|
+
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' ] } ] } ), bulkUpload.single( 'file' ), bulkUpdateBillingGroups );
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
|
-
import { createInvoice, invoiceDownload, clientInvoiceList, creditTransactionlist, pendingInvoices, applyDiscount, migrateInvoice, PaymentStatusChange, checkPaymentStatus, getInvoice, updateInvoice, getClientBasePricing } from '../controllers/invoice.controller.js';
|
|
3
|
-
import { isAllowedSessionHandler, accessVerification } from 'tango-app-api-middleware';
|
|
2
|
+
import { createInvoice, invoiceDownload, invoiceDownloadBulk, clientInvoiceList, creditTransactionlist, pendingInvoices, applyDiscount, migrateInvoice, PaymentStatusChange, checkPaymentStatus, getInvoice, invoiceAnnexure, updateInvoice, getClientBasePricing, deleteInvoice, approveInvoiceCsm, approveInvoiceFinance, approveInvoiceApproval } from '../controllers/invoice.controller.js';
|
|
3
|
+
import { isAllowedSessionHandler, accessVerification, validate } from 'tango-app-api-middleware';
|
|
4
|
+
import { getInvoiceHeads, updateInvoiceHeads } from '../controllers/applicationDefault.controllers.js';
|
|
5
|
+
import { updateInvoiceHeadsSchema } from '../dtos/validation.dtos.js';
|
|
4
6
|
export const invoiceRouter = express.Router();
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
invoiceRouter.post( '/createInvoice', isAllowedSessionHandler, createInvoice );
|
|
8
10
|
invoiceRouter.post( '/regerateInvoice', isAllowedSessionHandler, createInvoice );
|
|
11
|
+
invoiceRouter.post( '/invoiceDownload/bulk', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), invoiceDownloadBulk );
|
|
9
12
|
invoiceRouter.post( '/invoiceDownload/:invoiceId', isAllowedSessionHandler, invoiceDownload );
|
|
10
13
|
invoiceRouter.post( '/clientInvoiceList', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), clientInvoiceList );
|
|
11
14
|
invoiceRouter.post( '/creditTransactionlist', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), creditTransactionlist );
|
|
@@ -15,7 +18,14 @@ invoiceRouter.post( '/migrateInvoice', migrateInvoice );
|
|
|
15
18
|
invoiceRouter.post( '/PaymentStatusChange', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), PaymentStatusChange );
|
|
16
19
|
invoiceRouter.post( '/checkPaymentStatus', checkPaymentStatus );
|
|
17
20
|
invoiceRouter.get( '/getInvoice/:invoiceId', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), getInvoice );
|
|
21
|
+
invoiceRouter.get( '/invoiceAnnexure/:invoiceId', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), invoiceAnnexure );
|
|
18
22
|
invoiceRouter.put( '/updateInvoice', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), updateInvoice );
|
|
19
23
|
invoiceRouter.get( '/getClientBasePricing/:clientId', isAllowedSessionHandler, getClientBasePricing );
|
|
24
|
+
invoiceRouter.delete( '/deleteInvoice/:invoiceId', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), deleteInvoice );
|
|
20
25
|
|
|
26
|
+
invoiceRouter.post( '/approveInvoiceCsm', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'csmApproval', permissions: [ 'isEdit' ] } ] } ), approveInvoiceCsm );
|
|
27
|
+
invoiceRouter.post( '/approveInvoiceFinance', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'financeApproval', permissions: [ 'isEdit' ] } ] } ), approveInvoiceFinance );
|
|
28
|
+
invoiceRouter.post( '/approveInvoiceApproval', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), approveInvoiceApproval );
|
|
21
29
|
|
|
30
|
+
invoiceRouter.get( '/getInvoiceHeads', isAllowedSessionHandler, getInvoiceHeads );
|
|
31
|
+
invoiceRouter.post( '/updateInvoiceHeads', isAllowedSessionHandler, validate( updateInvoiceHeadsSchema ), updateInvoiceHeads );
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import model from 'tango-api-schema';
|
|
2
|
+
|
|
3
|
+
export async function findOneApplicationDefault( query, projection ) {
|
|
4
|
+
return model.applicationDefaultModel.findOne( query, projection );
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function upsertApplicationDefault( query, payload ) {
|
|
8
|
+
return model.applicationDefaultModel.findOneAndUpdate(
|
|
9
|
+
query,
|
|
10
|
+
{ $set: payload },
|
|
11
|
+
{ new: true, upsert: true },
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralised currency symbol map for invoices, transactions, and PDFs.
|
|
3
|
+
* Keys must match the values stored in billing.currency / transaction.currency
|
|
4
|
+
* enums (see tango-api-schema). Unknown keys fall back to '$' to preserve the
|
|
5
|
+
* pre-existing two-currency-ternary behaviour.
|
|
6
|
+
*/
|
|
7
|
+
export const CURRENCY_SYMBOLS = {
|
|
8
|
+
inr: '₹',
|
|
9
|
+
dollar: '$',
|
|
10
|
+
singaporedollar: 'S$',
|
|
11
|
+
euro: '€',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const symbolFor = ( currency ) => CURRENCY_SYMBOLS[currency] ?? '$';
|