| Rev |
Age |
Author |
Path |
Log message |
Diff |
| 37850 |
2 d 20 h |
amit |
/trunk/profitmandi-dao/src/main/ |
refactor(warehouse): retire hardcoded Delhi ids - allocation fallback vendor warehouse 7573 -> 13368 (central HR-NSSPL/GGN); Vivo Noida/Ghaziabad partner-sale rule 7573 -> 13370; drop unused InventoryWarehouse WH_DL/WH_HR_DL/WAREHOUSE_IDS; migration 20261005_hr_ggn_central_brand_mapping.sql (13368 central for every ACTIVE warehouse, all brands except Oppo/Vivo, 532 rows, applied on hadb1) |
|
| 37804 |
9 d 15 h |
amit |
/trunk/profitmandi-dao/src/main/ |
fix(fofo-store): store every GSTIN in upper case at the entity setter
FofoStore.setGstNumber now normalises through StringUtils.normalizeGstNumber,
so no write path can store a mixed-case GSTIN. Only the partner-update path
normalised before; the LOI and trial-form paths wrote the field directly,
which is how 06bmlpk3545g1z0 and 03AAccn9802g1zp reached the table. NIC
expects upper case, and case-sensitive comparisons (including the own-seller
GSTIN check behind a NIC refusal) silently failed to match.
uppercase_fofo_store_gstin_20260928.sql corrects the four rows already on
record; it is already applied on hadb1 (0 mixed-case GSTINs remain). |
|
| 37790 |
10 d 1 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 |
13 d 19 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 |
13 d 22 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 |
|
| 37656 |
21 d 22 h |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/ |
Point dao at the relocated KYC and SD Credit types (r37655)
Imports follow services.mandii -> services.kyc / services.sdcredit, and
RecordingService takes RawHttpResponse in place of MandiiResponse.
Gateway.MANDII is kept, with a comment saying why: it is persisted as a
string on FofoPayment.gateway and CreditAccount.gateway, and 740 historical
fofo.payment rows still carry it - removing the constant would make
Hibernate throw when reading them. |
|
| 37599 |
26 d 19 h |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/ |
Reuse the managed snapshot row in the sweep instead of building a second instance
Every sweep after the first failed with 'A different object with the same
identifier value was already associated with the session :
PartnerInvestment#107198' and wrote nothing.
selectByFofoIds returns managed entities. The loop then constructed a fresh
PartnerInvestment for the same id and called saveOrUpdate on it, which Hibernate
rejects. Sweep one survived only because the table was empty, so no row was
managed - the failure could not appear until a second run.
Now mutates the row already in the session when there is one, falling back to a
new instance only for partners with no snapshot. The previous base and gross stock
are captured before mutation, since comparing the row against itself afterwards
would report nothing as ever changed.
persist() is kept: a no-op for the managed rows (dirty checking flushes them), and
still the INSERT for partners appearing between sweeps.
Drops baseDiffersFrom(), now unused - the comparison is against the captured prior
value, still rounded to paise by the setter on both sides. |
|
| 37592 |
26 d 20 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 |
26 d 22 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 |
|
| 37481 |
37 d 21 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 |
37 d 23 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 4 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. |
|
| 37408 |
43 d 3 h |
amit |
/trunk/ |
Vivo IMEI activation: stop spending captchas on line items with no IMEI
76% of production captcha rejections were line items whose serial number is
null. Vivo answers those with {"msg":"参数为空"} -- "parameter is empty" --
and status 0, which this code recorded as a captcha failure. So a correctly
solved captcha looked wrong, no activated_imei row was written, the line item
stayed pending, and it came back every 5 minutes indefinitely. Those rows were
permanently consuming roughly 43% of the run quota, which is why every tick ran
full at 20/20 and the backlog never drained.
It also made the model look far worse than it is: measured accept rate 44%,
while the same solver scores 85-93% when the IMEI is present. True captcha
accuracy is around 77%.
Fixed at source: both named queries now exclude null and blank serial numbers,
so such line items never enter the pool (this also covers the Realme caller).
The loop additionally skips them before fetching a captcha, so no captcha,
solver call or Vivo request is spent discovering it.
Found via the diagnostics added in r37407 -- the previous log line recorded
five words and discarded the response that named the cause. |
|
| 37319 |
54 d 21 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. |
|
| 37204 |
65 d 3 h |
amit |
/trunk/profitmandi-dao/src/ |
external api v2: category spec + client category entities, cached category exposure, title util, item.brand_identifier, ContentPojo categoryId/categorySpecs |
|
| 37149 |
73 d 0 h |
amit |
/trunk/profitmandi-dao/src/main/ |
External partner API (dao): api_client/api_client_store auth tables + entities, store-scoped feed queries (ExternalFeedRepository), ApiClientAuthService + ExternalApiService, update_timestamp delta-sync columns on tag_listing/item/catalog/current_inventory_snapshot (migration_external_api_client.sql), Mongo siteContent lastModified stamp + paged reads. Prices exposed are mop/mrp only. |
|
| 37089 |
86 d 0 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 2 h |
ranu |
/trunk/ |
allocation of hid and opening stock suggested qty logic updated as per tarun sir |
|
| 37009 |
96 d 19 h |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/dao/entity/fofo/ |
Fix Selling Price - DP in IMEI drill-down to use actual inventory-item DP
selectLastMonthFrontEndImei (the per-IMEI breakdown behind each brand/model row)
still passed foi.dp (frozen list DP) into LastMonthFrontEndImeiModel, so the
drill-down did not reconcile with the brand/model totals fixed in r37008. Now
uses ii.unitPrice-ii.priceDropAmount, matching selectLastMonthFrontEndByImei. |
|
| 37008 |
96 d 19 h |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/dao/entity/fofo/ |
Fix Selling Price - DP to use actual inventory-item DP instead of frozen list DP
Brand-wise (selectFrontIncomeByBrand) and model-wise (selectFrontIncomeBrandWise)
front-income queries computed the margin as (foi.sellingPrice - foi.dp), where
foi.dp is the TagListing list price frozen on the sale line at order time. This
produced spurious negative margins (e.g. -6000 for units sold at cost). Now uses
the actual per-IMEI acquisition DP (ii.unitPrice - ii.priceDropAmount), the same
net-DP basis used elsewhere (InventoryItem.getNetPrice / scheme payout calc). |
|