| Rev |
Age |
Author |
Path |
Log message |
Diff |
| 37707 |
4 m |
amit |
/trunk/profitmandi-fofo/src/main/ |
GRN price mismatch: say which PO the invoice will be received against, and what to do if it closes first
Resolving a price mismatch discards the PO line and raises a replacement dated to the invoice. That
replacement is what the invoice is received against, so it is the one that has to stay open long
enough for the stock to arrive - and it closes on its own after four days for a movement, six for a
vendor order. Until now nothing said so: the GRN simply stopped matching and the correction sat
holding stock that had already arrived.
The confirmation now states what is about to happen and what to do if the stock lands late, and the
request screen explains what Po Id is. Reopening is offered only where it exists - a movement can be
reopened from Purchase Orders, a vendor order cannot and needs a fresh PO. |
|
| 37706 |
5 m |
amit |
/trunk/ |
cart: take the cart row before any line, to break a lock-ordering deadlock
InnoDB was rolling back cart edits with "Deadlock found when trying to get lock"
(GlitchTip #53, 27 deadlocks) and losing others to OptimisticLockException
"actual row count: 0; expected: 1" (11 issues, 42 events). Both are the same cause.
Two request paths took the same two rows in OPPOSITE orders. Adding a line INSERTs
into user.line, and line_cart_id_fk shared-locks the parent cart FIRST, line second.
Validation/hydration mutated the line rows FIRST and wrote cart.total_price second.
Run those concurrently on one cart and it is a cycle. Caught in the act on prod:
T1: INSERT user.line (cart_id=175180781) -> holds S on cart, waits S on line
T2: UPDATE user.cart SET total_price=269340.0, version=175 WHERE version=174
-> holds X on line, waits X on cart
*** WE ROLL BACK TRANSACTION (1)
Fix is to give every mutating path one order: cart, then lines. CartRepository gains
selectByIdForUpdate, and it is called as the first statement of each path that writes
a cart line. Re-taking it inside one request is a no-op.
A lock-ordering fix is all-or-nothing -- one path in the wrong order is enough to
re-form the cycle -- so this covers ALL TEN cart-line writers, not just the two that
happened to show up in the stack traces: createCartItem, clearCart, getCartValidation,
addItemsToCart (x2), addShoppingBag, validateForOpen (x2) and V2BillingController's
bind/unbindInsurance. The audit is worth re-running before adding another writer.
Note validateForOpen and getCartValidation WRITE despite their names (they correct
cart_line quantity/price and roll up cart.total_price), which is why a "validate" call
was ever holding write locks. The lock is placed accordingly; making those genuinely
read-only is a separate, larger change.
This also serialises concurrent edits to the SAME cart, which is what the @Version
column on Cart was already trying and failing to express. Different carts are
different rows, so there is no cost across partners. |
|
| 37705 |
6 m |
amit |
/trunk/profitmandi-fofo/src/main/ |
PO list: show the transaction id so a movement can be found from the invoice being received
An internal movement is raised as a transaction, so the invoice on the GRN desk leads straight back to its PO
through that id. The list is a DataTable, so having the column makes it searchable - which is how someone finds
the PO to reopen when a receipt arrives after auto-close, instead of hunting through date ranges.
Placed after Status so existing column positions are unchanged. |
|
| 37704 |
7 m |
amit |
/trunk/ |
Reopen a movement PO whose stock arrived late, and stop stranding GRN price corrections
Internal movements auto-close after four days, which fits 99.6% of them - 5,491 of 5,515 receipts
land inside the window. The remainder leave the PO closed with the stock still in transit and
nowhere to receive it: 998 internal POs closed during 2026 still holding 21,996 unreceived units.
A closed movement PO can now be reopened from the purchase order list. Reopening stamps
reopenedAt, and auto-close measures from WarehousePurchaseOrder.getOpenSince() - reopenedAt when
set, the PO date otherwise - so a reopened PO gets the same fresh window a new one gets instead of
being closed straight back on the next sweep. Only movements between our own warehouses: an
external vendor PO that has closed is settled with that vendor, not reopened unilaterally.
Separately, a GRN price correction now checks that the PO it just raised is one the invoice can
actually be received against. Matching reads POs that are open and approved for the same supplier
and warehouse dated on or before the invoice; it never looks at the PO being corrected, so what
matters is that the new PO is receivable. 54 were not - backdated into INIT by the old approval
gate, hence outside the match - and each stranded silently: original line discarded, GRN completed
without it, the correction left holding a reservation for stock that had already arrived.
isOpen() names the open set - INIT, READY, PARTIALLY_FULFILLED - that the movement and commitment
queries already read.
Migration sql/add_po_reopened_at_20260918.sql adds reopenedAt, nullable and additive. It must run
before this ships: the entity maps the column. |
|
| 37703 |
15 m |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/ |
GRN price mismatch: ask for the original PO to be reopened instead of stranding a correction PO
Resolving a price mismatch raised a correction PO dated to the supplier invoice, and stored its id
on the GRN request item to receive against. But by then the stock has already arrived and the PO it
corrects is closed - every one of the 54 raised this way had its mapped PO in CLOSED. The
correction therefore had nowhere to land: the GRN completed against the original, nothing was ever
received against the correction, and it sat holding a reservation for stock that was not coming.
Backdated by nature, it also parked in INIT under the old approval gate, so it could not have been
received even if anyone tried.
Resolving a mismatch against a closed PO now says so and names it, asking for that purchase order
to be reopened first, rather than silently creating a second PO that cannot be used.
WarehousePurchaseOrder.isOpen() names the open set - INIT, READY, PARTIALLY_FULFILLED - that the
movement and commitment queries already read, so it stops being restated per caller. |
|
| 37702 |
18 m |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/ |
Internal movement: an unapproved PO no longer dispatches stock
A PO raised on one of our own warehouses also raises the order that moves the stock, and that
order pays and processes immediately - so raising it ships goods. The approval gate never
governed that half. It set the PO to INIT and mailed an approval link, then created and processed
the order anyway, several lines later and without consulting the status it had just set. The gate
held back receiving - PO list, GRN matching, auto-close all skip INIT - while dispatch went ahead
unconditionally.
That is how PO/07-26/52029 was delivered and invoiced while frozen out of GRN. Its goods had to be
received against a second PO raised for the purpose, leaving the first holding a phantom
unfulfilled quantity for two months.
Order creation now waits on approval. WarehousePurchaseOrder.isApproved() names the rule once -
INIT is the only state before approval, every other state is something the order has already been
approved to do - matching how the PO list, GRN matching and the auto-close sweep already read it,
so callers stop restating it.
Backdated POs are approved at creation since r37682, so this holds nothing up today; it is what
keeps the two halves from separating again if any future rule leaves a PO unapproved. Nothing
raises the order later, so such a PO simply does not move stock and has to be raised again once
approved - the safe failure, and it is logged. |
|
| 37701 |
3 h 20 m |
amit |
/trunk/profitmandi-fofo/src/ |
PO create: expected stock counts toward the quantity, popover wording follows
Stock still to arrive is now part of what the order can take, so the breakdown reports it as a
qualifier on that number rather than as something set aside - 'Of these, still to arrive' instead
of 'On the way in (not yet received)', which read as though it could not be ordered.
Tests follow the two rule changes: expected receipts can be ordered against, a quantity beyond
what is held and expected together is still refused and says so, and availability counts expected
receipts toward what can move. 21 tests pass. |
|
| 37700 |
3 h 20 m |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/ |
Internal movement: order against expected stock, and stop reserving lines that can no longer ship
Two rules changed, both about stock that was being treated as unavailable when it is not.
Stock still to arrive now counts toward what a movement order can be raised for. A movement is
planned against what the warehouse will hold, so the order is raised now and dispatched once the
stock lands; previously a warehouse holding only inbound stock refused outright, even though the
receipt was already on an open order.
A purchase order line stops reserving stock once its own order moves past being submitted for
processing. Billed or shipped means the units have already come off the shelf; cancelled means
the line can never dispatch them. Holding either back a second time hid stock that was genuinely
free - on prod, 660 of 675 reserved units were on lines whose orders had already moved on. A line
whose order has not been raised at all is still a live promise and keeps its hold, and a line is
held only for the quantity still sitting in an in-process order, so a partly-shipped order
releases the rest.
priceFor and describeAvailability continue to read stock through one shared examine(), so what is
shown while choosing a quantity stays exactly what the order is created against. |
|
| 37699 |
5 h 29 m |
amit |
/trunk/profitmandi-fofo/src/main/ |
Fix: movement availability popover rendered empty except the closing note
The breakdown was built as a table. Bootstrap 3.4.1 sanitizes popover content against a
whitelist that includes neither table nor its rows and cells, and it removes a non-whitelisted
element together with everything inside it - so the whole table was dropped and only the note
survived, leaving a popover that referred to orders that were not shown.
Rebuilt from divs and spans, which are whitelisted. Sanitizing stays on rather than being
switched off for this popover: the content carries PO numbers and warehouse names read out of
the database.
Also stops the zero-quantity note pointing at orders that are not there - a warehouse whose
only stock is still inbound has nothing promised to point at. jsVersion bumped. |
|
| 37698 |
5 h 39 m |
amit |
/trunk/profitmandi-web/src/main/java/com/spice/profitmandi/web/v2/controller/fofo/ |
V2 getPricing: carry internal movement availability alongside the price
Mirrors the fofo change so the two /getPricing endpoints answer alike. V2 is dormant but
component-scanned, so it has to keep compiling against the service signature. |
|
| 37697 |
5 h 40 m |
amit |
/trunk/profitmandi-fofo/src/ |
PO create: show what can move, and which orders hold the rest, while the quantity is typed
Picking an item on a movement order already called getPricing, which read the sending
warehouse's stock and returned only a price. It now returns the availability too, so the row
can show it: an info marker beside the quantity box opens the breakdown - in stock, promised
with each holding order named by PO number and date, anything still arriving, and the quantity
this order can take.
The quantity box flags the moment what is typed passes that cap, so it is corrected before
submitting rather than after being refused. The marker turns amber when stock is partly
promised and red when none can move.
Outside vendors are unaffected: their pricing response carries no availability and their rows
are left exactly as they were. jsVersion bumped so the screen picks up the new script.
Tests cover the four cases that matter: what can move with orders named, zero movable reported
rather than refused, this order's cap kept separate from what the warehouse holds when stock
came in at two costs, and a warehouse holding none of the item still refusing. |
|
| 37696 |
5 h 40 m |
amit |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/ |
Internal movement: report what a warehouse can actually send, not just refuse on submit
The quantity a movement order can take was only discovered by submitting the order and being
refused. The stock reading that decides it already computed everything needed to say so in
advance - what is on the shelf, what older orders have promised, what is still arriving - and
then discarded it.
selectOpenOutboundCommitments returns those promises one row per order, with the PO number,
PO date and destination, instead of a single summed quantity. applyOutboundCommitments now
aggregates those rows and behaves exactly as before, so no extra query is run.
describeAvailability answers with the price plus that working. It shares one stock reading with
priceFor via examine(), so the cap shown while choosing a quantity and the cap enforced on
submit cannot drift apart. Unlike pricing it reports rather than refuses: stock that is entirely
promised comes back as zero movable with the orders holding it named, which is what lets someone
chase an abandoned order rather than only see a smaller number. |
|
| 37695 |
10 h 6 m |
amit |
/trunk/profitmandi-fofo/src/test/java/com/spice/profitmandi/service/warehouse/ |
test(warehouse): cost layer tests; update movement tests for cost-based allocation
- InventoryCostLayerTest: local-DB tests for layers vs stock on hand, movement priced at recorded cost, refusal across costs, promised stock held back, pending inbound offered but not dispatchable, and GRN recording vendor and cost
- InternalMovementPricingServiceTest: layers now keyed by cost, so same-cost stock moves together and a different cost needs its own order
- BillingPricingServiceTest: compare against recorded origins rather than the serial-trace source |
|
| 37694 |
10 h 6 m |
amit |
/trunk/profitmandi-dao/src/main/ |
feat(warehouse): record where stock came from and what it cost; price internal movements at that cost
- inventoryItem.origin_vendor_id / receipt_unit_cost and lineitem.origin_vendor_id (sql/add_inventory_cost_layer_20260917.sql, applied on prod 2026-09-17 in 75s): GRN copies both from the PO line it arrived on, so origin survives any number of hops and non-serialised stock keeps it too. Unknowable origin stays NULL - never a guess, never an internal supplier
- A movement now moves stock at the cost it was received at, not the origin vendor's current catalog TP. Layers are grouped by cost; a quantity spanning two costs is refused with both numbers, since a PO line carries one price
- Stock already promised on open outbound movement orders is no longer offered again (176 item/warehouse pairs on prod are fully promised today)
- Stock on open inbound POs is offered for planning at that order's price and reported as 'on the way in', but cannot be dispatched until received; the PO item picker lists those items too
- Backfill: 4,010 of 4,646 in-stock rows on prod got origin and cost; 636 stay unknown and are priced from the latest approved external catalog price as before |
|
| 37693 |
10 h 9 m |
ranu |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/migrations/ |
rbm ranking mailer |
|
| 37692 |
10 h 13 m |
vikas |
/trunk/ |
LMS + Airtel Calling |
|
| 37691 |
10 h 14 m |
ranu |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/migrations/ |
rbm rankijng mail |
|
| 37690 |
10 h 24 m |
ranu |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/migrations/ |
notification scheduler |
|
| 37689 |
10 h 25 m |
ranu |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/migrations/ |
notification scheduler |
|
| 37688 |
10 h 26 m |
amit |
/trunk/profitmandi-dao/src/main/resources/sql/ |
Record that the counter/privatedealuser rename was applied on hadb1 (suffix _dropped_20260917) |
|