Subversion Repositories SmartDukaan

Rev

Go to most recent revision | Show changed files | Details | Compare with Previous | Blame | RSS feed

Filtering Options

Rev Age Author Path Log message Diff
37569 31 d 23 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 23 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 32 d 8 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 12 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 23 h ranu /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ notification scheduler  
37510 40 d 16 h ranu /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ sales target and achievement added cron and flags  
37503 40 d 18 h ranu /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ sales target and achievement added cron and flags  
37499 40 d 19 h ranu /trunk/ sales target and achievement added cron and flags  
37492 40 d 22 h amit /trunk/ errors: wire the GlitchTip appender into cron and fofo logging

Completes r37491, which committed only web's log4j2.xml. cron's and fofo's were
held back because they carried local development log paths -- a user.home
expansion in cron and an absolute /Users path in fofo. Neither can ship: /Users
does not exist on the server, so fofo logging would fail to open its file and
the Alloy stream tailing /var/log/tomcat7/fofo/fofo.log would go dead.

Both files are restored to their production paths and carry the Sentry appender.
Whoever needs the local override should re-apply it locally rather than
committing it.
 
37491 40 d 23 h amit /trunk/ errors: ship ERROR events to GlitchTip via the log4j2 Sentry appender

io.sentry:sentry-log4j2:7.22.6 in all three deployables. Pinned to 7.x because
8.x drops Java 8; verified class major version 52.

minimumEventLevel=ERROR is load-bearing. It only became safe after r37490 moved
business validations to WARN -- 32% of the ERROR stream was HTTP 400s where the
user is simply told what to fix, and sending those would have made "insufficient
balance" the top issue and buried real bugs. Breadcrumbs come from INFO so an
issue arrives with the log lines that preceded it.

Attached to the application loggers only; framework noise is not our bug.

web's log4j2.xml is committed here. cron's and fofo's are held back: both carry
uncommitted local development paths (a user.home expansion, and an absolute
/Users path) that would break production logging and the Alloy log tailing.
Their Sentry blocks are staged locally and should land with whoever owns those
path edits.
 
37485 41 d 17 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ carlcare: enable tecno alongside itel

Itel went first because it is the brand we can CHECK -- the DCR pull independently
produces itel dates to reconcile against, where tecno has no second source at all.
That check has passed:

- a 50-imei read-only trial answered 50/50, no errors, no sign rejections, and
agreed with our catalog brand on all 50
- 18 of the 50 carried a date, and every one of those 18 fell AFTER our own
billing date (3 to 1,111 days, median ~220)
- the first live tick after deploy returned the same rate: 16 dates, 32 not-yet-
activated, 1 malformed response absorbed by the has("status") guard

Tecno is also the reason this class exists. Itel was already served by the DCR pull;
tecno has been served by nothing since 2024-11-20 and has been manual CSV ever since.

Capacity: ~10,400 itel + ~1,890 tecno pending against 14,400 lookups a day. The
round-robin splits a tick four ways only while all four queues have work; tecno is
much the smaller pool, so it is exhausted a few hours in, its queues then come back
empty and itel gets the full 50 again for the rest of the day. Itel still clears
daily -- roughly 1,875 + 10,650 = 12,525 itel lookups against a 10,401 pool.
 
37484 41 d 17 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/monitored/ cron metrics: register the status gauge as -1, not 0

The gauge was created when a job STARTS, and 0 means failure, so any job still
running its first execution after a restart reported FAILED. Selenium-driven and
report jobs take minutes, so this is not a narrow window -- it put ten jobs on
the dashboard as failed after the 2026-08-31 restart when none had failed.

-1 means 'has not finished a run yet'. CronJobFailing matches == 0, so those
jobs are simply absent from the alert until they genuinely complete once.
 
37482 41 d 17 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ carlcare: recover tecno/itel activation dates, and fix trunk broken by r37479

r37479/r37480 swept a working-copy edit to Application.java into the mail commit: trunk
has called scheduledTasks.checkCarlcareImeiActivation() since then without containing it,
so profitmandi-cron has not compiled. This adds the rest.

