Subversion Repositories SmartDukaan

Rev

Show changed files | Details | Compare with Previous | Blame | RSS feed

Filtering Options

Rev Age Author Path Log message Diff
37862 4 d 6 h ranu /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/migrations/ rbm ranking modal for september  
37858 4 d 8 h ranu /trunk/ remove tarun sir email from some storetimline tat email  
37857 4 d 9 h ranu /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/migrations/ remove tarun sir email from some storetimline tat email  
37848 5 d 7 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ feat(warehouse): daily 10:00 mail of stock in suspended/inactive warehouses (--sendSuspendedWarehouseStockAlert for a one-off); Shopify sync warehouse from shopify.warehouseId (default 13372 HR-NSSPL/UPW) instead of WAREHOUSE_NAME_MAP UP-WEST/NOIDA  
37833 8 d 23 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ feat(cron): --retryIrn re-files rejected e-invoices after their data is corrected - reset and filing in one transaction so the scheduled IRN job cannot race it  
37832 8 d 23 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ feat(cron): --closeMovementPO closes movement POs the way the PO screen's Close does (refund live orders, preclose); dry run by default  
37831 9 d 0 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/migrations/ feat(cron): movement billing takes only units at the order's price; PO dry run names each PO's vendor warehouse  
37828 9 d 1 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ fix(cron): movement billing looks POs up inside a transaction (driver has none - 'Could not obtain transaction-synchronized Session'); failure message no longer claims nothing was raised  
37827 9 d 1 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ feat(cron): --autoCreateMovementPO raises an internal movement PO from an upload file as the operator; dry run previews every PO, line, price and origin; --bill dry-runs billing of the raised POs  
37825 9 d 2 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ feat(cron): --autoBillMovement bills internal movement POs (dry run by default)

Bills the orders a movement PO raised the way the order management screen does:
orders parked on the Dummy (brand without a source mapping) are re-pointed to the
source vendor warehouse holding the stock (split when it spans several), serialized
units take IMEIs oldest first at qty 1 with a good last scan, then acceptOrders ->
addBillingDetailsForGrouppedOrders -> checkOrderDispatchEligibility, one invoice per
transaction, all-or-nothing. IRN / e-way bill follow from the existing cron job.

Usage: --autoBillMovement --pos=PO1,PO2 --email=<operator> --provider=<id> [--live]
Dry run (default) plans and validates only - billing opens its own REQUIRES_NEW
transaction (createLoanForBilling) that a rollback would not undo. Refuses a
transaction over 900 invoice lines (NIC caps an IRN at 1000). Prints the invoice
list for --autoGrnInternal.
 
37814 11 d 7 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ revert(samsung-rebilling): drop TODO, keep ritesh.chauhan1 on the mail

Removes the TODO(amit.gupta) comment added in r37812. The only change left from
r37812 is ritesh.chauhan1 on To alongside kamini.sharma; tarun.verma stays on CC.
 
37812 11 d 9 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ fix(samsung-rebilling): send rebilling mail to kamini.sharma and ritesh.chauhan1

praveen.sharma was dropped in r37631 (inactive auth user); ritesh.chauhan1 now joins
kamini.sharma on To, tarun.verma stays on CC. TODO(amit.gupta) records the open
points: recipients cannot open Manage PCM (menu 208 is Financial Services L1/L2),
and the CSV omits the invoice and PCM dates the query already returns.

Requires profitmandi-dao r37811 (missing-PCM rows); deploy dao and cron together.
 
37788 13 d 10 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.
 
37771 16 d 7 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ fix(movement): movement POs no longer auto-close; open movement PO digest to Warehouse L1/L2 at 09:00 and 17:00  
37751 17 d 6 h ranu /trunk/ aging po approval mail to some collected users  
37735 19 d 13 h amit /trunk/profitmandi-cron/src/main/ Remove dead third-party integrations: cron

- Toffee: attachToffeeInvoices (schedule already commented out), toffeeRollback
and the --tc option; tofee.* keys in run.properties
- Bharti Assist: sendBAGPendingPolicies, testBag/mapBag and the --bag /
--mapbag options
- HyperTrack geofence one-offs (--createGeofence, --getAllGeofences,
--deleteGeofences) and their hardcoded account keys
- SmartPing injection in ScheduledTasks; leftover DTDC comment
- aramex.tracking.url in run.properties
 
37729 19 d 13 h amit /trunk/ lead: one workable lead per mobile, with a 6-month supersede and an L2+ override

