tango-app-api-payment-subscription 3.5.19 → 3.5.20
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.
|
|
3
|
+
"version": "3.5.20",
|
|
4
4
|
"description": "paymentSubscription",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"nodemon": "^3.1.0",
|
|
30
30
|
"puppeteer": "^24.41.0",
|
|
31
31
|
"swagger-ui-express": "^5.0.0",
|
|
32
|
-
"tango-api-schema": "^2.6.
|
|
32
|
+
"tango-api-schema": "^2.6.37",
|
|
33
33
|
"tango-app-api-middleware": "^3.6.18",
|
|
34
34
|
"winston": "^3.12.0",
|
|
35
35
|
"winston-daily-rotate-file": "^5.0.0",
|
|
@@ -579,14 +579,20 @@ export async function brandInvoiceList( req, res ) {
|
|
|
579
579
|
return res.sendError( 'No data', 204 );
|
|
580
580
|
}
|
|
581
581
|
|
|
582
|
+
// Footer totals are shown in a single ₹ figure, but individual invoices may
|
|
583
|
+
// be in USD ($). Convert dollar invoices to INR at today's rate before
|
|
584
|
+
// summing so the ₹ total is correct (mirrors the table's per-invoice symbol
|
|
585
|
+
// → one combined rupee total).
|
|
586
|
+
const usdRate = await getUsdInrRate();
|
|
587
|
+
const toInr = ( inv, value ) => ( inv.currency === 'dollar' ? ( Number( value ) || 0 ) * usdRate : ( Number( value ) || 0 ) );
|
|
582
588
|
let summary = {
|
|
583
589
|
totalInvoices: allInvoices.length,
|
|
584
|
-
totalInvoiced: allInvoices.reduce( ( sum, inv ) => sum + ( inv.totalAmount
|
|
590
|
+
totalInvoiced: Math.round( allInvoices.reduce( ( sum, inv ) => sum + toInr( inv, inv.totalAmount ), 0 ) ),
|
|
585
591
|
// Footer totals over the FULL filtered set (not just the current page):
|
|
586
|
-
// stores, amount excl. GST and amount incl. GST.
|
|
592
|
+
// stores, amount excl. GST and amount incl. GST (USD → INR converted).
|
|
587
593
|
totalStores: allInvoices.reduce( ( sum, inv ) => sum + ( inv.stores || 0 ), 0 ),
|
|
588
|
-
totalAmountExclGst: allInvoices.reduce( ( sum, inv ) => sum + ( inv.amount
|
|
589
|
-
totalAmountInclGst: allInvoices.reduce( ( sum, inv ) => sum + ( inv.totalAmount
|
|
594
|
+
totalAmountExclGst: Math.round( allInvoices.reduce( ( sum, inv ) => sum + toInr( inv, inv.amount ), 0 ) ),
|
|
595
|
+
totalAmountInclGst: Math.round( allInvoices.reduce( ( sum, inv ) => sum + toInr( inv, inv.totalAmount ), 0 ) ),
|
|
590
596
|
pendingApproval: allInvoices.filter( ( inv ) => [ 'pendingCsm', 'pendingFinance', 'pendingApproval' ].includes( inv.status ) ).length,
|
|
591
597
|
pendingPayment: allInvoices.filter( ( inv ) => inv.paymentStatus === 'unpaid' && inv.status === 'approved' ).length,
|
|
592
598
|
paid: allInvoices.filter( ( inv ) => inv.paymentStatus === 'paid' ).length,
|
|
@@ -287,11 +287,16 @@ export async function createInvoice( req, res ) {
|
|
|
287
287
|
let baseDate = isAdvance ? dayjs().add( 1, 'month' ).startOf( 'month' ) : dayjs();
|
|
288
288
|
let products;
|
|
289
289
|
|
|
290
|
+
// Effective price type: when group-wise pricing applies, the GROUP's own
|
|
291
|
+
// price type (from its basepricing doc) wins; otherwise the client's.
|
|
292
|
+
const { priceType: groupPriceType } = await resolveBasePricingScope( group, getClient );
|
|
293
|
+
const effectivePriceType = groupPriceType || getClient?.priceType;
|
|
294
|
+
|
|
290
295
|
if ( advanceCoverProducts ) {
|
|
291
296
|
// Reuse the advance invoice's current-month line items (store counts +
|
|
292
297
|
// prices already set) and bill them as a normal monthly invoice.
|
|
293
298
|
products = advanceCoverProducts;
|
|
294
|
-
} else if (
|
|
299
|
+
} else if ( effectivePriceType === 'standard' ) {
|
|
295
300
|
products = await standardPrice( group, getClient, baseDate );
|
|
296
301
|
} else {
|
|
297
302
|
products = await stepPrice( group, getClient );
|
|
@@ -1207,20 +1212,25 @@ function inWords( num ) {
|
|
|
1207
1212
|
// Resolve which basepricing doc applies to a billing group. When the client
|
|
1208
1213
|
// has billingGroupWisePricing enabled AND a doc exists for this group, returns
|
|
1209
1214
|
// that group's doc; otherwise falls back to the brand-level doc (groupId unset).
|
|
1210
|
-
// Returns { query, groupId } where query is the mongo filter for the
|
|
1211
|
-
//
|
|
1215
|
+
// Returns { query, groupId, priceType } where query is the mongo filter for the
|
|
1216
|
+
// chosen doc, groupId is the id for aggregation lookups (null for brand), and
|
|
1217
|
+
// priceType is the group's own price type when group-wise (else null).
|
|
1212
1218
|
async function resolveBasePricingScope( group, getClient ) {
|
|
1213
1219
|
const brandQuery = { clientId: group.clientId, groupId: { $exists: false } };
|
|
1214
1220
|
if ( getClient?.billingGroupWisePricing && group?._id ) {
|
|
1215
1221
|
const groupIdStr = String( group._id );
|
|
1216
1222
|
const groupDoc = await basepricingService.findOne(
|
|
1217
|
-
{ clientId: group.clientId, groupId: groupIdStr }, { _id: 1 },
|
|
1223
|
+
{ clientId: group.clientId, groupId: groupIdStr }, { _id: 1, priceType: 1 },
|
|
1218
1224
|
);
|
|
1219
1225
|
if ( groupDoc ) {
|
|
1220
|
-
return {
|
|
1226
|
+
return {
|
|
1227
|
+
query: { clientId: group.clientId, groupId: groupIdStr },
|
|
1228
|
+
groupId: groupIdStr,
|
|
1229
|
+
priceType: groupDoc.priceType || null,
|
|
1230
|
+
};
|
|
1221
1231
|
}
|
|
1222
1232
|
}
|
|
1223
|
-
return { query: brandQuery, groupId: null };
|
|
1233
|
+
return { query: brandQuery, groupId: null, priceType: null };
|
|
1224
1234
|
}
|
|
1225
1235
|
|
|
1226
1236
|
async function standardPrice( group, getClient, baseDate ) {
|
|
@@ -2256,12 +2256,15 @@ export const priceList = async ( req, res ) => {
|
|
|
2256
2256
|
} else {
|
|
2257
2257
|
priceQuery.groupId = { $exists: false };
|
|
2258
2258
|
}
|
|
2259
|
-
let pricingDetails = await basePricingService.findOne( priceQuery, { standard: 1, step: 1, oneTimeFeePerStore: 1 } );
|
|
2259
|
+
let pricingDetails = await basePricingService.findOne( priceQuery, { standard: 1, step: 1, oneTimeFeePerStore: 1, priceType: 1 } );
|
|
2260
2260
|
if ( !pricingDetails ) {
|
|
2261
2261
|
return res.sendError( 'no data found', 204 );
|
|
2262
2262
|
}
|
|
2263
|
+
// The price type for THIS doc: the doc's own priceType wins (group-wise
|
|
2264
|
+
// pricing stores it per group); fall back to the requested priceType.
|
|
2265
|
+
const effectivePriceType = pricingDetails.priceType || req.body.priceType;
|
|
2263
2266
|
let data = [];
|
|
2264
|
-
if (
|
|
2267
|
+
if ( effectivePriceType == 'standard' ) {
|
|
2265
2268
|
data = pricingDetails.standard;
|
|
2266
2269
|
} else {
|
|
2267
2270
|
data = pricingDetails.step;
|
|
@@ -2301,7 +2304,7 @@ export const priceList = async ( req, res ) => {
|
|
|
2301
2304
|
if ( !product.billingType ) {
|
|
2302
2305
|
product.billingType = 'perStore';
|
|
2303
2306
|
}
|
|
2304
|
-
if (
|
|
2307
|
+
if ( effectivePriceType == 'standard' ) {
|
|
2305
2308
|
product.storeCount = item.storeCount;
|
|
2306
2309
|
} else {
|
|
2307
2310
|
product.showImg = false;
|
|
@@ -2351,6 +2354,8 @@ export const priceList = async ( req, res ) => {
|
|
|
2351
2354
|
let finalValue = parseFloat( discountTotalPrice ) + gstAmount;
|
|
2352
2355
|
let result = {
|
|
2353
2356
|
product: data,
|
|
2357
|
+
// The price type actually used for this doc (group-wise type wins).
|
|
2358
|
+
priceType: effectivePriceType,
|
|
2354
2359
|
oneTimeFeePerStore: pricingDetails.oneTimeFeePerStore != null ? pricingDetails.oneTimeFeePerStore : null,
|
|
2355
2360
|
totalActualPrice: originalTotalPrice,
|
|
2356
2361
|
totalNegotiatePrice: discountTotalPrice.toFixed( 2 ),
|
|
@@ -2516,16 +2521,23 @@ export const pricingListUpdate = async ( req, res ) => {
|
|
|
2516
2521
|
if ( req.body.oneTimeFeePerStore != null && req.body.oneTimeFeePerStore !== '' ) {
|
|
2517
2522
|
getPriceInfo.oneTimeFeePerStore = Number( req.body.oneTimeFeePerStore ) || 0;
|
|
2518
2523
|
}
|
|
2519
|
-
// Keep the group identity on the doc when saving group-wise
|
|
2524
|
+
// Keep the group identity + price type on the doc when saving group-wise
|
|
2525
|
+
// pricing, so each group remembers its own standard/step choice.
|
|
2520
2526
|
if ( req.body.groupId ) {
|
|
2521
2527
|
getPriceInfo.groupId = req.body.groupId;
|
|
2522
2528
|
if ( req.body.groupName ) {
|
|
2523
2529
|
getPriceInfo.groupName = req.body.groupName;
|
|
2524
2530
|
}
|
|
2531
|
+
getPriceInfo.priceType = req.body.type;
|
|
2525
2532
|
}
|
|
2526
2533
|
getPriceInfo.save().then( async () => {
|
|
2527
2534
|
let clientDetails = await paymentService.findOne( { clientId: req.body.clientId }, { priceType: 1, paymentInvoice: 1, planDetails: 1 } );
|
|
2528
|
-
|
|
2535
|
+
// For group-wise pricing the client-level priceType must NOT change —
|
|
2536
|
+
// each group keeps its own type on its basepricing doc. Only update the
|
|
2537
|
+
// client's priceType for brand-level (non-group) saves.
|
|
2538
|
+
if ( !req.body.groupId ) {
|
|
2539
|
+
clientDetails.priceType = req.body.type;
|
|
2540
|
+
}
|
|
2529
2541
|
|
|
2530
2542
|
// Update billingType per product in client's planDetails
|
|
2531
2543
|
if ( req.body.products && req.body.products.length && clientDetails.planDetails?.product?.length ) {
|
|
@@ -2690,11 +2702,15 @@ async function updatePricing( req, res, update ) {
|
|
|
2690
2702
|
let product = [];
|
|
2691
2703
|
let clientId = req.body.clientId;
|
|
2692
2704
|
let paymentInvoice = clientDetails.paymentInvoice;
|
|
2693
|
-
|
|
2705
|
+
// Group-wise pricing only creates/updates the GROUP's basepricing doc — it
|
|
2706
|
+
// must NOT re-run the client-level first-time setup (which resets the
|
|
2707
|
+
// client's currencyType, invoice recipients and product trial statuses).
|
|
2708
|
+
// That initialization is only for a brand-level (non-group) first save.
|
|
2709
|
+
if ( !update && !req.body.groupId ) {
|
|
2694
2710
|
let userDetails = await userService.findOne( { clientId: req.body.clientId, role: 'superadmin' } );
|
|
2695
2711
|
paymentInvoice.invoiceTo = [ userDetails.email ];
|
|
2696
2712
|
paymentInvoice.paymentAgreementTo = [ userDetails.email ];
|
|
2697
|
-
paymentInvoice.currencyType = userDetails.countryCode == '91' ? 'inr' : 'dollar';
|
|
2713
|
+
// paymentInvoice.currencyType = userDetails.countryCode == '91' ? 'inr' : 'dollar';
|
|
2698
2714
|
clientDetails.planDetails.product.forEach( ( item ) => {
|
|
2699
2715
|
product.push( {
|
|
2700
2716
|
productName: item.productName,
|
|
@@ -2706,6 +2722,12 @@ async function updatePricing( req, res, update ) {
|
|
|
2706
2722
|
} else {
|
|
2707
2723
|
product = clientDetails.planDetails.product;
|
|
2708
2724
|
}
|
|
2725
|
+
// Group-wise pricing: stop here — the group's basepricing doc was already
|
|
2726
|
+
// created/updated above. Do NOT touch the client's priceType, currency,
|
|
2727
|
+
// products or payment status (that's brand-level state only).
|
|
2728
|
+
if ( req.body.groupId ) {
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2709
2731
|
req.body = {
|
|
2710
2732
|
'camaraPerSqft': clientDetails.planDetails.storeSize,
|
|
2711
2733
|
'storesCount': clientDetails.planDetails.totalStores,
|