WHAT THIS IS

The tecno feed has been dead since 2024-11-20, when transsion decommissioned the SAP
OData hosts cms.tecno-mobile.com:8099 / cms.itel-mobile.com:8099. The imwav DCR portal
recovers itel but has nothing for tecno -- all three tecno logins together expose 17
imeis over five years, against 31,928 tecno rows in fofo.activated_imei. Everything since
has been manual CSV via /imei/upload.

Carlcare is transsion's own after-sales arm, and the warranty-check page on carlcare.in is
backed by a public per-imei endpoint that answers for tecno, itel and infinix with no
login, no cookie and no captcha:

GET ind-mis-carl.shalltry.com/CarlcareClient/electronic-card/check-extended_warranty-web?imei=
sign: md5(SALT + imei)

The sign header is mandatory (without it: code 10022 "Sorry, web sign is error"). The salt
is in the site's own bundle, _nuxt/085537c.js module 688, along with the base url; re-read
that bundle if it ever stops working. status 3 = activated and carries activeTime, status
2 = device known but not activated yet and activeTime is null.

It is also the semantically right source. The DCR portal serves an INVENTORY report and
the old SAP feed served a TERTIARY SALES report, whereas activeTime is the date the
handset was actually activated -- which is what fofo.activated_imei is meant to hold and
what tertiary payout is computed on.

Measured against hadb1 before writing any of this: tecno 355463920708766 -> 2026-08-28 and
itel 359207322028000 -> 2026-08-30, both exact matches to rows we already had. A read-only
trial of 50 itel imeis answered 50/50 with no errors and no sign rejections, agreed with
our catalog brand on all 50, and returned a date for 18 -- every one of those 18 falling
AFTER our own billing date, 3 to 1,111 days, median ~220.

SHAPE

Pool queries and saveActivation semantics are the vivo ones, so the two read alike, and no
DAO change was needed: the pending queries are already brand-generic. The far end is far
cheaper than vivo's, one signed GET per imei, so there is no captcha service, no cookie
store, no session seeding and no verdict reporting.

50 imeis every 5 minutes = 14,400 lookups a day against ~10,400 pending itel, so the whole
pool is covered daily with headroom. The pool reaches back to 2021, so the ticks are
themselves the backfill of the nov-2024 blackout; there is no one-off to run.

Two things worth knowing before changing it:

- The batch is drawn ROUND-ROBIN across each (brand, channel) queue, not by concatenating
them. A full daily pass can concatenate freely because it walks to the end, but a
50-at-a-time tick cannot: itel is ~1,550 secondary against ~8,851 tertiary, so the head
of a concatenated list is ~31 straight ticks of pure secondary before one tertiary imei
is asked about. The first trial batch was 100% secondary for exactly that reason.

- Every outcome stamps the row, failures included. This is the one deliberate departure
from vivo, which leaves a failure unrecorded so it retries next pass -- safe there
because the next pass is tomorrow. On a 5-minute cadence it is not: an unstamped imei is
due again in five minutes, the query keeps handing back the same 50 rows, the batch never
advances past them, and the endpoint is asked the same questions twelve times an hour for
as long as it keeps failing. See the runaway documented on oppoRealmeImeiActivation.

BRANDS is itel alone to start. That is a rollout order, not a limit of the endpoint: itel
is the brand we can CHECK, because the DCR pull independently produces itel dates to
reconcile against, where tecno has nothing. Add "Tecno" once a day's rows agree. Both feeds
may write itel meanwhile with no coordination -- the pool query only returns imeis whose
activationTimestamp is still null, so whatever one fills has left the other's pool.

Scheduled tick plus a --checkCarlcareImeiActivation flag, both wired.
 
37480 41 d 17 h amit /trunk/ mail: correct a wrong claim in r37479 -- the Google app password IS valid

r37479 stated googleMailSender's app password was rejected. That was wrong. The
test behind it resolved smtp.gmail.com over IPv6; repeating it over IPv4 with
the same credential gives AUTH OK on both 465 and 587.

