tango-app-api-payment-subscription 3.5.33 → 3.5.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tango-app-api-payment-subscription",
3
- "version": "3.5.33",
3
+ "version": "3.5.34",
4
4
  "description": "paymentSubscription",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -1804,27 +1804,27 @@ export async function bulkUpdateBillingGroups( req, res ) {
1804
1804
  }
1805
1805
  }
1806
1806
 
1807
- // Sixth pass: cross-group conflict with OTHER existing groups in the DB.
1808
- // A store already assigned to a different billing group cannot be claimed
1809
- // by this upload.
1807
+ // Sixth pass: cross-group MOVE detection with OTHER existing groups in the DB.
1808
+ // A store already assigned to a different billing group is treated as a
1809
+ // MOVE: it will be pulled out of its old group(s) during the write pass and
1810
+ // assigned to the group that claims it in this upload. (Cross-group
1811
+ // uniqueness WITHIN the upload is already enforced by the fourth pass.)
1810
1812
  const idsInUpload = Array.from( groupedByName.values() ).map( ( e ) => e._id );
1813
+ // Map of old-group _id → [storeIds to pull]. Only groups NOT in this upload
1814
+ // need an explicit pull; groups in the upload get their stores[] fully
1815
+ // rewritten in the write pass, which naturally drops released stores.
1816
+ const storesToPullByGroupId = new Map();
1811
1817
  if ( allStoreIds.length > 0 ) {
1812
- const conflictDocs = await billingService.find(
1818
+ const otherGroupDocs = await billingService.find(
1813
1819
  { clientId, stores: { $in: allStoreIds }, _id: { $nin: idsInUpload } },
1814
1820
  { _id: 1, groupName: 1, stores: 1 },
1815
1821
  );
1816
- for ( const doc of conflictDocs ) {
1817
- const conflictIds = ( doc.stores || [] ).filter( ( sid ) => allStoreIds.includes( String( sid ) ) );
1818
- for ( const sid of conflictIds ) {
1819
- const claimingGroupName = storeIdToGroup.get( String( sid ) );
1820
- const claimingEntry = groupedByName.get( claimingGroupName );
1821
- if ( claimingEntry ) {
1822
- errors.push( {
1823
- rowNumber: claimingEntry.firstRowIndex + 2,
1824
- groupName: claimingEntry.groupName,
1825
- errors: [ `Store ID ${sid} is already assigned to billing group "${doc.groupName}"` ],
1826
- } );
1827
- }
1822
+ for ( const doc of otherGroupDocs ) {
1823
+ const movedIds = ( doc.stores || [] )
1824
+ .map( ( sid ) => String( sid ) )
1825
+ .filter( ( sid ) => storeIdToGroup.has( sid ) );
1826
+ if ( movedIds.length > 0 ) {
1827
+ storesToPullByGroupId.set( String( doc._id ), movedIds );
1828
1828
  }
1829
1829
  }
1830
1830
  }
@@ -1839,6 +1839,19 @@ export async function bulkUpdateBillingGroups( req, res ) {
1839
1839
 
1840
1840
  const openSearchActivityLog = JSON.parse( process.env.OPENSEARCH ).activityLog;
1841
1841
 
1842
+ // Move pass: pull claimed stores out of any OTHER existing DB group first,
1843
+ // so a moved store never ends up assigned to two groups. Groups included in
1844
+ // this upload have their stores[] fully rewritten below, so only the
1845
+ // not-in-upload old groups need an explicit $pull here.
1846
+ for ( const [ oldGroupId, storeIds ] of storesToPullByGroupId ) {
1847
+ try {
1848
+ await billingService.updateOne( { _id: oldGroupId }, { $pull: { stores: { $in: storeIds } } } );
1849
+ } catch ( pullErr ) {
1850
+ logger.error( { error: pullErr, function: 'bulkUpdateBillingGroups.pull', oldGroupId } );
1851
+ return res.sendError( { error: `Failed to release stores from a previous group: ${pullErr?.message || pullErr}` }, 500 );
1852
+ }
1853
+ }
1854
+
1842
1855
  // Seventh pass: serial DB writes — one per billing group.
1843
1856
  let updated = 0;
1844
1857
  let aborted = null;
@@ -143,10 +143,18 @@ export async function createInvoice( req, res ) {
143
143
  // the auto-generated advance invoices).
144
144
  const customAdvanceMonths = req.body.advanceInvoice ?
145
145
  ( { quarterly: 3, halfyearly: 6, yearly: 12 }[req.body.advancePeriod] || 1 ) : 1;
146
+ // Advance invoices bill the UPCOMING period, so the first month label is
147
+ // NEXT month (start of month), matching the auto-generate branch's
148
+ // baseDate. Without this the labels started on the CURRENT month
149
+ // (e.g. quarterly in Jul produced Jul/Aug/Sep instead of Aug/Sep/Oct).
150
+ // billingDate / monthOfbilling / dueDate below keep using baseDate
151
+ // (creation date), also matching the auto path.
152
+ const advanceStart = req.body.advanceInvoice ?
153
+ dayjs().add( 1, 'month' ).startOf( 'month' ) : baseDate;
146
154
  if ( customAdvanceMonths > 1 ) {
147
155
  const expanded = [];
148
156
  for ( let m = 0; m < customAdvanceMonths; m++ ) {
149
- const monthLabel = baseDate.add( m, 'month' ).format( 'MMM YYYY' );
157
+ const monthLabel = advanceStart.add( m, 'month' ).format( 'MMM YYYY' );
150
158
  for ( const p of customProducts ) {
151
159
  expanded.push( { ...p, month: monthLabel } );
152
160
  }