There was no choke point for lead creation. Ten sites did `new Lead()` across four
modules -- three in web's LeadController, two in V2FofoLeadController, three in fofo's
LeadController, one in TrialServiceImpl and one in the cron LeadSyncRunner -- and only
ONE of them (fofo /createLead) checked for an existing lead at all. Result on live data:
5,137 mobiles carrying duplicate leads over 12,156 rows, worst case 29 on one number,
and two agents unknowingly working the same shop.

THE RULE, in new LeadCreationService, which all ten now route through:

no active lead on the number -> create
active, last activity >= 6 months -> retire the old one, create the new one, SILENTLY
active, last activity < 6 months -> BLOCK; only an L2+ user may override

Active = status in (pending, followUp) AND the assignee is still an active auth_user.
Last activity = GREATEST(lead.updated/created, MAX(lead_activity.created)).

The stale branch is deliberately quiet. A shop enquiring again after six months is a
handover, not a clash, and mailing on it would train the desk to ignore the alert -- so
only a genuine collision notifies. Live split: 195 stale against 1,241 fresh, and roughly
three blocks a month.

WHY "ACTIVE" ALSO MEANS A LIVE OWNER

331 open leads are assigned to 11 DEACTIVATED accounts (157 to sm@smartdukaan.com alone,
whose newest lead is from 2022). Counting them as active would block fresh enquiries
behind an account nobody can log in to and therefore nobody can close. Requiring a live
owner defuses all 331 without retiring a single row. Retirement here is only ever
REACTIVE -- triggered by a new entry on the same number. Nothing runs on a schedule.

ASSUMPTION worth flagging: a superseded lead becomes status=notInterested (stage DROPPED)
with closure_timestamp and reason 'Superseded after 6 months inactivity', rather than a
new `expired` status. "Closed" is an explicit allow-list in the UI --
Arrays.asList(notInterested, finalized) at V2FofoLeadController:150 and fofo
LeadController:313 -- and there are ~107 references to specific LeadStatus values, so a
new enum value would make these leads vanish from BOTH the open and closed screens.
Stage DROPPED keeps the nuance (the shop never said no) and still maps to notInterested
via LeadStage.toLegacyStatus().

OVERRIDE is L2+ in ANY team, not Call Center only: Sales L1 owns 1,063 of the 1,776 open
leads, so a Call-Center-only gate would funnel every team's collisions through three
people. The MAIL still goes to Call Center L2+, resolved from cs.position at send time
rather than hardcoded. An override is a TAKEOVER -- it closes the existing lead -- because
a second live lead is the exact thing the rule exists to prevent.

UNATTENDED CALLERS (cron sync, CSV upload, trial registration, AI intake) have nobody to
offer an override to, so they use createUnattended: skip the colliding row and mail the
desk rather than throwing. CSV reports imported/duplicateSkipped/duplicateMobiles back to
the operator instead of failing the whole file over one number.

ALSO FIXES selectByMobileNumber, which called getSingleResult and therefore threw
NonUniqueResultException on any mobile with more than one lead -- GlitchTip #99 and #1359,
both still firing. It now prefers the open lead, then the most recently touched.

NOT INCLUDED, deliberately: no DB unique constraint. 10 mobiles already carry more than
one open lead and would have to be resolved by hand first, which conflicts with the
no-auto-retirement rule. The service enforces the invariant going forward.

NEEDS A DBA STEP: user.lead.mobile is unindexed on 37,580 rows, so this check is a full
scan on every create. Index DDL is in the accompanying note; it has NOT been applied.
 
37726 21 d 9 h amit /trunk/ sentry: stop developer laptops reporting to the live GlitchTip board

GlitchTip #586 was 57 events tagged environment=production whose stack read
/opt/homebrew/Cellar/tomcat@8/8.5.100/libexec/... with server_name set to a
developer's machine. Nothing was wrong on prod: a laptop was posting into the
production project and was indistinguishable from it.

Two things combined to allow that. The DSN lives in log4j2.xml, which ships inside
every build, so any machine running this code can report. And the Sentry SDK
defaults `environment` to "production" when it is not set -- which it never was --
so local runs arrived pre-labelled as prod.

Adds sentry.properties to each module, read off the classpath by the SDK itself
(io.sentry.config.PropertiesProviderFactory) and merged over the appender's config.
Both keys used here are honoured by io.sentry.ExternalOptions in 7.22.6 (verified
against the jar): `enabled` and `environment`.

