Subversion Repositories SmartDukaan

Rev

Show changed files | Directory listing | RSS feed

Filtering Options

Rev Age Author Path Log message Diff
37791 1 m amit /trunk/profitmandi-fofo/src/main/ Sale returns: Finance-only B2C settlement after a NIC GSTIN refusal

Once NIC has refused a return because the buyer's GSTIN is cancelled or
invalid, only Finance may act on it - approve, retry or reject. The pending
queue shows the refusal reason and, on the day it happened, a "Refund as
B2C" action; Logistics sees "IRN failed - awaiting Finance" and no buttons.

The B2C screen states NIC's reason and every returned line with its value
including and excluding GST, and fixes the refund at the value net of GST
in whole rupees. Finance types that figure back by hand, enters a remark and
ticks the consent box before the button enables; the server re-checks the
amount, the remark, the consent and the same-day window, so the screen is a
convenience and not the control. Cancelling or finishing returns to the
queue, and a refused approval now reloads the queue so the B2C action
appears without a manual refresh.

jsVersion 438 -> 439 for return.js.
 
37790 5 m 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.
 
37789 1 h 13 m amit /trunk/profitmandi-common/src/main/java/com/spice/profitmandi/common/util/ FormattingUtils.formatDigit: print whole numbers, as intended

digitFormatter set the MINIMUM fraction digits twice and never set the
maximum, so Java's default of 3 applied: formatDigit(84745.76f) rendered
"84,745.758" - float noise at three decimals - where a whole number was
meant. The second call was clearly a typo for setMaximumFractionDigits(0).

Only one other call site (ItemCriteria "with Selling Price >= ..."), which
passes whole rupees and is unaffected. Needed by the B2C return refund,
which prints whole-rupee amounts on screen and in mails.
 
37788 22 h 55 m amit /trunk/profitmandi-cron/ activation: stop a broken JVM from spending the day's imei pool, and take the
selenium atom read out of the nested jar

Oppo/Realme activation collapsed on 21-22 Sep. Every WebDriver command failed with
java.util.zip.ZipException reading a Selenium JS atom:

W3CHttpCommandCodec.amendParameters:227 -> executeAtom:397
-> com.google.common.io.Resources.toString
-> org.springframework.boot.loader.jar.ZipInflaterInputStream.read -> ZipException

Scale, from fofo.activated_imei: a ~30-50 errors/day baseline became 17,508 on 21-Sep
and 27,768 on 22-Sep. On 22-Sep it touched 3,920 realme imeis for 0 dates and 4,824
oppo for 96, against a normal 76-100% hit rate. Realme burned its entire day pool by
11:07 and then correctly went quiet, having answered nothing.

TWO INDEPENDENT FAULTS, one fixed each way.

1. The pool was spent on an outage. restUnanswered stamps every unanswered imei so the
20-second tick advances instead of re-handing the same rows -- correct for a per-imei
failure, catastrophic for a systemic one, because a stamped row does not come back
until its rest expires. So a JVM that cannot read a jar quietly consumed a day of
payout data. Now: if NOTHING in the chunk was answered and the chunk had more than one
imei, that is infrastructure rather than a verdict, and nothing is stamped. Cost is a
re-ask of the same chunk next tick -- loud and self-limiting -- instead of the day.
Same class of bug as the carlcare transient refusal (r37537): far-end/our-end noise
must never be recorded as an answer.

2. The read itself. Selenium loads its atoms as classpath RESOURCES on essentially
every command, which inside a fat jar is a nested-jar read on the Spring Boot 2.0.2
(2018) loader. Three different inflater errors appeared on prod -- "invalid stored
block lengths", "invalid distance too far back", "invalid code lengths set" -- on a jar
whose outer AND extracted nested archives both pass `unzip -t`. Intact bytes with three
distinct inflater failures is a reader fault, not a file fault. bootJar now sets
requiresUnpack for selenium-remote-driver, so it is extracted to a real file at launch
and the atom read never touches the nested reader. Verified in the built jar: the entry
carries UNPACK:<sha1> and is STORED rather than DEFLATED.

TRAPS WORTH RECORDING.

- A restart is NOT a diagnosis here. It was restarted 13:16 on 22-Sep and still failed
for three more hours at ~1,260/hour, then a 16:36 restart came up clean -- same jar,
mtime unchanged. Anyone reading "restart fixed it" should distrust it.
- Concurrency alone does not explain it: up to 2 scheduler pools ran these tasks per
minute in the broken window AND in the healthy one.
- The GlitchTip board under-reported this badly (#1495 lastSeen 17-Sep while the log
held thousands on 21-22 Sep), so the board is not a reliable outage signal for cron.
Judge this lane on dates written in fofo.activated_imei, not on issue counts.
- Deploy the cron jar stop -> replace -> start. The jar on disk was overwritten in place
at 12:31 on 21-Sep while a JVM held it open, which is how this started.
 
37787 1 d 16 h amit /trunk/profitmandi-fofo/src/main/java/com/spice/profitmandi/web/controller/ feat(partner-access): demo grant picker lists Platinum partners only

Partner multi-select on Demo Partner Access filters active partners to the
current effective tier PLATINUM (PartnerTypeChangeService.getTypesForFofoIds,
same lookup as Indent/Order Management). Active grants table unchanged.
 
37786 2 d 21 h amit /trunk/profitmandi-fofo/src/main/ fix(po): read PO lines only from the PO items table

