| Rev |
Age |
Author |
Path |
Log message |
Diff |
| 37600 |
39 m |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/ |
Replace entity loads with scalar aggregates in the investment sweep
Order.lineItem is a ManyToOne with FetchType.EAGER, so getInTransitOrders() and
selectPendingGrnOrders() pulled ~6,865 Order entities plus a LineItem for each
into the session every two minutes - all dirty-checked at flush - when the sweep
only reads getRetailerId() and getTotalAmount(). That, plus ~1,690 FofoStore and
~984 PartnerInvestment rows, put roughly 17,000 entities in one session per pass
and is most of the ~10s each sweep was taking.
Adds two scalar named queries returning (retailerId, sum(totalAmount)) grouped by
partner. A projection never hydrates the entity, so the eager association never
fires - about 13,700 entities become ~1,000 rows. The predicates copy
selectOrders(ids, pendingOrderStatus) and selectPendingGrnOrders(ids) exactly,
including the SD_START_DATE floor, and the comment on the named queries says to
change them together.
Deliberately NOT done by making lineItem lazy: that mapping is shared by every
Order consumer in the codebase and flipping it globally is a far wider change than
this needs.
Entity-returning variants are untouched for their existing callers. Drops the now
unused sumOrderAmounts helper and the TransactionService dependency.
Not a fix for a problem - the sweep was comfortably inside its 120s window at ~8%
duty. It removes waste that would matter if the cadence were ever shortened. |
|
| 37599 |
1 h 9 m |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/ |
Reuse the managed snapshot row in the sweep instead of building a second instance
Every sweep after the first failed with 'A different object with the same
identifier value was already associated with the session :
PartnerInvestment#107198' and wrote nothing.
selectByFofoIds returns managed entities. The loop then constructed a fresh
PartnerInvestment for the same id and called saveOrUpdate on it, which Hibernate
rejects. Sweep one survived only because the table was empty, so no row was
managed - the failure could not appear until a second run.
Now mutates the row already in the session when there is one, falling back to a
new instance only for partners with no snapshot. The previous base and gross stock
are captured before mutation, since comparing the row against itself afterwards
would report nothing as ever changed.
persist() is kept: a no-op for the managed rows (dirty checking flushes them), and
still the INSERT for partners appearing between sweeps.
Drops baseDiffersFrom(), now unused - the comparison is against the captured prior
value, still rounded to paise by the setter on both sides. |
|
| 37598 |
1 h 22 m |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/service/ |
Add missing @Transactional to the investment sweep
The sweep failed on every run with 'Could not obtain transaction-synchronized
Session for current thread' and wrote nothing. BatchScheduledTasks deliberately
carries no class-level @Transactional - each partner gets its own via REQUIRES_NEW
in the helper beans - so the sweep ran outside a transaction and
sessionFactory.getCurrentSession() threw at the first repository call.
PartnerLimitHelper already does this correctly with @Transactional(readOnly=true)
on its read phase; the sweep simply did not mirror it. Compilation cannot catch
this - only a run can.
sweep() and refreshAgedStockDaily() both read and write, so they take a read-write
transaction. Annotations sit on the service rather than the caller because
BatchScheduledTasks is intentionally non-transactional, and the service is a
separate bean so the proxy applies. |
|
| 37597 |
1 h 24 m |
amit |
/trunk/profitmandi-dao/src/main/resources/sql/ |
Register /agedAppleImeis for the partner role
RoleInterceptor authorises every non-admin request through
RoleManager.isAuthorizedURI(), which regex-matches the URI against dtr.api rows
reachable via dtr.role_api and throws GE_1004 when nothing matches. The new
aged-Apple IMEI endpoint (r37593) had no row, so partners would have been denied -
the Java change alone does not expose an endpoint.
Adds the api row and links it to FOFO, mirroring /activatedImeis (id 215) and
/activated-imeis-grn-pending (id 285). FOFO_ADMIN needs no row because isAdmin()
short-circuits before the lookup.
Applied to hadb1: api id 330, linked to role FOFO. Both inserts are guarded by
NOT EXISTS so the script is safe to re-run per environment. |
|
| 37596 |
1 h 36 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 |
1 h 54 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 |
1 h 57 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 |
1 h 59 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 |
2 h 0 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 |
3 h 49 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 |
3 h 49 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 |
3 h 49 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 |
3 h 49 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 |
5 h 28 m |
ranu |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/dao/model/ |
ticket download option given and some enhancement on notification panel |
|
| 37586 |
5 h 30 m |
ranu |
/trunk/ |
drr view modification |
|
| 37585 |
1 d 4 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 5 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 5 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 6 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 6 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. |
|