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.19",
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.36",
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 || 0 ), 0 ),
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 || 0 ), 0 ),
589
- totalAmountInclGst: allInvoices.reduce( ( sum, inv ) => sum + ( inv.totalAmount || 0 ), 0 ),
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 ( getClient?.priceType === 'standard' ) {
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 chosen doc
1211
- // and groupId is the group id to use in aggregation lookups (or null for brand).
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 { query: { clientId: group.clientId, groupId: groupIdStr }, groupId: groupIdStr };
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 ( req.body.priceType == 'standard' ) {
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 ( req.body.priceType == 'standard' ) {
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 pricing.
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
- clientDetails.priceType = req.body.type;
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
- if ( !update ) {
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,