tango-app-api-payment-subscription 3.0.60-dev → 3.0.61-dev

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/index.js CHANGED
@@ -1,8 +1,12 @@
1
1
 
2
2
 
3
3
  import { paymentSubscriptionRouter } from './src/routes/paymentSubscription.routes.js';
4
+ import { invoiceRouter } from './src/routes/invoice.routes.js';
5
+ import { billingRouter } from './src/routes/billing.routes.js';
6
+
7
+
4
8
  import { paymentDocs } from './src/docs/payment.docs.js';
5
9
 
6
- export { paymentSubscriptionRouter, paymentDocs };
10
+ export { paymentSubscriptionRouter, paymentDocs, invoiceRouter, billingRouter };
7
11
 
8
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tango-app-api-payment-subscription",
3
- "version": "3.0.60-dev",
3
+ "version": "3.0.61-dev",
4
4
  "description": "paymentSubscription",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -25,8 +25,8 @@
25
25
  "mongodb": "^6.4.0",
26
26
  "nodemon": "^3.1.0",
27
27
  "swagger-ui-express": "^5.0.0",
28
- "tango-api-schema": "^2.0.115",
29
- "tango-app-api-middleware": "^3.1.19",
28
+ "tango-api-schema": "^2.0.131",
29
+ "tango-app-api-middleware": "^3.1.26",
30
30
  "winston": "^3.12.0",
31
31
  "winston-daily-rotate-file": "^5.0.0"
32
32
  },
