| Rev |
Age |
Author |
Path |
Log message |
Diff |
| 37790 |
10 d 11 h |
amit |
/trunk/profitmandi-dao/src/main/ |
Return refund as B2C when NIC refuses the buyer's GSTIN
A return whose credit note NIC refuses because the BUYER's GSTIN is
cancelled or invalid could not be settled at all: the approval threw, and
with it went the refund, the stock and the return rows (NSUPDL5176, Mobile
Hub, GSTIN 08DCUPD7948K1ZP). Where the invoice itself had never been filed
the return was instead refunded with no credit note at all, leaving the
refund undocumented.
Raising the note automatically is not the answer - it is Finance's call,
and the GST on a cancelled-GSTIN sale is not recoverable, so paying the
full value back loses it. Both flows therefore record the refusal and stop.
GstProService: getIrnFailureReason (NIC's refusal for an invoice),
isInvalidBuyerGstin (that refusal is about the buyer's registration -
deliberately NOT a state-code mismatch, which is a data error to fix and
retry, nor anything naming our own seller GSTIN) and briefIrnReason (one
plain-ASCII line of 128 chars; credit_note is latin1 and sql_mode is empty,
so anything else would store as '?').
PurchaseReturnServiceImpl: both the invoice-return and the debit-note
refund detect that refusal, record it through ReturnIrnFailureRecorder and
throw ReturnIrnGstinFailureException. The recorder commits in REQUIRES_NEW
because the approval it records is about to roll back, and it only inserts -
return_irn_failure carries no foreign key on purpose, since an FK check
would take a shared lock on the very PurchaseReturnOrder row the dying
transaction may still hold.
Finance may then settle the return as B2C for the rest of that calendar day:
refundAsB2c credits the value NET of GST in whole rupees (B2cRefundQuote,
HALF_UP per line, so the line table and the total always agree), restores
the stock and issues a local credit note for exactly that amount with no tax
on its lines, no IRN and NIC's reason recorded. The amount is fixed - the
caller must pass back the quoted figure - and a remark and explicit consent
are required. On a later day the option only reopens after a fresh approval
attempt is refused again, so NIC is always re-checked first.
Mails: every refusal notifies Accounts L2 and above; the B2C settlement
notifies them and the partner's Warehouse L1/L2, both tabulated.
The partner account statement is unaffected: it skips RETURNS notes and
credits returns from returnorderinfo, so the new notes cannot double-credit.
DEPLOY ORDER: apply sql/add_return_b2c_refund_20260921.sql BEFORE any war
built from this dao. CreditNote now maps irn_skip_reason and b2c, so every
credit-note read in web, fofo and cron fails until the columns exist.
Untested beyond compilation. |
|
| 37776 |
14 d 6 h |
amit |
/trunk/profitmandi-dao/src/main/ |
returns: reverse a refunded debit-note return whose goods never reached the warehouse
Once a return was refunded nothing could undo it - rejectReturn refuses an already
refunded one - so a return booked and paid for goods that never arrived left phantom
warehouse stock, the unit missing from partner stock, the order marked returned, a
wallet credit and a filed credit note, with no way back.
ReturnReversalService.reverse(imei, reason, by, dryRun) undoes exactly one unit:
the warehouse return scan (row deleted so a genuine return later is not rejected as a
duplicate) and the stock it added, partner stock with its schemes/price drop/offers,
the order's return quantity and status, the wallet refund as a REVERSAL entry, and the
return item / debit note. Guards refuse anything that has moved since the refund, and a
dry run reports the plan without writing.
The credit note follows movement and the NIC clock: inside the 24h window, and only
when the note credits nothing but this unit, its IRN is cancelled and the note marked
cancelled; past it a DBN with its own IRN is issued against the note
(CreditNoteService.issueReturnReversalDebitNote, stored as CN_CANCELLATION with no
margin month so the statement does not show it twice - the wallet REVERSAL is the
statement line); a note never filed at NIC is voided locally. Every local write happens
first and NIC last, so a refusal rolls the whole reversal back.
fofo.return_reversal (return_reversal_20260921.sql) records each reversal and, unique
per return item, makes it once-only. Also adds cancelled=0 to the RETURNS_CN branches of
the account statement queries, without which a cancelled note keeps crediting the
statement - no current effect, no RETURNS note is cancelled today. |
|
| 37764 |
14 d 9 h |
amit |
/trunk/profitmandi-dao/src/main/ |
feat(store-closure): require reason, remark and approval mail to close a store; mail closure report
- fofo.store_closure audit table (reason, remark, approval document, closed_by); migration applied on hadb1
- StoreClosureService: validates and closes, queues closure report with approval attached to the
partner's Sales L2, top 3 Sales levels, top Accounts level and the closer
- StoreAccess: single home for store close/deactivate/activate/extend-billing access lists;
neeraj.gupta can close, mohit.gulati removed from extend billing |
|
| 37732 |
17 d 13 h |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/dao/repository/fofo/ |
purchase return: look up the open return against a document
selectOpenByDocumentReference returns the return still outstanding against a
document reference - neither refunded nor rejected - or null when none is.
Backs a duplicate guard on the invoice-return submit path: the same invoice was
being submitted twice, the closest pair eight seconds apart, and finance was
rejecting the extras by hand. A settled return deliberately does not count, since
rejecting a return is precisely what frees the invoice to be raised again.
Repository lands first so the method exists before the caller uses it. |
|
| 37628 |
23 d 13 h |
amit |
/trunk/profitmandi-dao/src/ |
feat(mail): filter inactive auth users from outgoing mail
InactiveAuthUserRecipientFilter drops @smartdukaan.com recipients whose auth.auth_user is inactive (cached, 5 min refresh, fail-open). Remove inactive hardcoded recipients (sm@, praveen.sharma, tejus.lohani). |
|
| 37592 |
27 d 7 h |
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. |
|
| 37588 |
27 d 9 h |
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 |
|
| 37535 |
34 d 3 h |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/ |
purchase return: guard invoice cancellation, and surface rejected-with-DN returns
PurchaseReturnServiceImpl gains assertInvoiceNotCancelled and assertNotGrnd, so a
cancellation is refused when the invoice is already cancelled or the goods have been
GRN'd - a return cannot be undone once the stock has been received in.
PurchaseReturnOrderRepositoryImpl also counts returns that were rejected but never
acknowledged by the retailer while carrying a debit note, which otherwise fell out
of the pending queue despite still owing an action. |
|
| 37481 |
38 d 8 h |
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. |
|
| 37471 |
38 d 10 h |
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. |
|
| 37464 |
38 d 15 h |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/ |
Track aged Apple stock on partner investment; add category filter to stock value lookup
selectPartnerStockValueMap now takes a nullable categoryId so the same query
serves both the existing Live Demo exclusion (null = all categories) and the
new Apple handset lookup (category 10006). Existing callers pass null and are
behaviourally unchanged.
PartnerDailyInvestment carries agedAppleStockAmount, populated in both the
single and batch investment paths. It is JPA-@Transient, so no schema change,
and it still travels through the partnerStat.tmp serialization the FOFO
investment screen reads. Deliberately not part of getTotalInvestment() -- the
deduction is a credit-limit policy, so stock value is unchanged for checkout,
the investment-OK gates and partner-facing screens. |
|
| 37446 |
42 d 6 h |
amit |
/trunk/ |
Per-brand batch sizes, and stop chrome forking a GPU process it cannot use
maxResults was hardcoded in the shared repository methods, so Oppo and Vivo were
forced to the same secondary batch (10) and all three to the same tertiary (10).
It is now a parameter, set per brand at the call site.
Sizing is arithmetic, from measured IN-BATCH per-imei time. Solving
M * 86400 / (300 + M*t) = needed/day:
brand needed/day t M required set to
Vivo 9,407 0.8s 36 50 clears, ~12,700/day
Realme 1,973 13.4s 10 10 was 5 = ~1,177/day, short
Oppo 4,798 14.6s 88 10 HELD, see below
Correcting an earlier measurement of mine: I reported Vivo at 13.6s per imei and
concluded its backlog could not be cleared. That averaged across the ~300s idle
gaps BETWEEN batches. In-batch it is 0.8s -- Vivo is 17x faster than I said, is
idle ~97% of the time, and 50 clears its pool comfortably. There is no wait in
the Vivo path; it is simply fast.
Oppo is deliberately NOT raised. At 14.6s it would need M=88, which means
20-minute batches and near-permanent chrome sessions. But that 14.6s predates the
retry cap (r37445), which cuts exhausted imeis from 20 attempts to 7 and should
drop it sharply. Re-measure before sizing Oppo, rather than guessing high on a
box with 3GB free.
Also: --disable-gpu, --disable-dev-shm-usage, --disable-software-rasterizer on
both selenium tasks. Headless needs no GPU yet chrome forks a gpu-process per
browser -- 6 were alive across the fleet, pure overhead. No behaviour change.
Batch size does not raise peak concurrency (fixedDelay means one batch per job at
a time, so never more than 4 drivers). It raises DUTY CYCLE, which converts
chrome's footprint from intermittent to sustained. That matters here: tomcat is
9.4GB, available is ~3GB, and the two OOM kills this month both took tomcat.
Cron-only deploy. The dao signature change has no callers outside cron. |
|
| 37377 |
48 d 12 h |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/dao/repository/fofo/ |
Queries behind the Sale Returns action-pending queue
The Sale Returns screen could only ever list purchase return orders received inside the
chosen date window, which left the returns that actually need someone unreachable: a
return order is written at receive time, so a debit note the warehouse has not received
yet has no return order at all and could not appear however the list was sorted.
- selectPendingByWarehouseIds returns every unsettled return order with no date bound -
received but unrefunded, or rejected but not yet acknowledged by the retailer. A return
nobody acted on only gets older, so bounding it by date is what buried it
- selectUnreceivedSince finds debit notes with no return order against them, which is the
only place a not-yet-received return exists. Cancelled notes are excluded
- selectEarliestCreateTimestamp exposes when the receive/refund flow went live. Notes
raised before the first return order ever recorded were settled through the older
item-level flow and are not a queue anyone can work, so the caller uses this to bound
the lookup off the data rather than off a date pinned in code
- both listing queries now take an optional fofoId, so the partner filter is a predicate
rather than a post-filter - filtering after the 200 row cap would silently drop a
partner's older rows
Counts on live data: 6 unsettled return orders, 24 unreceived debit notes since the flow
started, against 5,618 older notes correctly left out. |
|
| 37327 |
52 d 8 h |
ranu |
/trunk/ |
super retailer club 5 live |
|
| 37319 |
55 d 8 h |
amit |
/trunk/ |
Fix partner-performance tertiary: aggregate order items (qty*mop), not the POS-typed order header
The tertiary panel summed fofo_order.total_amount - a price typed at the partner POS
and never validated against the catalogue - and attributed each whole order to its
first line item's brand via .get(0). One mistyped digit inflated reported sell-out
10x, and mixed-brand orders booked 100% to the first brand, leaving the rest at zero.
Defect dates from r32000/r32034 (May 2023); the panel was the only tertiary consumer
diverging from the qty*mop basis.
- New FofoOrder.selectMonthlyBrandTertiary: sum(quantity*mop) grouped by
(year*100+month) and item brand - the same basis selectPartnerTertiarySales
already uses for the DSR and the partner tier calculation
- PerformanceController: replaces two entity-loading queries (every FofoOrder and
FofoOrderItem for 6 months) with one aggregate; all items now count, each under
its own brand
- Month labels unchanged - toMonthLabel rebuilds the MMM''uu key, template untouched
- Remove V2FofoPerformanceController, the /v2/fofo JSON copy carrying the same defect
Verified against dev DB for fofo_id 175139501: Jul 2026 now 16,94,939 (panel
previously showed 50,92,440); all six months match the qty*mop basis. |
|
| 37248 |
63 d 11 h |
vikas |
/trunk/ |
Updated Escalation Level |
|
| 37136 |
77 d 8 h |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/ |
Release scheme payout and price-hike deduction on every GRN scan instead of purchase completion; serialize per-purchase processing with FOR UPDATE lock on fofo.purchase to prevent duplicate credits/debits under concurrent runs |
|
| 37089 |
86 d 11 h |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/dao/ |
Optimize partnerPerformance data access: fofoId-scoped + month-bucketed query variants
- Order: fofoId-scoped billing-avg named queries (replace all-partner scan+filter)
- MonthlyTarget: selectByDatesAndFofoId batches 7 per-month lookups into one
- SchemeInOut/OfferPayout: month-bucketed ...ByMonth earnings queries collapsing the
per-month loop; new MonthlyBrandIncomeModel / MonthlyOfferPayoutModel
All additive; existing shared queries and their callers unchanged. |
|
| 37055 |
90 d 12 h |
ranu |
/trunk/ |
allocation of hid and opening stock suggested qty logic updated as per tarun sir |
|
| 36981 |
99 d 11 h |
ranu |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/dao/repository/fofo/ |
changeList |
|