Subversion Repositories SmartDukaan

Rev

Show changed files | Directory listing | RSS feed

Filtering Options

Rev Age Author Path Log message Diff
37596 4 m amit /trunk/profitmandi-fofo/src/main/java/com/spice/profitmandi/web/controller/ Mirror the gateway sanction when a hard limit is released

/resetHardLimit set hard_limit=false and reverted credit_limit to suggested_limit
but never touched dtr.credit_account, so the SIDBI/SDDIRECT gateway kept the old
pinned sanction until some later full limit pass rewrote it. Releasing a hard
limit can move the number a long way - RJAMR501 Inder Mobile is pinned at 800,000
against a suggested 909,114, so the gateway would under-report him by 109,114.

This was previously masked rather than absent: the 20-minute pass rewrote ~220
partners every run because float noise made almost every comparison look like a
change, which repaired the mirror by accident. Rounding that comparison to paise
(r37589) removed the accidental repair and moving the full pass to daily (r37595)
stretched the window to most of a day, so the gap is now worth closing properly.

Mirrors sanctioned/available in the same request, matching what
/creditRequirement already does, and stamps update_timestamp so the change is
visible as a real limit movement.
 
37595 23 m amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ Move the full partner-limit pass from every 20 minutes to daily

The 2-minute sweep already recalculates the limit for every partner whose base
moved, which covers investment and utilisation - the inputs that actually change.
The full 980-partner rescan only exists for inputs base cannot see: credit risk,
the SIDBI floor and hard_limit.

Those move on a far slower clock. RISK_DECREASE_DAYS_THRESHOLD is 90 days, and
transaction.fofo_sidbi_sanction has had no new row since Oct 2024 and no recorded
settlement. Running the rescan 72 times a day to catch them was 71 wasted passes.

Folded into the existing daily job, after the aged-stock refresh so the limit pass
sees haircuts for partners whose Apple or demo stock crossed its threshold
overnight. Leaves two scheduled jobs instead of three.

Limit latency is unaffected - that is the 2-minute sweep's job and it is unchanged
(~3 partners per window show a changed base, peak 18). The --updatePartnerLimitWithBatch
CLI entrypoint is untouched for manual runs.
 
37594 26 m amit /trunk/profitmandi-fofo/src/main/webapp/WEB-INF/views/ftl/ Format activated-stock amounts as currency on the dashboard banners

Both banners rendered the raw float - '112682.0' instead of a formatted amount -
because the value sat in a plain <strong> with no currency class, so
common.js formatCurrency() never picked it up. The sibling
dashboard-grn-pending-activated-imeis.vm already had the class, which is why one
banner formatted and the one above it did not.

No jsVersion bump needed: template-only change, no JS or CSS touched.
 
37593 28 m amit /trunk/profitmandi-fofo/src/main/ Show partners their aged Apple stock and why it lowers their credit limit

Apple handsets held past 30 days are deducted when the credit limit is
calculated, but nothing told the partner - they saw a limit drop with no
explanation. Adds a dashboard banner alongside the existing 'activated but not
billed' one, shown only when the partner actually has such stock (70 of 980
today), with the unit count, the value, and View Imeis for the detail.

- dashboard-aged-apple-stock.vm, parsed in dashboard1.vm under
#if($investments.aged_apple_qty > 0)
- count and value both read from the maintained partner_investment snapshot, so
the banner never runs its own query and always matches the amount the limit is
actually reduced by
- amount wrapped in <span class="currency"> so common.js formatCurrency() renders
it instead of a raw float
- /agedAppleImeis + aged-apple-imeis.vm list the units, excluding activated ones -
those are already deducted as activated stock and shown in their own banner, so
no handset appears in both
- click handler added to the existing activated-imeis.js rather than inline in the
fragment: an inline script in an AJAX-injected fragment re-registers
$(document).on on every load and stacks duplicate requests
- jsVersion 420 -> 421
 
37592 28 m amit /trunk/profitmandi-dao/src/main/ Fix live-demo deduction lost in the investment snapshot; maintain aged-Apple qty

BUG FIX to r37588. InventoryService.getTotalAmountInStock() returns stock net of
live-demo units held past 90 days, and both original investment paths assigned
that netted value to inStockAmount - which is why
PartnerDailyInvestment.getTotalInvestment() carries no separate demo term. The
sweep stored the RAW snapshot sum and never applied the deduction, overstating
investment for 328 partners by Rs 5.19 crore and their limits by roughly Rs 1.3
crore. Maxwell validated clean earlier only because he holds no demo stock.

