tango-app-api-payment-subscription 3.5.12 → 3.5.14
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/.claude/settings.local.json +9 -0
- package/package.json +1 -1
- package/scripts/create-billing-groups-by-country.js +201 -0
- package/src/controllers/brandsBilling.controller.js +32 -5
- package/src/controllers/invoice.controller.js +21 -12
- package/src/controllers/paymentSubscription.controllers.js +224 -205
package/package.json
CHANGED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// One-shot script: creates billing groups by store country for specific
|
|
2
|
+
// clients. For each target client it reads every store, groups storeIds by
|
|
3
|
+
// `storeProfile.country`, and creates one billing group per distinct country
|
|
4
|
+
// holding all the storeIds in that country.
|
|
5
|
+
//
|
|
6
|
+
// Scope: ONLY clientIds 387 and 193 (override with --clients=a,b).
|
|
7
|
+
//
|
|
8
|
+
// Idempotency / existing groups: if a billing group with the same
|
|
9
|
+
// (clientId, groupName=<country>) already exists, its `stores` array is
|
|
10
|
+
// OVERWRITTEN to match the current country membership (not duplicated).
|
|
11
|
+
//
|
|
12
|
+
// One store, one group: a store placed in a country group is pulled out of the
|
|
13
|
+
// primary "Default Group" so it lives in exactly one billing group (mirrors
|
|
14
|
+
// createBillingGroup).
|
|
15
|
+
//
|
|
16
|
+
// Stores with a missing/empty country are NOT grouped — they remain in the
|
|
17
|
+
// primary "Default Group". Any legacy fallback group named UNKNOWN_GROUP_NAME
|
|
18
|
+
// (from an earlier version of this script) is deleted; its stores already live
|
|
19
|
+
// in the Default Group, so nothing is lost.
|
|
20
|
+
//
|
|
21
|
+
// Connection: reuses the app's own getConnection() (config/database) so it
|
|
22
|
+
// connects exactly like the server does — reading mongo_username /
|
|
23
|
+
// mongo_password from .env plus host/name/authSource from config/env/env.js.
|
|
24
|
+
// Override with MONGO_URI if you want to point it elsewhere.
|
|
25
|
+
//
|
|
26
|
+
// Run modes:
|
|
27
|
+
// DRY RUN (default) — prints what it WOULD create/update, writes nothing:
|
|
28
|
+
// node scripts/create-billing-groups-by-country.js
|
|
29
|
+
// APPLY — actually writes:
|
|
30
|
+
// node scripts/create-billing-groups-by-country.js --apply
|
|
31
|
+
//
|
|
32
|
+
// Other flags:
|
|
33
|
+
// --clients=387,193 override the target client list
|
|
34
|
+
|
|
35
|
+
import mongoose from 'mongoose';
|
|
36
|
+
import 'dotenv/config';
|
|
37
|
+
import { getConnection } from '../config/database/database.js';
|
|
38
|
+
|
|
39
|
+
const DEFAULT_CLIENTS = [ '387', '193' ];
|
|
40
|
+
|
|
41
|
+
// Fallback billing group for stores that have no storeProfile.country.
|
|
42
|
+
const UNKNOWN_GROUP_NAME = 'Unknown';
|
|
43
|
+
|
|
44
|
+
function parseArgs( argv ) {
|
|
45
|
+
const apply = argv.includes( '--apply' ) || process.env.APPLY === 'true';
|
|
46
|
+
let clients = DEFAULT_CLIENTS;
|
|
47
|
+
const clientsArg = argv.find( ( a ) => a.startsWith( '--clients=' ) );
|
|
48
|
+
if ( clientsArg ) {
|
|
49
|
+
clients = clientsArg.slice( '--clients='.length ).split( ',' ).map( ( c ) => c.trim() ).filter( Boolean );
|
|
50
|
+
}
|
|
51
|
+
return { apply, clients };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function run() {
|
|
55
|
+
const { apply, clients } = parseArgs( process.argv.slice( 2 ) );
|
|
56
|
+
|
|
57
|
+
console.log( `Target clients: ${clients.join( ', ' )}` );
|
|
58
|
+
console.log( `Mode: ${apply ? 'APPLY (writing to DB)' : 'DRY RUN (no writes)'}` );
|
|
59
|
+
console.log( '' );
|
|
60
|
+
|
|
61
|
+
// Prefer an explicit MONGO_URI override; otherwise build the connection the
|
|
62
|
+
// same way the app does via getConnection().
|
|
63
|
+
if ( process.env.MONGO_URI ) {
|
|
64
|
+
await mongoose.connect( process.env.MONGO_URI );
|
|
65
|
+
} else {
|
|
66
|
+
const { uri, options } = getConnection();
|
|
67
|
+
await mongoose.connect( uri, options );
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// strict:false so we don't pin to a specific tango-api-schema version.
|
|
71
|
+
const Store = mongoose.model(
|
|
72
|
+
'_cbgStore',
|
|
73
|
+
new mongoose.Schema( {}, { strict: false } ),
|
|
74
|
+
'stores',
|
|
75
|
+
);
|
|
76
|
+
const Billing = mongoose.model(
|
|
77
|
+
'_cbgBilling',
|
|
78
|
+
new mongoose.Schema( {}, { strict: false } ),
|
|
79
|
+
'billings',
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
let totalGroupsCreated = 0;
|
|
83
|
+
let totalGroupsUpdated = 0;
|
|
84
|
+
let totalPulledFromPrimary = 0;
|
|
85
|
+
let totalUnknownGroupsDeleted = 0;
|
|
86
|
+
const noCountryStores = [];
|
|
87
|
+
|
|
88
|
+
for ( const clientId of clients ) {
|
|
89
|
+
const stores = await Store.find(
|
|
90
|
+
{ clientId: clientId },
|
|
91
|
+
{ 'storeId': 1, 'storeName': 1, 'storeProfile.country': 1 },
|
|
92
|
+
).lean();
|
|
93
|
+
|
|
94
|
+
console.log( `=== Client ${clientId}: ${stores.length} store(s) ===` );
|
|
95
|
+
|
|
96
|
+
if ( !stores.length ) {
|
|
97
|
+
console.log( ' No stores found. Skipping.\n' );
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Group storeIds by country. Stores with no country are NOT grouped — they
|
|
102
|
+
// remain in the primary "Default Group".
|
|
103
|
+
const byCountry = new Map();
|
|
104
|
+
for ( const s of stores ) {
|
|
105
|
+
const country = ( s.storeProfile?.country || '' ).trim();
|
|
106
|
+
if ( !country ) {
|
|
107
|
+
noCountryStores.push( { clientId, storeId: s.storeId, storeName: s.storeName } );
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if ( !byCountry.has( country ) ) {
|
|
111
|
+
byCountry.set( country, [] );
|
|
112
|
+
}
|
|
113
|
+
byCountry.get( country ).push( s.storeId );
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Tidy up the legacy fallback group: country-less stores belong in the
|
|
117
|
+
// primary group, so any previously-created "Unknown" group is removed.
|
|
118
|
+
// (Its stores are also in the primary group, so nothing is lost.)
|
|
119
|
+
const unknownGroup = await Billing.findOne( { clientId: clientId, groupName: UNKNOWN_GROUP_NAME } );
|
|
120
|
+
if ( unknownGroup ) {
|
|
121
|
+
console.log( ` [DELETE] legacy "${UNKNOWN_GROUP_NAME}" group (_id ${unknownGroup._id}) — country-less stores stay in Default Group` );
|
|
122
|
+
if ( apply ) {
|
|
123
|
+
await Billing.deleteOne( { _id: unknownGroup._id } );
|
|
124
|
+
}
|
|
125
|
+
totalUnknownGroupsDeleted++;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if ( !byCountry.size ) {
|
|
129
|
+
console.log( ' No stores with a country value. Nothing to group.\n' );
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for ( const [ country, storeIds ] of byCountry ) {
|
|
134
|
+
const existing = await Billing.findOne( { clientId: clientId, groupName: country } );
|
|
135
|
+
|
|
136
|
+
if ( existing ) {
|
|
137
|
+
console.log( ` [UPDATE] group "${country}" exists (_id ${existing._id}) → set ${storeIds.length} store(s)` );
|
|
138
|
+
if ( apply ) {
|
|
139
|
+
await Billing.updateOne(
|
|
140
|
+
{ _id: existing._id },
|
|
141
|
+
{ $set: { stores: storeIds } },
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
totalGroupsUpdated++;
|
|
145
|
+
} else {
|
|
146
|
+
console.log( ` [CREATE] group "${country}" with ${storeIds.length} store(s)` );
|
|
147
|
+
if ( apply ) {
|
|
148
|
+
await Billing.create( {
|
|
149
|
+
clientId: clientId,
|
|
150
|
+
groupName: country,
|
|
151
|
+
groupTag: 'store',
|
|
152
|
+
country: country,
|
|
153
|
+
stores: storeIds,
|
|
154
|
+
} );
|
|
155
|
+
}
|
|
156
|
+
totalGroupsCreated++;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// A store lives in exactly one group: once it's in a country group, pull
|
|
160
|
+
// it out of the primary "Default Group" (mirrors createBillingGroup).
|
|
161
|
+
if ( apply ) {
|
|
162
|
+
const pullRes = await Billing.updateOne(
|
|
163
|
+
{ clientId: clientId, isPrimary: true },
|
|
164
|
+
{ $pull: { stores: { $in: storeIds } } },
|
|
165
|
+
);
|
|
166
|
+
if ( pullRes.modifiedCount ) {
|
|
167
|
+
totalPulledFromPrimary += storeIds.length;
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
totalPulledFromPrimary += storeIds.length;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
console.log( ` → pulled country-matched stores out of primary "Default Group"` );
|
|
174
|
+
console.log( '' );
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log( '--- Summary ---' );
|
|
178
|
+
console.log( `Groups created: ${totalGroupsCreated}` );
|
|
179
|
+
console.log( `Groups updated: ${totalGroupsUpdated}` );
|
|
180
|
+
console.log( `Legacy "${UNKNOWN_GROUP_NAME}" groups deleted: ${totalUnknownGroupsDeleted}` );
|
|
181
|
+
console.log( `Store memberships pulled from primary group: ${totalPulledFromPrimary}` );
|
|
182
|
+
if ( noCountryStores.length ) {
|
|
183
|
+
console.log( `Stores with no country (left in "Default Group"): ${noCountryStores.length}` );
|
|
184
|
+
noCountryStores.slice( 0, 50 ).forEach( ( s ) =>
|
|
185
|
+
console.log( ` - client ${s.clientId} | ${s.storeId} | ${s.storeName || ''}` ),
|
|
186
|
+
);
|
|
187
|
+
if ( noCountryStores.length > 50 ) {
|
|
188
|
+
console.log( ` ...and ${noCountryStores.length - 50} more` );
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if ( !apply ) {
|
|
192
|
+
console.log( '\nDRY RUN complete. No data was written. Re-run with --apply to write.' );
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
await mongoose.disconnect();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
run().catch( ( err ) => {
|
|
199
|
+
console.error( err );
|
|
200
|
+
process.exit( 1 );
|
|
201
|
+
} );
|
|
@@ -586,16 +586,34 @@ export async function brandInvoiceList( req, res ) {
|
|
|
586
586
|
};
|
|
587
587
|
|
|
588
588
|
if ( req.body.export ) {
|
|
589
|
+
// Mirror the on-screen table exactly: same columns (excl. GST and incl.
|
|
590
|
+
// GST as separate amounts), same currency symbol, and the same status
|
|
591
|
+
// text the table renders via getInvoiceStatus().
|
|
592
|
+
const currencySymbols = {
|
|
593
|
+
inr: '₹', dollar: '$', singaporedollar: 'S$', euro: '€', aed: 'AED',
|
|
594
|
+
};
|
|
595
|
+
const statusFor = ( inv ) => {
|
|
596
|
+
if ( inv.status === 'pendingCsm' ) return 'Pending CSM';
|
|
597
|
+
if ( inv.status === 'pendingFinance' ) return 'Pending Finance';
|
|
598
|
+
if ( inv.status === 'pendingApproval' ) return 'Pending Approval';
|
|
599
|
+
if ( inv.status === 'pending' ) return 'Pending Approval';
|
|
600
|
+
if ( inv.paymentStatus === 'partial' ) return 'Partial';
|
|
601
|
+
if ( inv.paymentStatus === 'unpaid' ) return 'Pending Payment';
|
|
602
|
+
if ( inv.paymentStatus === 'paid' ) return 'Paid';
|
|
603
|
+
return inv.status;
|
|
604
|
+
};
|
|
589
605
|
const exportdata = [];
|
|
590
606
|
allInvoices.forEach( ( element ) => {
|
|
607
|
+
const symbol = currencySymbols[element.currency] || '$';
|
|
591
608
|
exportdata.push( {
|
|
592
609
|
'Invoice #': element.invoice,
|
|
593
610
|
'Billing Group': element.groupName,
|
|
594
611
|
'Period': dayjs( element.billingDate ).format( 'MMM YYYY' ),
|
|
595
612
|
'Generated': dayjs( element.billingDate ).format( 'DD MMM YYYY' ),
|
|
596
613
|
'No of Stores': element.stores,
|
|
597
|
-
'Amount': element.
|
|
598
|
-
'
|
|
614
|
+
'Amount (excl. GST)': `${symbol}${Number( element.amount || 0 ).toFixed( 2 )}`,
|
|
615
|
+
'Amount (incl. GST)': `${symbol}${Number( element.totalAmount || 0 ).toFixed( 2 )}`,
|
|
616
|
+
'Status': statusFor( element ),
|
|
599
617
|
} );
|
|
600
618
|
} );
|
|
601
619
|
await download( exportdata, res );
|
|
@@ -705,18 +723,27 @@ export async function latestDailyPricing( req, res ) {
|
|
|
705
723
|
logger.error( { error: bpErr, function: 'latestDailyPricing.basePrice', clientId: req.body.clientId } );
|
|
706
724
|
}
|
|
707
725
|
// Billing type per product (perStore / perZone / perCamera) from the client
|
|
708
|
-
// plan — drives whether the amount multiplies by camera/zone count.
|
|
726
|
+
// plan — drives whether the amount multiplies by camera/zone count. The
|
|
727
|
+
// same client doc supplies the invoice-amount currency below.
|
|
709
728
|
const billingTypeByProduct = {};
|
|
729
|
+
// Invoice Amount currency comes from the client's paymentInvoice.currencyType
|
|
730
|
+
// (normalised to 'dollar' / 'inr' like the rest of this file). Falls back to
|
|
731
|
+
// the base-pricing currency only when the client has no currencyType set.
|
|
732
|
+
let invoiceCurrency = bpCurrency;
|
|
710
733
|
try {
|
|
711
734
|
const planClient = await clientService.findOne(
|
|
712
735
|
{ clientId: req.body.clientId },
|
|
713
|
-
{ 'planDetails.product.productName': 1, 'planDetails.product.billingType': 1 },
|
|
736
|
+
{ 'planDetails.product.productName': 1, 'planDetails.product.billingType': 1, 'paymentInvoice.currencyType': 1 },
|
|
714
737
|
);
|
|
715
738
|
for ( const p of ( planClient?.planDetails?.product || [] ) ) {
|
|
716
739
|
if ( p.productName ) {
|
|
717
740
|
billingTypeByProduct[p.productName] = p.billingType || 'perStore';
|
|
718
741
|
}
|
|
719
742
|
}
|
|
743
|
+
const clientCurrency = planClient?.paymentInvoice?.currencyType;
|
|
744
|
+
if ( clientCurrency ) {
|
|
745
|
+
invoiceCurrency = clientCurrency === 'dollar' ? 'dollar' : 'inr';
|
|
746
|
+
}
|
|
720
747
|
} catch ( btErr ) {
|
|
721
748
|
logger.error( { error: btErr, function: 'latestDailyPricing.billingType', clientId: req.body.clientId } );
|
|
722
749
|
}
|
|
@@ -828,7 +855,7 @@ export async function latestDailyPricing( req, res ) {
|
|
|
828
855
|
...( store._doc || store ),
|
|
829
856
|
invoiceAmount: Math.round( invoiceAmount * 100 ) / 100,
|
|
830
857
|
invoiceBreakdown,
|
|
831
|
-
invoiceCurrency:
|
|
858
|
+
invoiceCurrency: invoiceCurrency,
|
|
832
859
|
};
|
|
833
860
|
} );
|
|
834
861
|
|
|
@@ -324,7 +324,15 @@ export async function createInvoice( req, res ) {
|
|
|
324
324
|
$filter: {
|
|
325
325
|
input: '$stores',
|
|
326
326
|
as: 'item',
|
|
327
|
-
|
|
327
|
+
// A store is "running" if it has working days for ANY product.
|
|
328
|
+
// Edge products (tangoZone/tangoTraffic) use daysDifference;
|
|
329
|
+
// tangoTrax uses daysDifferenceTrax. A trax-only store has
|
|
330
|
+
// daysDifference = 0 but daysDifferenceTrax > 0 — counting only
|
|
331
|
+
// daysDifference would wrongly drop it and report stores: 0.
|
|
332
|
+
cond: { $and: [
|
|
333
|
+
{ $or: [ { $gt: [ '$$item.daysDifference', 0 ] }, { $gt: [ '$$item.daysDifferenceTrax', 0 ] } ] },
|
|
334
|
+
{ $in: [ '$$item.storeId', group.stores ] },
|
|
335
|
+
] },
|
|
328
336
|
},
|
|
329
337
|
},
|
|
330
338
|
},
|
|
@@ -440,10 +448,11 @@ async function buildAnnexureRows( invoiceInfo, getgroup ) {
|
|
|
440
448
|
const billingMonthEnd = new Date( billingMonth.endOf( 'month' ).toISOString() );
|
|
441
449
|
const monthDays = billingMonth.daysInMonth();
|
|
442
450
|
const invoiceCurrency = symbolFor( invoiceInfo.currency );
|
|
443
|
-
// basepricing negotiatePrice is stored in
|
|
444
|
-
//
|
|
445
|
-
//
|
|
446
|
-
|
|
451
|
+
// basepricing negotiatePrice is stored in the client's own billing currency
|
|
452
|
+
// (INR for INR clients, the foreign currency for dollar/AED clients). Invoice
|
|
453
|
+
// generation uses negotiatePrice verbatim (no FX), so the annexure must too —
|
|
454
|
+
// converting here would make the annexure unit price disagree with the actual
|
|
455
|
+
// billed amount (e.g. a $45 negotiated price wrongly shown as $0.48).
|
|
447
456
|
|
|
448
457
|
const annexClient = await clientService.findOne( { clientId: invoiceInfo.clientId }, { 'planDetails.product': 1 } );
|
|
449
458
|
const billingTypeMap = {};
|
|
@@ -504,9 +513,9 @@ async function buildAnnexureRows( invoiceInfo, getgroup ) {
|
|
|
504
513
|
units = s.trafficCameraCount;
|
|
505
514
|
}
|
|
506
515
|
}
|
|
507
|
-
//
|
|
508
|
-
//
|
|
509
|
-
const price =
|
|
516
|
+
// negotiatePrice is already in the invoice currency — use it verbatim,
|
|
517
|
+
// matching invoice generation.
|
|
518
|
+
const price = Number( s.standard?.negotiatePrice ) || 0;
|
|
510
519
|
const runningCost = s.workingdays >= monthDays ?
|
|
511
520
|
Math.round( price * units * 100 ) / 100 :
|
|
512
521
|
Math.round( ( price / monthDays ) * s.workingdays * units * 100 ) / 100;
|
|
@@ -1713,7 +1722,6 @@ async function stepPrice( group, getClient ) {
|
|
|
1713
1722
|
function processArray( array1, array2 ) {
|
|
1714
1723
|
let updatedArray = [];
|
|
1715
1724
|
|
|
1716
|
-
let firstPriceAssigned = false;
|
|
1717
1725
|
for ( let item of array1 ) {
|
|
1718
1726
|
let remainingStores = item.storeCount;
|
|
1719
1727
|
|
|
@@ -1721,16 +1729,17 @@ async function stepPrice( group, getClient ) {
|
|
|
1721
1729
|
let [ min, max ] = range.storeRange.split( '-' ).map( Number );
|
|
1722
1730
|
|
|
1723
1731
|
if ( remainingStores > 0 ) {
|
|
1724
|
-
console.log( firstPriceAssigned );
|
|
1725
1732
|
let applicableStores = Math.min( remainingStores, max - min + 1 );
|
|
1726
1733
|
updatedArray.push( {
|
|
1727
1734
|
productName: item.productName,
|
|
1728
1735
|
workingdays: item.workingdays,
|
|
1729
1736
|
storeCount: applicableStores,
|
|
1730
|
-
|
|
1737
|
+
// Each tier is charged at its own range's negotiated price. (Was a
|
|
1738
|
+
// hardcoded 1100 for every tier after the first — a placeholder
|
|
1739
|
+
// that ignored the actual step price of the next range.)
|
|
1740
|
+
price: range.negotiatePrice,
|
|
1731
1741
|
} );
|
|
1732
1742
|
remainingStores -= applicableStores;
|
|
1733
|
-
firstPriceAssigned = true;
|
|
1734
1743
|
}
|
|
1735
1744
|
}
|
|
1736
1745
|
}
|
|
@@ -3325,13 +3325,201 @@ export const dailyPricingInsertOld = async ( req, res ) => {
|
|
|
3325
3325
|
}
|
|
3326
3326
|
};
|
|
3327
3327
|
|
|
3328
|
+
// Computes the daily-pricing entry for a single store. Pure per-store work
|
|
3329
|
+
// (independent DB reads + calculation) so stores can run concurrently.
|
|
3330
|
+
// storeIndex is the store's original position in getStore — required for the
|
|
3331
|
+
// step-pricing storeRange check, so it must be preserved when parallelizing.
|
|
3332
|
+
const processStoreDailyPricing = async ( store, storeIndex, getClient, getBaseprice, date ) => {
|
|
3333
|
+
let productList = [];
|
|
3334
|
+
let query = [
|
|
3335
|
+
{
|
|
3336
|
+
$match: {
|
|
3337
|
+
clientId: getClient.clientId,
|
|
3338
|
+
},
|
|
3339
|
+
},
|
|
3340
|
+
{ $unwind: '$stores' },
|
|
3341
|
+
{
|
|
3342
|
+
$match: {
|
|
3343
|
+
'stores.storeId': store.storeId,
|
|
3344
|
+
},
|
|
3345
|
+
},
|
|
3346
|
+
{
|
|
3347
|
+
$project: {
|
|
3348
|
+
'stores.daysDifference': 1,
|
|
3349
|
+
'stores.daysDifferenceTrax': 1,
|
|
3350
|
+
'dateString': 1,
|
|
3351
|
+
},
|
|
3352
|
+
},
|
|
3353
|
+
{
|
|
3354
|
+
$sort: {
|
|
3355
|
+
_id: -1,
|
|
3356
|
+
},
|
|
3357
|
+
},
|
|
3358
|
+
{ $limit: 1 },
|
|
3359
|
+
];
|
|
3360
|
+
let dailyData = await dailyPriceService.aggregate( query );
|
|
3361
|
+
let cameraDetails = await cameraService.find( { storeId: store.storeId, clientId: getClient.clientId, isActivated: true, isUp: true }, { streamName: 1, productModule: 1 } );
|
|
3362
|
+
// console.log( '🚀 ~ dailyPricingInsert ~ cameraDetails:', cameraDetails );
|
|
3363
|
+
|
|
3364
|
+
let trafficCameraCount = cameraDetails.filter( ( cam ) =>
|
|
3365
|
+
( cam.productModule || [] ).some( ( mod ) =>
|
|
3366
|
+
( mod.productName === 'tangoTraffic' || mod.productName === 'tangoTracking' ) && mod.checked === true,
|
|
3367
|
+
),
|
|
3368
|
+
).length;
|
|
3369
|
+
|
|
3370
|
+
let zoneCameraCount = cameraDetails.filter( ( cam ) =>
|
|
3371
|
+
( cam.productModule || [] ).some( ( mod ) =>
|
|
3372
|
+
mod.productName === 'tangoZone' && mod.checked === true,
|
|
3373
|
+
),
|
|
3374
|
+
).length;
|
|
3375
|
+
|
|
3376
|
+
let zoneCameraStreamNames = cameraDetails.filter( ( cam ) =>
|
|
3377
|
+
( cam.productModule || [] ).some( ( mod ) =>
|
|
3378
|
+
mod.productName === 'tangoZone' && mod.checked === true,
|
|
3379
|
+
),
|
|
3380
|
+
).map( ( cam ) => cam.streamName );
|
|
3381
|
+
let allcameraname = cameraDetails?.map( ( cam ) => cam?.streamName );
|
|
3382
|
+
console.log( '🚀 ~ dailyPricingInsert ~ zoneCameraStreamNames:', zoneCameraStreamNames );
|
|
3383
|
+
let taggingDetails = await taggingService.find( { storeId: store.storeId, clientId: getClient.clientId, productName: 'tangoZone', coordinates: { $ne: [] }, streamName: { $in: allcameraname } }, { tagName: 1 } );
|
|
3384
|
+
let zoneCount = taggingDetails.length;
|
|
3385
|
+
let zoneName = taggingDetails.map( ( item ) => item.tagName );
|
|
3386
|
+
let firstDate = dayjs( store?.edge?.firstFileDate ).format( 'YYYY-MM-DD' );
|
|
3387
|
+
let workingdays;
|
|
3388
|
+
let workingdaystrax;
|
|
3389
|
+
const givenDate = dayjs( date );
|
|
3390
|
+
console.log( '🚀 ~ dailyPricingInsert ~ cameraCount:', store.storeId, trafficCameraCount, zoneCameraCount, zoneCount );
|
|
3391
|
+
const isFirstDayOfMonth = givenDate.isSame( dayjs().startOf( 'month' ), 'day' );
|
|
3392
|
+
if ( store?.edge.firstFile ) {
|
|
3393
|
+
if ( firstDate < date && store?.status == 'active' &&
|
|
3394
|
+
dailyData[0]?.dateString != dayjs( date, 'YYYY-MM-DD' ).format( 'YYYY-MM-DD' ) ) {
|
|
3395
|
+
if ( dailyData.length && dailyData[0]?.stores?.daysDifference&&!isFirstDayOfMonth ) {
|
|
3396
|
+
workingdays = dailyData[0]?.stores?.daysDifference + 1;
|
|
3397
|
+
} else {
|
|
3398
|
+
workingdays = 1;
|
|
3399
|
+
}
|
|
3400
|
+
} else {
|
|
3401
|
+
if ( dailyData[0]?.stores?.daysDifference&&!isFirstDayOfMonth ) {
|
|
3402
|
+
workingdays = dailyData[0]?.stores?.daysDifference;
|
|
3403
|
+
} else {
|
|
3404
|
+
workingdays = 0;
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
console.log( dailyData[0]?.stores );
|
|
3409
|
+
if ( store?.status == 'active' &&
|
|
3410
|
+
dailyData[0]?.dateString != dayjs( date, 'YYYY-MM-DD' ).format( 'YYYY-MM-DD' ) ) {
|
|
3411
|
+
if ( dailyData.length && dailyData[0]?.stores?.daysDifferenceTrax&&!isFirstDayOfMonth ) {
|
|
3412
|
+
workingdaystrax = dailyData[0]?.stores?.daysDifferenceTrax + 1;
|
|
3413
|
+
} else {
|
|
3414
|
+
workingdaystrax = 1;
|
|
3415
|
+
}
|
|
3416
|
+
} else {
|
|
3417
|
+
if ( dailyData[0]?.stores?.daysDifferenceTrax&&!isFirstDayOfMonth ) {
|
|
3418
|
+
workingdaystrax = dailyData[0]?.stores?.daysDifferenceTrax;
|
|
3419
|
+
} else {
|
|
3420
|
+
workingdaystrax = 0;
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
let priceDetails = getClient.priceType == 'standard' ? getBaseprice.standard : getBaseprice.step;
|
|
3424
|
+
priceDetails = priceDetails.filter( ( item1 ) =>
|
|
3425
|
+
getClient.planDetails.product.some( ( item2 ) =>
|
|
3426
|
+
item2.productName === item1.productName && item2.status === 'live',
|
|
3427
|
+
),
|
|
3428
|
+
);
|
|
3429
|
+
for ( let storeProductIndex = 0; storeProductIndex < store.product.length; storeProductIndex++ ) {
|
|
3430
|
+
let productDetails;
|
|
3431
|
+
if ( getClient.priceType == 'standard' ) {
|
|
3432
|
+
productDetails = priceDetails.find( ( item ) => item.productName == store.product[storeProductIndex] );
|
|
3433
|
+
} else {
|
|
3434
|
+
productDetails = priceDetails.find( ( item ) => {
|
|
3435
|
+
let range = item.storeRange.split( '-' );
|
|
3436
|
+
if ( parseInt( range[0] ) <= ( storeIndex + 1 ) && parseInt( range[1] ) >= ( storeIndex + 1 ) ) {
|
|
3437
|
+
return ( item.productName == store.product[storeProductIndex] && parseInt( range[0] ) <= ( storeIndex + 1 ) && parseInt( range[1] ) >= ( storeIndex + 1 ) );
|
|
3438
|
+
}
|
|
3439
|
+
} );
|
|
3440
|
+
if ( !productDetails ) {
|
|
3441
|
+
let stepProductDetails = priceDetails.filter( ( item ) => item.productName == store.product[storeProductIndex] );
|
|
3442
|
+
productDetails = stepProductDetails[stepProductDetails.length - 1];
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
if ( productDetails ) {
|
|
3446
|
+
console.log( '-------', workingdays, workingdaystrax, productDetails.productName );
|
|
3447
|
+
let newObject = {
|
|
3448
|
+
productName: productDetails.productName,
|
|
3449
|
+
workingdays: productDetails.productName==='tangoTrax'?workingdaystrax:workingdays,
|
|
3450
|
+
};
|
|
3451
|
+
productList.push( newObject );
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
return {
|
|
3455
|
+
storeId: store.storeId,
|
|
3456
|
+
store: store._id,
|
|
3457
|
+
storeName: store.storeName,
|
|
3458
|
+
status: store?.status,
|
|
3459
|
+
firstFile: store?.edge?.firstFile || false,
|
|
3460
|
+
edgefirstFileDate: store?.edge?.firstFileDate || null,
|
|
3461
|
+
date: new Date( date ),
|
|
3462
|
+
daysDifference: workingdays,
|
|
3463
|
+
daysDifferenceTrax: workingdaystrax,
|
|
3464
|
+
products: productList,
|
|
3465
|
+
camera: cameraDetails.map( ( item ) => item.streamName ),
|
|
3466
|
+
trafficCameraCount: trafficCameraCount,
|
|
3467
|
+
zoneCameraCount: zoneCameraCount,
|
|
3468
|
+
zoneCount: zoneCount,
|
|
3469
|
+
zoneName: zoneName,
|
|
3470
|
+
};
|
|
3471
|
+
};
|
|
3472
|
+
|
|
3473
|
+
// Processes daily pricing for a single client. Self-contained so multiple
|
|
3474
|
+
// clients can run concurrently via Promise.all in dailyPricingInsert.
|
|
3475
|
+
const processClientDailyPricing = async ( clientId, date ) => {
|
|
3476
|
+
let getClient = await paymentService.findOne( { clientId: clientId } );
|
|
3477
|
+
if ( getClient ) {
|
|
3478
|
+
let getBaseprice = await basePriceService.findOne( { clientId: clientId } );
|
|
3479
|
+
let getStore = await storeService.find( { 'clientId': clientId } );
|
|
3480
|
+
console.log( '==========>', clientId, getStore.length );
|
|
3481
|
+
if ( getStore.length && getBaseprice ) {
|
|
3482
|
+
// Process stores in bounded batches so large clients (200+ stores) run
|
|
3483
|
+
// in parallel without exhausting the DB connection pool. storeIndex is
|
|
3484
|
+
// preserved so step-pricing ranges still resolve correctly.
|
|
3485
|
+
const STORE_BATCH_SIZE = 20;
|
|
3486
|
+
let storeList = [];
|
|
3487
|
+
for ( let i = 0; i < getStore.length; i += STORE_BATCH_SIZE ) {
|
|
3488
|
+
let batch = getStore.slice( i, i + STORE_BATCH_SIZE );
|
|
3489
|
+
let batchResults = await Promise.all(
|
|
3490
|
+
batch.map( ( store, batchIndex ) =>
|
|
3491
|
+
processStoreDailyPricing( store, i + batchIndex, getClient, getBaseprice, date ),
|
|
3492
|
+
),
|
|
3493
|
+
);
|
|
3494
|
+
storeList.push( ...batchResults );
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
let activestores = storeList.filter( ( store ) => store.status=='active' );
|
|
3498
|
+
console.log( '------', activestores.length );
|
|
3499
|
+
let params = {
|
|
3500
|
+
clientId: clientId,
|
|
3501
|
+
stores: storeList,
|
|
3502
|
+
dateISO: new Date( date ),
|
|
3503
|
+
accountType: getClient?.planDetails?.subscriptionType,
|
|
3504
|
+
status: getClient?.status,
|
|
3505
|
+
activeStores: activestores?.length,
|
|
3506
|
+
brandName: getClient?.clientName,
|
|
3507
|
+
proRate: getClient?.paymentInvoice?.proRate,
|
|
3508
|
+
dateString: dayjs( date, 'YYYY-MM-DD' ).format( 'YYYY-MM-DD' ),
|
|
3509
|
+
};
|
|
3510
|
+
await dailyPriceService.updateOne( { clientId: clientId, dateString: params.dateString }, params, { upsert: true } );
|
|
3511
|
+
} else {
|
|
3512
|
+
// console.log( 'store or base price not found' );
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
};
|
|
3516
|
+
|
|
3328
3517
|
export const dailyPricingInsert = async ( req, res ) => {
|
|
3329
3518
|
try {
|
|
3330
3519
|
let requestData = req.body;
|
|
3331
|
-
let clientlist;
|
|
3332
3520
|
let requestClient = [];
|
|
3333
3521
|
if ( !requestData?.clientId || !requestData?.clientId?.length ) {
|
|
3334
|
-
clientlist = await paymentService.find( { 'status': 'active' } );
|
|
3522
|
+
let clientlist = await paymentService.find( { 'status': 'active' } );
|
|
3335
3523
|
for ( let client of clientlist ) {
|
|
3336
3524
|
requestClient.push( client.clientId );
|
|
3337
3525
|
}
|
|
@@ -3342,214 +3530,45 @@ export const dailyPricingInsert = async ( req, res ) => {
|
|
|
3342
3530
|
if ( !requestData?.date ) {
|
|
3343
3531
|
requestData.date = dayjs().format( 'YYYY-MM-DD' );
|
|
3344
3532
|
}
|
|
3345
|
-
if ( requestData.clientId && requestClient.length > 0 ) {
|
|
3346
|
-
for ( let clientIndex = 0; clientIndex < requestClient.length; clientIndex++ ) {
|
|
3347
|
-
let getClient = await paymentService.findOne( { clientId: requestClient[clientIndex] } );
|
|
3348
|
-
if ( getClient ) {
|
|
3349
|
-
let getBaseprice = await basePriceService.findOne( { clientId: requestClient[clientIndex] } );
|
|
3350
|
-
let getStore = await storeService.find( { 'clientId': requestClient[clientIndex] } );
|
|
3351
|
-
console.log( '==========>', getStore.length );
|
|
3352
|
-
if ( getStore.length ) {
|
|
3353
|
-
let storeList = [];
|
|
3354
|
-
for ( let storeIndex = 0; storeIndex < getStore.length; storeIndex++ ) {
|
|
3355
|
-
let productList = [];
|
|
3356
|
-
if ( getBaseprice ) {
|
|
3357
|
-
// await axios.get( `${JSON.parse( process.env.URL ).oldapidomain}/processedDayData/getDailyData?clientId=${requestClient[clientIndex]}&storeId=${getStore[storeIndex].storeId}&date=${requestData.date}`, { headers: { Authorization: 'Bearer d47433f8-9a33-47c7-ba43-1a0fbac28f66' } } ).then( async ( response ) => {
|
|
3358
|
-
// let processedFileDate = response.data?.data?.firstFileDate || null;
|
|
3359
|
-
let query = [
|
|
3360
|
-
{
|
|
3361
|
-
$match: {
|
|
3362
|
-
clientId: getClient.clientId,
|
|
3363
|
-
},
|
|
3364
|
-
},
|
|
3365
|
-
{ $unwind: '$stores' },
|
|
3366
|
-
{
|
|
3367
|
-
$match: {
|
|
3368
|
-
'stores.storeId': getStore[storeIndex].storeId,
|
|
3369
|
-
},
|
|
3370
|
-
},
|
|
3371
|
-
{
|
|
3372
|
-
$project: {
|
|
3373
|
-
'stores.daysDifference': 1,
|
|
3374
|
-
'stores.daysDifferenceTrax': 1,
|
|
3375
|
-
'dateString': 1,
|
|
3376
|
-
},
|
|
3377
|
-
},
|
|
3378
|
-
{
|
|
3379
|
-
$sort: {
|
|
3380
|
-
_id: -1,
|
|
3381
|
-
},
|
|
3382
|
-
},
|
|
3383
|
-
{ $limit: 1 },
|
|
3384
|
-
];
|
|
3385
|
-
let dailyData = await dailyPriceService.aggregate( query );
|
|
3386
|
-
let cameraDetails = await cameraService.find( { storeId: getStore[storeIndex].storeId, clientId: requestClient[clientIndex], isActivated: true, isUp: true }, { streamName: 1, productModule: 1 } );
|
|
3387
|
-
// console.log( '🚀 ~ dailyPricingInsert ~ cameraDetails:', cameraDetails );
|
|
3388
|
-
|
|
3389
|
-
let trafficCameraCount = cameraDetails.filter( ( cam ) =>
|
|
3390
|
-
( cam.productModule || [] ).some( ( mod ) =>
|
|
3391
|
-
( mod.productName === 'tangoTraffic' || mod.productName === 'tangoTracking' ) && mod.checked === true,
|
|
3392
|
-
),
|
|
3393
|
-
).length;
|
|
3394
|
-
|
|
3395
|
-
let zoneCameraCount = cameraDetails.filter( ( cam ) =>
|
|
3396
|
-
( cam.productModule || [] ).some( ( mod ) =>
|
|
3397
|
-
mod.productName === 'tangoZone' && mod.checked === true,
|
|
3398
|
-
),
|
|
3399
|
-
).length;
|
|
3400
|
-
|
|
3401
|
-
let zoneCameraStreamNames = cameraDetails.filter( ( cam ) =>
|
|
3402
|
-
( cam.productModule || [] ).some( ( mod ) =>
|
|
3403
|
-
mod.productName === 'tangoZone' && mod.checked === true,
|
|
3404
|
-
),
|
|
3405
|
-
).map( ( cam ) => cam.streamName );
|
|
3406
|
-
let allcameraname = cameraDetails?.map( ( cam ) => cam?.streamName );
|
|
3407
|
-
console.log( '🚀 ~ dailyPricingInsert ~ zoneCameraStreamNames:', zoneCameraStreamNames );
|
|
3408
|
-
let taggingDetails = await taggingService.find( { storeId: getStore[storeIndex].storeId, clientId: requestClient[clientIndex], productName: 'tangoZone', coordinates: { $ne: [] }, streamName: { $in: allcameraname } }, { tagName: 1 } );
|
|
3409
|
-
let zoneCount = taggingDetails.length;
|
|
3410
|
-
let zoneName = taggingDetails.map( ( item ) => item.tagName );
|
|
3411
|
-
let firstDate = dayjs( getStore[storeIndex]?.edge?.firstFileDate ).format( 'YYYY-MM-DD' );
|
|
3412
|
-
let workingdays;
|
|
3413
|
-
let workingdaystrax;
|
|
3414
|
-
const givenDate = dayjs( requestData.date );
|
|
3415
|
-
console.log( '🚀 ~ dailyPricingInsert ~ cameraCount:', getStore[storeIndex].storeId, trafficCameraCount, zoneCameraCount, zoneCount );
|
|
3416
|
-
const isFirstDayOfMonth = givenDate.isSame( dayjs().startOf( 'month' ), 'day' );
|
|
3417
|
-
if ( getStore[storeIndex]?.edge.firstFile ) {
|
|
3418
|
-
if ( firstDate < requestData.date && getStore[storeIndex]?.status == 'active' &&
|
|
3419
|
-
dailyData[0]?.dateString != dayjs( requestData.date, 'YYYY-MM-DD' ).format( 'YYYY-MM-DD' ) ) {
|
|
3420
|
-
if ( dailyData.length && dailyData[0]?.stores?.daysDifference&&!isFirstDayOfMonth ) {
|
|
3421
|
-
workingdays = dailyData[0]?.stores?.daysDifference + 1;
|
|
3422
|
-
} else {
|
|
3423
|
-
workingdays = 1;
|
|
3424
|
-
}
|
|
3425
|
-
} else {
|
|
3426
|
-
if ( dailyData[0]?.stores?.daysDifference&&!isFirstDayOfMonth ) {
|
|
3427
|
-
workingdays = dailyData[0]?.stores?.daysDifference;
|
|
3428
|
-
} else {
|
|
3429
|
-
workingdays = 0;
|
|
3430
|
-
}
|
|
3431
|
-
}
|
|
3432
|
-
}
|
|
3433
|
-
console.log( dailyData[0]?.stores );
|
|
3434
|
-
if ( getStore[storeIndex]?.status == 'active' &&
|
|
3435
|
-
dailyData[0]?.dateString != dayjs( requestData.date, 'YYYY-MM-DD' ).format( 'YYYY-MM-DD' ) ) {
|
|
3436
|
-
if ( dailyData.length && dailyData[0]?.stores?.daysDifferenceTrax&&!isFirstDayOfMonth ) {
|
|
3437
|
-
workingdaystrax = dailyData[0]?.stores?.daysDifferenceTrax + 1;
|
|
3438
|
-
} else {
|
|
3439
|
-
workingdaystrax = 1;
|
|
3440
|
-
}
|
|
3441
|
-
} else {
|
|
3442
|
-
if ( dailyData[0]?.stores?.daysDifferenceTrax&&!isFirstDayOfMonth ) {
|
|
3443
|
-
workingdaystrax = dailyData[0]?.stores?.daysDifferenceTrax;
|
|
3444
|
-
} else {
|
|
3445
|
-
workingdaystrax = 0;
|
|
3446
|
-
}
|
|
3447
|
-
}
|
|
3448
|
-
let priceDetails = getClient.priceType == 'standard' ? getBaseprice.standard : getBaseprice.step;
|
|
3449
|
-
priceDetails = priceDetails.filter( ( item1 ) =>
|
|
3450
|
-
getClient.planDetails.product.some( ( item2 ) =>
|
|
3451
|
-
item2.productName === item1.productName && item2.status === 'live',
|
|
3452
|
-
),
|
|
3453
|
-
);
|
|
3454
|
-
for ( let storeProductIndex = 0; storeProductIndex < getStore[storeIndex].product.length; storeProductIndex++ ) {
|
|
3455
|
-
let productDetails;
|
|
3456
|
-
if ( getClient.priceType == 'standard' ) {
|
|
3457
|
-
productDetails = priceDetails.find( ( item ) => item.productName == getStore[storeIndex].product[storeProductIndex] );
|
|
3458
|
-
} else {
|
|
3459
|
-
productDetails = priceDetails.find( ( item ) => {
|
|
3460
|
-
let range = item.storeRange.split( '-' );
|
|
3461
|
-
if ( parseInt( range[0] ) <= ( storeIndex + 1 ) && parseInt( range[1] ) >= ( storeIndex + 1 ) ) {
|
|
3462
|
-
return ( item.productName == getStore[storeIndex].product[storeProductIndex] && parseInt( range[0] ) <= ( storeIndex + 1 ) && parseInt( range[1] ) >= ( storeIndex + 1 ) );
|
|
3463
|
-
}
|
|
3464
|
-
} );
|
|
3465
|
-
if ( !productDetails ) {
|
|
3466
|
-
let stepProductDetails = priceDetails.filter( ( item ) => item.productName == getStore[storeIndex].product[storeProductIndex] );
|
|
3467
|
-
productDetails = stepProductDetails[stepProductDetails.length - 1];
|
|
3468
|
-
}
|
|
3469
|
-
}
|
|
3470
|
-
if ( productDetails ) {
|
|
3471
|
-
console.log( '-------', workingdays, workingdaystrax, productDetails.productName );
|
|
3472
|
-
let newObject = {
|
|
3473
|
-
productName: productDetails.productName,
|
|
3474
|
-
workingdays: productDetails.productName==='tangoTrax'?workingdaystrax:workingdays,
|
|
3475
|
-
};
|
|
3476
|
-
productList.push( newObject );
|
|
3477
|
-
}
|
|
3478
|
-
}
|
|
3479
|
-
storeList.push(
|
|
3480
|
-
{
|
|
3481
|
-
storeId: getStore[storeIndex].storeId,
|
|
3482
|
-
store: getStore[storeIndex]._id,
|
|
3483
|
-
storeName: getStore[storeIndex].storeName,
|
|
3484
|
-
status: getStore[storeIndex]?.status,
|
|
3485
|
-
firstFile: getStore[storeIndex]?.edge?.firstFile || false,
|
|
3486
|
-
edgefirstFileDate: getStore[storeIndex]?.edge?.firstFileDate || null,
|
|
3487
|
-
date: new Date( requestData.date ),
|
|
3488
|
-
daysDifference: workingdays,
|
|
3489
|
-
daysDifferenceTrax: workingdaystrax,
|
|
3490
|
-
products: productList,
|
|
3491
|
-
camera: cameraDetails.map( ( item ) => item.streamName ),
|
|
3492
|
-
trafficCameraCount: trafficCameraCount,
|
|
3493
|
-
zoneCameraCount: zoneCameraCount,
|
|
3494
|
-
zoneCount: zoneCount,
|
|
3495
|
-
zoneName: zoneName,
|
|
3496
|
-
},
|
|
3497
|
-
);
|
|
3498
|
-
console.log( storeIndex, getStore.length - 1 );
|
|
3499
|
-
if ( storeIndex == getStore.length - 1 ) {
|
|
3500
|
-
let activestores = storeList.filter( ( store ) => store.status=='active' );
|
|
3501
|
-
console.log( '------', activestores.length );
|
|
3502
|
-
let params = {
|
|
3503
|
-
clientId: requestClient[clientIndex],
|
|
3504
|
-
stores: storeList,
|
|
3505
|
-
dateISO: new Date( requestData.date ),
|
|
3506
|
-
accountType: getClient?.planDetails?.subscriptionType,
|
|
3507
|
-
status: getClient?.status,
|
|
3508
|
-
activeStores: activestores?.length,
|
|
3509
|
-
brandName: getClient?.clientName,
|
|
3510
|
-
proRate: getClient?.paymentInvoice?.proRate,
|
|
3511
|
-
dateString: dayjs( requestData.date, 'YYYY-MM-DD' ).format( 'YYYY-MM-DD' ),
|
|
3512
|
-
};
|
|
3513
|
-
await dailyPriceService.updateOne( { clientId: requestClient[clientIndex], dateString: params.dateString }, params, { upsert: true } );
|
|
3514
|
-
}
|
|
3515
|
-
// } ).catch( ( error ) => {
|
|
3516
|
-
// logger.error( { error: error, function: 'old processedDayData' } );
|
|
3517
|
-
// } );
|
|
3518
|
-
} else {
|
|
3519
|
-
// console.log( 'base price not found' );
|
|
3520
|
-
}
|
|
3521
|
-
}
|
|
3522
|
-
} else {
|
|
3523
|
-
// console.log( 'store not found' );
|
|
3524
|
-
}
|
|
3525
|
-
}
|
|
3526
|
-
// console.log( clientIndex, requestClient.length-1 );
|
|
3527
|
-
if ( clientIndex == requestClient.length - 1 ) {
|
|
3528
|
-
// let teamsAlertUrls = process.env.teamsAlertURL ? JSON.parse( process.env.teamsAlertURL ) : '';
|
|
3529
|
-
let totalcount = await dailyPriceService.find( { dateString: dayjs( requestData.date, 'YYYY-MM-DD' ).format( 'YYYY-MM-DD' ) } );
|
|
3530
|
-
let teamsMsg = `${totalcount.length} clients data is inserted on ${dayjs( requestData.date, 'YYYY-MM-DD' ).format( 'YYYY-MM-DD' )}`;
|
|
3531
|
-
console.log( teamsMsg );
|
|
3532
|
-
// if ( teamsAlertUrls.invoiceAlert ) {
|
|
3533
|
-
// sendTeamsNotification( teamsAlertUrls.invoiceAlert, teamsMsg );
|
|
3534
|
-
// }
|
|
3535
3533
|
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3534
|
+
if ( requestClient.length > 0 ) {
|
|
3535
|
+
// Process clients in bounded batches so we run in parallel without
|
|
3536
|
+
// exhausting the DB connection pool with hundreds of concurrent clients.
|
|
3537
|
+
const BATCH_SIZE = 5;
|
|
3538
|
+
for ( let i = 0; i < requestClient.length; i += BATCH_SIZE ) {
|
|
3539
|
+
let batch = requestClient.slice( i, i + BATCH_SIZE );
|
|
3540
|
+
await Promise.all(
|
|
3541
|
+
batch.map( ( clientId ) =>
|
|
3542
|
+
processClientDailyPricing( clientId, requestData.date ).catch( ( error ) => {
|
|
3543
|
+
logger.error( { error: error, function: 'processClientDailyPricing', clientId: clientId } );
|
|
3544
|
+
} ),
|
|
3545
|
+
),
|
|
3546
|
+
);
|
|
3547
|
+
}
|
|
3545
3548
|
|
|
3546
|
-
|
|
3547
|
-
|
|
3549
|
+
// let teamsAlertUrls = process.env.teamsAlertURL ? JSON.parse( process.env.teamsAlertURL ) : '';
|
|
3550
|
+
let totalcount = await dailyPriceService.find( { dateString: dayjs( requestData.date, 'YYYY-MM-DD' ).format( 'YYYY-MM-DD' ) } );
|
|
3551
|
+
let teamsMsg = `${totalcount.length} clients data is inserted on ${dayjs( requestData.date, 'YYYY-MM-DD' ).format( 'YYYY-MM-DD' )}`;
|
|
3552
|
+
console.log( teamsMsg );
|
|
3553
|
+
// if ( teamsAlertUrls.invoiceAlert ) {
|
|
3554
|
+
// sendTeamsNotification( teamsAlertUrls.invoiceAlert, teamsMsg );
|
|
3555
|
+
// }
|
|
3556
|
+
|
|
3557
|
+
const SES = JSON.parse( process.env.SES );
|
|
3558
|
+
let fromEmail = SES.accountsEmail;
|
|
3559
|
+
console.log( process.env.invoiceAlert );
|
|
3560
|
+
let invoiceEmails = JSON.parse( process.env.invoiceAlert );
|
|
3561
|
+
let mailSubject = `Daily Invoice Alert ${dayjs( requestData.date, 'YYYY-MM-DD' ).format( 'YYYY-MM-DD' )}`;
|
|
3562
|
+
if ( invoiceEmails ) {
|
|
3563
|
+
const result = await sendEmailWithSES( invoiceEmails?.email, mailSubject, teamsMsg, '', fromEmail, [] );
|
|
3564
|
+
console.log( '🚀 ~ dailyPricingInsert ~ result:', result );
|
|
3548
3565
|
}
|
|
3549
|
-
|
|
3566
|
+
|
|
3567
|
+
return res.sendSuccess( 'Price Details Inserted Successfully' );
|
|
3550
3568
|
}
|
|
3569
|
+
return res.sendSuccess( 'success' );
|
|
3551
3570
|
} catch ( e ) {
|
|
3552
|
-
logger.error( { error: e, function: '
|
|
3571
|
+
logger.error( { error: e, function: 'dailyPricingInsert' } );
|
|
3553
3572
|
return res.sendError( e, 500 );
|
|
3554
3573
|
}
|
|
3555
3574
|
};
|