tango-app-api-payment-subscription 3.5.10 → 3.5.12

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.10",
3
+ "version": "3.5.12",
4
4
  "description": "paymentSubscription",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -19,7 +19,12 @@ dotenv.config();
19
19
 
20
20
  const args = process.argv.slice( 2 );
21
21
  const dryRun = args.includes( '--dry-run' );
22
- const STATIC_EMAIL = args.find( ( a ) => a.includes( '@' ) ) || 'ayyanarkalusulingam13@gmail.com';
22
+
23
+ // Default reminder recipients seeded onto every active client. Any email(s)
24
+ // passed as args are added on top, de-duped.
25
+ const DEFAULT_EMAILS = [ 'sathish@tangotech.co.in', 'ayyanarkalusulingam13@gmail.com' ];
26
+ const argEmails = args.filter( ( a ) => a.includes( '@' ) );
27
+ const REMINDER_EMAILS = [ ...new Set( [ ...DEFAULT_EMAILS, ...argEmails ] ) ];
23
28
 
24
29
  // Mirrors the app's DEFAULTS() in paymentReminder.controller.js.
25
30
  const DEFAULT_TEMPLATES = {
@@ -34,7 +39,7 @@ async function run() {
34
39
  // Reuse the app's own connection builder (reads mongo_* env config).
35
40
  const { uri, options } = getConnection();
36
41
  await mongoose.connect( uri, options );
37
- console.log( `Connected. Static recipient: ${STATIC_EMAIL}${dryRun ? ' (DRY RUN — no writes)' : ''}\n` );
42
+ console.log( `Connected. Recipients: ${REMINDER_EMAILS.join( ', ' )}${dryRun ? ' (DRY RUN — no writes)' : ''}\n` );
38
43
 
39
44
  const clients = await model.clientModel.find(
40
45
  { status: 'active' },
@@ -60,7 +65,7 @@ async function run() {
60
65
  {
61
66
  $set: {
62
67
  clientId: c.clientId,
63
- reminderEmails: [ STATIC_EMAIL ],
68
+ reminderEmails: REMINDER_EMAILS,
64
69
  templates: DEFAULT_TEMPLATES,
65
70
  updatedBy: 'seed-script',
66
71
  },
@@ -10,6 +10,7 @@ import { leadGet } from '../services/lead.service.js';
10
10
  import { findOneClient } from '../services/clientPayment.services.js';
11
11
  import * as billingService from '../services/billing.service.js';
12
12
  import * as storeService from '../services/store.service.js';
13
+ import * as basePriceService from '../services/basePrice.service.js';
13
14
  import { symbolFor } from '../utils/currency.js';
14
15
  export const subscribedStoreList = async ( req, res ) => {
15
16
  try {
@@ -106,6 +107,29 @@ export const subscribedStoreList = async ( req, res ) => {
106
107
 
107
108
  );
108
109
 
110
+ // Selected stores for the group being edited. The backend resolves these
111
+ // itself from the group's `stores` (by groupId) so the FE doesn't have to
112
+ // send/compute them. It can also accept an explicit selectedStoreIds list.
113
+ // Each returned store gets an `isSelected` flag (drives the checkbox), and
114
+ // selected stores are floated to the top across ALL pages (before
115
+ // pagination) so they appear first.
116
+ // The live form selection (selectedStoreIds) is authoritative — it matches
117
+ // exactly what's checked in the UI (including unsaved edits). Only fall back
118
+ // to the saved group's stores when the FE didn't send a selection.
119
+ let selectedStoreIds = Array.isArray( req.body.selectedStoreIds ) ? req.body.selectedStoreIds : [];
120
+ if ( !selectedStoreIds.length && req.body.groupId ) {
121
+ const group = await findOne( { _id: req.body.groupId }, { stores: 1 } );
122
+ if ( group && Array.isArray( group.stores ) ) {
123
+ selectedStoreIds = group.stores;
124
+ }
125
+ }
126
+ // Always tag isSelected (false when nothing is selected) + sort selected
127
+ // first; storeId secondary keeps the order stable within each partition.
128
+ pipeline.push(
129
+ { $addFields: { isSelected: { $in: [ '$storeId', selectedStoreIds ] } } },
130
+ { $sort: { isSelected: -1, storeId: 1 } },
131
+ );
132
+
109
133
  const facetStage = {
110
134
  $facet: {
111
135
  data: [
@@ -855,6 +879,23 @@ export async function getLeadProducts( req, res ) {
855
879
  }
856
880
  }
857
881
 
882
+ // Tango product catalog with base prices (the clientId-absent basepricing doc).
883
+ // Used by the Assign-CSM popup to show each onboarding product's base price
884
+ // next to the negotiated-price input. Returns [{ productName, basePrice }].
885
+ export async function getBaseProducts( req, res ) {
886
+ try {
887
+ const catalog = await basePriceService.findOne( { clientId: { $exists: false } }, { basePricing: 1, _id: 0 } );
888
+ const products = ( catalog?.basePricing || [] ).map( ( p ) => ( {
889
+ productName: p.productName,
890
+ basePrice: p.basePrice,
891
+ } ) );
892
+ return res.sendSuccess( { products } );
893
+ } catch ( error ) {
894
+ logger.error( { error: error, function: 'getBaseProducts' } );
895
+ return res.sendError( error, 500 );
896
+ }
897
+ }
898
+
858
899
  export async function getClientProducts( req, res ) {
859
900
  try {
860
901
  const client = await findOneClient( { clientId: req.params.id }, { 'planDetails.product': 1, '_id': 0 } );
@@ -426,6 +426,13 @@ export async function brandInvoiceList( req, res ) {
426
426
  },
427
427
  } ];
428
428
 
429
+ // Client users only ever see APPROVED invoices — the approval pipeline
430
+ // (pendingCsm/pendingFinance/pendingApproval/pending) is internal to tango
431
+ // and must not be exposed to clients, even in the raw list response.
432
+ if ( req.user?.userType === 'client' ) {
433
+ query.push( { $match: { status: 'approved' } } );
434
+ }
435
+
429
436
  // If the user picked an explicit Month or Year, ignore durationFilter so the
430
437
  // two ranges don't conflict (e.g. "current month" + April would always 0).
431
438
  const hasMonthYear = ( req.body.monthFilter && String( req.body.monthFilter ) !== '' ) || ( req.body.yearFilter && String( req.body.yearFilter ) !== '' );
@@ -677,6 +684,76 @@ export async function latestDailyPricing( req, res ) {
677
684
  } );
678
685
  }
679
686
 
687
+ // Per-product price (basepricing) + month length, used to prorate the
688
+ // invoice amount: (price / daysInMonth) * workingDays. Computed once here so
689
+ // BOTH the export and the on-screen table use the same numbers.
690
+ const priceByProduct = {};
691
+ let bpCurrency = 'inr';
692
+ try {
693
+ const bp = await basePriceService.findOne(
694
+ { clientId: req.body.clientId },
695
+ { standard: 1, currency: 1 },
696
+ );
697
+ bpCurrency = bp?.currency || 'inr';
698
+ for ( const p of ( bp?.standard || [] ) ) {
699
+ const price = Number( p.negotiatePrice ) || Number( p.basePrice ) || 0;
700
+ if ( p.productName ) {
701
+ priceByProduct[p.productName] = price;
702
+ }
703
+ }
704
+ } catch ( bpErr ) {
705
+ logger.error( { error: bpErr, function: 'latestDailyPricing.basePrice', clientId: req.body.clientId } );
706
+ }
707
+ // Billing type per product (perStore / perZone / perCamera) from the client
708
+ // plan — drives whether the amount multiplies by camera/zone count.
709
+ const billingTypeByProduct = {};
710
+ try {
711
+ const planClient = await clientService.findOne(
712
+ { clientId: req.body.clientId },
713
+ { 'planDetails.product.productName': 1, 'planDetails.product.billingType': 1 },
714
+ );
715
+ for ( const p of ( planClient?.planDetails?.product || [] ) ) {
716
+ if ( p.productName ) {
717
+ billingTypeByProduct[p.productName] = p.billingType || 'perStore';
718
+ }
719
+ }
720
+ } catch ( btErr ) {
721
+ logger.error( { error: btErr, function: 'latestDailyPricing.billingType', clientId: req.body.clientId } );
722
+ }
723
+ const daysInMonth = dayjs( record.dateISO ).daysInMonth();
724
+ const prettyProduct = ( name ) => String( name || '' )
725
+ .replace( /^tango/i, '' )
726
+ .replace( /([a-z])([A-Z])/g, '$1 $2' )
727
+ .replace( /^./, ( ch ) => ch.toUpperCase() );
728
+ // Units to bill: perZone/perCamera multiply by the store's zone/camera
729
+ // count (same rule as invoice generation + the annexure); else per-store=1.
730
+ const productUnits = ( store, product ) => {
731
+ const billingType = billingTypeByProduct[product.productName] || 'perStore';
732
+ if ( product.productName === 'tangoZone' ) {
733
+ if ( billingType === 'perZone' && ( store.zoneCount || 0 ) > 0 ) {
734
+ return store.zoneCount;
735
+ }
736
+ if ( billingType === 'perCamera' && ( store.zoneCameraCount || 0 ) > 0 ) {
737
+ return store.zoneCameraCount;
738
+ }
739
+ } else if ( product.productName === 'tangoTraffic' ) {
740
+ if ( billingType === 'perCamera' && ( store.trafficCameraCount || 0 ) > 0 ) {
741
+ return store.trafficCameraCount;
742
+ }
743
+ }
744
+ return 1;
745
+ };
746
+ const productInvoiceAmount = ( store, product ) => {
747
+ const price = priceByProduct[product.productName] || 0;
748
+ const workingDays = Number( product.workingdays ) || 0;
749
+ if ( price <= 0 || workingDays <= 0 ) {
750
+ return 0;
751
+ }
752
+ const units = productUnits( store, product );
753
+ const days = Math.min( workingDays, daysInMonth );
754
+ return Math.round( ( ( price / daysInMonth ) * days * units ) * 100 ) / 100;
755
+ };
756
+
680
757
  if ( req.body.export ) {
681
758
  const exportdata = [];
682
759
  stores.forEach( ( store ) => {
@@ -697,6 +774,7 @@ export async function latestDailyPricing( req, res ) {
697
774
  'Zone Camera Count': isZone ? ( store.zoneCameraCount || 0 ) : 0,
698
775
  'Zone Count': isZone ? ( store.zoneCount || 0 ) : 0,
699
776
  'Working Days': product.workingdays || 0,
777
+ 'Invoice Amount': productInvoiceAmount( store, product ),
700
778
  'Status': store.status || '',
701
779
  } );
702
780
  } );
@@ -711,6 +789,7 @@ export async function latestDailyPricing( req, res ) {
711
789
  'Zone Camera Count': 0,
712
790
  'Zone Count': 0,
713
791
  'Working Days': 0,
792
+ 'Invoice Amount': 0,
714
793
  'Status': store.status || '',
715
794
  } );
716
795
  }
@@ -725,6 +804,34 @@ export async function latestDailyPricing( req, res ) {
725
804
  storeList = stores.slice( skip, skip + Number( req.body.limit ) );
726
805
  }
727
806
 
807
+ // Invoice Amount per store: prorate each product by the days it ran this
808
+ // month — (price / daysInMonth) * workingDays — summed across the store's
809
+ // products, plus a per-product breakdown for the UI. Uses the shared
810
+ // priceByProduct / daysInMonth / productInvoiceAmount computed above.
811
+ storeList = storeList.map( ( store ) => {
812
+ let invoiceAmount = 0;
813
+ const invoiceBreakdown = [];
814
+ for ( const product of ( store.products || [] ) ) {
815
+ const amount = productInvoiceAmount( store, product );
816
+ invoiceAmount += amount;
817
+ // One breakdown row per product on the store (so the UI can show
818
+ // Traffic: ₹x, Zone: ₹y …).
819
+ invoiceBreakdown.push( {
820
+ productName: product.productName,
821
+ label: prettyProduct( product.productName ),
822
+ price: priceByProduct[product.productName] || 0,
823
+ workingDays: Number( product.workingdays ) || 0,
824
+ amount,
825
+ } );
826
+ }
827
+ return {
828
+ ...( store._doc || store ),
829
+ invoiceAmount: Math.round( invoiceAmount * 100 ) / 100,
830
+ invoiceBreakdown,
831
+ invoiceCurrency: bpCurrency,
832
+ };
833
+ } );
834
+
728
835
  // Monthly Billing Summary — one row per month of the brand's invoice
729
836
  // history (stores billed + invoice amount), newest first. The UI tags the
730
837
  // current/last-generated rows and computes month-over-month deltas.
@@ -153,6 +153,10 @@ export const validatePriceSchema = joi.object( {
153
153
  type: joi.string().optional(),
154
154
  clientId: joi.string().required(),
155
155
  products: joi.array().optional(),
156
+ pricing: joi.array().items( joi.object( {
157
+ productName: joi.string().required(),
158
+ negotiatePrice: joi.number().required(),
159
+ } ) ).optional(),
156
160
  } );
157
161
 
158
162
  export const validatePriceParams = {
@@ -286,6 +290,12 @@ export const subscribedStoreListBody = joi.object( {
286
290
  state: joi.array().optional(),
287
291
  city: joi.array().optional(),
288
292
  getFilters: joi.boolean().optional(),
293
+ // Storeids selected in the group being edited — backend floats them to the
294
+ // top (selected-first ordering across pages).
295
+ selectedStoreIds: joi.array().items( joi.string() ).optional(),
296
+ // Group being edited — backend resolves its selected stores and tags each
297
+ // returned store with isSelected (drives checkbox + selected-first order).
298
+ groupId: joi.string().optional().allow( '' ),
289
299
  } );
290
300
 
291
301
  export const subscribedStoreListSchema = {
@@ -44,6 +44,11 @@
44
44
  <p style="margin:0;">To discuss reinstatement and clear the outstanding amount, please contact us. We'd like to help you resolve this and restore your account.</p>
45
45
  </td>
46
46
  </tr>
47
+ <tr>
48
+ <td style="background-color:#ffffff;padding:6px 24px 6px 30px;font-size:14px;line-height:150%;color:#8a93a2;">
49
+ <p style="margin:0;font-style:italic;">If you have already paid this invoice, please disregard this email.</p>
50
+ </td>
51
+ </tr>
47
52
  <tr>
48
53
  <td style="background-color:#ffffff;padding:14px 24px 24px 30px;font-size:16px;line-height:150%;color:#384860;">
49
54
  <p style="margin:0;">Best regards,<br>{{companyName}}</p>
@@ -44,6 +44,11 @@
44
44
  <p style="margin:0;">To take your account off hold, please complete the outstanding payment. For any queries, please reach out to our customer support team.</p>
45
45
  </td>
46
46
  </tr>
47
+ <tr>
48
+ <td style="background-color:#ffffff;padding:6px 24px 6px 30px;font-size:14px;line-height:150%;color:#8a93a2;">
49
+ <p style="margin:0;font-style:italic;">If you have already paid this invoice, please disregard this email.</p>
50
+ </td>
51
+ </tr>
47
52
  <tr>
48
53
  <td style="background-color:#ffffff;padding:14px 24px 24px 30px;font-size:16px;line-height:150%;color:#384860;">
49
54
  <p style="margin:0;">Best regards,<br>{{companyName}}</p>
@@ -44,6 +44,11 @@
44
44
  <p style="margin:0;">To reactivate your account, please complete the outstanding payment as soon as possible. For any queries, please reach out to our customer support team.</p>
45
45
  </td>
46
46
  </tr>
47
+ <tr>
48
+ <td style="background-color:#ffffff;padding:6px 24px 6px 30px;font-size:14px;line-height:150%;color:#8a93a2;">
49
+ <p style="margin:0;font-style:italic;">If you have already paid this invoice, please disregard this email.</p>
50
+ </td>
51
+ </tr>
47
52
  <tr>
48
53
  <td style="background-color:#ffffff;padding:14px 24px 24px 30px;font-size:16px;line-height:150%;color:#384860;">
49
54
  <p style="margin:0;">Best regards,<br>{{companyName}}</p>
@@ -3,7 +3,7 @@ export const billingRouter = express.Router();
3
3
  import { accessVerification, isAllowedSessionHandler, validate } from 'tango-app-api-middleware';
4
4
  import { getPaymentReminder, savePaymentReminder } from '../controllers/paymentReminder.controller.js';
5
5
  import { triggerPaymentReminders } from '../controllers/paymentReminderTrigger.controller.js';
6
- import { createBillingGroup, deleteBillingGroup, getAllBillingGroups, getBillingGroups, getClientProducts, getInvoices, getLeadProducts, onetimePayment, subscribedStoreList, updateBillingGroup, gstinLookup } from '../controllers/billing.controllers.js';
6
+ import { createBillingGroup, deleteBillingGroup, getAllBillingGroups, getBillingGroups, getClientProducts, getInvoices, getLeadProducts, getBaseProducts, onetimePayment, subscribedStoreList, updateBillingGroup, gstinLookup } from '../controllers/billing.controllers.js';
7
7
  import { billingGroupSchema, clientProductsValid, createBillingGroupsSchema, deleteBillingGroupsSchema, getBillingGroupsSchema, getInvoiceSchema, leadProductsValid, onetimeFeeValid, subscribedStoreListSchema, updateBillingGroupsSchema } from '../dtos/validation.dtos.js';
8
8
 
9
9
 
@@ -28,6 +28,8 @@ billingRouter.get( '/getLeadProducts/:email', isAllowedSessionHandler, validate(
28
28
 
29
29
  billingRouter.get( '/getClientProducts/:id', isAllowedSessionHandler, validate( clientProductsValid ), getClientProducts );
30
30
 
31
+ billingRouter.get( '/getBaseProducts', isAllowedSessionHandler, getBaseProducts );
32
+
31
33
 
32
34
  billingRouter.get( '/gst-lookup/:gstin', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'Global', name: 'Billing', permissions: [ 'isAdd' ] } ] } ), gstinLookup );
33
35
 
@@ -6,12 +6,12 @@ import { isAllowedSessionHandler, accessVerification } from 'tango-app-api-middl
6
6
  export const brandsBillingRouter = express.Router();
7
7
 
8
8
  brandsBillingRouter.post( '/brandsBillingList', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), brandsBillingList );
9
- brandsBillingRouter.post( '/brandInvoiceList', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), brandInvoiceList );
9
+ brandsBillingRouter.post( '/brandInvoiceList', isAllowedSessionHandler, accessVerification( { userType: [ 'tango', 'client' ], access: [ ] } ), brandInvoiceList );
10
10
  brandsBillingRouter.post( '/latestDailyPricing', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), latestDailyPricing );
11
11
  brandsBillingRouter.post( '/brandBillingGroups', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), brandBillingGroups );
12
12
  brandsBillingRouter.put( '/updateDailyPricingWorkingDays', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), updateDailyPricingWorkingDays );
