| Rev |
Age |
Author |
Path |
Log message |
Diff |
| 37584 |
20 m |
ranu |
/trunk/profitmandi-fofo/src/main/webapp/WEB-INF/views/ftl/ |
ticket download option given and some enhancement on notification panel |
|
| 37583 |
23 m |
ranu |
/trunk/profitmandi-fofo/src/main/webapp/WEB-INF/views/ftl/ |
ticket download option given and some enhancement on notification panel |
|
| 37582 |
30 m |
ranu |
/trunk/profitmandi-fofo/src/main/webapp/WEB-INF/views/ftl/ |
ticket download option given and some enhancement on notification panel |
|
| 37581 |
45 m |
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 |
49 m |
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 h 57 m |
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 h 57 m |
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 |
2 h 26 m |
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. |
|
| 37576 |
2 h 41 m |
ranu |
/trunk/profitmandi-fofo/src/main/webapp/WEB-INF/views/ftl/ |
ticket download option given and some enhancement on notification panel |
|
| 37575 |
2 h 53 m |
ranu |
/trunk/profitmandi-fofo/src/main/webapp/WEB-INF/views/ftl/ |
ticket download option given and some enhancement on notification panel |
|
| 37574 |
2 h 58 m |
amit |
/trunk/profitmandi-fofo/src/ |
web offer: print the EMI scheme code, not a guessed expansion
NCE / LCE / CIB are industry standard and now appear verbatim:
Instant cashback. Rs.3,000 on Credit Card EMI (3, 6 months);
Rs.3,000 on Credit Card Full Swipe. LCE on 3, 6 months. Banks: ...
The expansions were not ours to invent. emi_scheme.display_name carries 'Cashback
Instant Benefit' for CIB, and the evidence contradicts it: the Sep'26 cell
'3 NC 6,9,12 CIB' puts CIB in ONE ladder with NCE, so it describes who bears the
interest on a tenure rather than how a cashback is delivered - and 53 of 55 CIB rows
carry a separate cashback amount anyway, which that expansion would make redundant.
Pine Labs' own docs define ICB as Instant Cashback on UPI; the circular writes CIB 60
times and ICB never.
A guessed expansion on a partner badge risks stating the opposite of the truth - for
CIB, advertising a benefit where the customer may in fact be bearing the interest.
The code cannot be wrong that way.
A scheme is still stated ONLY when the circular gave one; tenures with no scheme are
described as nothing at all.
Also fixes the singular: 'NCE on 3 month', not '3 months'. |
|
| 37573 |
2 h 59 m |
ranu |
/trunk/profitmandi-fofo/src/main/webapp/WEB-INF/views/ftl/ |
ui look and feel color changing mode for drr |
|
| 37572 |
3 h 9 m |
amit |
/trunk/profitmandi-fofo/src/ |
web offer: state the whole offer in the title, and the EMI scheme in the detail
Title now states EVERY payment mode, highest first, in the house style of the
hand-written rows:
SEPTEMBER 2026 Samsung Cashback Offer-3000 On Credit Card Full Swipe
& 3000 On Credit Card EMI & 3000 On UPI
A partner scanning the card should see the whole offer, not just its best number.
Brand keeps its catalog casing (Samsung, not SAMSUNG). A scheme-only offer is still
titled by its scheme - calling it a cashback offer is what produced the live
'Cashback Offer- NA' badges.
Detail additions:
- the EMI scheme, when the circular gave one: '... Low Cost EMI on 3, 6 months.'
The circular states it in its own column ('3m,6m LCE' beside '3000 CC Full
Swipe/CC EMI/UPI') and a partner needs both halves.
- eligible tenures on an EMI benefit: 'Rs.3,000 on Credit Card EMI (3, 6 months)'.
Cashback 'on Credit Card EMI' is not actionable without knowing which tenures
qualify. Full Swipe and UPI carry no tenure, so they get none.
⚠️ A scheme is stated ONLY when the circular gave one. Tenures with no scheme are
described as nothing at all - naming them would read as a no-cost or low-cost claim
that was never made.
Tests 14 -> 16. |
|
| 37571 |
3 h 10 m |
amit |
/trunk/profitmandi-fofo/src/ |
offer circular: the month unit was swallowing the EMI scheme marker
'M' is the month UNIT, not a separator, but the token loop flushed on it. '3m,6m LCE'
tokenises to 3, M, 6, M, LCE - each M banked its month as NONE, so the LCE arrived
with nothing pending and was silently lost.
74 Sep'26 offers carry a scheme in the tenure cell that never reached offer_tenure;
22 of them through this path ('3m,6m LCE', '6m,9m LCE', '3m & 6m LCE', '3m, 6m, 9m,
12m, 18m, 24m LCE'). The rest are bare 'CIB' cells with no months at all, which is a
separate case and left alone.
It also fixes '3M (NCE), 6M & Above LCE', where the M flushed BOTH groups before
either marker was seen - so an offer that is no-cost on 3 months and low-cost beyond
recorded neither.
This is money: no-cost EMI costs the customer nothing and low-cost does not, so a lost
marker is a term a partner could quote wrongly at the counter.
Months with no marker anywhere still bank as NONE via the end-of-cell flush - the same
answer the M branch used to give for a genuinely unmarked cell.
New TenureParserTest, 6 cases, covering the regression and the shapes that must not
change. |
|
| 37570 |
3 h 13 m |
ranu |
/trunk/profitmandi-fofo/src/main/webapp/WEB-INF/views/ftl/ |
drr view color change |
|
| 37569 |
3 h 38 m |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
fcm: stop holding a row lock on the whole batch across every FCM call
sendNotification lived in ScheduledTasks, which carries a class-level @Transactional,
so one pass was one transaction: it loaded EVERY pending row uncapped, then per row
made an FCM call over the network and stamped sentTimestamp on the entity. A dirty
row stays write-locked until commit, so a run held a lock on every notification in
the batch for the SUM of all the remote waits. Volume is real -- 11,581 sent on
2026-09-09, 12,036 on 2026-09-05 -- and HttpClientFactory sets a 10s socket timeout,
so one bad run could hold thousands of locks for minutes.
That is the shape behind a 33 SECOND average InnoDB row-lock wait server-wide
(Innodb_row_lock_time 63,291s over 15.4 days uptime, max 51,941ms against a 50s
innodb_lock_wait_timeout -- which is where LockAcquisitionException comes from).
Moved to PushNotificationSendService using the same seam the invoicing pass already
uses in InvoiceService.updateIrnsToInvoices: one short read transaction to pick up
the batch and build the payloads, NO transaction across the network call, and one
REQUIRES_NEW transaction per row to record the outcome. No lock is held while waiting
on FCM.
It is a separate bean, and reaches its own transactional methods through an injected
self-reference, because REQUIRES_NEW is applied by a Spring proxy and a plain
self-invocation would bypass it -- same idiom, for the same reason, as InvoiceService.
Also caps the batch at 500/run (30,000/hour against a 12,000 peak day) using the
already-present but never-wired selectPendingNotifications(limit); the previous query
was uncapped, which is what let a campaign burst become one multi-minute transaction.
Send outcomes are unchanged: 200 stamps now, anything else stamps the 1970 sentinel.
The HttpClient is now closed, which it previously was not. |
|
| 37568 |
3 h 39 m |
amit |
/trunk/profitmandi-fofo/src/main/java/com/spice/profitmandi/web/offercircular/ |
offer circular: a failed web-offer sync must not be silent
Swallowing the exception was right - a circular that parsed correctly must stay
PUBLISHED, and the sync is re-runnable while the parse is the expensive part.
Swallowing it SILENTLY was not.
A TINYINT(1) cast threw in the sync on every ingest from 2026-09-08 to r37553 and
nothing looked wrong: processed_at advanced, the circular published, the email said
success. The only symptom was web_offer.synced_at standing still, which nobody was
watching. It went unnoticed for two days.
Now:
- Summary carries webOfferSync, and states it in EVERY case - the counts on success,
'FAILED - <cause>' on failure, 'NOT RUN' if it never executed. A missing line would
be indistinguishable from a sync that never ran, so there is no missing line.
- That lands in offer_document.ingest_summary, so the review screen shows it.
- The notification email carries it too, and a failure renders as a red
'Web offers NOT updated' block saying partners are still seeing the previous
offers and that the parse itself is fine.
- The sync now runs BEFORE published(), so its outcome is part of what gets recorded
rather than an afterthought. It is still REQUIRES_NEW with its exception caught, so
it still cannot fail the ingest. |
|
| 37567 |
3 h 46 m |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ |
Schedule the catalogue delist daily at 05:30
Runs 30 minutes before the existing 06:00 scheduledPushDataToSolr in the same
class, so the same morning's reindex publishes it. A bulk UPDATE fires no
TagListingChangeListener event, so without that ordering the portal would lag
until 18:00.
Gated on catalog.autoDelist.enabled with an inline default of true - env profiles
have no fallback between each other, so a key missing from one profile's
properties would otherwise stop the context booting.
Also exposes --delistDeadListings for a manual run, honouring the same flag. |
|
| 37566 |
3 h 46 m |
amit |
/trunk/profitmandi-dao/src/main/ |
Delist logic v3: require a month of no movement; add CatalogDelistService
Zero stock alone was too weak a test. It fired on SKUs partners were still
transacting, where the recent movement was often the very sale that emptied the
stock. v3 additionally requires no movement for a month on either side:
- SmartDukaan via warehouse.scanNew (3.4M rows, live). Note warehouse.scan is a
dead Saholic relic - 2,476 rows, last write 2012.
- any partner via fofo.scan_record, any scan type.
Phrased as NOT EXISTS so they short-circuit on the first recent row and use the
(inventoryItemId, scannedAt) / (inventory_item_id, create_timestamp) indexes.
An item that has never moved still qualifies, which is correct.
Also in v3:
- the internal pseudo-brands (Dummy, FOC, FOC HANDSET, Live Demo) are no longer
excluded and are swept like anything else.
- vendor-PO guard narrowed to status = 1. PARTIALLY_FULFILLED is UNREACHABLE,
not merely rare: none of the 8 PO-status writes in the codebase sets it, no
native SQL writes the column, and status = 2 has 0 rows across 51,593+ POs
back to 2011. Partial receipt is modelled per line (unfulfilledQuantity), so a
part-received PO sits in READY and is already caught. Documented so it is not
'restored' later as a supposed gap.
CatalogDelistService/Impl mirrors the migration statement for statement, for the
daily scheduled run. Bulk SQL rather than per-row updateActiveById +
publishStatusChange, which would commit to Solr once per catalog (~1,750 times
on the 2026-09-10 backlog); the job is instead scheduled ahead of the existing
06:00 full reindex. The dark-model step is deliberately house-wide rather than
run-scoped so it converges instead of leaving pre-existing dark models on a
stale RUNNING forever.
rollback_delist_recent_movement_20260910.sql restores the 24 listings / 21
models that the v2 run delisted before NEW 3 existed; their partners had
transacted them within the month. Id list frozen, not re-derived. |
|
| 37565 |
12 h 43 m |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ |
IMEI activation: 20s tick lanes for oppo/realme/vivo, idle once the day clears
Replaces the two once-a-day passes with chunk-per-tick lanes. Each tick takes one
chunk and returns; when a brand's pool comes back empty its turn is skipped, and
when everything is clear the ticks do nothing and stay silent until midnight.
- browser lane (StandAlone): one chunk of 25 for one brand every 20s, oppo and
realme by turns. Still exactly one ChromeDriver alive at a time.
- vivo lane: its own tick, chunk of 250. It must not share the browser lane --
0.24s an imei against oppo's 10.2s means it would need ~70 hours behind them
for work it does alone in 39 minutes.
The snapshot is gone; the pool query is the cursor. That is what makes a restart
cost one chunk instead of the day: the 12:03 restart on 09-Sep forfeited ~4,000
lookups and the whole afternoon, and last_finish had read -1 for three days.
The snapshot existed to stop the re-ask loop (realme, 29-Aug: 4,524 requests
against 1,004 distinct imeis). That is now closed at the source instead -- oppo,
realme and motorola stamp every imei they asked about, not just the ones that
produced a map entry, so a failed lookup rests until tomorrow rather than coming
back on the next tick. Motorola is fixed pre-emptively; nothing schedules it yet.
Also:
- secondary and tertiary are merged by turns rather than concatenated. Safe while
a pass walked to the end; without that guarantee oppo's 163 tertiary serials sat
behind 3,819 secondary ones and would only be reached on a day that cleared.
- vivo abandons a tick rather than the chunk when the captcha solver returns no
code -- one probe per 20s while it is down instead of 250, and no rows rested
over a transient outage.
- the funnel gauges move from a pass to a day. due is measured on the first tick
after midnight, the rest accumulate, and last_finish_epoch becomes a real
completion clock. Truncation is detected at the midnight rollover, which is the
case that never reaches an end-of-run at all.
This does not create capacity. At the 21s/imei measured on 09-Sep the pool still
needs ~35 hours and will not clear; it now rolls over visibly instead of silently.
The lever for that is DAYS=1. |
|