The COMMITTED values are the safe ones -- enabled=false, environment=dev -- so a
plain local build is silent. build.gradle rewrites both from -Penv= alongside the
env.property it already writes, so only a deliberate -Penv=staging|prod build
reports, and it carries the right environment tag. tasks.build.doLast restores the
safe default afterwards, mirroring the existing handling of env.property.

Verified both directions: default build leaves enabled=false/environment=dev,
-Penv=prod yields enabled=true/environment=prod.

Note this makes the board trustworthy rather than merely quieter: events can now be
filtered on environment, and anything unlabelled is a build that predates this.
 
37704 22 d 21 h 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.
 
37693 23 d 7 h ranu /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/migrations/ rbm ranking mailer  
37691 23 d 7 h ranu /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/migrations/ rbm rankijng mail  
37690 23 d 7 h ranu /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/migrations/ notification scheduler  
37689 23 d 7 h ranu /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/migrations/ notification scheduler  
37687 23 d 8 h ranu /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ rbm ranking mailer  
37686 23 d 8 h ranu /trunk/ for rbm ranking mail on month end  
37684 23 d 8 h amit /trunk/profitmandi-cron/src/main/ Schedule the knowlarity insights pull here instead of in the fofo tomcat

Eight times a day, unchanged times (11:40, 13:40, 15:40, 17:40, 18:40, 19:15,
20:00, 20:40) so the shape of the day's data does not move. Calls
KnowlarityInsightsSyncService (dao r37683).

This lands next to KnowlarityCallMonitorScheduler on purpose: that one owns the
WebSocket status feed into cs.rbm_break_log, this one owns the periodic KPI pull
into cs.agent_daily_insight. They are the two halves of the same integration and
were previously split across two processes for no reason other than history.

What it replaces: the same schedule inside the fofo tomcat, where every run
started an ~850MB headless chrome on a box that holds a -Xmx8g tomcat and a
-Xmx2g cron jar on 16GB and has been kernel-OOM-killed twice. A run is now four
HTTPS calls, ~2-3 seconds.

⚠ Only fires under --spring.profiles.active=scheduled; a one-shot CLI run does
not start the schedulers.

staging.properties gains the knowlarity block. It had NO knowlarity keys at all,
which means the WebSocket call monitor has never run there either -- this fixes
both. Same credentials as prod; there is no separate SR tenant for staging.

Cadence is worth revisiting separately: the 8 slots were chosen when a run cost
75 seconds and 850MB. At 2 seconds, hourly or every 15 minutes during the
10:00-21:00 window (matching the call monitor) would be nearly free.
 
37677 23 d 10 h ranu /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/b2b/ price drop and hike fixed  
37675 23 d 11 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ Remove the dead Samsung and Amazon Selenium paths

Both were manual one-shots that nothing runs any more, and each started an
850MB chrome tree that now has to queue on the browser lane, so they are cost
without a caller.

Evidence they are dead rather than merely idle:
- No crontab entry, no cron.d file and no script references --samsung or
--amazonPurchase, and neither flag appears even once in cron.log.
- fofo.activated_imei has ZERO Samsung rows written by the cron (auth_id 0) in
the last 90 days. All 2,331 Samsung rows in that window are auth_id 307, i.e.
the human CSV upload, most recently 16-Sep. The scraper is not what keeps
Samsung current; people are.
- ScheduledSkeleton.fetchImeiActivation() had already been retired in place --
its @Scheduled was commented out with 'No longer scheduled'.
- RunOnceTasks.amazonPurchase() reads /Users/amit/Downloads/amazon.xlsx, a
laptop path that cannot exist on the server.

Removed: SamsungIMEIActivationService, the whole scheduled/amazon package
(AmazonPurchaseService, OrderSummary, OrderRow, AmazonUser), their RunOnceTasks
callers and helpers (fetchImeiActivation, amazonPurchase, getOrderSummary,
parseRow), the two Application CLI blocks and the retired ScheduledSkeleton
wrapper. The amazon package had no importers outside RunOnceTasks.

Both also leaked a chrome profile dir on every run -- AmazonPurchaseService
never called quit() at all, and SamsungIMEIActivationService called it outside
any finally -- so this removes two leak sources rather than fixing them.

Untouched: RunOnceTasks.mailDashboardScreenshots() is a third dead Selenium
one-shot (also zero invocations) but it mails a report, so it is left for a
separate decision.
 
37673 23 d 12 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ Hold the browser lane around the oppo/realme/motorola drivers