Fixed by storing what is measured and deriving what is computed: the column is
now in_stock_gross_amount and getNetInStockAmount() applies the haircut. The demo
holding moves on its own daily clock, so storing the netted figure would have
meant re-netting it in three places every time the haircut changed - the same
'two things must move together' trap behind the original cache bug. Deriving it
means a daily demo refresh flows through to stock and base with nothing to sync.

Also:
- aged_apple_qty stored on the snapshot and maintained on the same daily cycle as
aged_apple_stock_amount, so the partner banner reads a saved count instead of
running a count query per dashboard load, and count and value can never disagree
- selectPartnerStockCountMap mirrors selectPartnerStockValueMap, including the
activated exclusion
- selectAgedAppleStock lists the units behind the haircut for the partner view
- FofoUser exposes aged_apple_stock / aged_apple_qty
- stock-moved detection compares GROSS: a demo unit crossing 90 days changes net
stock but nothing physically moved, and that is the daily refresh's job

Schema: partner_investment_snapshot.sql updated (in_stock_gross_amount,
aged_apple_qty). Applied to prod - the table was empty, so it was dropped and
recreated from the script.
 
37591 2 h 17 m amit /trunk/profitmandi-web/src/main/java/com/spice/profitmandi/web/v2/controller/fofo/ Route V2 admin wallet adjustment through WalletServiceImpl

Byte-identical duplicate of the fofo /walletUpdate handler, with the same missing
row lock. Points at the shared walletService.applyManualAdjustment so both
modules share one code path.
 
37590 2 h 17 m amit /trunk/profitmandi-fofo/src/main/java/com/spice/profitmandi/web/controller/ Route admin wallet adjustment through WalletServiceImpl

/walletUpdate hand-rolled the balance update and history row, bypassing the
service entirely, and read the wallet with selectByRetailerId instead of
selectByRetailerIdForUpdate - so unlike every other wallet path it took no row
lock, and a concurrent adjustment and purchase could lose an update.

Now calls walletService.applyManualAdjustment, which reuses getManualReference
for the reference allocation and routes the movement through
addAmountToWallet/consumeAmountFromWallet(forced). Behaviour is preserved: admin
debits stay forced, so they can still push a wallet negative as before. Removes
~45 duplicated lines.
 
37589 2 h 18 m amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ Drive credit limits off the investment sweep; stop phantom limit writes

Adds the 2-minute sweep schedule and points the limit recalculation at the
partners whose base_value actually moved, instead of rescanning all 980 every
20 minutes. Measured on production: 3 partners change per 2-minute window on
average, peak 18.

- ScheduledSkeleton: 2-min sweepPartnerInvestment, daily refreshAgedStockDaily
at 00:20; the 20-min full pass is kept as a backstop for inputs base does not
capture (risk, the SIDBI floor, hard_limit)
- BatchScheduledTasks.sweepPartnerInvestment: sweep, then limits for changed
bases only
- calculateChangedPartnerLimits(restrictTo) to support that
- scaleMoney(): round to paise before comparing and storing. The limit comes out
of a double multiply, so 65123.7400 round-tripped as 65123.740000000005 and
compareTo called it a change. 208 of 230 'changed' partners in one production
run differed by under half a paisa, rewriting sd_credit_requirement and
dtr.credit_account ~14,000 times a day for identical values and churning the
SIDBI mirror. Real changes were never below a rupee, so 2dp cannot suppress one.
- getFirstBillingDates: one grouped query instead of one per partner

Credit limits for 24 partners will rise on the first sweep after deploy - that is
the aged-Apple double-deduction correction landing (see r37588), not a defect.

NOTE: cron is a standalone Spring Boot jar; the new schedules only fire once it
is restarted with --spring.profiles.active=scheduled.
 
37588 2 h 18 m amit /trunk/profitmandi-dao/src/main/ Serve partner investment from a 2-minute snapshot instead of a 3-hour cache

getInvestment() was @Cacheable on a 3-hour Redis cache that addAmountToWallet
evicted but consumeAmountFromWallet did not. A partner whose advance payment was
swept straight to a loan had the money counted twice - once as a still-cached
wallet balance, once as the reduced utilisation - overstating their credit limit
by the payment x their tier until the cache expired.