The real fault is not the credential and not the bean config, both of which are
correct. SMTP from this host works over IPv4 only:

smtp.gmail.com IPv4 -> AUTH OK IPv6 -> 535 5.7.8 Username and Password not accepted
smtp-relay IPv4 -> 250 MAIL FROM IPv6 -> 550 5.7.1 Invalid credentials for relay

The JVM prefers IPv4, which is the only reason mail leaves this box at all.
Anything that prefers IPv6 fails on both paths.
 
37479 41 d 17 h amit /trunk/ mail: make the relay the default sender, not the Google identity

Correcting r37474-37476. Those made googleMailSender @Primary on the assumption
its credentials worked. Tested against the live servers from the prod host:

googleMailSender 535 5.7.8 Username and Password not accepted (BadCredentials)
-- the app password in the source is no longer valid
relay over IPv4 250 OK for MAIL FROM:<noreply@smartdukaan.com>
relay over IPv6 550 5.7.1 Invalid credentials for relay

So promoting google would have replaced one broken default with another. The
relay is what actually delivers today and it becomes 'mailSender'. It does not
authenticate -- Workspace authorises it by allowlisted source IP -- so sending
as noreply@ is legitimate there and AuthenticatedIdentityMailSender correctly
leaves it alone.

googleMailSender stays available by qualifier. Point the default back at it once
a valid app password is issued for sdtech@smartdukaan.com.

Also noted in the javadoc: the relay allowlist covers the IPv4 address only, so
anything that prefers IPv6 will be refused.
 
37474 41 d 18 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ mail: delete the dead SendGrid bean from cron, make the authenticated identity primary

The @Primary bean named 'mailSender' was SendGrid, so every unqualified
JavaMailSender injection got it -- and SendGrid rejects with 535, which is what
was failing 10 report/notification cron jobs (dailyTrackingReport,
monthlyTargetForPartner, sendFeebackSalesAndRbm and others). 420 occurrences in
one log.

googleMailSender now answers to both 'googleMailSender' and 'mailSender' and is
@Primary, so those injections resolve to a sender that works and whose From is
rewritten to the authenticated identity. No call site changes.
 
37472 41 d 19 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ IMEI activation: one snapshotted daily pass per brand, on one thread, with per-brand metrics

Four @Scheduled jobs every 5 minutes become two daily passes. Oppo and realme
share one thread and alternate in 25-imei chunks, so exactly one ChromeDriver is
alive at a time instead of four; vivo keeps its own thread since it is direct
HTTP and does not contend for a browser.

The pass snapshots its pool before any browser starts and walks that list to the
end. It never re-queries, and that is the actual fix. A failed lookup never
reaches dateMap.put, so no row is written, so createTimestamp is not bumped, so
the imei was eligible again on the next tick five minutes later. Measured 29-Aug:
realme issued 4,524 requests against 1,004 distinct imeis -- 4.5 asks each, 78%
of the day's budget spent re-asking -- while oppo, which rarely fails, sat at
1.03. More requests hardened the block, which caused more failures. A pass bounds
that: a failure costs one retry tomorrow, never one in five minutes.

This supersedes the r37447/r37448/r37449 argument about driver count, which was
about the wrong variable. That argument blamed realme's collapse on CPU
contention pushing the captcha render past the element waits. The logs do not
support it: on 29-Aug oppo took ZERO canvas timeouts across all 24 hours on the
same box, same six cores, same driver count, same captcha vendor, load average
0.9 -- including the 15:00-23:00 window in which realme solved nothing at all.
Realme's own canvas wait is 15s against oppo's 8s, so the longer wait is the one
expiring. What realme's timeout rate tracks is its own daily request volume, and
it resets at midnight: 920/day -> 0.3%, 3,467/day -> 28%, 4,524/day -> 75%. That
is realme.com declining to serve the widget.

DAYS=0 is deliberate and is not an off-by-one: the pool filter is
createTimestamp < now().atStartOfDay().minusDays(DAYS), so DAYS=1 measures
against yesterday midnight and silently yields a two-day cadence, which is what
oppo and realme were running.