Takes BrowserLane (common r37672) before new ChromeDriver and releases it after
quit() returns -- the 850MB is resident for the whole chunk, not just at
startup, so bracketing only the constructor would protect nothing.

Five minutes of waiting, then give up and return what we have: the only things
that can hold the lane that long are the other brand mid-chunk or the knowlarity
scrape in the fofo tomcat, and giving up costs nothing because an unstamped imei
stays pending and the lane's next turn picks it up.

CheckMotorolaWarrantyTask carries a comment saying 'do not let this job overlap
the oppo/realme window' that nothing ever enforced. It is wired here too so it is
already safe whenever it gets a trigger -- it still has none today.

Note on sizing, since the obvious knob is the wrong one: shrinking CHUNK from 25
was evaluated and rejected. The idle window is a fixed 20s bolted onto a variable
work period, so 25->10 moves the duty cycle only 96% -> 91% while costing 8.4%
of daily throughput and 2.5x the driver launches. The lever for duty cycle is the
fixedDelay gap, not the chunk size.
 
37658 24 d 8 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ Remove Mandii onboarding tasks from cron (r37655)

RunOnceTasks loses mandiiUser/mandiiUsers, their setCreditAccount helper and
the now-unused encodeFileToBase64Binary, plus the MandiiService autowire -
133 lines that pushed partner KYC into Mandii and wrote back MANDII credit
accounts. The matching --mandiiUser / --mandiiUsers CLI options are dropped
from Application. ScheduledTasks had an unused MandiiService field.
OnBoardingRelatedSchelduleTask imports follow services.mandii -> services.kyc.
 
37644 25 d 8 h amit /trunk/ Cron batch infra: count real SUCCESS rows instead of deriving them, add INCOMPLETE status for runs that died mid-loop, stale-batch reaper, admin force-finalize; markItemSuccess joins the work transaction  
37642 25 d 8 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ Hot Deal brand: remove the 00:20 window-sync job and its CLI flag - no date windows any more  
37636 25 d 8 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ refactor(cron): remove vendoritempricing one-offs; log catalog migration listing prices

- Remove migrateVendorItemPricing (2023 one-off) and its flag; fixOrders no longer reads vendoritempricing
- CatalogMigration sets listing prices via TagListingPriceService
 
37631 25 d 12 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ feat(mail): wire inactive-recipient filter, clean addresses, remove attendance alerts

Wire MailRecipientFilter into both mail senders. Remove inactive hardcoded recipients, fix typo addresses, send market-share reminder to tech@. Delete sendAttendanceMorningAlert/EveningAlert, sendMailToHR and their CLI options.
 
37617 26 d 20 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ Undo an internal GRN so its invoice can be received again

A receipt taken before the serials were split left stock that cannot be corrected in place: a
serialised unit is held one to a row, so an invoice that should have created several rows created one,
and the rows that are missing cannot be added without also unpicking the running figures the receipt
moved. Reversing the receipt and taking it again through the same path is the only way to arrive at
what the invoice actually says.

The reversal removes what receiving created - the scans, the inventory units, the invoice items, the
purchase and the supplier invoice - and gives back the two running figures it moved: the warehouse
availability, and the quantity taken off the purchase order line. Both are worked out from the rows
being deleted rather than recomputed, so whatever the receipt added is exactly what comes off. A
purchase order the receipt closed is opened again. Availability is kept only as a running count with
nothing to rebuild it from, which is why it is adjusted by what is known to have been added rather
than by anything inferred.

An invoice whose stock has moved since is refused, not worked around. A unit that has been scanned out
or partly consumed is no longer the receipt's to remove, and deleting around it would leave the
warehouse holding stock that no record explains. Each invoice is reversed on its own so one refusal
leaves the rest untouched, and a dry run reports everything it would delete and every figure it would
change without writing.

Invoices are named explicitly rather than selected by date, so a reversal can only ever touch what it
was given.
 
37616 26 d 20 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/migrations/ Split a line item's serials into one inventory unit each

A serialised line item carries one serial per unit in serial_number, comma separated. The internal
GRN passed that whole field across as a single serial, so a line of four units became one unit whose
serial was the four serials joined together - a string no scan can ever match. Where the joined
string ran past the 128 characters the inventory column allows, the receipt failed outright; where it
fitted, it was accepted and the stock was quietly understated.

The serials are now split out and each unit is received on its own, which is what grnPoModels expects
- it creates one inventory row per serial.

A serial count that does not match the line's quantity now skips the invoice. Receiving fewer units
than were billed is exactly the failure this had, and it is not something to infer a best guess from:
the invoice is left for someone to look at instead.
 
