Subversion Repositories SmartDukaan

Rev

Show changed files | Directory listing | RSS feed

Filtering Options

Rev Age Author Path Log message Diff
37489 8 h 36 m amit /trunk/profitmandi-common/src/main/java/com/spice/profitmandi/common/util/ Restore Utils.html and Utils.htmlJson removed by r37477

r37477 reorganised Utils while consolidating the mail APIs and dropped its only two
instance methods. They have no Java callers -- they exist solely for Velocity, where
AppConfig binds new Utils() as $vmUtils -- so the compiler, IDE find-usages and any
grep over *.java all reported them dead.

With htmlJson gone, data-paramslist="$vmUtils.htmlJson(...)" stopped rendering usable
JSON on every report link (admin.vm:840, admin.vm:910, reports.vm:51). reports.js:7
then read $(this).data("paramslist") as undefined and returned true, letting the
browser follow the plain <a href> natively -- a GET against the @PostMapping
/reports/{projectName}/{fileName} (ReportsController:126), answered with GE_1007
"Request method 'GET' not supported". The modal POST path was the only way that
endpoint was ever reachable, so every partner report broke at once.

Confirmed against prod: zero occurrences in fofo.log for Aug 13/20/26/28/30, and 87
today starting 18:37:23, right after today's ROOT.war deploy. 13 distinct reports hit.

$vmUtils.html had two further callers that also come back:
offer_margin_detail_partner.vm:12 and :241 (offer description and notes).

Restored verbatim from r37476, and commented as template-only so the next refactor
does not read them as dead code again.
 
37488 9 h 21 m ranu /trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/dao/model/ rbm drr dashboard , all today po rbm view maped  
37487 9 h 22 m ranu /trunk/ rbm drr dashboard , all today po rbm view maped  
37486 9 h 38 m amit /trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/service/mail/ mail: fix prod outage -- MailOutboxService must not implement MailQueue

r37478 added 'implements MailQueue' to MailOutboxService. That took down both
ROOT.war and profitmandi-web.war on the 18:19 deploy today; every route 404'd
because neither Spring context would start.

MailOutboxService has @Transactional methods, so Spring has to proxy it, and
@EnableTransactionManagement in both WebDBContextConfigure classes runs with the
default proxyTargetClass=false. While the class implemented no interface Spring
proxied it with a CGLIB subclass, which is still a MailOutboxService. Adding an
interface switched it to a JDK proxy implementing only MailQueue, so all 39
sites that inject the concrete MailOutboxService failed:

BeanNotOfRequiredTypeException: Bean named 'mailOutboxService' is expected to
be of type MailOutboxService but was actually of type com.sun.proxy.$Proxy1519

The interface moves to a new MailQueueAdapter, which delegates to
MailOutboxService and carries no transactional annotations. MailQueueHolder and
EmailServiceImpl inject it by the MailQueue interface, so Utils.sendMail* and
EmailService still reach the outbox unchanged. MailOutboxService goes back to
implementing nothing, and its ~112 existing call sites are untouched.

Chose this over proxyTargetClass=true, which would have flipped proxying for
every interface-implementing service in the app -- too broad for a hot fix.

Verified with javap inside both built WARs: MailOutboxService declares no
interfaces, MailQueueAdapter implements MailQueue.

Note for cron: it shares the dao jar, so a rebuild picks this up. Do not restart
cron on trunk without rebuilding it first.
 
37485 9 h 53 m 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 9 h 54 m 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.
 
37483 9 h 59 m amit /trunk/profitmandi-dao/src/main/ mail: migrate mail_outbox SENDGRID rows to RELAY (applied to production)

SENDGRID never meant SendGrid -- resolveSender() already routed it to the
Workspace relay, which is why outbox mail kept being delivered while direct
JavaMailSender injections failed against the real, dead SendGrid bean. So this
relabels rows without changing where anything is sent.

Applied to hadb1 2026-08-31 18:10 IST:
before SENDGRID=5956 RELAY=0 GOOGLE=4896 (total 10852)
after SENDGRID=0 RELAY=5956 GOOGLE=4896 (total 10852)
backup dtr.mail_outbox_sendgrid_backup_20260831_1810 (5956 rows)

The one PENDING row (id=26838) migrated cleanly and still routes to the relay.
Only GOOGLE and RELAY remain in the column. Rollback statement is in the script.

The comment claiming legacy SENDGRID rows exist is now false and has been
corrected; SendGrid appears nowhere in the codebase.
 
37482 10 h 11 m 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.
 
37481 10 h 12 m amit /trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/dao/ imei activation: one 2-day billing floor for every brand, and drop two dead methods

The pending pool is a UNION of two channels, not an intersection: an imei qualifies
if WE billed it to the partner (selectImeiActivationByBrand) or the PARTNER billed it
on to the end customer (selectImeiActivationByBrandTertiary). The two are disjoint by
construction -- the secondary query excludes anything carrying a FofoLineItem -- so a
unit moves from one to the other as it sells through and is never asked about twice.
Said so on the interface, since the pairing was only documented at the call sites.

billedBefore moves from now-1d to now-2d, so both channels ask only about stock billed
MORE THAN 2 DAYS ago. Anything sold in the last 48 hours has essentially never been
activated yet, so the lookup is spent for nothing; it is not lost, the same imei comes
back into the pool as soon as it crosses the floor, and again every day after that
until it activates. Kept in the repository rather than per caller so vivo, oppo, realme,
motorola and the new carlcare pass inherit one rule instead of drifting apart.

