tango-app-api-payment-subscription 3.5.15 → 3.5.17
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 +2 -2
- package/src/controllers/brandsBilling.controller.js +360 -0
- package/src/controllers/invoice.controller.js +212 -267
- package/src/controllers/paymentSubscription.controllers.js +183 -18
- package/src/dtos/validation.dtos.js +7 -0
- package/src/routes/brandsBilling.routes.js +2 -1
- package/src/routes/paymentSubscription.routes.js +12 -0
- package/src/services/clientPayment.services.js +5 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
/* eslint-disable new-cap */
|
|
3
|
-
import { logger, download, sendEmailWithSES, insertOpenSearchData, getOpenSearchData, updateOpenSearchData, getOpenSearchById } from 'tango-app-api-middleware';
|
|
3
|
+
import { logger, download, sendEmailWithSES, insertOpenSearchData, getOpenSearchData, updateOpenSearchData, getOpenSearchById, fileUpload, customSignedUrl } from 'tango-app-api-middleware';
|
|
4
4
|
import * as paymentService from '../services/clientPayment.services.js';
|
|
5
5
|
import * as basePriceService from '../services/basePrice.service.js';
|
|
6
6
|
import * as storeService from '../services/store.service.js';
|
|
@@ -85,6 +85,7 @@ export const clientBillingSubscriptionInfo = async ( req, res, next ) => {
|
|
|
85
85
|
billingDetails: 1,
|
|
86
86
|
price: 1,
|
|
87
87
|
priceType: 1,
|
|
88
|
+
billingGroupWisePricing: 1,
|
|
88
89
|
virtualAccount: 1,
|
|
89
90
|
paymentInvoice: 1,
|
|
90
91
|
},
|
|
@@ -259,6 +260,7 @@ export const clientBillingSubscriptionInfo = async ( req, res, next ) => {
|
|
|
259
260
|
currentPlanInfo.dueLimitReached = getPI;
|
|
260
261
|
currentPlanInfo.price = clientInfo[0].price || '--';
|
|
261
262
|
currentPlanInfo.priceType = clientInfo[0].priceType || '--';
|
|
263
|
+
currentPlanInfo.billingGroupWisePricing = clientInfo[0].billingGroupWisePricing || false;
|
|
262
264
|
currentPlanInfo.subscriptionType = clientInfo[0].planDetails.subscriptionType || '--';
|
|
263
265
|
currentPlanInfo.subscriptionPeriod = clientInfo[0].planDetails.subscriptionPeriod || '--';
|
|
264
266
|
currentPlanInfo.storeCount = storeCount || '--';
|
|
@@ -2246,7 +2248,15 @@ export const invoiceList = async ( req, res ) => {
|
|
|
2246
2248
|
|
|
2247
2249
|
export const priceList = async ( req, res ) => {
|
|
2248
2250
|
try {
|
|
2249
|
-
|
|
2251
|
+
// Group-wise pricing: when a groupId is sent, read that group's own
|
|
2252
|
+
// basepricing doc; otherwise read the brand-level doc (groupId unset).
|
|
2253
|
+
const priceQuery = { clientId: req.body.clientId };
|
|
2254
|
+
if ( req.body.groupId ) {
|
|
2255
|
+
priceQuery.groupId = req.body.groupId;
|
|
2256
|
+
} else {
|
|
2257
|
+
priceQuery.groupId = { $exists: false };
|
|
2258
|
+
}
|
|
2259
|
+
let pricingDetails = await basePricingService.findOne( priceQuery, { standard: 1, step: 1, oneTimeFeePerStore: 1 } );
|
|
2250
2260
|
if ( !pricingDetails ) {
|
|
2251
2261
|
return res.sendError( 'no data found', 204 );
|
|
2252
2262
|
}
|
|
@@ -2301,19 +2311,25 @@ export const priceList = async ( req, res ) => {
|
|
|
2301
2311
|
product.showImg = true;
|
|
2302
2312
|
product.showEditDelete = true;
|
|
2303
2313
|
}
|
|
2304
|
-
|
|
2314
|
+
// Last tier absorbs whatever stores remain after the earlier tiers.
|
|
2315
|
+
// Never let it go negative when the actual store count is smaller
|
|
2316
|
+
// than the defined tier ranges (e.g. 83 stores across 1-50 / 51-100
|
|
2317
|
+
// left the last tier at 83 - 100 = -17).
|
|
2318
|
+
product.storeCount = Math.max( item.storeCount, 0 );
|
|
2305
2319
|
product.lastIndex = true;
|
|
2306
|
-
} else if ( index == 0 ) {
|
|
2307
|
-
product.storeCount = 100;
|
|
2308
|
-
item.storeCount = item.storeCount - 100;
|
|
2309
2320
|
} else {
|
|
2310
|
-
|
|
2311
|
-
|
|
2321
|
+
// Non-last tier: use its own range size (end - start + 1), but cap at
|
|
2322
|
+
// the stores still remaining so a tier can't claim more stores than
|
|
2323
|
+
// exist. The first tier previously hardcoded 100, which overcounted
|
|
2324
|
+
// (and pushed the last tier negative) whenever the range wasn't 1-100.
|
|
2325
|
+
product.showImg = index != 0;
|
|
2326
|
+
let rangeArray = String( product.storeRange || '' ).split( '-' );
|
|
2312
2327
|
let startNumber = parseInt( rangeArray[0] );
|
|
2313
2328
|
let endNumber = parseInt( rangeArray[1] );
|
|
2314
|
-
let diff = endNumber - startNumber + 1;
|
|
2315
|
-
|
|
2316
|
-
|
|
2329
|
+
let diff = ( endNumber - startNumber + 1 ) || 0;
|
|
2330
|
+
let assigned = Math.max( Math.min( diff, item.storeCount ), 0 );
|
|
2331
|
+
product.storeCount = assigned;
|
|
2332
|
+
item.storeCount = item.storeCount - assigned;
|
|
2317
2333
|
}
|
|
2318
2334
|
}
|
|
2319
2335
|
// let discountPrice = product.basePrice * ( product.discountPercentage / 100 );
|
|
@@ -2335,6 +2351,7 @@ export const priceList = async ( req, res ) => {
|
|
|
2335
2351
|
let finalValue = parseFloat( discountTotalPrice ) + gstAmount;
|
|
2336
2352
|
let result = {
|
|
2337
2353
|
product: data,
|
|
2354
|
+
oneTimeFeePerStore: pricingDetails.oneTimeFeePerStore != null ? pricingDetails.oneTimeFeePerStore : null,
|
|
2338
2355
|
totalActualPrice: originalTotalPrice,
|
|
2339
2356
|
totalNegotiatePrice: discountTotalPrice.toFixed( 2 ),
|
|
2340
2357
|
actualPrice: totalProductPrice,
|
|
@@ -2353,7 +2370,15 @@ export const priceList = async ( req, res ) => {
|
|
|
2353
2370
|
|
|
2354
2371
|
export const pricingListUpdate = async ( req, res ) => {
|
|
2355
2372
|
try {
|
|
2356
|
-
|
|
2373
|
+
// Group-wise pricing: target the group's own basepricing doc when a groupId
|
|
2374
|
+
// is sent; otherwise the brand-level doc (groupId unset).
|
|
2375
|
+
const priceQuery = { clientId: req.body.clientId };
|
|
2376
|
+
if ( req.body.groupId ) {
|
|
2377
|
+
priceQuery.groupId = req.body.groupId;
|
|
2378
|
+
} else {
|
|
2379
|
+
priceQuery.groupId = { $exists: false };
|
|
2380
|
+
}
|
|
2381
|
+
let getPriceInfo = await basePricingService.findOne( priceQuery, { standard: 1, step: 1 } );
|
|
2357
2382
|
let findClient = await paymentService.findOneClient( { clientId: req.body.clientId } );
|
|
2358
2383
|
|
|
2359
2384
|
console.log( getPriceInfo );
|
|
@@ -2363,8 +2388,10 @@ export const pricingListUpdate = async ( req, res ) => {
|
|
|
2363
2388
|
pricingType: findClient.priceType,
|
|
2364
2389
|
};
|
|
2365
2390
|
|
|
2366
|
-
|
|
2367
|
-
|
|
2391
|
+
// Old-data snapshot for the audit log. Guard on getPriceInfo: a brand-new
|
|
2392
|
+
// billing-group doc has none yet, and the no-doc path is handled below.
|
|
2393
|
+
if ( getPriceInfo && findClient.priceType==='standard' ) {
|
|
2394
|
+
( getPriceInfo.standard || [] ).map( ( item ) => {
|
|
2368
2395
|
oldData = {
|
|
2369
2396
|
...oldData,
|
|
2370
2397
|
[item.productName+' '+'negotiatePrice']: item.negotiatePrice,
|
|
@@ -2376,8 +2403,8 @@ export const pricingListUpdate = async ( req, res ) => {
|
|
|
2376
2403
|
};
|
|
2377
2404
|
}
|
|
2378
2405
|
} );
|
|
2379
|
-
} else {
|
|
2380
|
-
getPriceInfo.step.map( ( item ) => {
|
|
2406
|
+
} else if ( getPriceInfo ) {
|
|
2407
|
+
( getPriceInfo.step || [] ).map( ( item ) => {
|
|
2381
2408
|
oldData = {
|
|
2382
2409
|
...oldData,
|
|
2383
2410
|
[item.productName+' '+'negotiatePrice']: item.negotiatePrice,
|
|
@@ -2484,6 +2511,18 @@ export const pricingListUpdate = async ( req, res ) => {
|
|
|
2484
2511
|
} else {
|
|
2485
2512
|
getPriceInfo.step = req.body.products;
|
|
2486
2513
|
}
|
|
2514
|
+
// Brand-level one-time fee per store. Only overwrite when the request
|
|
2515
|
+
// actually carries a value so other save paths don't wipe it.
|
|
2516
|
+
if ( req.body.oneTimeFeePerStore != null && req.body.oneTimeFeePerStore !== '' ) {
|
|
2517
|
+
getPriceInfo.oneTimeFeePerStore = Number( req.body.oneTimeFeePerStore ) || 0;
|
|
2518
|
+
}
|
|
2519
|
+
// Keep the group identity on the doc when saving group-wise pricing.
|
|
2520
|
+
if ( req.body.groupId ) {
|
|
2521
|
+
getPriceInfo.groupId = req.body.groupId;
|
|
2522
|
+
if ( req.body.groupName ) {
|
|
2523
|
+
getPriceInfo.groupName = req.body.groupName;
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2487
2526
|
getPriceInfo.save().then( async () => {
|
|
2488
2527
|
let clientDetails = await paymentService.findOne( { clientId: req.body.clientId }, { priceType: 1, paymentInvoice: 1, planDetails: 1 } );
|
|
2489
2528
|
clientDetails.priceType = req.body.type;
|
|
@@ -2554,7 +2593,15 @@ export const pricingListUpdate = async ( req, res ) => {
|
|
|
2554
2593
|
|
|
2555
2594
|
async function updatePricing( req, res, update ) {
|
|
2556
2595
|
let baseProduct = await basePricingService.findOne( { clientId: { $exists: false } }, { basePricing: 1 } );
|
|
2557
|
-
|
|
2596
|
+
// Group-wise pricing: scope the doc to the group when a groupId is sent;
|
|
2597
|
+
// otherwise the brand-level doc (groupId unset).
|
|
2598
|
+
const pricingDocQuery = { clientId: req.body.clientId };
|
|
2599
|
+
if ( req.body.groupId ) {
|
|
2600
|
+
pricingDocQuery.groupId = req.body.groupId;
|
|
2601
|
+
} else {
|
|
2602
|
+
pricingDocQuery.groupId = { $exists: false };
|
|
2603
|
+
}
|
|
2604
|
+
let getPriceInfo = await basePricingService.findOne( pricingDocQuery, { standard: 1, step: 1 } );
|
|
2558
2605
|
let clientDetails = await paymentService.findOne( { clientId: req.body.clientId } );
|
|
2559
2606
|
if ( clientDetails ) {
|
|
2560
2607
|
let products = clientDetails.planDetails.product.map( ( item ) => item.productName );
|
|
@@ -2625,12 +2672,20 @@ async function updatePricing( req, res, update ) {
|
|
|
2625
2672
|
step: stepList,
|
|
2626
2673
|
clientId: req.body.clientId,
|
|
2627
2674
|
};
|
|
2675
|
+
// Stamp the group identity so group-wise pricing docs are distinguishable
|
|
2676
|
+
// from the brand-level doc.
|
|
2677
|
+
if ( req.body.groupId ) {
|
|
2678
|
+
data.groupId = req.body.groupId;
|
|
2679
|
+
if ( req.body.groupName ) {
|
|
2680
|
+
data.groupName = req.body.groupName;
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2628
2683
|
console.log( '🚀 ~ updatePricing ~ data:', data );
|
|
2629
2684
|
if ( !getPriceInfo ) {
|
|
2630
2685
|
await basePricingService.create( data );
|
|
2631
2686
|
} else {
|
|
2632
2687
|
delete data.clientId;
|
|
2633
|
-
await basePricingService.updateOne(
|
|
2688
|
+
await basePricingService.updateOne( pricingDocQuery, data );
|
|
2634
2689
|
}
|
|
2635
2690
|
let product = [];
|
|
2636
2691
|
let clientId = req.body.clientId;
|
|
@@ -4130,3 +4185,113 @@ export async function createDefaultbillings( req, res ) {
|
|
|
4130
4185
|
}
|
|
4131
4186
|
}
|
|
4132
4187
|
|
|
4188
|
+
// ---------------------------------------------------------------------------
|
|
4189
|
+
// Brand documents (Plans & Subscription > Documents accordion).
|
|
4190
|
+
// Upload a single PDF for a client, store it in the assets bucket under
|
|
4191
|
+
// <clientId>/documents/, and record { documentName, path } on the client's
|
|
4192
|
+
// additionalDocuments array. List returns the docs with signed URLs.
|
|
4193
|
+
// ---------------------------------------------------------------------------
|
|
4194
|
+
export async function uploadClientDocument( req, res ) {
|
|
4195
|
+
try {
|
|
4196
|
+
const clientId = String( req.body?.clientId || '' );
|
|
4197
|
+
const documentName = String( req.body?.documentName || '' ).trim();
|
|
4198
|
+
const expiryDateRaw = req.body?.expiryDate;
|
|
4199
|
+
const file = req.file; // multer single('file')
|
|
4200
|
+
|
|
4201
|
+
if ( !clientId ) {
|
|
4202
|
+
return res.sendError( 'clientId is required', 400 );
|
|
4203
|
+
}
|
|
4204
|
+
if ( !documentName ) {
|
|
4205
|
+
return res.sendError( 'documentName is required', 400 );
|
|
4206
|
+
}
|
|
4207
|
+
if ( !file ) {
|
|
4208
|
+
return res.sendError( 'file is required', 400 );
|
|
4209
|
+
}
|
|
4210
|
+
// PDF only.
|
|
4211
|
+
if ( file.mimetype !== 'application/pdf' ) {
|
|
4212
|
+
return res.sendError( 'Only PDF files are allowed', 400 );
|
|
4213
|
+
}
|
|
4214
|
+
|
|
4215
|
+
const client = await paymentService.findOneClient( { clientId }, { clientId: 1 } );
|
|
4216
|
+
if ( !client ) {
|
|
4217
|
+
return res.sendError( 'Client not found', 404 );
|
|
4218
|
+
}
|
|
4219
|
+
|
|
4220
|
+
const bucket = JSON.parse( process.env.BUCKET );
|
|
4221
|
+
// Unique-ish file name so re-uploads with the same display name don't clash.
|
|
4222
|
+
const safeName = documentName.replace( /[^a-zA-Z0-9-_]/g, '_' );
|
|
4223
|
+
const fileName = `${safeName}_${Date.now()}.pdf`;
|
|
4224
|
+
const key = `${clientId}/documents/`;
|
|
4225
|
+
|
|
4226
|
+
await fileUpload( {
|
|
4227
|
+
Bucket: bucket.assets,
|
|
4228
|
+
Key: key,
|
|
4229
|
+
fileName: fileName,
|
|
4230
|
+
ContentType: file.mimetype,
|
|
4231
|
+
body: file.buffer,
|
|
4232
|
+
} );
|
|
4233
|
+
|
|
4234
|
+
const storedPath = `${key}${fileName}`;
|
|
4235
|
+
const expiryDate = expiryDateRaw ? new Date( expiryDateRaw ) : null;
|
|
4236
|
+
await paymentService.pushAdditionalDocument( { clientId }, {
|
|
4237
|
+
documentName: documentName,
|
|
4238
|
+
path: storedPath,
|
|
4239
|
+
expiryDate: ( expiryDate && !isNaN( expiryDate.getTime() ) ) ? expiryDate : null,
|
|
4240
|
+
uploadedAt: new Date(),
|
|
4241
|
+
} );
|
|
4242
|
+
|
|
4243
|
+
return res.sendSuccess( { documentName, path: storedPath, expiryDate } );
|
|
4244
|
+
} catch ( error ) {
|
|
4245
|
+
logger.error( { error: error, function: 'uploadClientDocument' } );
|
|
4246
|
+
return res.sendError( error, 500 );
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
|
|
4250
|
+
export async function getClientDocuments( req, res ) {
|
|
4251
|
+
try {
|
|
4252
|
+
const clientId = String( req.query?.clientId || req.params?.clientId || '' );
|
|
4253
|
+
if ( !clientId ) {
|
|
4254
|
+
return res.sendError( 'clientId is required', 400 );
|
|
4255
|
+
}
|
|
4256
|
+
const client = await paymentService.findOneClient( { clientId }, { additionalDocuments: 1 } );
|
|
4257
|
+
const bucket = JSON.parse( process.env.BUCKET );
|
|
4258
|
+
const docs = await Promise.all( ( client?.additionalDocuments || [] ).map( async ( d ) => {
|
|
4259
|
+
let url = '';
|
|
4260
|
+
try {
|
|
4261
|
+
url = await customSignedUrl( { Bucket: bucket.assets, file_path: d.path }, 8 );
|
|
4262
|
+
} catch ( e ) {
|
|
4263
|
+
url = '';
|
|
4264
|
+
}
|
|
4265
|
+
return {
|
|
4266
|
+
_id: d._id,
|
|
4267
|
+
documentName: d.documentName,
|
|
4268
|
+
path: d.path,
|
|
4269
|
+
expiryDate: d.expiryDate,
|
|
4270
|
+
uploadedAt: d.uploadedAt,
|
|
4271
|
+
url,
|
|
4272
|
+
};
|
|
4273
|
+
} ) );
|
|
4274
|
+
return res.sendSuccess( { documents: docs } );
|
|
4275
|
+
} catch ( error ) {
|
|
4276
|
+
logger.error( { error: error, function: 'getClientDocuments' } );
|
|
4277
|
+
return res.sendError( error, 500 );
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
|
|
4281
|
+
// Toggle billing-group-wise pricing on the client. When enabled, pricing is
|
|
4282
|
+
// maintained per billing group (separate basepricing docs keyed by groupId).
|
|
4283
|
+
export async function setBillingGroupWisePricing( req, res ) {
|
|
4284
|
+
try {
|
|
4285
|
+
const clientId = String( req.body?.clientId || '' );
|
|
4286
|
+
const enabled = req.body?.enabled === true || req.body?.enabled === 'true';
|
|
4287
|
+
if ( !clientId ) {
|
|
4288
|
+
return res.sendError( 'clientId is required', 400 );
|
|
4289
|
+
}
|
|
4290
|
+
await paymentService.updateOne( { clientId }, { billingGroupWisePricing: enabled } );
|
|
4291
|
+
return res.sendSuccess( { clientId, billingGroupWisePricing: enabled } );
|
|
4292
|
+
} catch ( error ) {
|
|
4293
|
+
logger.error( { error: error, function: 'setBillingGroupWisePricing' } );
|
|
4294
|
+
return res.sendError( error, 500 );
|
|
4295
|
+
}
|
|
4296
|
+
}
|
|
4297
|
+
|
|
@@ -153,6 +153,11 @@ export const validatePriceSchema = joi.object( {
|
|
|
153
153
|
type: joi.string().optional(),
|
|
154
154
|
clientId: joi.string().required(),
|
|
155
155
|
products: joi.array().optional(),
|
|
156
|
+
oneTimeFeePerStore: joi.number().optional().allow( null, '' ),
|
|
157
|
+
// Billing-group-wise pricing: when present, the pricing is saved to / read
|
|
158
|
+
// from the group's own basepricing doc instead of the brand-level one.
|
|
159
|
+
groupId: joi.string().optional().allow( null, '' ),
|
|
160
|
+
groupName: joi.string().optional().allow( null, '' ),
|
|
156
161
|
pricing: joi.array().items( joi.object( {
|
|
157
162
|
productName: joi.string().required(),
|
|
158
163
|
negotiatePrice: joi.number().required(),
|
|
@@ -177,6 +182,8 @@ export const revisedParams = {
|
|
|
177
182
|
export const validatePriceListSchema = joi.object( {
|
|
178
183
|
priceType: joi.string().required(),
|
|
179
184
|
clientId: joi.string().required(),
|
|
185
|
+
// Optional billing group filter for group-wise pricing.
|
|
186
|
+
groupId: joi.string().optional().allow( null, '' ),
|
|
180
187
|
} );
|
|
181
188
|
|
|
182
189
|
export const validatePriceListParams = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import express from 'express';
|
|
3
|
-
import { brandsBillingList, brandInvoiceList, latestDailyPricing, brandBillingGroups, updateDailyPricingWorkingDays, updateDailyPricingStoreField, getClientBillingInfo, bulkDownloadBillingGroups, bulkUpdateBillingGroups, billingSummary, additionalProducts } from '../controllers/brandsBilling.controller.js';
|
|
3
|
+
import { brandsBillingList, brandInvoiceList, latestDailyPricing, brandBillingGroups, updateDailyPricingWorkingDays, updateDailyPricingStoreField, getClientBillingInfo, bulkDownloadBillingGroups, bulkUpdateBillingGroups, billingSummary, additionalProducts, additionalProductExport } from '../controllers/brandsBilling.controller.js';
|
|
4
4
|
import { isAllowedSessionHandler, accessVerification } from 'tango-app-api-middleware';
|
|
5
5
|
|
|
6
6
|
export const brandsBillingRouter = express.Router();
|
|
@@ -17,3 +17,4 @@ brandsBillingRouter.post( '/bulk-update-billing-groups', isAllowedSessionHandler
|
|
|
17
17
|
brandsBillingRouter.post( '/billingSummary', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), billingSummary );
|
|
18
18
|
brandsBillingRouter.get( '/billingSummary', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), billingSummary );
|
|
19
19
|
brandsBillingRouter.get( '/additionalProducts', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), additionalProducts );
|
|
20
|
+
brandsBillingRouter.get( '/additionalProductExport', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'TangoAdmin', name: 'invoiceApproval', permissions: [] } ] } ), additionalProductExport );
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
|
|
2
2
|
import express from 'express';
|
|
3
|
+
import multer from 'multer';
|
|
3
4
|
import * as paymentController from '../controllers/paymentSubscription.controllers.js';
|
|
4
5
|
import { validate, isAllowedSessionHandler, accessVerification } from 'tango-app-api-middleware';
|
|
5
6
|
import * as validationDtos from '../dtos/validation.dtos.js';
|
|
6
7
|
import { validateClient } from '../utils/validations/client.validation.js';
|
|
7
8
|
export const paymentSubscriptionRouter = express.Router();
|
|
8
9
|
|
|
10
|
+
// PDF document upload (Plans & Subscription > Documents). In-memory storage,
|
|
11
|
+
// 10MB cap; the controller streams the buffer to the assets S3 bucket.
|
|
12
|
+
const documentUpload = multer( { storage: multer.memoryStorage(), limits: { fileSize: 10 * 1024 * 1024 } } );
|
|
13
|
+
|
|
9
14
|
paymentSubscriptionRouter.post( '/addBilling', isAllowedSessionHandler, accessVerification( {
|
|
10
15
|
userType: [ 'tango', 'client' ], access: [
|
|
11
16
|
{ featureName: 'Global', name: 'Subscription', permissions: [ 'isAdd' ] },
|
|
@@ -146,4 +151,11 @@ paymentSubscriptionRouter.put( '/pushNotification/update/:notificationId', isAll
|
|
|
146
151
|
paymentSubscriptionRouter.post( '/updateRemind/:notificationId', isAllowedSessionHandler, validate( validationDtos.validateId ), paymentController.updateRemind );
|
|
147
152
|
paymentSubscriptionRouter.post( '/createDefaultbillings', paymentController.createDefaultbillings );
|
|
148
153
|
|
|
154
|
+
// Brand documents (Plans & Subscription > Documents accordion).
|
|
155
|
+
paymentSubscriptionRouter.post( '/client-document/upload', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'Global', name: 'Subscription', permissions: [ 'isEdit' ] } ] } ), documentUpload.single( 'file' ), paymentController.uploadClientDocument );
|
|
156
|
+
paymentSubscriptionRouter.get( '/client-document/list', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'Global', name: 'Subscription', permissions: [] } ] } ), paymentController.getClientDocuments );
|
|
157
|
+
|
|
158
|
+
// Toggle billing-group-wise pricing for a client (tango only, edit perm).
|
|
159
|
+
paymentSubscriptionRouter.post( '/billingGroupWisePricing', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ], access: [ { featureName: 'Global', name: 'Subscription', permissions: [ 'isEdit' ] } ] } ), paymentController.setBillingGroupWisePricing );
|
|
160
|
+
|
|
149
161
|
|
|
@@ -16,6 +16,11 @@ export const updateOne = ( query = {}, record = {} ) => {
|
|
|
16
16
|
return model.clientModel.updateOne( query, { $set: record } );
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
// Push a document into the client's additionalDocuments array.
|
|
20
|
+
export const pushAdditionalDocument = ( query = {}, document = {} ) => {
|
|
21
|
+
return model.clientModel.updateOne( query, { $push: { additionalDocuments: document } } );
|
|
22
|
+
};
|
|
23
|
+
|
|
19
24
|
export const aggregate = ( query = [] ) => {
|
|
20
25
|
return model.clientModel.aggregate( query );
|
|
21
26
|
};
|