Replaced with fofo.partner_investment, refreshed every 2 minutes by
PartnerInvestmentSweepService. Every coupled term is read in one pass, so wallet
and utilisation (and in-stock and aged-Apple) can never come from different
moments. A shorter TTL would only have made the error rarer - it scales with the
payment, not the delay.

- PartnerInvestment entity/repository + partner_investment_snapshot.sql
- PartnerInvestmentSweepService: 9 batched reads, stores base_value for change
detection, refreshes aged-Apple for partners whose stock moved intra-day
- getInvestment/getInvestmentsForFofoStores both read the snapshot, so the two
paths no longer disagree; live compute retained as fallback
- selectActivatedStockAmountByFofoIds: batches an N+1 that cost 233ms x 1690
partners (6.6 min -> 1.0 s)
- getFirstBillingDates: batches another N+1 (4.6 s -> 0.67 s)
- selectPendingGrnOrders(List) now applies the same SD_START_DATE floor as the
single-partner overload; the two were reporting different GRN-pending
- selectPartnerStockValueMap takes excludeActivated: an activated Apple handset
held past the aging window was added once to in-stock and subtracted twice
(activated stock, then the aged haircut). 90 units, 24 partners, Rs 65.19 lakh
double-deducted. Live-demo passes false - no overlap there.
- applyManualAdjustment: routes the two hand-rolled admin wallet adjustments
through WalletServiceImpl so they take the FOR UPDATE lock
- add_idx_order_grn_pending.sql: covering index, GRN query 1320ms -> 514ms
 
37587 3 h 56 m ranu /trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/dao/model/ ticket download option given and some enhancement on notification panel  
37586 3 h 59 m ranu /trunk/ drr view modification  
37585 1 d 2 h amit /trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/dao/repository/offers/ Fix web-offer sync: selectModelOffers join order (Unknown column 'o.id' in 'on clause')

selectModelOffers led its FROM with offers.offer_product p and then joined
offers.offer_raw_row r ON r.offer_id = o.id, referencing alias o one join
before o was introduced. An ON clause resolves only against tables already
in the join order, so MySQL rejected it with SQLSyntaxErrorException:
Unknown column 'o.id' in 'on clause'. Every per-model sync since r37581 has
therefore failed - webOfferSync=FAILED on the 15:04 and 17:14 re-ingests of
document 4 - leaving the 166 legacy NULL-key row-badges active and zero
consolidated badges published.

Reordered to lead with offers.offer o, matching the proven shape of the
sibling selectRowOffers query. All three are inner joins so the result set
is unchanged; BEST_OFFER_WINS and ORDER BY p.catalog_id still resolve.

Verified by executing the corrected statement read-only against prod for
document 4: 733 rows over 314 distinct catalog ids, the expected model
count. Not caught by tests because all 53 offercircular tests are
stub-based (no Spring, no DB) and never execute this SQL.

Failure was visible only because r37568 records webOfferSync= in
ingest_summary; the runner still swallows the exception by design.
 
37584 1 d 4 h ranu /trunk/profitmandi-fofo/src/main/webapp/WEB-INF/views/ftl/ ticket download option given and some enhancement on notification panel  
37583 1 d 4 h ranu /trunk/profitmandi-fofo/src/main/webapp/WEB-INF/views/ftl/ ticket download option given and some enhancement on notification panel  
37582 1 d 4 h ranu /trunk/profitmandi-fofo/src/main/webapp/WEB-INF/views/ftl/ ticket download option given and some enhancement on notification panel  
37581 1 d 4 h amit /trunk/profitmandi-dao/src/main/ web offer sync: retire the pre-consolidation badges instead of deleting them

The sync now deactivates any CIRCULAR row with a NULL circular_offer_key on its next
run. A row-keyed badge is a fragment of what a consolidated badge says, so leaving it
active would show a partner the same offer twice - once whole and once in pieces.

That removes the DELETE from the migration entirely, which is strictly better:

* nothing is destroyed, so a bad sync is one UPDATE away from being undone
* web_offer_product has NO foreign key to web_offer, so the DELETE had to clear its
532 rows separately or strand them as orphans
* no window where a circular has no badges at all - the old ones stay live until the
new ones exist

The migration is now purely additive: one ALTER, safe to run ahead of the deploy, which
is how it was applied to prod.
 
37580 1 d 4 h amit /trunk/ web offer: state an EMI scheme the circular gave without months

Rs.2,500 on Credit Card EMI (CIB) with IDFC First Bank, Kotak Mahindra Bank