@@ -0,0 +1,672 @@
1
+ import { download, logger } from 'tango-app-api-middleware';
2
+ import { aggregate } from '../services/store.service.js';
3
+ import * as invoice from '../services/invoice.service.js';
4
+ import { aggregatebilling, countDocuments, create, deleteOne, find, findOne, updateMany, updateOne } from '../services/billing.service.js';
5
+ import mongoose from 'mongoose';
6
+ import dayjs from 'dayjs';
7
+
8
+
9
+ export const subscribedStoreList = async ( req, res ) => {
10
+ try {
11
+ const matchStage = {
12
+ $match: {
13
+ clientId: req.body.clientId,
14
+ product: {
15
+ $ne: [],
16
+ },
17
+ },
18
+ };
19
+
20
+ if ( req.body.searchValue ) {
21
+ matchStage.$match.$or = [
22
+ {
23
+ storeName: {
24
+ $regex: req.body.searchValue,
25
+ $options: 'i',
26
+ },
27
+ },
28
+ {
29
+ storeId: {
30
+ $regex: req.body.searchValue,
31
+ $options: 'i',
32
+ },
33
+ },
34
+ ];
35
+ }
36
+
37
+ if ( req.body.country || req.body.state || req.body.city ) {
38
+ matchStage.$match.$and = [];
39
+
40
+ if ( req.body.country ) {
41
+ matchStage.$match.$and.push( {
42
+ 'storeProfile.country': { $in: req.body.country },
43
+ } );
44
+ }
45
+
46
+ if ( req.body.state ) {
47
+ matchStage.$match.$and.push( {
48
+ 'storeProfile.state': { $in: req.body.state },
49
+ } );
50
+ }
51
+
52
+ if ( req.body.city ) {
53
+ matchStage.$match.$and.push( {
54
+ 'storeProfile.city': { $in: req.body.city },
55
+ } );
56
+ }
57
+
58
+ if ( matchStage.$match.$and.length === 0 ) {
59
+ delete matchStage.$match.$and;
60
+ }
61
+ }
62
+
63
+
64
+ const pipeline = [
65
+ matchStage,
66
+ {
67
+ $project: {
68
+ 'storeName': 1,
69
+ 'storeId': 1,
70
+ 'product': 1,
71
+ 'storeProfile.country': 1,
72
+ 'storeProfile.state': 1,
73
+ 'storeProfile.city': 1,
74
+ },
75
+ },
76
+ ];
77
+
78
+ if ( req.body?.sortColumn && req.body?.sortBy ) {
79
+ pipeline.push(
80
+ {
81
+ $addFields: {
82
+ sortField: {
83
+ $toLower: `$${req.body.sortColumn}`,
84
+ },
85
+ },
86
+ },
87
+ {
88
+ $sort: {
89
+ [req.body.sortColumn]: req.body.sortBy,
90
+ },
91
+ },
92
+ );
93
+ }
94
+
95
+ pipeline.push(
96
+ {
97
+ $project: {
98
+ sortField: 0,
99
+ },
100
+ },
101
+
102
+ );
103
+
104
+ const facetStage = {
105
+ $facet: {
106
+ data: [
107
+ {
108
+ $skip: ( ( req.body.offset - 1 ) * req.body.limit ),
109
+ },
110
+ {
111
+ $limit: ( req.body.limit ),
112
+ },
113
+ {
114
+ $project: {
115
+ storeProfile: 0,
116
+ _id: 0,
117
+ },
118
+ },
119
+ ],
120
+ pageInfo: [
121
+ {
122
+ $count: 'count',
123
+ },
124
+ ],
125
+ },
126
+ };
127
+
128
+ if ( req.body.getFilters ) {
129
+ facetStage.$facet['countries'] = [
130
+ {
131
+ $match: {
132
+ $and: [
133
+ {
134
+ 'storeProfile.country': {
135
+ $exists: true,
136
+ },
137
+ },
138
+ {
139
+ 'storeProfile.country': {
140
+ $ne: null,
141
+ },
142
+ },
143
+ {
144
+ 'storeProfile.country': {
145
+ $ne: '',
146
+ },
147
+ },
148
+ ],
149
+ },
150
+ },
151
+ {
152
+ $group: {
153
+ _id: '$storeProfile.country',
154
+ },
155
+ },
156
+ ],
157
+ facetStage.$facet['states'] = [
158
+ {
159
+ $match: {
160
+ $and: [
161
+ {
162
+ 'storeProfile.state': {
163
+ $exists: true,
164
+ },
165
+ },
166
+ {
167
+ 'storeProfile.state': {
168
+ $ne: null,
169
+ },
170
+ },
171
+ {
172
+ 'storeProfile.state': {
173
+ $ne: '',
174
+ },
175
+ },
176
+ ],
177
+ },
178
+ },
179
+ {
180
+ $group: {
181
+ _id: '$storeProfile.state',
182
+ },
183
+ },
184
+ ],
185
+ facetStage.$facet['cities'] = [
186
+ {
187
+ $match: {
188
+ $and: [
189
+ {
190
+ 'storeProfile.city': {
191
+ $exists: true,
192
+ },
193
+ },
194
+ {
195
+ 'storeProfile.city': {
196
+ $ne: null,
197
+ },
198
+ },
199
+ {
200
+ 'storeProfile.city': {
201
+ $ne: '',
202
+ },
203
+ },
204
+ ],
205
+ },
206
+ },
207
+ {
208
+ $group: {
209
+ _id: '$storeProfile.city',
210
+ },
211
+ },
212
+ ];
213
+ }
214
+
215
+ pipeline.push( facetStage );
216
+
217
+ pipeline.push( {
218
+ $unwind: {
219
+ path: '$pageInfo',
220
+ },
221
+ } );
222
+
223
+ const storeList = await aggregate( pipeline );
224
+
225
+ if ( !storeList[0] ) {
226
+ return res.sendError( 'No data found', 204 );
227
+ }
228
+
229
+ return res.sendSuccess( storeList[0] );
230
+ } catch ( error ) {
231
+ logger.error( { error: error, function: 'subscribedStoreList' } );
232
+ return res.sendError( error, 500 );
233
+ }
234
+ };
235
+
236
+ export const getAllBillingGroups = async ( req, res ) => {
237
+ try {
238
+ const billingGroups = await find( { clientId: req.query.clientId } );
239
+
240
+ return res.sendSuccess( billingGroups );
241
+ } catch ( error ) {
242
+ logger.error( { error: error, function: 'getBillingGroups' } );
243
+ return res.sendError( error, 500 );
244
+ }
245
+ };
246
+
247
+
248
+ export const createBillingGroup = async ( req, res ) => {
249
+ try {
250
+ if ( req.body?.stores?.length ) {
251
+ await updateMany( { clientId: req.body.clientId }, { $pull: { stores: { $in: req.body.stores } } } );
252
+ }
253
+
254
+ const createGroup = await create( req.body );
255
+
256
+ return res.sendSuccess( createGroup );
257
+ } catch ( error ) {
258
+ logger.error( { error: error, function: 'createBillingGroup' } );
259
+ return res.sendError( error, 500 );
260
+ }
261
+ };
262
+
263
+ export const updateBillingGroup = async ( req, res ) => {
264
+ try {
265
+ const previousStores = await findOne( { _id: new mongoose.Types.ObjectId( req.body._id ) }, { stores: 1, isPrimary: 1 } );
266
+
267
+ if ( req.body?.stores && !req.body?.isPrimary ) {
268
+ const removedStores = previousStores?.stores?.filter( ( val ) => !req.body?.stores.includes( val ) );
269
+
270
+ if ( removedStores.length ) {
271
+ await updateOne( { _id: new mongoose.Types.ObjectId( req.body._id ) }, { $pull: { stores: { $in: removedStores } } } );
272
+ await updateOne( { clientId: req.body.clientId, isPrimary: true }, { $push: { stores: { $each: removedStores } } } );
273
+ }
274
+
275
+ const addedStores = req.body?.stores?.filter( ( val ) => !previousStores?.stores.includes( val ) );
276
+
277
+ if ( addedStores.length ) {
278
+ await updateMany( { clientId: req.body.clientId, _id: { $ne: new mongoose.Types.ObjectId( req.body._id ) } }, { $pull: { stores: { $in: req.body.stores } } } );
279
+ await updateOne( { _id: new mongoose.Types.ObjectId( req.body._id ) }, { $push: { stores: { $each: addedStores } } } );
280
+ }
281
+ }
282
+
283
+ delete req.body?.stores;
284
+
285
+ const update = await updateOne( { _id: new mongoose.Types.ObjectId( req.body._id ) }, req.body );
286
+
287
+ return res.sendSuccess( update );
288
+ } catch ( error ) {
289
+ logger.error( { error: error, function: 'updateBillingGroup' } );
290
+ return res.sendError( error, 500 );
291
+ }
292
+ };
293
+
294
+ export const deleteBillingGroup = async ( req, res ) => {
295
+ try {
296
+ const previousGroup = await findOne( { _id: new mongoose.Types.ObjectId( req.query._id ) }, { stores: 1, clientId: 1 } );
297
+
298
+ if ( previousGroup?.stores?.length ) {
299
+ await updateOne( { clientId: previousGroup.clientId, isPrimary: true }, { $push: { stores: { $each: previousGroup?.stores } } } );
300
+ }
301
+
302
+ const deletedGroup = await deleteOne( { _id: new mongoose.Types.ObjectId( req.query._id ), isPrimary: false } );
303
+
304
+ return res.sendSuccess( deletedGroup );
305
+ } catch ( error ) {
306
+ logger.error( { error: error, function: 'deleteBillingGroup' } );
307
+ return res.sendError( error, 500 );
308
+ }
309
+ };
310
+
311
+ export const getBillingGroups = async ( req, res ) => {
312
+ try {
313
+ const allGroups = await countDocuments( { clientId: req.body.clientId } );
314
+
315
+ const matchStage = {
316
+ $match: {
317
+ clientId: req.body.clientId,
318
+ },
319
+ };
320
+
321
+ if ( req.body.searchValue ) {
322
+ matchStage.$match.$or = [
323
+ {
324
+ groupName: {
325
+ $regex: req.body.searchValue,
326
+ $options: 'i',
327
+ },
328
+ },
329
+ ];
330
+ }
331
+
332
+
333
+ const pipeline = [
334
+ matchStage,
335
+ {
336
+ $project: {
337
+ _id: 1,
338
+ groupName: 1,
339
+ groupTag: 1,
340
+ registeredCompanyName: 1,
341
+ gst: 1,
342
+ addressLineOne: 1,
343
+ addressLineTwo: 1,
344
+ city: 1,
345
+ state: 1,
346
+ country: 1,
347
+ pinCode: 1,
348
+ placeOfSupply: 1,
349
+ po: 1,
350
+ stores: { $size: '$stores' },
351
+ proRata: 1,
352
+ paymentCategory: 1,
353
+ currency: 1,
354
+ isInstallationOneTime: 1,
355
+ installationFee: 1,
356
+ paymentCycle: 1,
357
+ paymentTerm: 1,
358
+ generateInvoiceTo: 1,
359
+ attachAnnexure: 1,
360
+ isPrimary: 1,
361
+ },
362
+ },
363
+ ];
364
+
365
+ if ( req.body?.sortColumn && req.body?.sortBy ) {
366
+ pipeline.push(
367
+ {
368
+ $addFields: {
369
+ sortField: {
370
+ $toLower: `$${req.body.sortColumn}`,
371
+ },
372
+ },
373
+ },
374
+ {
375
+ $sort: {
376
+ [req.body.sortColumn]: req.body.sortBy,
377
+ },
378
+ },
379
+ );
380
+ }
381
+
382
+ pipeline.push(
383
+ {
384
+ $project: {
385
+ sortField: 0,
386
+ },
387
+ },
388
+
389
+ );
390
+
391
+ const facetStage = {
392
+ $facet: {
393
+ data: [
394
+ {
395
+ $skip: ( ( req.body.offset - 1 ) * req.body.limit ),
396
+ },
397
+ {
398
+ $limit: ( req.body.limit ),
399
+ },
400
+ ],
401
+ pageInfo: [
402
+ {
403
+ $count: 'count',
404
+ },
405
+ ],
406
+ },
407
+ };
408
+
409
+ if ( req.body?.isExport ) {
410
+ facetStage.$facet.data = [];
411
+ }
412
+
413
+ pipeline.push( facetStage );
414
+
415
+ pipeline.push( {
416
+ $unwind: {
417
+ path: '$pageInfo',
418
+ },
419
+ } );
420
+
421
+ const groupList = await aggregatebilling( pipeline );
422
+
423
+ if ( !groupList[0] ) {
424
+ return res.sendError( 'No data found', 204 );
425
+ }
426
+
427
+ if ( req.body.isExport ) {
428
+ const exportResult = [];
429
+ for ( let group of groupList[0].data ) {
430
+ exportResult.push( {
431
+ 'Group name': group.groupName||'',
432
+ 'Stores': group.stores||'',
433
+ 'GST number': group.gst||'',
434
+ 'Place of supply': group.placeOfSupply || '',
435
+ 'Tag': group.groupTag||'',
436
+ } );
437
+ }
438
+ await download( exportResult, res );
439
+ return;
440
+ }
441
+ groupList[0].pageInfo['total'] = allGroups;
442
+
443
+ return res.sendSuccess( groupList[0] );
444
+ } catch ( error ) {
445
+ logger.error( { error: error, function: 'subscribedStoreList' } );
446
+ return res.sendError( error, 500 );
447
+ }
448
+ };
449
+
450
+
451
+ export const getInvoices = async ( req, res ) => {
452
+ try {
453
+ const allInvoices = await invoice.count( { clientId: req.body.clientId } );
454
+
455
+ const matchStage = {
456
+ $match: {
457
+ clientId: req.body.clientId,
458
+ },
459
+ };
460
+
461
+ let filterStartDate = '';
462
+ let filterEndDate = '';
463
+
464
+ if ( req.body?.filter && req.body.filter == 'current' ) {
465
+ filterStartDate = new Date( dayjs().startOf( 'month' ).format( 'YYYY-MM-DD' ) );
466
+ filterEndDate = new Date( dayjs().endOf( 'month' ).format( 'YYYY-MM-DD' ) );
467
+ }
468
+ if ( req.body?.filter && req.body.filter == 'prev' ) {
469
+ filterStartDate = new Date( dayjs().subtract( 1, 'month' ).startOf( 'month' ).format( 'YYYY-MM-DD' ) );
470
+ filterEndDate = new Date( dayjs().subtract( 1, 'month' ).endOf( 'month' ).format( 'YYYY-MM-DD' ) );
471
+ }
472
+ if ( req.body?.filter && req.body.filter == 'last' ) {
473
+ filterStartDate = new Date( dayjs().subtract( 2, 'month' ).startOf( 'month' ).format( 'YYYY-MM-DD' ) );
474
+ filterEndDate = new Date( dayjs().endOf( 'month' ).format( 'YYYY-MM-DD' ) );
475
+ }
476
+
477
+ if ( req.body?.filter && !req.body?.searchValue ) {
478
+ matchStage.$match['$and'] = [
479
+ { billingDate: { $gte: filterStartDate } },
480
+ { billingDate: { $lte: filterEndDate } },
481
+ ];
482
+ }
483
+
484
+ if ( req.body.searchValue ) {
485
+ matchStage.$match.$or = [
486
+ {
487
+ invoice: {
488
+ $regex: req.body.searchValue,
489
+ $options: 'i',
490
+ },
491
+ },
492
+ ];
493
+ }
494
+
495
+
496
+ const pipeline = [
497
+ matchStage,
498
+ {
499
+ $project: {
500
+ '_id': 1,
501
+ 'invoice': 1,
502
+ 'billingDate': 1,
503
+ 'totalAmount': 1,
504
+ 'stores': 1,
505
+ 'paymentStatus': 1,
506
+ 'paymentTerm': 1,
507
+ 'currency': 1,
508
+ 'products.productName': 1,
509
+ },
510
+ },
511
+ ];
512
+
513
+ if ( req.body?.sortColumn && req.body?.sortBy ) {
514
+ pipeline.push(
515
+ {
516
+ $addFields: {
517
+ sortField: {
518
+ $toLower: `$${req.body.sortColumn}`,
519
+ },
520
+ },
521
+ },
522
+ {
523
+ $sort: {
524
+ [req.body.sortColumn]: req.body.sortBy,
525
+ },
526
+ },
527
+ );
528
+ }
529
+
530
+ pipeline.push(
531
+ {
532
+ $project: {
533
+ sortField: 0,
534
+ },
535
+ },
536
+
537
+ );
538
+
539
+ const facetStage = {
540
+ $facet: {
541
+ data: [
542
+ {
543
+ $skip: ( ( req.body.offset - 1 ) * req.body.limit ),
544
+ },
545
+ {
546
+ $limit: ( req.body.limit ),
547
+ },
548
+ ],
549
+ pageInfo: [
550
+ {
551
+ $count: 'count',
552
+ },
553
+ ],
554
+ },
555
+ };
556
+
557
+ if ( req.body?.isExport ) {
558
+ facetStage.$facet.data = [];
559
+ }
560
+
561
+ pipeline.push( facetStage );
562
+
563
+ pipeline.push( {
564
+ $unwind: {
565
+ path: '$pageInfo',
566
+ },
567
+ } );
568
+
569
+ const invoiceList = await invoice.aggregate( pipeline );
570
+
571
+
572
+ if ( !invoiceList[0] ) {
573
+ return res.sendError( 'No data found', 204 );
574
+ }
575
+
576
+ if ( req.body.isExport ) {
577
+ const exportResult = [];
578
+ for ( let invoice of invoiceList[0].data ) {
579
+ exportResult.push( {
580
+ 'Invoice': invoice.invoice||'',
581
+ 'Billing date': dayjs( invoice.billingDate ) ||'',
582
+ 'Total amount': invoice.totalAmount||'',
583
+ 'Store count': invoice.stores || '',
584
+ 'Status': invoice.paymentStatus||'',
585
+ 'Products': invoice.products.length ? invoice.products.map( ( val ) => val.productName ).join( ', ' ) : '',
586
+ } );
587
+ }
588
+ await download( exportResult, res );
589
+ return;
590
+ }
591
+
592
+ function transformData( data ) {
593
+ const result = [];
594
+ const groups = {};
595
+
596
+ data.forEach( ( invoiceData ) => {
597
+ if ( invoiceData.paymentStatus === 'unpaid' ) {
598
+ const currentDate = dayjs();
599
+ const givenDate = dayjs( invoiceData.billingDate );
600
+ const daysDifference = currentDate.diff( givenDate, 'day' );
601
+ if ( daysDifference <= invoiceData.paymentTerm && daysDifference >= 0 ) {
602
+ invoiceData.paymentStatus = 'due';
603
+ } else {
604
+ invoiceData.paymentStatus = 'unpaid';
605
+ }
606
+ }
607
+ invoiceData.products = invoiceData.products.map( ( product ) => product.productName );
608
+ const tempProduct = new Set();
609
+ invoiceData.products.forEach( ( product ) => {
610
+ tempProduct.add( product );
611
+ } );
612
+ invoiceData.products = Array.from( tempProduct );
613
+
614
+ const billingDate = invoiceData.billingDate;
615
+
616
+ if ( !groups[billingDate] ) {
617
+ groups[billingDate] = {
618
+ summary: `Summary - ${dayjs( billingDate ).format( 'MMM YYYY' )}`,
619
+ products: new Set(),
620
+ stores: 0,
621
+ totalAmount: 0,
622
+ billingDate: billingDate,
623
+ invoices: [],
624
+ paymentStatus: invoiceData.paymentStatus,
625
+ currency: invoiceData.currency,
626
+ };
627
+ }
628
+
629
+ groups[billingDate].invoices.push( invoiceData );
630
+ groups[billingDate].stores += invoiceData.stores;
631
+ groups[billingDate].totalAmount += invoiceData.totalAmount;
632
+
633
+ if ( invoiceData.paymentStatus === 'unpaid' ) {
634
+ const currentDate = dayjs();
635
+ const givenDate = dayjs( invoiceData.billingDate );
636
+ const daysDifference = currentDate.diff( givenDate, 'day' );
637
+ if ( daysDifference <= invoiceData.paymentTerm && daysDifference >= 0 ) {
638
+ groups[billingDate].paymentStatus = 'due';
639
+ } else {
640
+ groups[billingDate].paymentStatus = 'unpaid';
641
+ }
642
+ }
643
+
644
+ invoiceData.products.forEach( ( product ) => {
645
+ groups[billingDate].products.add( product );
646
+ } );
647
+ } );
648
+
649
+
650
+ // eslint-disable-next-line guard-for-in
651
+ for ( const billingDate in groups ) {
652
+ const group = groups[billingDate];
653
+ group.products = Array.from( group.products );
654
+ result.push( group );
655
+ }
656
+
657
+ return result;
658
+ }
659
+
660
+ invoiceList[0].data = transformData( invoiceList[0].data );
661
+
662
+
663
+ invoiceList[0].pageInfo['total'] = allInvoices;
664
+
665
+ return res.sendSuccess( invoiceList[0] );
666
+ } catch ( error ) {
667
+ logger.error( { error: error, function: 'subscribedStoreList' } );
668
+ return res.sendError( error, 500 );
669
+ }
670
+ };
671
+
672
+