Create PO collected rows from every table on the page ($("table > tbody > tr")). The aging-SKU
approval modal added in r37750 has its own table (#pmscpFlaggedRows); after a flagged attempt was
abandoned, its rows were sent as lines with itemId 0 and the server rejected the PO with
"Items are duplicate [0]". The create, add-row and over-cap loops now read #purchase-order-table only.
Bump JS version to 438.
 
37785 2 d 22 h amit /trunk/profitmandi-dao/src/main/resources/sql/ feat(cs): demo partner access menu for Sales L6 and above

menu_category rows for Sales (category 4) ordinals 5-9 (L6..Final) on the
Demo Partner Access menu. INSERT IGNORE on the PK (latin1 column rejects a
utf8-literal NOT EXISTS compare). Applied on hadb1 2026-09-25.
 
37784 2 d 22 h vikas /trunk/ Corrected LMS Data and Added filter  
37783 2 d 23 h vikas /trunk/profitmandi-fofo/src/main/ Corrected LMS Data  
37782 2 d 23 h amit /trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/dao/ fix(catalog): add serializedOnly overloads of selectAllItems/selectAllBrands

r37776 committed InventoryServiceImpl calling the 3-arg selectAllItems and
selectAllBrands, but the repository overloads and the named-query parameters
they bind were left uncommitted, so a build from trunk failed.

serializedOnly = true restricts the model/brand pickers to IMEI items; the
2-arg forms delegate with false, unchanged behaviour.
 
37781 3 d 1 h amit /trunk/profitmandi-web/src/main/java/com/spice/profitmandi/web/ feat(partner-access): demo partners in app switcher + impersonate

- /getPartners, /getPartnersList: position partners + active demo partners
(copies the cached getAuthUserPartnerEmailMapping set, never mutates it)
- /impersonate: an active demo grant also passes the access check

Needs dao r37779 and migration_demo_partner_access.sql applied.
 
37780 3 d 1 h amit /trunk/profitmandi-fofo/src/ feat(partner-access): demo partner access page + validation

- Demo Partner Access page (Admin Control): grant partners to a Sales person,
list and revoke active grants; server-guarded by canManage
- Partner access dropdown: position partners + active demo partners "(Demo)";
shared cached mapping is copied, never mutated
- /login-as-partner-readonly: partner must be in caller's positions or demo
grants (was unchecked; Partner access is its only caller)
- /mobileapp?emailId=: Sales position holders get the partner app token only
for position/demo partners; other admins (Partner Info) unchanged
- DemoPartnerAccessTest (5, local DB, rolled back)
- jsVersion 437

Needs dao r37779 and migration_demo_partner_access.sql applied.
 
37779 3 d 1 h amit /trunk/profitmandi-dao/src/main/ feat(cs): demo partner access for sales team

New cs.demo_partner_access binding (sales auth user -> partner) used only to
open a partner's dashboard/app for demos. Read only at partner-view entry
points; CsService position mappings are untouched, so performance, targets,
reports and cron mails are unaffected.

- DemoPartnerAccess entity + repository
- DemoPartnerAccessService: grant (sales-only, active store, skips duplicate
and position-mapped), soft revoke, isAllowed (position OR demo),
demo emails for app lists, canManage (mirrors sidebar menu rule)
- AdminUser.ALL_MENU_EMAILS extracted so canManage and the sidebar share it
- migration_demo_partner_access.sql: table + "Demo Partner Access" menu
under Admin Control (NOT yet applied on hadb1)
 
37778 3 d 16 h amit /trunk/profitmandi-dao/src/main/ fix(catalog): delist cron retires only models it darkened in the same run

Step 4 scanned every mobile model with no active listing and moved it to OTHER,
so new launches (categorised before listing) and hand-paused models were retired
the next morning, e.g. Nothing Phone 4 1026596-603 on 2026-09-15. Scope it to
models whose listing this run delisted (_delist_audit run_date = today).
Add fix_dark_model_retire_20260924.sql to restore the 16 models still stuck on OTHER.
 
37777 3 d 18 h amit /trunk/profitmandi-fofo/src/ returns: finance endpoint to reverse a return whose goods never reached the warehouse

PUT /return/reverse?imei=&reason=&dryRun= calls ReturnReversalService (r37776) for one
IMEI. Finance only, same canRefund gate as the refund it undoes, and dryRun defaults to
true so a call without it reports the plan and writes nothing.

ReturnReversalTest covers the four cases against the local database with NIC on the
sandbox: the dry run writes nothing, a note past its 24h window issues a DBN and undoes
every effect of the refund (warehouse scan and stock, partner stock and offers, order
status, wallet, return item, debit note, audit row), a note inside the window has its IRN
cancelled and drops out of the statement, and bad input or a second reversal is refused.
Run it as `gradle :test --tests ...` - without the colon the filter also reaches
profitmandi-common and fails with "No tests found".
 
37776 3 d 18 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.
 
37775 3 d 18 h ranu /trunk/ aging po approval process  
37774 3 d 19 h amit /trunk/profitmandi-fofo/src/main/ feat: show sanction request investment numbers as whole rupees

Adds a global $nf0 Velocity formatter (en-IN grouping, 0 decimals, HALF_UP) and
uses it for Total/Short Investment on the sanction request and RBM L2 screens.
 
37773 3 d 19 h amit /trunk/profitmandi-fofo/src/main/java/com/spice/profitmandi/web/controller/ fix: show live partner investment on sanction request approval screens

Total/short investment on /getSanctionRequest, /getRbmL2SanctionRequest and the
row re-render after submit now read the 2-minute partner_investment snapshot via
PartnerInvestmentService instead of yesterday's partner_daily_investment row.
 
37772 3 d 19 h amit /trunk/profitmandi-web/src/main/java/com/spice/profitmandi/web/v2/controller/fofo/ fix(supplier): V2 supplier save derives state from GSTIN  

Show All