tango-app-api-payment-subscription 3.1.12 → 3.1.14-alpha.1

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.
@@ -1,274 +1,147 @@
1
- import { aggregatebilling } from '../services/billing.service.js';
2
1
  import * as invoiceService from '../services/invoice.service.js';
3
2
  import * as dailyPricingService from '../services/dailyPrice.service.js';
4
3
  import * as clientService from '../services/clientPayment.services.js';
5
- // import * as basePricingService from '../services/basePrice.service.js';
4
+ import * as billingService from '../services/billing.service.js';
6
5
  import dayjs from 'dayjs';
7
-
8
-
9
- import { logger } from 'tango-app-api-middleware';
6
+ import { logger, checkFileExist, signedUrl, download, sendEmailWithSES } from 'tango-app-api-middleware';
10
7
  import Handlebars from 'handlebars';
11
8
  import fs from 'fs';
12
9
  import path from 'path';
13
10
  import htmlpdf from 'html-pdf-node';
11
+ import * as basepricingService from '../services/basePrice.service.js';
12
+ import * as paymentAccountService from '../services/paymentAccount.service.js';
13
+
14
14
  export async function createInvoice( req, res ) {
15
15
  try {
16
- for ( let client of req.body.clientList ) {
17
- let invoiceGroup = await aggregatebilling( [
18
- {
19
- $match: {
20
- clientId: client,
21
- },
22
- },
23
- ] );
24
- for ( let group of invoiceGroup ) {
25
- let Finacialyear = getCurrentFinancialYear();
26
- let previousinvoice = await invoiceService.findandsort( {}, {}, { invoiceIndex: -1 } );
27
- let invoiceNo = '00001';
28
- if ( previousinvoice && previousinvoice.length > 0 ) {
29
- invoiceNo = Number( previousinvoice[0].invoiceIndex ) + 1;
30
- invoiceNo = invoiceNo.toString().padStart( 5, '0' );
31
- }
32
-
33
- let address = group.addressLineOne + group.addressLineTwo + group.city + ',' + group.state + ',' + group.country + ' -' + group.pinCode;
34
-
16
+ let invoiceGroupList = [];
17
+ if ( req.body.allClient ) {
18
+ if ( req.body.clientList && req.body.clientList.length > 0 ) {
19
+ req.body.clientList = req.body.clientList;
20
+ } else {
21
+ let clients = await clientService.find( { status: 'active' } );
22
+ req.body.clientList = clients.map( ( client ) => client.clientId );
23
+ }
35
24
 
36
- const currentMonthDays = dayjs().daysInMonth();
37
- let products = await dailyPricingService.aggregate( [
25
+ for ( let client of req.body.clientList ) {
26
+ let invoiceGroup = await billingService.aggregatebilling( [
38
27
  {
39
28
  $match: {
40
29
  clientId: client,
41
30
  },
42
31
  },
43
- {
44
- $sort: { dateISO: -1 },
45
- },
46
- { $limit: 1 },
47
- {
48
- $unwind: {
49
- path: '$stores',
50
- preserveNullAndEmptyArrays: false,
51
- },
52
- },
53
- {
54
- $unwind: {
55
- path: '$stores.products',
56
- preserveNullAndEmptyArrays: false,
57
- },
58
- },
59
- {
60
- $project: {
61
- productName: '$stores.products.productName',
62
- storeId: '$stores.storeId',
63
- workingDays: '$stores.products.workingdays',
64
- },
65
- },
66
- {
67
- $group: {
68
- _id: {
69
- productName: '$productName',
70
- storeId: '$storeId',
71
- },
72
- workingdays: { $first: '$workingDays' },
73
- },
74
- },
75
- {
76
- $group: {
77
- _id: {
78
- productName: '$_id.productName',
79
- workingdays: '$workingdays',
80
- },
81
- storeCount: { $sum: 1 },
82
- },
83
- },
84
- {
85
- $project: {
86
- _id: 0,
87
- productName: '$_id.productName',
88
- workingdays: '$_id.workingdays',
89
- storeCount: '$storeCount',
90
- },
91
- },
92
- {
93
- $lookup: {
94
- from: 'basepricings',
95
- let: { clientId: client },
96
- pipeline: [
97
- {
98
- $match: {
99
- $expr: {
100
- $eq: [ '$clientId', '$$clientId' ],
101
- },
102
- },
103
- },
104
- {
105
- $project: {
106
- standard: 1,
107
- step: 1,
108
- },
109
- },
110
- ],
111
- as: 'basepricing',
112
- },
113
- },
114
- {
115
- $unwind: { path: '$basepricing', preserveNullAndEmptyArrays: true },
116
- },
117
- {
118
- $project: {
119
- productName: 1,
120
- workingdays: 1,
121
- storeCount: 1,
122
- standard: {
123
- $filter: {
124
- input: '$basepricing.standard',
125
- as: 'standard',
126
- cond: { $eq: [ '$$standard.productName', '$productName' ] },
127
- },
128
- },
129
- step: '$basepricing.step',
130
- },
131
- },
132
- {
133
- $unwind: { path: '$standard', preserveNullAndEmptyArrays: true },
134
- },
135
- {
136
- $project: {
137
- productName: 1,
138
- workingdays: 1,
139
- period: {
140
- $cond: {
141
- if: { $lt: [ '$workingdays', currentMonthDays ] },
142
- then: 'prorate',
143
- else: 'fullmonth',
144
- },
145
- },
146
- storeCount: 1,
147
- standardPrice: '$standard.negotiatePrice',
148
- runningCost: {
149
- $round: [
150
- {
151
- $multiply: [
152
- { $divide: [ '$standard.negotiatePrice', currentMonthDays ] },
153
- '$workingdays',
154
- ],
155
- },
156
- 2,
157
- ],
158
- },
159
- perstorecost: {
160
- $cond: {
161
- if: { $eq: [ '$workingdays', currentMonthDays ] },
162
- then: {
163
- $multiply: [ '$standard.negotiatePrice', '$storeCount' ] },
164
- else: {
165
- $round: [
166
- {
167
- $multiply: [
168
- {
169
- $multiply: [
170
- { $divide: [ '$standard.negotiatePrice', currentMonthDays ] },
171
- '$workingdays',
172
- ],
173
- },
174
- '$storeCount',
175
- ],
176
- },
177
- 2,
178
- ] },
179
- },
180
- },
32
+ ] );
33
+ for ( let invGrp of invoiceGroup ) {
34
+ invoiceGroupList.push( invGrp );
35
+ }
36
+ }
37
+ }
38
+ if ( req.body.invoiceId ) {
39
+ let findInvoice = await invoiceService.findOne( { invoice: req.body.invoiceId } );
40
+ let invoiceGroup = await billingService.findOne( { _id: findInvoice.groupId } );
41
+ invoiceGroupList.push( invoiceGroup );
42
+ }
43
+
44
+ for ( let group of invoiceGroupList ) {
45
+ let Finacialyear = getCurrentFinancialYear();
46
+ let previousinvoice = await invoiceService.findandsort( {}, {}, { invoiceIndex: -1 } );
47
+ let invoiceNo = '00001';
48
+ if ( previousinvoice && previousinvoice.length > 0 ) {
49
+ invoiceNo = Number( previousinvoice[0].invoiceIndex ) + 1;
50
+ invoiceNo = invoiceNo.toString().padStart( 5, '0' );
51
+ }
52
+
53
+ let address = group.addressLineOne + group.addressLineTwo + group.city + ',' + group.state + ',' + group.country + ' -' + group.pinCode;
54
+ let getClient = await clientService.findOne( { clientId: group.clientId, status: 'active' } );
55
+ let products;
56
+
57
+ if ( getClient.priceType === 'standard' ) {
58
+ products = await standardPrice( group );
59
+ } else {
60
+ products = await stepPrice( group );
61
+ }
62
+ let amount = products.reduce( ( sum, product ) => sum + product.amount, 0 );
63
+ let taxList = [];
64
+ let totalAmount = 0;
65
+ if ( group.gst && group.gst.slice( 0, 2 ) == '33' ) {
66
+ let taxAmount = ( amount * 18 ) / 100;
67
+ totalAmount = Math.round( amount + taxAmount );
68
+ taxList.push(
69
+ {
70
+ 'currency': '₹',
71
+ 'type': 'CGST',
72
+ 'value': 9,
73
+ 'taxAmount': ( ( amount * 9 ) / 100 ).toFixed( 2 ),
74
+ }, {
75
+ 'currency': '₹',
76
+ 'type': 'SGST',
77
+ 'value': 9,
78
+ 'taxAmount': ( ( amount * 9 ) / 100 ).toFixed( 2 ),
181
79
  },
182
- },
183
- {
184
- $group: {
185
- _id: {
186
- productName: '$productName',
187
- period: '$period',
188
- },
189
- storeCount: { $sum: '$storeCount' },
190
- amount: { $sum: '$perstorecost' },
80
+ );
81
+ } else {
82
+ let taxAmount = ( amount * 18 ) / 100;
83
+ totalAmount = Math.round( amount + taxAmount );
84
+ taxList.push(
85
+ {
86
+ 'currency': '₹',
87
+ 'type': 'IGST',
88
+ 'value': 18,
89
+ 'taxAmount': ( taxAmount ).toFixed( 2 ),
191
90
  },
91
+ );
92
+ }
93
+
94
+ let totalStoreCount = await dailyPricingService.aggregate( [
95
+ {
96
+ $match: {
97
+ clientId: group.clientId,
192
98
  },
193
- {
194
- $project: {
195
- _id: 0,
196
- productName: '$_id.productName',
197
- period: '$_id.period',
198
- price: {
199
- $round: [ { $divide: [ '$amount', '$storeCount' ] }, 2 ] },
200
- storeCount: 1,
201
- amount: 1,
202
- description: {
203
- $cond: {
204
- if: { $eq: [ '$_id.productName', 'tangoZone' ] },
205
- then: 'Product category/section analytics',
206
- else: 'Customer Footfall Analytics',
207
- },
99
+ },
100
+ {
101
+ $sort: { dateISO: -1 },
102
+ },
103
+ { $limit: 1 },
104
+ {
105
+ $project: {
106
+ stores: {
107
+ $filter: {
108
+ input: '$stores',
109
+ as: 'item',
110
+ cond: { $gt: [ '$$item.daysDifference', 0 ] },
208
111
  },
209
- HsnNumber: '998314',
210
- month: { $literal: dayjs().format( 'MMM YYYY' ) },
211
112
  },
212
113
  },
213
- ] );
114
+ },
214
115
 
215
- let amount = products.reduce( ( sum, product ) => sum + product.amount, 0 );
216
- let taxList = [];
217
- let totalAmount = 0;
218
- if ( group.gst && group.gst.slice( 0, 2 ) == '33' ) {
219
- let taxAmount = ( amount * 18 ) / 100;
220
- totalAmount = ( amount + taxAmount ).toFixed( 2 );
221
- taxList.push(
222
- {
223
- 'currency': '',
224
- 'type': 'CGST',
225
- 'value': 9,
226
- 'taxAmount': ( ( amount * 9 ) / 100 ).toFixed( 2 ),
227
- }, {
228
- 'currency': '₹',
229
- 'type': 'SGST',
230
- 'value': 9,
231
- 'taxAmount': ( ( amount * 9 ) / 100 ).toFixed( 2 ),
232
- },
233
- );
234
- } else {
235
- let taxAmount = ( amount * 18 ) / 100;
236
- totalAmount = ( amount + taxAmount ).toFixed( 2 );
237
- taxList.push(
238
- {
239
- 'currency': '₹',
240
- 'type': 'IGST',
241
- 'value': 18,
242
- 'taxAmount': ( taxAmount ).toFixed( 2 ),
243
- },
244
- );
245
- }
246
- let data = {
247
- invoice: `INV-${Finacialyear}-${invoiceNo}`,
248
- products: products,
249
- status: 'pending',
250
- amount: amount,
251
- invoiceIndex: invoiceNo,
252
- tax: taxList,
253
- companyName: group.registeredCompanyName,
254
- companyAddress: address,
255
- PlaceOfSupply: group.placeOfSupply,
256
- GSTNumber: group.gst,
257
- totalAmount: totalAmount,
258
- clientId: client,
259
- paymentMethod: '',
260
- billingDate: new Date(),
261
- stores: group.stores.length,
262
- };
263
- let getClient = await clientService.findOne( { clientId: client, status: 'active' } );
264
- let invoiceExists = await invoiceService.findOne( { monthOfbilling: req.body.monthOfbilling, clientId: getClient.clientId } );
265
- if ( invoiceExists ) {
266
- logger.info( `invoice already exist for the month${req.body.monthOfbilling} for ${getClient.clientId}` );
267
- } else {
268
- await invoiceService.create( data );
269
- }
116
+ ] );
117
+
118
+
119
+ let data = {
120
+ groupName: group.groupName,
121
+ groupId: group._id,
122
+ invoice: `INV-${Finacialyear}-${invoiceNo}`,
123
+ products: products,
124
+ status: 'pending',
125
+ amount: Math.round( amount ),
126
+ invoiceIndex: invoiceNo,
127
+ tax: taxList,
128
+ companyName: group.registeredCompanyName,
129
+ companyAddress: address,
130
+ PlaceOfSupply: group.placeOfSupply,
131
+ GSTNumber: group.gst,
132
+ totalAmount: Math.round( totalAmount ),
133
+ clientId: group.clientId,
134
+ paymentMethod: '',
135
+ billingDate: new Date(),
136
+ stores: totalStoreCount.length?totalStoreCount[0].stores.length:0,
137
+ currency: group.currency ? group.currency : 'inr',
138
+ };
139
+ if ( req.body.invoiceId ) {
140
+ await invoiceService.deleteRecord( { invoice: req.body.invoiceId } );
270
141
  }
142
+ await invoiceService.create( data );
271
143
  }
144
+
272
145
  res.sendSuccess( 'Invoice Created SuccessFully' );
273
146
  } catch ( error ) {
274
147
  logger.error( { error: error, function: 'createInvoice' } );
@@ -294,7 +167,7 @@ export async function invoiceDownload( req, res ) {
294
167
  if ( invoiceInfo ) {
295
168
  let clientDetails = await clientService.findOne( { clientId: invoiceInfo.clientId } );
296
169
  invoiceInfo.products.forEach( ( item, index ) => {
297
- item.index = index+1;
170
+ item.index = index + 1;
298
171
  let [ firstWord, secondWord ] = item.productName.replace( /([a-z])([A-Z])/g, '$1 $2' ).split( ' ' );
299
172
  firstWord = firstWord.charAt( 0 ).toUpperCase() + firstWord.slice( 1 );
300
173
  item.productName = firstWord + ' ' + secondWord;
@@ -330,8 +203,12 @@ export async function invoiceDownload( req, res ) {
330
203
  totalAmount: invoiceInfo.totalAmount.toLocaleString( 'en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 } ),
331
204
  invoiceDate,
332
205
  dueDate,
206
+ discountPercentage: invoiceInfo.discountPercentage ? invoiceInfo.discountPercentage : 0,
207
+ discountAmount: invoiceInfo.discountAmount ? invoiceInfo.discountAmount : 0,
208
+ logo: `${JSON.parse( process.env.URL ).apiDomain}/logo.png`,
333
209
  };
334
210
  const currentMonthDays = dayjs().daysInMonth();
211
+ let getgroup = await billingService.findOne( { _id: invoiceInfo.groupId } );
335
212
  let annuxureData = await dailyPricingService.aggregate( [
336
213
  {
337
214
  $match: {
@@ -342,6 +219,17 @@ export async function invoiceDownload( req, res ) {
342
219
  $sort: { dateISO: -1 },
343
220
  },
344
221
  { $limit: 1 },
222
+ {
223
+ $project: {
224
+ stores: {
225
+ $filter: {
226
+ input: '$stores',
227
+ as: 'item',
228
+ cond: { $in: [ '$$item.storeId', getgroup.stores ] },
229
+ },
230
+ },
231
+ },
232
+ },
345
233
  {
346
234
  $unwind: {
347
235
  path: '$stores',
@@ -370,6 +258,10 @@ export async function invoiceDownload( req, res ) {
370
258
  workingdays: -1,
371
259
  },
372
260
  },
261
+ {
262
+ $match: { workingdays: { $gt: 0 } },
263
+ },
264
+
373
265
  {
374
266
  $lookup: {
375
267
  from: 'basepricings',
@@ -453,7 +345,8 @@ export async function invoiceDownload( req, res ) {
453
345
  ],
454
346
  );
455
347
  invoiceData.annuxureData = annuxureData;
456
-
348
+ let virtualAccount = await paymentAccountService.findOneAccount( { clientId: invoiceInfo.clientId } );
349
+ invoiceData.virtualAccount = virtualAccount;
457
350
  const templateHtml = fs.readFileSync( path.resolve( path.dirname( '' ) ) + '/src/hbs/invoicePdf.hbs', 'utf8' );
458
351
  const template = Handlebars.compile( templateHtml );
459
352
  const html = template( { ...invoiceData } );
@@ -476,25 +369,18 @@ export async function invoiceDownload( req, res ) {
476
369
  htmlpdf.generatePdf( file, options ).then( async function( pdfBuffer ) {
477
370
  if ( req.body.sendInvoice ) {
478
371
  let mailSubject = `Invoice for ${monthName} - Tango/${clientDetails.clientName}`;
479
- let mailbody = `<div>Dear Team,</div>
480
- <div style="margin-top:10px">
481
- Please find attached the Invoice for the month of ${monthName}'24.
482
- </div>
483
- <div style="margin-top:10px">
484
- Best Regards,
485
- </div>
486
- <div style="margin-top:5px">
487
- Tango Finance
488
- </div>`;
489
-
372
+ const templateHtml = fs.readFileSync( path.resolve( path.dirname( '' ) ) + '/src/hbs/invoicepaymentemail.hbs', 'utf8' );
373
+ const template = Handlebars.compile( templateHtml );
374
+ const mailbody = template( { ...invoiceData } );
490
375
  let filename = `${clientDetails.clientName}-${invoiceData.invoice}-${monthName}.pdf`;
491
376
  let attachments = {
492
377
  filename: `${filename}`,
493
378
  content: pdfBuffer,
494
379
  contentType: 'application/pdf', // e.g., 'application/pdf'
495
380
  };
496
- clientDetails.paymentInvoice.invoiceCC = [ ...clientDetails.paymentInvoice.invoiceCC, ...[ 'sireesha@tangotech.co.in' ] ];
497
- const result = await sendEmailWithSES( clientDetails.paymentInvoice.invoiceTo, mailSubject, mailbody, attachments, 'no-reply@tangotech.ai', clientDetails.paymentInvoice.invoiceCC );
381
+ let billingGroup = await billingService.findOne( { _id: invoiceInfo.groupId } );
382
+
383
+ const result = await sendEmailWithSES( billingGroup.generateInvoiceTo, mailSubject, mailbody, attachments, 'no-reply@tangotech.ai' );
498
384
  if ( result ) {
499
385
  await invoiceService.updateOne( { _id: req.params.invoiceId }, { status: req.body.status } );
500
386
  return res.sendSuccess( result );
@@ -524,3 +410,664 @@ function inWords( num ) {
524
410
 
525
411
  return str.toLowerCase().split( ' ' ).map( ( word ) => word.charAt( 0 ).toUpperCase() + word.slice( 1 ) ).join( ' ' );
526
412
  }
413
+
414
+
415
+ async function standardPrice( group ) {
416
+ const currentMonthDays = dayjs().daysInMonth();
417
+ let products = await dailyPricingService.aggregate( [
418
+ {
419
+ $match: {
420
+ clientId: group.clientId,
421
+ },
422
+ },
423
+ {
424
+ $sort: { dateISO: -1 },
425
+ },
426
+ { $limit: 1 },
427
+ {
428
+ $project: {
429
+ stores: {
430
+ $filter: {
431
+ input: '$stores',
432
+ as: 'item',
433
+ cond: { $in: [ '$$item.storeId', group.stores ] },
434
+ },
435
+ },
436
+ },
437
+ },
438
+ {
439
+ $unwind: {
440
+ path: '$stores',
441
+ preserveNullAndEmptyArrays: false,
442
+ },
443
+ },
444
+ {
445
+ $unwind: {
446
+ path: '$stores.products',
447
+ preserveNullAndEmptyArrays: false,
448
+ },
449
+ },
450
+ {
451
+ $project: {
452
+ productName: '$stores.products.productName',
453
+ storeId: '$stores.storeId',
454
+ workingDays: '$stores.products.workingdays',
455
+ storeStatus: '$stores.status',
456
+ },
457
+ },
458
+ {
459
+ $match: {
460
+ workingDays: { $gt: 0 },
461
+ },
462
+ },
463
+ {
464
+ $project: {
465
+ productName: 1,
466
+ storeId: 1,
467
+ prorate: { $literal: group.proRata },
468
+ workingDays: 1,
469
+ storeStatus: 1,
470
+ },
471
+ },
472
+ {
473
+ $project: {
474
+ productName: 1,
475
+ storeId: 1,
476
+ workingDays: {
477
+ $cond: { if: { $and: [ { $gte: [ '$workingDays', 15 ] }, { $eq: [ '$storeStatus', 'active' ] }, { $eq: [ '$prorate', 'before15' ] } ] }, then: currentMonthDays, else: '$workingDays' },
478
+ },
479
+ storeStatus: 1,
480
+ },
481
+ },
482
+ {
483
+ $group: {
484
+ _id: {
485
+ productName: '$productName',
486
+ storeId: '$storeId',
487
+ },
488
+ workingdays: { $first: '$workingDays' },
489
+ },
490
+ },
491
+ {
492
+ $group: {
493
+ _id: {
494
+ productName: '$_id.productName',
495
+ workingdays: '$workingdays',
496
+ },
497
+ storeCount: { $sum: 1 },
498
+ },
499
+ },
500
+ {
501
+ $project: {
502
+ _id: 0,
503
+ productName: '$_id.productName',
504
+ workingdays: '$_id.workingdays',
505
+ storeCount: '$storeCount',
506
+ },
507
+ },
508
+ {
509
+ $lookup: {
510
+ from: 'basepricings',
511
+ let: { clientId: group.clientId },
512
+ pipeline: [
513
+ {
514
+ $match: {
515
+ $expr: {
516
+ $eq: [ '$clientId', '$$clientId' ],
517
+ },
518
+ },
519
+ },
520
+ {
521
+ $project: {
522
+ standard: 1,
523
+ step: 1,
524
+ },
525
+ },
526
+ ],
527
+ as: 'basepricing',
528
+ },
529
+ },
530
+ {
531
+ $unwind: { path: '$basepricing', preserveNullAndEmptyArrays: true },
532
+ },
533
+ {
534
+ $project: {
535
+ productName: 1,
536
+ workingdays: 1,
537
+ storeCount: 1,
538
+ standard: {
539
+ $filter: {
540
+ input: '$basepricing.standard',
541
+ as: 'standard',
542
+ cond: { $eq: [ '$$standard.productName', '$productName' ] },
543
+ },
544
+ },
545
+ step: '$basepricing.step',
546
+ },
547
+ },
548
+ {
549
+ $unwind: { path: '$standard', preserveNullAndEmptyArrays: true },
550
+ },
551
+ {
552
+ $project: {
553
+ productName: 1,
554
+ workingdays: 1,
555
+ period: {
556
+ $cond: {
557
+ if: { $lt: [ '$workingdays', currentMonthDays ] },
558
+ then: 'prorate',
559
+ else: 'fullmonth',
560
+ },
561
+ },
562
+ storeCount: 1,
563
+ standardPrice: '$standard.negotiatePrice',
564
+ runningCost: {
565
+ $round: [
566
+ {
567
+ $multiply: [
568
+ { $divide: [ '$standard.negotiatePrice', currentMonthDays ] },
569
+ '$workingdays',
570
+ ],
571
+ },
572
+ 2,
573
+ ],
574
+ },
575
+ perstorecost: {
576
+ $cond: {
577
+ if: { $eq: [ '$workingdays', currentMonthDays ] },
578
+ then: {
579
+ $multiply: [ '$standard.negotiatePrice', '$storeCount' ],
580
+ },
581
+ else: {
582
+ $round: [
583
+ {
584
+ $multiply: [
585
+ {
586
+ $multiply: [
587
+ { $divide: [ '$standard.negotiatePrice', currentMonthDays ] },
588
+ '$workingdays',
589
+ ],
590
+ },
591
+ '$storeCount',
592
+ ],
593
+ },
594
+ 2,
595
+ ],
596
+ },
597
+ },
598
+ },
599
+ },
600
+ },
601
+ {
602
+ $group: {
603
+ _id: {
604
+ productName: '$productName',
605
+ period: '$period',
606
+ },
607
+ storeCount: { $sum: '$storeCount' },
608
+ amount: { $sum: '$perstorecost' },
609
+ },
610
+ },
611
+ {
612
+ $project: {
613
+ _id: 0,
614
+ productName: '$_id.productName',
615
+ period: '$_id.period',
616
+ price: {
617
+ $round: [ { $divide: [ '$amount', '$storeCount' ] }, 2 ],
618
+ },
619
+ storeCount: 1,
620
+ amount: 1,
621
+ description: {
622
+ $cond: {
623
+ if: { $eq: [ '$_id.productName', 'tangoZone' ] },
624
+ then: 'Product category/section analytics',
625
+ else: 'Customer Footfall Analytics',
626
+ },
627
+ },
628
+ HsnNumber: '998314',
629
+ month: { $literal: dayjs().format( 'MMM YYYY' ) },
630
+ },
631
+ },
632
+ ] );
633
+
634
+
635
+ return products;
636
+ }
637
+
638
+
639
+ async function stepPrice( group ) {
640
+ const currentMonthDays = dayjs().daysInMonth();
641
+ let products = await dailyPricingService.aggregate( [
642
+ {
643
+ $match: {
644
+ clientId: group.clientId,
645
+ },
646
+ },
647
+ {
648
+ $sort: { dateISO: -1 },
649
+ },
650
+ { $limit: 1 },
651
+ {
652
+ $project: {
653
+ stores: {
654
+ $filter: {
655
+ input: '$stores',
656
+ as: 'item',
657
+ cond: { $in: [ '$$item.storeId', group.stores ] },
658
+ },
659
+ },
660
+ },
661
+ },
662
+ {
663
+ $unwind: {
664
+ path: '$stores',
665
+ preserveNullAndEmptyArrays: false,
666
+ },
667
+ },
668
+ {
669
+ $unwind: {
670
+ path: '$stores.products',
671
+ preserveNullAndEmptyArrays: false,
672
+ },
673
+ },
674
+ {
675
+ $project: {
676
+ productName: '$stores.products.productName',
677
+ storeId: '$stores.storeId',
678
+ workingDays: '$stores.products.workingdays',
679
+ storeStatus: '$stores.status',
680
+ },
681
+ },
682
+ {
683
+ $match: {
684
+ workingDays: { $gt: 0 },
685
+ },
686
+ },
687
+ {
688
+ $project: {
689
+ productName: 1,
690
+ storeId: 1,
691
+ prorate: { $literal: group.proRata },
692
+ workingDays: 1,
693
+ storeStatus: 1,
694
+ },
695
+ },
696
+ {
697
+ $project: {
698
+ productName: 1,
699
+ storeId: 1,
700
+ workingDays: {
701
+ $cond: { if: { $and: [ { $gte: [ '$workingDays', 15 ] }, { $eq: [ '$storeStatus', 'active' ] }, { $eq: [ '$prorate', 'before15' ] } ] }, then: currentMonthDays, else: '$workingDays' },
702
+ },
703
+ storeStatus: 1,
704
+ },
705
+ }, {
706
+ $group: {
707
+ _id: {
708
+ productName: '$productName',
709
+ storeId: '$storeId',
710
+ },
711
+ workingdays: { $first: '$workingDays' },
712
+ },
713
+ },
714
+ {
715
+ $group: {
716
+ _id: {
717
+ productName: '$_id.productName',
718
+ workingdays: '$workingdays',
719
+ },
720
+ storeCount: { $sum: 1 },
721
+ },
722
+ },
723
+ {
724
+ $project: {
725
+ _id: 0,
726
+ productName: '$_id.productName',
727
+ workingdays: '$_id.workingdays',
728
+ storeCount: '$storeCount',
729
+ },
730
+ },
731
+ {
732
+ $sort: {
733
+ workingdays: -1,
734
+ },
735
+ },
736
+
737
+
738
+ ] );
739
+ let stepPrice = await basepricingService.findOne( { clientId: group.clientId } );
740
+ let data = products;
741
+ let pricing = stepPrice.step;
742
+
743
+ const applyPricing = ( data, pricing ) => {
744
+ let totalcount = 0;
745
+ return data.map( ( item ) => {
746
+ totalcount = totalcount + item.storeCount;
747
+
748
+
749
+ let applicablePricing = pricing.find( ( price ) => {
750
+ const [ minRange, maxRange ] = price.storeRange.split( '-' ).map( Number );
751
+ return totalcount >= minRange && totalcount <= maxRange;
752
+ } );
753
+ if ( item.workingdays === currentMonthDays ) {
754
+ item.period = 'fullMonth';
755
+ item.runningCost = item.storeCount * applicablePricing.negotiatePrice;
756
+ } else {
757
+ item.period = 'proRate';
758
+ item.runningCost = ( item.storeCount * ( applicablePricing.negotiatePrice / currentMonthDays ) * item.workingdays ).toFixed( 2 );
759
+ }
760
+
761
+ return {
762
+ ...item,
763
+ negotiatePrice: applicablePricing.negotiatePrice,
764
+ perstorecost: ( ( applicablePricing.negotiatePrice / currentMonthDays ) * item.workingdays ).toFixed( 2 ),
765
+ };
766
+ } );
767
+ };
768
+
769
+ const result = applyPricing( data, pricing );
770
+ const groupedData = result.reduce( ( acc, item ) => {
771
+ const { productName, period, runningCost } = item;
772
+ const key = `${productName}_${period}`;
773
+ if ( !acc[key] ) {
774
+ acc[key] = {
775
+ productName,
776
+ period,
777
+ totalRunningCost: 0,
778
+ count: 0,
779
+ };
780
+ }
781
+
782
+ acc[key].totalRunningCost += parseFloat( runningCost );
783
+ acc[key].count += item.storeCount;
784
+
785
+ return acc;
786
+ }, {} );
787
+
788
+ // Calculating average running cost
789
+ const finalresult = Object.values( groupedData ).map( ( group ) => {
790
+ let description = '';
791
+ if ( group.productName === 'tangoTraffic' ) {
792
+ description = 'Customer Footfall Analytics';
793
+ } else if ( group.productName === 'tangoZone' ) {
794
+ description = 'Product category/section analytics';
795
+ } else {
796
+ description = '';
797
+ }
798
+ return {
799
+ productName: group.productName,
800
+ period: group.period,
801
+ storeCount: group.count,
802
+ description: description,
803
+ HsnNumber: '998314',
804
+ amount: group.totalRunningCost,
805
+ month: dayjs().format( 'MMM YYYY' ),
806
+ price: ( group.totalRunningCost / group.count ).toFixed( 2 ),
807
+ };
808
+ } );
809
+
810
+ return finalresult;
811
+ }
812
+
813
+
814
+ export async function clientInvoiceList( req, res ) {
815
+ try {
816
+ let findClients = await clientService.find( { clientId: { $in: req.body.clientId }, status: 'active' }, { clientId: 1 } );
817
+ findClients = findClients.map( ( a ) => {
818
+ return a.clientId;
819
+ } );
820
+ let query = [ {
821
+ $match: {
822
+ clientId: { $in: findClients },
823
+ },
824
+ }, {
825
+ $lookup: {
826
+ from: 'clients',
827
+ let: { clientId: '$clientId' },
828
+ pipeline: [
829
+ {
830
+ $match: {
831
+ $expr: {
832
+ $and: [
833
+ { $eq: [ '$clientId', '$$clientId' ] },
834
+ ],
835
+ },
836
+ },
837
+ },
838
+ {
839
+ $project: {
840
+ clientName: 1,
841
+ logo: '$profileDetails.logo',
842
+ currencyType: '$paymentInvoice.currencyType',
843
+ },
844
+ },
845
+ ],
846
+ as: 'clientDetails',
847
+ },
848
+ },
849
+ {
850
+ $unwind: { path: '$clientDetails', preserveNullAndEmptyArrays: true },
851
+ },
852
+ {
853
+ $project: {
854
+ clientName: '$clientDetails.clientName',
855
+ logo: '$clientDetails.logo',
856
+ currencyType: '$currency',
857
+ invoice: 1,
858
+ stores: 1,
859
+ totalAmount: 1,
860
+ groupName: 1,
861
+ status: 1,
862
+ paymentStatus: 1,
863
+ clientId: 1,
864
+ billingDate: 1,
865
+ },
866
+ },
867
+ ];
868
+ if ( req.body.searchValue && req.body.searchValue != '' ) {
869
+ query.push( {
870
+ $match: {
871
+ $or: [
872
+ { groupName: { $regex: req.body.searchValue, $options: 'i' } },
873
+ { clientName: { $regex: req.body.searchValue, $options: 'i' } },
874
+ { paymentStatus: { $regex: req.body.searchValue, $options: 'i' } },
875
+ ],
876
+ },
877
+ } );
878
+ }
879
+ if ( req.body.sortColumName && req.body.sortColumName !== '' && req.body.sortBy ) {
880
+ query.push( {
881
+ $sort: { [req.body.sortColumName]: req.body.sortBy },
882
+ } );
883
+ }
884
+ let count = await invoiceService.aggregate( query );
885
+ if ( count.length == 0 ) {
886
+ return res.sendError( 'No data', 204 );
887
+ }
888
+
889
+ if ( req.body.export ) {
890
+ const exportdata = [];
891
+ count.forEach( ( element ) => {
892
+ exportdata.push( {
893
+ 'Client': element.clientName,
894
+ 'Invoice #': element.invoice,
895
+ 'Billing date': dayjs( element.billingDate ).format( 'DD MMM, YYYY' ),
896
+ 'Group Name': element.groupName,
897
+ 'Amount': element.totalAmount,
898
+ 'Stores': element.stores,
899
+ 'Payment Status': element.paymentStatus,
900
+ 'Approval Status': element.status,
901
+
902
+ } );
903
+ } );
904
+ await download( exportdata, res );
905
+ return;
906
+ }
907
+ if ( req.body.limit && req.body.offset && !req.body.export ) {
908
+ query.push(
909
+ { $skip: ( req.body.offset - 1 ) * req.body.limit },
910
+ { $limit: Number( req.body.limit ) },
911
+ );
912
+ }
913
+ let invoiceList = await invoiceService.aggregate( query );
914
+ const bucket = JSON.parse( process.env.BUCKET );
915
+ for ( let client of invoiceList ) {
916
+ if ( client?.logo && client?.logo != '' ) {
917
+ const isLogoExist = await checkFileExist( { Bucket: bucket.assets, Key: `${client.clientId}/logo/${client?.logo}` } );
918
+ if ( isLogoExist ) {
919
+ client.logo = await signedUrl( { Bucket: bucket.assets, file_path: `${client.clientId}/logo/${client?.logo}` } );
920
+ } else {
921
+ client.logo = '';
922
+ }
923
+ } else {
924
+ client.logo = '';
925
+ }
926
+ }
927
+ res.sendSuccess( { count: count.length, data: invoiceList } );
928
+ } catch ( error ) {
929
+ logger.error( { error: error, function: 'clientInvoiceList' } );
930
+ return res.sendError( error, 500 );
931
+ }
932
+ }
933
+
934
+
935
+ export async function creditTransactionlist( req, res ) {
936
+ try {
937
+ let findClients = await clientService.find( { clientId: { $in: req.body.clientId }, status: 'active' }, { clientId: 1 } );
938
+ findClients = findClients.map( ( a ) => {
939
+ return a.clientId;
940
+ } );
941
+ let query = [ {
942
+ $match: {
943
+ clientId: { $in: findClients },
944
+ },
945
+ }, {
946
+ $lookup: {
947
+ from: 'clients',
948
+ let: { clientId: '$clientId' },
949
+ pipeline: [
950
+ {
951
+ $match: {
952
+ $expr: {
953
+ $and: [
954
+ { $eq: [ '$clientId', '$$clientId' ] },
955
+ ],
956
+ },
957
+ },
958
+ },
959
+ {
960
+ $project: {
961
+ clientName: 1,
962
+ logo: '$profileDetails.logo',
963
+ currencyType: '$paymentInvoice.currencyType',
964
+ },
965
+ },
966
+ ],
967
+ as: 'clientDetails',
968
+ },
969
+ },
970
+ {
971
+ $unwind: { path: '$clientDetails', preserveNullAndEmptyArrays: true },
972
+ },
973
+ {
974
+ $project: {
975
+ clientName: '$clientDetails.clientName',
976
+ logo: '$clientDetails.logo',
977
+ currencyType: '$clientDetails.currencyType',
978
+ clientId: 1,
979
+ credit: 1,
980
+ },
981
+ },
982
+ ];
983
+ if ( req.body.searchValue && req.body.searchValue != '' ) {
984
+ query.push( {
985
+ $match: {
986
+ $or: [
987
+ { clientName: { $regex: req.body.searchValue, $options: 'i' } },
988
+ { paymentStatus: { $regex: req.body.searchValue, $options: 'i' } },
989
+ ],
990
+ },
991
+ } );
992
+ }
993
+ if ( req.body.sortColumName && req.body.sortColumName !== '' && req.body.sortBy ) {
994
+ query.push( {
995
+ $sort: { [req.body.sortColumName]: req.body.sortBy },
996
+ } );
997
+ }
998
+ let count = await paymentAccountService.aggregate( query );
999
+ if ( req.body.export ) {
1000
+ const exportdata = [];
1001
+ count.forEach( ( element ) => {
1002
+ exportdata.push( {
1003
+ 'Client': element.clientName,
1004
+ 'Balance Credits': element.credit,
1005
+ } );
1006
+ } );
1007
+ await download( exportdata, res );
1008
+ return;
1009
+ }
1010
+ if ( req.body.limit && req.body.offset && !req.body.export ) {
1011
+ query.push(
1012
+ { $skip: ( req.body.offset - 1 ) * req.body.limit },
1013
+ { $limit: Number( req.body.limit ) },
1014
+ );
1015
+ }
1016
+ let TransactionsList = await paymentAccountService.aggregate( query );
1017
+ const bucket = JSON.parse( process.env.BUCKET );
1018
+ for ( let client of TransactionsList ) {
1019
+ if ( client?.logo && client?.logo != '' ) {
1020
+ const isLogoExist = await checkFileExist( { Bucket: bucket.assets, Key: `${client.clientId}/logo/${client?.logo}` } );
1021
+ if ( isLogoExist ) {
1022
+ client.logo = await signedUrl( { Bucket: bucket.assets, file_path: `${client.clientId}/logo/${client?.logo}` } );
1023
+ } else {
1024
+ client.logo = '';
1025
+ }
1026
+ } else {
1027
+ client.logo = '';
1028
+ }
1029
+ }
1030
+
1031
+
1032
+ res.sendSuccess( { count: count.length, data: TransactionsList } );
1033
+ } catch ( error ) {
1034
+ logger.error( { error: error, function: 'creditTransactionlist' } );
1035
+ return res.sendError( error, 500 );
1036
+ }
1037
+ }
1038
+
1039
+ export async function pendingInvoices( req, res ) {
1040
+ try {
1041
+ let invoicelist = await invoiceService.find( { clientId: req.body.clientId, paymentStatus: 'unpaid' } );
1042
+ res.sendSuccess( invoicelist );
1043
+ } catch ( error ) {
1044
+ logger.error( { error: error, function: 'pendingInvoices' } );
1045
+ return res.sendError( error, 500 );
1046
+ }
1047
+ }
1048
+
1049
+ export async function applyDiscount( req, res ) {
1050
+ try {
1051
+ let invoice = await invoiceService.findOne( { invoice: req.body.invoice } );
1052
+ if ( invoice ) {
1053
+ invoice.discountAmount = ( ( invoice.amount * req.body.discount ) / 100 ).toFixed( 2 );
1054
+ invoice.amount = invoice.amount - invoice.discountAmount;
1055
+ invoice.discountPercentage = req.body.discount;
1056
+ if ( invoice.currency === 'inr' ) {
1057
+ if ( invoice.tax.length ) {
1058
+ for ( let i = 0; i < invoice.tax.length; i++ ) {
1059
+ invoice.tax[i].taxAmount = ( ( invoice.amount * invoice.tax[i].value ) / 100 ).toFixed( 2 );
1060
+ invoice.totalAmount = ( Number( invoice.amount ) + Number( invoice.tax[i].taxAmount ) ).toFixed( 2 );
1061
+ }
1062
+ }
1063
+ }
1064
+
1065
+
1066
+ await invoiceService.updateOne( { invoice: req.body.invoice }, invoice );
1067
+ }
1068
+ res.sendSuccess( 'updated Successfully' );
1069
+ } catch ( error ) {
1070
+ logger.error( { error: error, function: 'applyDiscount' } );
1071
+ return res.sendError( error, 500 );
1072
+ }
1073
+ }