tango-app-api-payment-subscription 3.5.28 → 3.5.30

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.
@@ -0,0 +1,108 @@
1
+ # Quarterly Invoice Creation Script — clientId 449 (Apr/May/Jun 2025)
2
+
3
+ **Date:** 2026-07-09
4
+ **File to create:** `scripts/create-quarterly-invoices-449.js`
5
+
6
+ ## Goal
7
+
8
+ Create **3 invoices** for clientId `449`, one per billing group, each covering the
9
+ **Apr–Jun 2025 quarter (FY 25-26)** as three per-month line-item blocks. This is a
10
+ one-off backfill of quarterly billing.
11
+
12
+ Target groups (all confirmed to exist on clientId 449, all INR, all GST prefix `33`):
13
+
14
+ | Group name | _id | GST | stores |
15
+ |-------------------------------|----------------------------|-----------------|--------|
16
+ | LILLE BY CHALLANI | `6a4f37925d8071b6ae34d1a2` | 33AALCC7452J1ZF | 6 |
17
+ | FEDHA JEWELS PRIVATE LIMITED | `6a4f37298b8646bec2a1f630` | 33AAFCF8793V1ZL | 7 |
18
+ | CHALLANI JEWELLERY MART | `6a4f32644cde0c2869be2e52` | 33AAIFC5600Q1ZC | 2 |
19
+
20
+ Client 449: `priceType: standard`, `status: active`.
21
+
22
+ ## Invoice series & shape
23
+
24
+ - **Series: `INV-`** (normal series), NOT TINV-. Number format `INV-25-26-NNNNN`,
25
+ continuing the highest `invoiceIndex` for `^INV-25-26-` (matches controller logic).
26
+ - `advanceInvoice: false`, `advancePeriod` omitted.
27
+ - **One invoice document per group** whose `products` array contains three month-blocks
28
+ (Apr 2025, May 2025, Jun 2025), each line tagged `month: 'MMM YYYY'`.
29
+ - `advanceMonths: 3` recorded for reference (bundled quarter), but the invoice is a normal
30
+ INV- invoice — the quarterly nature lives in the line items, not the advance flag.
31
+ - No `storeDetails` block (that is TINV-only in the controller).
32
+
33
+ ## Pricing — actual per-month snapshots, prorated
34
+
35
+ Client 449 is `priceType: standard`, so we replicate the **standard-price** path from
36
+ `createInvoice` → `standardPrice()`. For each group, for each of Apr/May/Jun 2025:
37
+
38
+ 1. **Snapshot selection:** pick the latest daily-pricing doc with
39
+ `dateISO <= end-of-month`, sorted `dateISO: -1`, `limit 1` — the actual snapshot for
40
+ that month (differs from the controller, which always takes the newest snapshot).
41
+ 2. **Days basis:** `daysInMonth` of that month (Apr=30, May=31, Jun=30).
42
+ 3. **Basepricing doc:** resolved via the same rule as `resolveBasePricingScope` —
43
+ group-wise doc when `client.billingGroupWisePricing` is on AND a group doc exists,
44
+ else the brand-level doc (`groupId` unset). Read `negotiatePrice` from `standard[]`.
45
+ 4. **overallStore products:** filter to `group.stores`, keep `workingdays > 0`, group by
46
+ (product, workingdays), sum store counts. Prorate:
47
+ `workingdays >= daysInMonth` → `price * count` (full month);
48
+ else `(price / daysInMonth) * workingdays * count`. `proRata: 'flat'` on the group
49
+ forces full-month working days.
50
+ 5. **billingType** perZone / perCamera for `tangoZone` / `tangoTraffic`: multiply by zone
51
+ / camera counts exactly as the controller does.
52
+ 6. **billingMethod `eachStore`** products: emit one line per store (per-store proration),
53
+ as in the controller.
54
+ 7. Tag every produced line with `month: 'MMM YYYY'` for that month.
55
+
56
+ The three months' line arrays are concatenated into the single `products` array.
57
+
58
+ **Empty-month handling:** if a month has no snapshot `<=` its end, or the group has no
59
+ running stores that month, that month contributes zero lines. The script logs this
60
+ **loudly** (WARN per group/month) so a short invoice is never produced silently.
61
+
62
+ ## Invoice document fields (mirrors controller `data`)
63
+
64
+ - `invoice: INV-25-26-NNNNN`, `invoiceIndex`
65
+ - `groupId`, `groupName`, `clientId: '449'`
66
+ - `companyName` = uppercased `registeredCompanyName`; `companyAddress` built from group
67
+ address fields (`addressLineOne+addressLineTwo+city,state,country -pinCode`), matching
68
+ the controller
69
+ - `GSTNumber` = group.gst, `PlaceOfSupply` = group.placeOfSupply
70
+ - `products` = concatenated month-blocks
71
+ - `amount` = Σ line amounts (roundAmount, inr); `tax` = CGST 9% + SGST 9% (GST prefix `33`);
72
+ `totalAmount` = roundAmount(amount + tax)
73
+ - `currency: 'inr'`, `stores` = running-store count from the latest snapshot within Jun 2025
74
+ - `billingDate` = today (creation date, per controller convention), `monthOfbilling: '04'`
75
+ - `dueDate` = billingDate + (group.paymentTerm || 30) days
76
+ - `status: 'pendingCsm'`, `paymentStatus: 'unpaid'`, `paymentMethod: 'Online'`
77
+ - `advanceInvoice: false`, `advanceMonths: 3`
78
+
79
+ ## Number allocation across the 3 invoices
80
+
81
+ Compute the starting index once (max `invoiceIndex` for `^INV-25-26-`), then increment
82
+ locally per group so the three invoices get consecutive numbers within one run. In
83
+ dry-run, the numbers shown are provisional (no write, so the counter is not advanced).
84
+
85
+ ## Safety & idempotency
86
+
87
+ - **Dry-run by default.** Prints, per group: invoice number, per-month per-product
88
+ breakdown (product, storeCount, price, working-days/period, amount), subtotal, CGST,
89
+ SGST, total, and any empty-month WARNs. Writes nothing.
90
+ - **`--apply`** performs the inserts via `invoiceModel.create`.
91
+ - **Duplicate guard:** before creating a group's invoice, check for an existing
92
+ `INV-`-series invoice for that `groupId` already carrying a `products.month = 'Apr 2025'`
93
+ line. If found, SKIP that group with a warning (prevents double-billing on re-run).
94
+ - **No side effects:** no PDF, no email, no OpenSearch activity log, no PO mapping.
95
+ - **Connection:** reuse `getConnection()` from `config/database/database.js` (or
96
+ `MONGO_URI` override), and the `tango-api-schema` models — same as existing scripts.
97
+
98
+ ## Flags
99
+
100
+ - `node scripts/create-quarterly-invoices-449.js` → DRY RUN
101
+ - `node scripts/create-quarterly-invoices-449.js --apply` → write
102
+ - `--clientId=449` (default 449), `--year=2025` (Q1 = Apr/May/Jun of this calendar year;
103
+ FY label derived as `YY-(YY+1)`)
104
+
105
+ ## Out of scope
106
+
107
+ TINV/advance series, multi-client support, PDF/email generation, non-standard (step)
108
+ pricing beyond what `standardPrice` covers for this client.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tango-app-api-payment-subscription",
3
- "version": "3.5.28",
3
+ "version": "3.5.30",
4
4
  "description": "paymentSubscription",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -29,7 +29,7 @@
