| Rev |
Age |
Author |
Path |
Log message |
Diff |
| 37801 |
10 d 7 h |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/service/integrations/pinelabs/ |
Disable Pine Labs offer discovery behind a config switch (default off)
Turns off the live Pine Labs per-item offer discovery on the user's
instruction. New pinelabs.offer.discovery.enabled, default FALSE, so the
integration is off in every environment unless a properties file opts in.
Gated at the single point all discovery funnels through
(PinelabsAffordabilityServiceImpl.discoverOffers), so one switch stops the
nightly cache loop, a single-item refresh and the /pinelabs/offers endpoint
without editing the six call sites. discoverOffersRawJson gated too.
cacheAllItemOffers returns before its listing query, since the loop costs
4,507 items and ~12 minutes of a cron thread even when every call is a no-op.
Degrades gracefully rather than breaking the product surface: an empty
OfferDiscoveryResponse is exactly what the existing catch block returns, and
getGroupedCachedOffersForItems already drops an entry with an empty issuer
map. The Redis cache is a 24h TTL, so live badges drain within a day rather
than vanishing mid-request.
The @Value carries a default deliberately — pinelabs.api.base.url and the
credentials carry none, so a missing key stops the whole context (the r37495
class of outage). A kill switch must never be able to do that.
SCOPE: offer discovery ONLY. Payment-gateway traffic (orders, refunds,
callbacks, webhooks) and offer create/validate/downpayment are untouched,
so no live money path changes behaviour.
@Scheduled on ScheduledSkeleton.fetchOffersByItem is intentionally left in
place — with the flag off it is one log line, and re-enabling stays a config
change rather than a code change.
Build: dao + web + cron + fofo compile, BUILD SUCCESSFUL. |
|
| 37800 |
10 d 9 h |
ranu |
/trunk/profitmandi-common/src/main/java/com/spice/profitmandi/common/model/ |
offer-radar notification commit |
|
| 37799 |
10 d 10 h |
ranu |
/trunk/ |
offer-radar notification commit |
|
| 37798 |
10 d 11 h |
amit |
/trunk/profitmandi-fofo/src/main/ |
Show a settled debit note as settled instead of offering Receive
The DN screens decided everything from "does a purchase_return_order exist",
which no return settled before the receive workflow ever had. A note settled
years ago therefore read "Pending Receive" with a Receive button, and receiving
one handed a partner back a phone already returned, refunded and resold
(DN UPBLY975/4, IMEI 864973083197734).
The debit note's own status now carries the fact - CREATED means there is
genuinely something to receive, anything else means it is settled - so the
screens read it directly instead of re-deriving it from warehouse scans on
every page load. r37797 backfilled the 4,154 notes the old flow left behind.
- invoice-return-results, debit-notes-table, debit-note-details,
receive-debit-note: Receive only while the note is CREATED with no return
order; otherwise "Processed - settled earlier".
- debit-notes-table: Refund only on a note received and not yet settled,
rather than on every row.
- PurchaseReturnController: the admin debit-note list now loads the return
orders its Refund button needs.
Deploy with profitmandi-dao r37797. |
|
| 37797 |
10 d 11 h |
amit |
/trunk/profitmandi-dao/src/main/ |
Stop a settled debit note being received a second time
A return settled before the receive workflow (first purchase_return_order
2026-03-16) wrote the warehouse SALE_RET scan, a returnorderinfo row and a
wallet refund, but never touched the debit note: it stayed CREATED with no
purchase_return_order. Those are exactly the two things the receive screens
read, so 5,557 fully settled notes looked like they were still awaiting
receipt, Receive button included.
Receiving one of them (DN UPBLY975/4, IMEI 864973083197734, Antu Enterprises)
restored to the partner a phone that had already been returned, refunded
Rs 38,999 and resold to another partner, and a duplicate debit note followed
that Finance could not refund.
- receiveDebitNoteItems: refuse a note that already has a return order, one
whose status is not CREATED, and one whose every unit is already back at the
warehouse. Checked BEFORE the condition-mismatch gate - a mismatch there
returns into rejectOnConditionMismatch without reaching any later check, and
acknowledging that rejection hands the stock back to the partner. That is
how the Antu case slipped through.
- getAlreadyReturnedDebitNotes: the last of those checks, batched for a page of
notes. Mirrors applyReceipt - SALE_RET/DOA_IN/SALE_RET_UNUSABLE for the unit
against the order its invoice was billed on - so a screen can never disagree
with what the refund step will accept.
- processInvoiceReturn: one open return per document.
- sql/backfill_debit_note_status_old_flow_20260924.sql: APPLIED on hadb1
2026-09-24. 4,154 notes -> APPROVED, 6,639 items -> RETURNED, on the same
evidence (returned and refunded). Open notes 5,557 -> 1,404; the rest are
left CREATED for review, bucketed in fofo._dn_backfill_20260922. |
|
| 37796 |
10 d 11 h |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/dao/entity/user/ |
fix(cart): order with a carry bag fails - Could not copy property 'discountedPrice'
splitCarryBagLine (r37792) copies the bag line with BeanUtils.copyProperties.
CartLine.discountedPrice is a nullable Float but its getter returned primitive
float, so unboxing the null threw NPE, surfaced as InvocationTargetException,
failing every order whose cart had a carry bag. Accessors now take/return Float;
they have no other callers. |
|
| 37795 |
10 d 12 h |
ranu |
/trunk/profitmandi-fofo/src/main/webapp/WEB-INF/views/ftl/ |
offer-radar notification commit |
|
| 37794 |
10 d 12 h |
ranu |
/trunk/ |
offer-radar notification commit |
|
| 37793 |
10 d 12 h |
amit |
/trunk/profitmandi-web/src/main/java/com/spice/profitmandi/web/ |
feat(cart): carry bag pricing via CarryBagQuote in V2 cart and order create
V2CartController prices and rebalances the carry bag through
CartService.getCarryBagQuote: one bag at Rs 1 per smartphone over Rs 12,000,
extras at listing, Bronze at listing for all. The old resolveCarryBagPrice /
CARRY_BAG_PREMIUM_PRICE path and its partnerTypeChangeService dependency are
removed. OrderController splits the blended carry bag line into its two
priced orders when partitioning the cart, so the same SKU appears twice on
one invoice at its two prices.
Needs profitmandi-dao r37792; deploy together with the partner apps. |
|
| 37792 |
10 d 12 h |
amit |
/trunk/profitmandi-dao/src/ |
feat(cart): carry bag at Rs 1 per smartphone over Rs 12,000, extras at listing price
One carry bag (item 32046) is priced at Rs 1 for each smartphone (category
10006) in the cart selling above Rs 12,000; bags beyond that count are priced
at the carry bag's listing price, and Bronze partners pay listing for all.
Closes the leak where bag-only carts of hundreds of bags at Rs 1 were placed
from the old app. CarryBagQuote (via CartService.getCarryBagQuote) is the
single rule; the cart carries it as one line at a blended price.
- CartService/CartServiceImpl, CartResponse, OpenCartValidationResult: expose
the quote on the cart.
- OrderLineAllocator: an item may now have more than one cart line (the flat
and listing-priced parts); its units fill the lines in order.
- TransactionServiceImpl: item quantities merge instead of failing on a
duplicate item id.
- PurchaseServiceImpl: partner GRN creates one stock record per billed price;
the GRN screen shows the blended unit price; dead
createScannedNonSerializedItem removed.
- PurchaseReturnServiceImpl: debit-note PDF picks the order billed at the
returned stock's price; refundOrder spreads a non-serialized return over the
item's orders with room left (same price first) instead of loading it all
on the first order.
Tests: CarryBagQuoteTest 6/6, OrderLineAllocatorTest 13/13.
Deploy with profitmandi-web (same change) and the partner apps. |
|
| 37791 |
10 d 13 h |
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 |
10 d 13 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. |
|
| 37789 |
10 d 14 h |
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 |
11 d 12 h |
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 |
12 d 5 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 |
13 d 10 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 |
13 d 11 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 |
13 d 12 h |
vikas |
/trunk/ |
Corrected LMS Data and Added filter |
|
| 37783 |
13 d 12 h |
vikas |
/trunk/profitmandi-fofo/src/main/ |
Corrected LMS Data |
|
| 37782 |
13 d 13 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. |
|