37615 26 d 21 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/migrations/ Receive internal transfers the way the portal does, not via the Excel upload

The live run failed on every invoice with "Column 'status' cannot be null". The Excel upload path it
was calling, addPORowModels, persists a supplier invoice without ever setting a status, and the
column does not allow one to be absent, so that route cannot complete a receipt at all.

Rather than change a path the portal shares, this follows what the Receive Invoice screens actually
do: record the supplier invoice, record its items through InvoiceService.createInvoiceItem, then hand
it to PurchaseOrderService.grnPoModels, the call behind the Create GRN button. grnPoModels sets the
invoice to received itself, so the status is never left for the caller to remember.

Recording the invoice items matters beyond the receipt: warehouse.invoice_item is what the buying
reports join against, and the Excel path never wrote those rows either.

An invoice is carried as one entry per item rather than one per order, since that is the shape
grnPoModels expects - every serial of an item arrives together and a non serialised item arrives as a
single quantity. There is no supplier document to attach, which is ordinary here; most existing
warehouse invoices carry none.

Resolving the purchase order now uses the mapping recorded when the internal PO was raised - the
transaction it created, held on the purchase order - instead of walking every order to find it.
An invoice whose orders do not share that one transaction is skipped rather than guessed at.
 
37614 26 d 21 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/migrations/ Run each internal GRN in a transaction that is actually applied

The internal GRN one-off failed immediately with "no transaction is in progress", thrown while
flushing the Hibernate session at commit.

The per invoice method was annotated to start its own transaction, but it sat in the same bean as
the loop that called it. Spring applies @Transactional through a proxy, and a call from one method
of a bean to another never leaves the object, so the annotation was inert - the receiving ran with
no transaction at all while the driver had suspended the surrounding one. The repositories still
bound a session to the thread, and the flush at commit then found nothing to flush into.

The receiving moves to its own bean, so the driver now reaches it through the proxy and the
transaction is real. The driver keeps no transaction of its own, which is what lets one invoice
fail without disturbing those already received.

No change to what is received or to the conditions under which an invoice is skipped.
 
37613 26 d 22 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ Receive internal transfer stock that was billed but never GRNed

Stock moved between our own warehouses is dispatched against an internal purchase order and
billed, but the destination warehouse still has to receive it by hand through the portal. Where
that never happened the units exist on an invoice and nowhere in inventory, and there is no way
to clear a backlog of them short of keying each invoice in again.

This adds a cron one-off that receives them. Per invoice it builds the rows the Excel GRN upload
would have carried and hands them to PurchaseOrderService.addPORowModels, which creates the
supplier invoice, the purchase and the inventory items in a single call, so none of receiving is
reimplemented here - the portal and this take the same path and can only ever agree.

It is deliberately narrow about what it will touch. Only INTERNAL buyers, because nothing should
be able to receive a partner's goods on their behalf. Only invoices with no supplier invoice
already recorded, so a repeat run skips what it has already done rather than receiving twice.
Only invoices resolving to a single purchase order, since addPORowModels resolves one order for
the whole map it is given and would otherwise attribute an invoice to the wrong one - for the
same reason each invoice is passed on its own. A serialized line whose serial number is missing
aborts its invoice instead of creating a unit no scan could ever match.

Each invoice commits or rolls back on its own, so one bad invoice cannot undo the ones already
received, and a dry run reports what it would receive without writing anything.

The receipt is recorded against the destination warehouse, which is what warehouse inventory is
keyed by. The buyer on the order is read only to confirm the store is internal; partner side
inventory is a separate receipt and is not touched.
 
37602 28 d 11 h amit /trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/ Remove no-op refreshSnapshotAgeing task; restore originalInventoryItemId backfill call

refreshSnapshotAgeing ran every 30 minutes aggregating warehouse.inventoryItem.rootInvoiceDate,
a column nothing ever wrote, so its UPDATE ... JOIN matched zero rows on every run and
currentinventorysnapshot.oldest_invoice_date was never populated on any of 11,686 rows.
Removed along with the sessionFactory field it was the only consumer of.

Application.java had migrations.migrateWarehouseOriginalInventoryItemId(batchSize) commented
out while still logging 'Starting migration...' and 'Migration completed.', so
--migrateOriginalInventoryItemId reported success while doing nothing. Restored the call;
it remains opt-in via the CLI flag and cannot fire on its own.

Requires profitmandi-dao r37601 (entity fields removed).