52 Sep'26 offers have a bare 'CIB' tenure cell - the circular names the EMI type and
gives no months. TenureParser correctly yields no tenure row for a cell with no digits,
so the type vanished and the badge said only 'on Credit Card EMI'. Every one of those
52 carries a cashback, so it was 52 offers' worth of SKUs silent about the EMI they
apply to.

The type is stated because the circular stated it. The MONTHS are not invented,
because it did not give them - the same line the CIB expansion was left on.

Read from offer_raw_row.col_emi_tenure rather than stored: 'scheme, no month' is not a
tenure, and offer_tenure.tenure_months is NOT NULL, so persisting it would mean a fake
0 row standing for a fact that is not a tenure.

Only ever attached to an EMI mode, and real tenures always win over the bare code.

ModelOfferTest 8 -> 11.
 
37579 1 d 6 h amit /trunk/profitmandi-fofo/src/ web offer: one badge per model, consolidating everything the circular grants it

Edge 60 Pro 12+256 carried five badges - a Full Swipe cashback, an EMI cashback and
three EMI schemes - and the partner's card listed all five. It now carries one:

SEPTEMBER 2026 Motorola Cashback Offer-2,500 On Credit Card EMI
& 2,000 On Credit Card Full Swipe

Models whose consolidated terms are IDENTICAL share a badge, so 314 models collapse to
~107 rows while every model still appears in exactly one.

Rules, from the taxonomy: any model has cashback on Full Swipe and/or EMI, instant or
deferred, plus NCE/LCE/CIB with tenures, and any of it may differ per bank.
- 'Upto' only where a mode carries several values - which is what per-bank pricing
looks like. One value is stated plainly.
- Highest value first.
- Banks ride with each line only when they DIFFER; one shared set is hoisted to a
single trailing 'Banks:' line. A third of models need the per-line form.
- An amount whose window is narrower than the badge's states its own end date. Apple
1025780 pays Rs.6,000 to the 16th then Rs.4,000 to the 26th; without this the badge
would advertise Rs.6,000 for ten days it is not available.

⚠️ Banks and tenures belong to the OFFER a line came from, never to the model. On Edge
60 Pro the no-cost EMI is ICICI on 3 and 6 months but SBI on 3 only, and the EMI
cashback is IDFC/Kotak with no tenure at all. Attaching the model's tenures to its
cashback would promise no-cost EMI on a bank that never offered it. This also corrects
an earlier reading of mine that called the SBI 3-month line a duplicate of ICICI's -
it is a different bank, not noise.

New ModelOfferTest, 8 cases, every one taken from a real Sep'26 row.
 
37578 1 d 6 h amit /trunk/profitmandi-dao/src/main/ web offer sync: read what the circular grants each MODEL, not each row

selectModelOffers returns one row per (catalog_id, benefit), carrying the context that
qualifies it - the banks it applies to, its tenures and their scheme, the timing and
the window. Consolidation happens in the service; this is the read it needs.

LEFT JOIN on the benefit, because a scheme-only offer carries no cashback and dropping
it here would lose the NCE/LCE badge entirely.

Content-keyed writes alongside: selectSyncedByKey, insertKeyedWebOffer,
replaceProducts, deactivateMissingKeys. A badge is now identified by a sha256 of its
terms, so models with identical terms share one and a model appears in exactly one.

Migration add_web_offer_per_model.sql adds circular_offer_key + its unique key, and
drops the row-keyed CIRCULAR badges - one of those is a fragment of what a
content-keyed badge says and there is no mapping from five fragments to one whole, so
they are regenerated by the next ingest.

⚠️ web_offer_product has NO foreign key to web_offer, so its rows do not cascade and
are deleted explicitly or they are orphaned. web_offer_sync_shadow does cascade.
source='CIRCULAR' only - the 3,850 hand-authored rows are never touched.
 
37577 1 d 6 h amit /trunk/profitmandi-fofo/src/ web offer: an EMI cashback names its tenures WITH their scheme

Rs.2,500 on Credit Card EMI (NCE 3, 6; LCE 9, 12 months)

A bare '3, 6, 9, 12 months' says which tenures earn the cashback but not which of
them are no-cost - and at the counter the two facts are only useful together.

The trailing scheme sentence is dropped when an EMI benefit already carried it, since
it would state the same thing twice. It stays when the cashback is on Full Swipe only:
EMI is still available, it just is not what the cashback is on.

Tenures whose scheme the circular never gave keep their months but carry no label -
the months are real, the scheme would be invented.

Tests 16 -> 18.
 

Show All