Subversion Repositories SmartDukaan

Rev

Show changed files | Directory listing | RSS feed

Filtering Options

Rev Age Author Path Log message Diff
37817 8 d 3 h amit /trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/ fix(movement): shelf stock before arriving, one PO per cost, close PO on invoice cancel

- Internal movement fills from shelf stock before stock still arriving (a pending PO dated at
midnight ranked ahead of same-day receipts, pricing movements from units that never shipped).
- A quantity spanning stock at different costs is raised as one PO per cost at once instead of
being refused (first PO takes every item's oldest cost, the next the following cost).
- Refuse a movement from a warehouse to itself.
- Cancelling a movement's invoice/DC takes the order's qty off its PO and pre-closes it when
nothing is left open, as refunds already did.
 
37797 10 d 3 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.
 
37792 10 d 4 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.
 
37790 10 d 5 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 0 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.
 
37647 22 d 22 h amit /trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/service/ Invoice/DN return: file credit note at NIC only when the invoice IRN was filed

- GstProService.hasFiledIrn: invoice has a real IRN with ack date (DCs excluded)
- applyInvoiceReturnViaCreditNote: filed IRN -> CN + NIC CRN as before;
not filed + INTERNAL buyer -> CN issued locally, NIC skipped;
not filed + other buyer -> refund without CN, CN sequence untouched
- DN refund path: not filed + INTERNAL buyer -> local CN, NIC skipped
Fixes approval failing with 'Recipient GSTIN state code does not match' on
internal invoices whose IRN was never generated (NSPRJ41943, NSPRJ41950).
 
37634 23 d 3 h amit /trunk/profitmandi-dao/src/main/ feat(pricing): tag_listing price change log; retire vendoritempricing writes/reads in dao; price drops no longer touch vendor catalog pricing

- New catalog.tag_listing_price_log (sql/create_tag_listing_price_log_20260915.sql, applied on prod 2026-09-15) + TagListingPriceService: the single setter for tag_listing DP/MOP/MRP, logging old/new values, source and user
- Remove applyPriceDrop (r37622): price drops write only price_drop + tag_listing; sql/revert_price_drop_vendor_catalog_pricing_20260915.sql restores the 30 rows it wrote (run on prod)
- VendorCatalogPricingService: reference TP = latest approved external TP (single + batched); internal suppliers rejected on create, bulk upload and approval
- Stop copying pricing into vendoritempricing on approval and item upload; tag listing detail TP from reference TP; indent DP from tag_listing only
 
37622 24 d 3 h amit /trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/ feat(billing): price warehouse billing from the external supplier of the billed stock; auto-approve price drop DP/MOP into vendor catalog pricing

- BillingPricingService resolves TP/NLC per order from vendor_catalog_pricing of the most recent external supplier of the units scanned out (serial trace, else own external PO); units reversed by SALE_RET are ignored
- Falls back to the latest approved external catalog price when no supplier can be traced; vendorId stays the warehouse vendor
- addBillingDetailsForGrouppedOrders no longer reads vendoritempricing (removes NPE when the row is missing); order.vendorId set to the origin supplier
- VendorCatalogPricingService.applyPriceDrop writes approved pricing logs for external vendors with the price drop DP/MOP, keeping each vendor's TP on the effective date
 
37588 27 d 2 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
 
37563 28 d 17 h amit /trunk/profitmandi-dao/src/main/ Filter delisted SKUs out of creation pickers; add rolling 24m delist migration

Creation screens could offer items whose catalog.tag_listing.active = 0:
- Item.selectAllModels (scheme/offer model dropdown) joined tag_listing with
no active predicate. Adding tl.active = true also gives the model-level
rollup, since callers dedupe to catalogItemId: a model whose colours are
all inactive now yields no rows, one keeping a listed colour stays.
- The shared partner item picker had no active filter at all. Added a second
basis via getAllPartnerItemStringDescription(anyColor, activeOnly);
activeOnly participates in the fofoItems cache key so the two bases cannot
serve each other's cached results. Default stays false for screens that
inspect or edit existing records.
- New TagListing.selectActiveItemIds named query backs the filter.

Also adds the rolling 24-month delist migration: zero stock both sides, no
outstanding vendor PO (status IN (1,2), external supplier, unfulfilled > 0 --
INIT is excluded as it is a drawer of stale drafts), and re-categorises
fully-dark mobile models to OTHER. Idempotent, audited, rollback documented.
 
37535 33 d 21 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.
 
37529 35 d 2 h amit /trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/service/ purchase return: auto-approve invoice cancellation where it is safe

GstProService.isCancellableWithoutApproval: a DC invoice cancels without approval;
anything else needs the order to be unshipped AND the IRN to still be cancellable.

PurchaseReturnServiceImpl marks the order INVOICE_CANCELLED with the refund actor,
timestamp and reason recorded, and sets each line item's returnQty to its full
quantity - a cancelled invoice returns everything on it.

Committed on behalf of the working copy; dao goes first so the method exists before
the fofo controller that calls it.
 
37464 38 d 9 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.
 
37308 56 d 1 h amit /trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/service/inventory/ DN receipt: reject on condition mismatch instead of silently reclassifying

The receive screen defaulted the Condition dropdown to GOOD and receiveDebitNoteItems
overwrote purchase_return_item.type with whatever was submitted. A DOA return received
without touching the dropdown was rewritten BAD->GOOD, so on reject -> acknowledge the
restoreInventory branch put the unit back into good (sellable) stock instead of bad.
UKHD948/57: IMEI 353243710802439 was declared DOA, rejected as 'wrong entry', restored
as good stock and resold at MOP 3h44m later.

- receiveDebitNoteItems now compares each submitted condition against the stored one and
rejects the WHOLE debit note on any mismatch (PRO + rejectReturn are both per-DN and
there is no model for a partial receipt). PRO is still persisted so the receipt attempt
stays on record, then stamped rejected; no scans, no refund, no IRN work.
- Removed the pri.setReturnType(rt) overwrite. Past the gate the submitted condition always
equals the stored one, and applyReceipt already reads it off the entity. Rejected returns
now keep the declared type, so restoreReturnedItems restores to the right bucket.
- New notifyReturnRejectedConditionMismatch: to partner, cc Logistics L2/L3, RBM L1/L2 and
Sales L1 + first populated level in L2..L4. Sales ladder is sparse (L2 262 partners,
L3 64, L4 1582 of 1524 open stores) so a fixed L2/L3 rule resolves to nobody for most.
- getTeamEmails now filters inactive users and dedupes. csService.getAuthUserByCategoryId
(2-arg) does not filter active unlike its 1-arg sibling; fixed here rather than in
CsServiceImpl because that overload has ~25 other call sites where dropping inactive
users would change report scoping.
 
37290 58 d 17 h amit /trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/service/ Credit Note IRN: credit only the returned units, not the whole invoice

refundOrder persisted the CreditNote and its lines from the returned
inventory items, but then called generateCreditNoteIrn(invoiceNumber, ...),
which rebuilt the NIC payload by re-reading the original invoice - every
line at billed quantity. A 1-pc DOA return against NSDL37092 therefore
tried to register a CN covering the full invoice (2 phones + 27 carry
bags, Rs 26,664.27) against a Rs 13,332 wallet refund, and the legacy
4-digit HSN on the carry-bag line failed NIC validation with error 2311.

The wallet credit, the CreditNote row and its lines were always correct;
only the e-invoice payload was wrong.

- InvoiceService.getInvoicePdfModelForIrn: optional returnedQtyByOrderId
restricts the item list to the returned orders and prices each line at
the returned quantity. The existing single-arg method delegates with
null, so invoice PDF generation is unchanged. Margin-scheme, delivery
challan, IMEI-suffix and HSN handling apply to the reduced line as-is.
- GstProService.generateCreditNoteIrn: 4-arg overload taking the map; the
3-arg version delegates with null for whole-invoice returns.
- PurchaseReturnServiceImpl.refundOrder: passes orderReturnQtyMap, which
was already built for the ReturnOrderInfo rows.

Verified against the NIC sandbox using the real NSDL37092 rows: the old
payload is rejected with error 2311, the new payload is accepted
(DocTyp CRN, ItemCnt 1, MainHsnCode 85171300, TotInvVal 13331.99).

Not covered here: applyInvoiceReturnViaCreditNote still sends the whole
invoice, so an invoice partially returned earlier re-credits those units.
 
37244 63 d 9 h amit /trunk/profitmandi-dao/src/ Hot deal attributes: five mandatory fields (warranty_months 0-12, item_condition NEW/OPEN_BOX/REFURBISHED, activated, finance_mapping, affordability) on model_hot_deal - entity+enum+attributes holder, service validation (no defaults), addDeal/updateDeal signatures, selectActiveByCatalogItemIds/selectAllActive repo queries, hotDealTags on FofoCatalogResponse, DDL script, tests (14 green)  
37136 77 d 2 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  
37132 77 d 7 h amit /trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/service/inventory/ Add bulk approval of vendor catalog pricing via file re-upload: approveBulkVendorCatalogPricing validates re-uploaded file rows against pending requests (all-or-nothing) and approves them; extract single-row approval into approveVendorCatalogPricingLog; share workbook parsing between upload and verify paths  
37024 93 d 7 h amit /trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/service/inventory/ FOFO add-to-cart: show held Live Demo IMEIs as disabled 'Sellable from <date>' checkboxes instead of falling back to quantity field  
36999 98 d 2 h amit /trunk/profitmandi-dao/src/main/ Price-hike deduction (self-contained): revert hike logic from price-drop flow + remove deduct_on_hike flag; add GRN hook (PurchaseServiceImpl, before schemes) and on-demand executor endpoint that debit under-charged units billed in the hike's [affected_on, created_on] window. Idempotent per (hike, imei).  

Show All