13
13
  brandsBillingRouter.put( '/updateDailyPricingStoreField', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), updateDailyPricingStoreField );
14
- brandsBillingRouter.post( '/getClientBillingInfo', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), getClientBillingInfo );
14
+ brandsBillingRouter.post( '/getClientBillingInfo', isAllowedSessionHandler, accessVerification( { userType: [ 'tango', 'client' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), getClientBillingInfo );
15
15
  brandsBillingRouter.get( '/bulk-download-billing-groups', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), bulkDownloadBillingGroups );
16
16
  brandsBillingRouter.post( '/bulk-update-billing-groups', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), bulkUpdateBillingGroups );
17
17
  brandsBillingRouter.post( '/billingSummary', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), billingSummary );
@@ -21,11 +21,11 @@ function superadminBypass( accessConfig ) {
21
21
  }
22
22
 
23
23
 
24
- invoiceRouter.post( '/createInvoice', isAllowedSessionHandler, createInvoice );
25
- invoiceRouter.post( '/regerateInvoice', isAllowedSessionHandler, createInvoice );
24
+ invoiceRouter.post( '/createInvoice', isAllowedSessionHandler, superadminBypass( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), createInvoice );
25
+ invoiceRouter.post( '/regerateInvoice', isAllowedSessionHandler, superadminBypass( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), createInvoice );
26
26
  invoiceRouter.post( '/invoiceDownload/bulk', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), invoiceDownloadBulk );
