| Rev |
Age |
Author |
Path |
Log message |
Diff |
| 37644 |
26 d 7 h |
amit |
/trunk/ |
Cron batch infra: count real SUCCESS rows instead of deriving them, add INCOMPLETE status for runs that died mid-loop, stale-batch reaper, admin force-finalize; markItemSuccess joins the work transaction |
|
| 37642 |
26 d 7 h |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
Hot Deal brand: remove the 00:20 window-sync job and its CLI flag - no date windows any more |
|
| 37636 |
26 d 7 h |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ |
refactor(cron): remove vendoritempricing one-offs; log catalog migration listing prices
- Remove migrateVendorItemPricing (2023 one-off) and its flag; fixOrders no longer reads vendoritempricing
- CatalogMigration sets listing prices via TagListingPriceService |
|
| 37631 |
26 d 11 h |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ |
feat(mail): wire inactive-recipient filter, clean addresses, remove attendance alerts
Wire MailRecipientFilter into both mail senders. Remove inactive hardcoded recipients, fix typo addresses, send market-share reminder to tech@. Delete sendAttendanceMorningAlert/EveningAlert, sendMailToHR and their CLI options. |
|
| 37617 |
27 d 19 h |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ |
Undo an internal GRN so its invoice can be received again
A receipt taken before the serials were split left stock that cannot be corrected in place: a
serialised unit is held one to a row, so an invoice that should have created several rows created one,
and the rows that are missing cannot be added without also unpicking the running figures the receipt
moved. Reversing the receipt and taking it again through the same path is the only way to arrive at
what the invoice actually says.
The reversal removes what receiving created - the scans, the inventory units, the invoice items, the
purchase and the supplier invoice - and gives back the two running figures it moved: the warehouse
availability, and the quantity taken off the purchase order line. Both are worked out from the rows
being deleted rather than recomputed, so whatever the receipt added is exactly what comes off. A
purchase order the receipt closed is opened again. Availability is kept only as a running count with
nothing to rebuild it from, which is why it is adjusted by what is known to have been added rather
than by anything inferred.
An invoice whose stock has moved since is refused, not worked around. A unit that has been scanned out
or partly consumed is no longer the receipt's to remove, and deleting around it would leave the
warehouse holding stock that no record explains. Each invoice is reversed on its own so one refusal
leaves the rest untouched, and a dry run reports everything it would delete and every figure it would
change without writing.
Invoices are named explicitly rather than selected by date, so a reversal can only ever touch what it
was given. |
|
| 37616 |
27 d 19 h |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/migrations/ |
Split a line item's serials into one inventory unit each
A serialised line item carries one serial per unit in serial_number, comma separated. The internal
GRN passed that whole field across as a single serial, so a line of four units became one unit whose
serial was the four serials joined together - a string no scan can ever match. Where the joined
string ran past the 128 characters the inventory column allows, the receipt failed outright; where it
fitted, it was accepted and the stock was quietly understated.
The serials are now split out and each unit is received on its own, which is what grnPoModels expects
- it creates one inventory row per serial.
A serial count that does not match the line's quantity now skips the invoice. Receiving fewer units
than were billed is exactly the failure this had, and it is not something to infer a best guess from:
the invoice is left for someone to look at instead. |
|
| 37615 |
27 d 20 h |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/migrations/ |
Receive internal transfers the way the portal does, not via the Excel upload
The live run failed on every invoice with "Column 'status' cannot be null". The Excel upload path it
was calling, addPORowModels, persists a supplier invoice without ever setting a status, and the
column does not allow one to be absent, so that route cannot complete a receipt at all.
Rather than change a path the portal shares, this follows what the Receive Invoice screens actually
do: record the supplier invoice, record its items through InvoiceService.createInvoiceItem, then hand
it to PurchaseOrderService.grnPoModels, the call behind the Create GRN button. grnPoModels sets the
invoice to received itself, so the status is never left for the caller to remember.
Recording the invoice items matters beyond the receipt: warehouse.invoice_item is what the buying
reports join against, and the Excel path never wrote those rows either.
An invoice is carried as one entry per item rather than one per order, since that is the shape
grnPoModels expects - every serial of an item arrives together and a non serialised item arrives as a
single quantity. There is no supplier document to attach, which is ordinary here; most existing
warehouse invoices carry none.
Resolving the purchase order now uses the mapping recorded when the internal PO was raised - the
transaction it created, held on the purchase order - instead of walking every order to find it.
An invoice whose orders do not share that one transaction is skipped rather than guessed at. |
|
| 37614 |
27 d 20 h |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/migrations/ |
Run each internal GRN in a transaction that is actually applied
The internal GRN one-off failed immediately with "no transaction is in progress", thrown while
flushing the Hibernate session at commit.
The per invoice method was annotated to start its own transaction, but it sat in the same bean as
the loop that called it. Spring applies @Transactional through a proxy, and a call from one method
of a bean to another never leaves the object, so the annotation was inert - the receiving ran with
no transaction at all while the driver had suspended the surrounding one. The repositories still
bound a session to the thread, and the flush at commit then found nothing to flush into.
The receiving moves to its own bean, so the driver now reaches it through the proxy and the
transaction is real. The driver keeps no transaction of its own, which is what lets one invoice
fail without disturbing those already received.
No change to what is received or to the conditions under which an invoice is skipped. |
|
| 37613 |
27 d 21 h |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ |
Receive internal transfer stock that was billed but never GRNed
Stock moved between our own warehouses is dispatched against an internal purchase order and
billed, but the destination warehouse still has to receive it by hand through the portal. Where
that never happened the units exist on an invoice and nowhere in inventory, and there is no way
to clear a backlog of them short of keying each invoice in again.
This adds a cron one-off that receives them. Per invoice it builds the rows the Excel GRN upload
would have carried and hands them to PurchaseOrderService.addPORowModels, which creates the
supplier invoice, the purchase and the inventory items in a single call, so none of receiving is
reimplemented here - the portal and this take the same path and can only ever agree.
It is deliberately narrow about what it will touch. Only INTERNAL buyers, because nothing should
be able to receive a partner's goods on their behalf. Only invoices with no supplier invoice
already recorded, so a repeat run skips what it has already done rather than receiving twice.
Only invoices resolving to a single purchase order, since addPORowModels resolves one order for
the whole map it is given and would otherwise attribute an invoice to the wrong one - for the
same reason each invoice is passed on its own. A serialized line whose serial number is missing
aborts its invoice instead of creating a unit no scan could ever match.
Each invoice commits or rolls back on its own, so one bad invoice cannot undo the ones already
received, and a dry run reports what it would receive without writing anything.
The receipt is recorded against the destination warehouse, which is what warehouse inventory is
keyed by. The buyer on the order is read only to confirm the store is internal; partner side
inventory is a separate receipt and is not touched. |
|
| 37602 |
29 d 10 h |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ |
Remove no-op refreshSnapshotAgeing task; restore originalInventoryItemId backfill call
refreshSnapshotAgeing ran every 30 minutes aggregating warehouse.inventoryItem.rootInvoiceDate,
a column nothing ever wrote, so its UPDATE ... JOIN matched zero rows on every run and
currentinventorysnapshot.oldest_invoice_date was never populated on any of 11,686 rows.
Removed along with the sessionFactory field it was the only consumer of.
Application.java had migrations.migrateWarehouseOriginalInventoryItemId(batchSize) commented
out while still logging 'Starting migration...' and 'Migration completed.', so
--migrateOriginalInventoryItemId reported success while doing nothing. Restored the call;
it remains opt-in via the CLI flag and cannot fire on its own.
Requires profitmandi-dao r37601 (entity fields removed). |
|
| 37595 |
30 d 5 h |
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. |
|
| 37589 |
30 d 7 h |
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. |
|
| 37569 |
31 d 13 h |
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. |
|
| 37567 |
31 d 13 h |
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. |
|
| 37565 |
31 d 22 h |
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. |
|
| 37537 |
37 d 1 h |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
carlcare activation: retry a transient failure once before giving up
One retry after a 1.5s backoff - long enough to clear the far end's rate window,
short enough to still fit inside a scheduler tick. A transient network blip was
otherwise recorded as a hard activation failure for that IMEI. |
|
| 37518 |
39 d 13 h |
ranu |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
notification scheduler |
|
| 37510 |
40 d 6 h |
ranu |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ |
sales target and achievement added cron and flags |
|
| 37503 |
40 d 8 h |
ranu |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ |
sales target and achievement added cron and flags |
|
| 37499 |
40 d 9 h |
ranu |
/trunk/ |
sales target and achievement added cron and flags |
|