29
29
  "nodemon": "^3.1.0",
30
30
  "puppeteer": "^24.41.0",
31
31
  "swagger-ui-express": "^5.0.0",
32
- "tango-api-schema": "^2.6.43",
32
+ "tango-api-schema": "^2.6.46",
33
33
  "tango-app-api-middleware": "^3.6.18",
34
34
  "winston": "^3.12.0",
35
35
  "winston-daily-rotate-file": "^5.0.0",
@@ -641,6 +641,13 @@ export async function brandInvoiceList( req, res ) {
641
641
  if ( inv.paymentStatus === 'paid' ) {
642
642
  continue;
643
643
  }
644
+ // Only invoices with a real (generated) invoice number count toward the
645
+ // Outstanding / Overdue / Pending Payment cards. Pre-finance drafts carry
646
+ // a temporary DRAFT- number and must be excluded until the real INV/TINV
647
+ // number is minted at finance approval.
648
+ if ( String( inv.invoice || '' ).startsWith( 'DRAFT-' ) ) {
649
+ continue;
650
+ }
644
651
  const excl = toInr( inv, inv.amount );
645
652
  const incl = toInr( inv, inv.totalAmount || inv.amount );
646
653
  cards.outstandingExclAmount += excl;
@@ -2042,6 +2049,14 @@ export async function billingSummary( req, res ) {
2042
2049
  { $match: {
2043
2050
  status: 'approved',
2044
2051
  paymentStatus: { $in: [ 'unpaid', 'partial', 'due' ] },
2052
+ // Outstanding aging counts only regular invoices. Advance invoices use
2053
+ // the separate TINV- series (quarterly/half-yearly/yearly) and must be
2054
+ // excluded so their balances don't inflate the aging buckets/outstanding
2055
+ // total. Matches the invoice-list 'all' filter idiom. A missing invoice
2056
+ // # is treated as a regular invoice.
2057
+ $expr: { $not: { $regexMatch: {
2058
+ input: { $ifNull: [ '$invoice', '' ] }, regex: '^TINV-',
2059
+ } } },
2045
2060
  } },
2046
2061
  { $addFields: {
2047
2062
  dueDateD: { $cond: [