27
27
  invoiceRouter.post( '/invoiceDownload/:invoiceId', isAllowedSessionHandler, invoiceDownload );
28
- invoiceRouter.post( '/clientInvoiceList', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), clientInvoiceList );
28
+ invoiceRouter.post( '/clientInvoiceList', isAllowedSessionHandler, accessVerification( { userType: [ 'tango', 'client' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), clientInvoiceList );
29
29
  invoiceRouter.post( '/creditTransactionlist', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), creditTransactionlist );
30
30
  invoiceRouter.post( '/pendingInvoices', isAllowedSessionHandler, pendingInvoices );
31
31
  invoiceRouter.post( '/applyDiscount', isAllowedSessionHandler, superadminBypass( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), applyDiscount );
@@ -33,9 +33,9 @@ invoiceRouter.post( '/migrateInvoice', migrateInvoice );
33
33
  invoiceRouter.post( '/PaymentStatusChange', isAllowedSessionHandler, superadminBypass( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), PaymentStatusChange );
34
34
  invoiceRouter.post( '/recordPayment', isAllowedSessionHandler, superadminBypass( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), recordPayment );
35
35
  invoiceRouter.post( '/checkPaymentStatus', checkPaymentStatus );
36
- invoiceRouter.get( '/getInvoice/:invoiceId', isAllowedSessionHandler, superadminBypass( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), getInvoice );
37
- invoiceRouter.get( '/invoiceAnnexure/:invoiceId', isAllowedSessionHandler, superadminBypass( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), invoiceAnnexure );
38
- invoiceRouter.get( '/invoiceBankDetails/:invoiceId', isAllowedSessionHandler, superadminBypass( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), invoiceBankDetails );
36
+ invoiceRouter.get( '/getInvoice/:invoiceId', isAllowedSessionHandler, superadminBypass( { userType: [ 'tango', 'client' ], access: [ ] } ), getInvoice );
37
+ invoiceRouter.get( '/invoiceAnnexure/:invoiceId', isAllowedSessionHandler, superadminBypass( { userType: [ 'tango', 'client' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), invoiceAnnexure );
38
+ invoiceRouter.get( '/invoiceBankDetails/:invoiceId', isAllowedSessionHandler, superadminBypass( { userType: [ 'tango', 'client' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), invoiceBankDetails );
39
39
  invoiceRouter.put( '/updateInvoice', isAllowedSessionHandler, superadminBypass( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), updateInvoice );
40
40
  invoiceRouter.get( '/getClientBasePricing/:clientId', isAllowedSessionHandler, getClientBasePricing );
41
41
  invoiceRouter.delete( '/deleteInvoice/:invoiceId', isAllowedSessionHandler, superadminBypass( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [ 'isEdit' ] } ] } ), deleteInvoice );