Costs almost nothing today -- secondary pool, old floor vs new:

itel 1550 -> 1550 realme 849 -> 849 oppo 2021 -> 2019
vivo 5018 -> 5016 motorola 1168 -> 1153 tecno 502 -> 495

26 rows across six brands, each deferred by one day.

Removed as dead:

selectImeiActivationPendingByRealme -- a verbatim duplicate of
selectImeiActivationPendingByBrand down to the named query and the parameters. Nothing
called it; StandAlone already used the brand-generic method for realme and said so in
a comment, which is now updated.

selectImeiSoldNotActivatedByBrand -- interface, impl and named query. No callers
anywhere in web, fofo, cron or dao.
 
37480 10 h 16 m 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 10 h 21 m 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.
 
37478 10 h 32 m amit /trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/service/mail/ mail: MailOutboxService implements MailQueue

Adds the single entry point, queue(MailRequest). The twenty-odd queueMail*
overloads remain as delegates because ~112 call sites use them, but they now
funnel through one path rather than each building their own argument list.

Selecting the transport moves here too: MailSenderType.GOOGLE maps to the
authenticated sender, anything else to the relay.
 
37477 10 h 32 m amit /trunk/profitmandi-common/src/main/java/com/spice/profitmandi/common/ mail: one way to send mail, and nothing sends inline

There were three overlapping APIs -- Utils.sendMail* (7 static overloads),
EmailServiceImpl.sendMail* (7 more) and MailOutboxService.queueMail* (20). The
first two built their own MimeMessage, hardcoded From: noreply@smartdukaan.com
in eleven places, and pushed straight down whichever JavaMailSender the caller
passed.

That From is why mail was refused: an authenticated Workspace session may send
only as the account it logged in as, and noreply@ is a different identity in the
same domain. Sending inline also meant a failed send was lost outright and took
the calling job down with it, which is how one expired credential came to mark
ten report jobs FAILED.

New in profitmandi-common:
MailQueue - the single entry point; queue() returns once the mail is
recorded, not once it is sent
MailRequest - one value object replacing the 34 overloads
MailSenderType - GOOGLE (authenticated, From rewritten) or RELAY (IP-authorised)

The interface lives in common while MailOutboxService implements it in dao,
because dao depends on common and not the reverse -- Utils and EmailService
could not otherwise reach the outbox at all.

Utils and EmailService keep their signatures so the ~38 call sites still
compile, but now delegate and ignore the JavaMailSender argument; choosing a
transport was never the caller's business. Both are marked deprecated.
MailQueueHolder bridges the static helpers to the bean and is documented as a
compromise, not a pattern.
 
37476 10 h 52 m amit /trunk/profitmandi-fofo/src/main/java/com/spice/profitmandi/web/config/ mail: delete the dead SendGrid bean from fofo, make the authenticated identity primary

googleMailSender is now @Primary and answers to 'mailSender'. Two senders remain:
the authenticated Workspace identity and the IP-authorised relay.
 
37475 10 h 59 m amit /trunk/profitmandi-web/src/main/java/com/spice/profitmandi/web/config/ mail: delete the dead SendGrid bean from web, make the authenticated identity primary

Removes the live SendGrid bean plus two commented-out corpses (an old
build@shop2020.in sender and a dead alias). googleMailSender is now @Primary and
answers to 'mailSender', so unqualified injections get the working authenticated
sender instead of one that rejects with 535.
 
37474 10 h 59 m 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.
 
37473 10 h 59 m amit /trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/service/mail/ mail: retire the SENDGRID sender label

SendGrid was never actually used by the outbox -- resolveSender() already fell
through to the Workspace relay for it, which is why outbox mail kept working
while direct JavaMailSender injections failed.

Two senders remain, each named for what it is:
GOOGLE - authenticates as sdtech@smartdukaan.com; AuthenticatedIdentityMailSender
rewrites every From to that identity, because Workspace binds an
authenticated session to one address and rejects any other with
'535 Authorization failed: Authenticated user is not authorized to send mail'
RELAY - Workspace relay, no auth, authorised by source IP, may send as noreply@

Default convenience methods now route to RELAY explicitly rather than through a
label that named a provider we do not use.
 
37472 12 h 3 m 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.
 
37471 12 h 3 m amit /trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/dao/ IMEI activation pools: skip stock billed today or yesterday

Both pool named queries now exclude serials whose sale is inside the last 48
hours -- o.billingTimestamp for the secondary path, fo.createTimestamp for the
tertiary one. A handset sold in the last two days has essentially never been
activated yet, so the lookup is spent for nothing: measured on prod, the
sold-within-180-days cohort returns an activation date on 2.4% (vivo) to 8.4%
(oppo) of lookups, against 43-97% for stock sold over a year ago.

billedBefore is set in ActivatedImeiRepositoryImpl rather than passed by the
caller, so no method signature changes and every brand inherits it -- vivo,
oppo, realme and motorola all draw from these two queries and nothing outside
profitmandi-cron uses them.

This is correctness rather than capacity: it trims 30 imeis from the oppo pool
and 9 from realme.
 
37470 12 h 21 m 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.
 

Show All