Sizing measured on prod for a midnight start: oppo 4,133 and realme 2,118 imeis,
11.7h + 8.4h = 20.1 hours of a single thread. It fits with no slack; if the
'pass finished' counts come in short of 'pass starting', the lever is DAYS=1
rather than a second thread.

Observability: ImeiActivationGauges publishes the funnel per brand on
/actuator/prometheus, which alloy already scrapes on this host -- due, churned,
captcha_shown, captcha_solved, answered, dates_found, errors, run_seconds and
last_finish_epoch. Each stage fails differently and says what broke. Rates are
left to PromQL. The stage that matters for health is answered: churned>0 with
answered==0 is precisely the shape of both silent outages this year (oppo wrote
nothing for a week; the vivo captcha solver was dead for 46 days). dates_found is
deliberately NOT a health signal -- when the multi-year backlog drained at the
end of August, yield fell from ~100% to 2-3% on the same day across all three
brands with nothing broken.

Nagios cleanup: the Nagios server and every NRPE daemon are gone, so
WriteToPropertiesFile and the commented-out blocks that fed
nagios-cron.properties are deleted, and NagiosMonitorTasks is renamed
BalanceMonitorTasks for the transport it actually uses. Noted there that nothing
calls it -- there is no @Scheduled entry and no other caller -- which is why both
balance gauges have always read -1.
 
37470 41 d 19 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ Remove dead Itel/Tecno SAP activation services

Both services call SAP OData endpoints that Transsion decommissioned on 2024-11-20:

ItelImeiActivationService -> cms.itel-mobile.com:8099/.../ZTERTIARY_SALES_REPRT_SRV
TecnoImeiActivation -> cms.tecno-mobile.com:8099/.../ZTERTIARY_SALES_REPRT_SRV

Both hosts now refuse connections outright (verified from the prod app server).
That decommissioning is what stopped Itel and Tecno activation ingest within the
same hour on 2024-11-20; everything since has arrived via manual CSV upload.
Neither service has produced a row in 21 months and neither can again.

Removed:
- both service classes
- ScheduledTasks.checkItelImeiActivation / .checkTecnoImeiActivation, their
@Autowired fields and imports (the only callers)
- the --checkItelImeiActivation / --checkTecnoImeiActivation startup args

Unaffected: ItelImeiActivationNewService and checkItelImeiActivationNew, which
target the current imwav portal and were fixed in r37467.
 
37467 42 d 0 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/itelImeiActivation/ Fix Itel/Tecno activation pull: live session tokens, correct credentials, per-account isolation

The imwav pull has never worked since it replaced the SAP feed. Three defects:

- Login response was discarded and the listing call sent p-auth/utoken values
captured by hand in Jul-2023, so every request went out with an expired session.
Now takes utoken/urToken from the login response.
- Itel (IN404338I) carried a stale password (Dukaan@1234), which is why its pull
started failing Feb-2026. Corrected, and the two missing Tecno branch accounts
(Noida IN411226T, Lucknow IN404339T) added - the code previously knew only
IN404338T, which has no enterprise scope and returns nothing.
- Existing rows were looked up by the activation date string instead of the IMEI.
serial_number is the primary key, so the lookup always missed and re-persisted a
row that already existed, colliding on duplicate key on any re-pull.

Also pulls each account independently so one dead login cannot abort the remaining
accounts, and fails loudly when the portal rejects a login or the report call
instead of proceeding into a confusing downstream error.
 
37465 42 d 0 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ Exclude aged Apple stock from investment when suggesting partner credit limits

Apple handsets a partner has held more than 30 days from GRN no longer count
toward the investment that drives the suggested limit. Applied in
getSuggestedAmount via getCreditableInvestment, covering the SDDIRECT,
hundred-percent and SIDBI branches. The aged amount is logged alongside each
limit change so a drop can be traced.

Scoped to the limit calculation only -- getTotalInvestment() is untouched, so
checkout payment options, the investment-OK gates and partner-facing screens
see the same stock value as before.

Measured on prod at time of commit: 580 units / ~3.94 Cr across 88 partners.
 

Show All