| Rev |
Age |
Author |
Path |
Log message |
Diff |
| 37859 |
10 h 51 m |
amit |
/trunk/profitmandi-web/src/main/ |
fix(payment): send partners back to app.smartdukaan.com after wallet top-up via CCAvenue
r34902 moved angular.app.url to https://smartdukaan.com/, which does not serve the
partner app, so the post-payment redirect to pages/home/payment-status/{id} returned 404.
The payment itself was recorded; only the landing page was broken.
Also drops the extra slash in the redirect format (base URL already ends in /) and adds
the trailing slash to the dev value to match. |
|
| 37852 |
1 d 9 h |
amit |
/trunk/profitmandi-web/src/main/java/com/spice/profitmandi/web/v2/controller/fofo/ |
fix(warehouse): V2 dashboards list ACTIVE warehouses instead of forcing 7573; courier/rider default warehouse 13368 |
|
| 37847 |
1 d 10 h |
amit |
/trunk/profitmandi-web/src/main/java/com/spice/profitmandi/web/ |
refactor(warehouse): read warehouse lists and labels from BillingWarehouseService instead of WAREHOUSE_MAP/WAREHOUSE_NAME_MAP |
|
| 37836 |
1 d 18 h |
aman |
/trunk/ |
Lead Management: the calendar date range now applies to follow-up leads too, in both the lead list and the CSV download. Previously follow-ups were appended undated, and the download (non-field-sales path) appended them even when the list did not, so a date-filtered download contained all follow-up leads from all time. 'All' status now consistently includes follow-ups in every branch.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JjVXnkrysiXEa2YYRn4BqL |
|
| 37821 |
5 d 10 h |
aman |
/trunk/ |
Lead Management: CSV download now also honours the lead table's search text (searchTerm), so the exported rows match the filtered rows on screen. Applies to both the fofo download and the V2 API download.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JjVXnkrysiXEa2YYRn4BqL |
|
| 37820 |
5 d 10 h |
aman |
/trunk/ |
Lead Management: make Excel/CSV download honour the same filters as the on-screen list. V2 download no longer folds followUp into the status list when All is chosen (it skipped the status/color/date query and exported only follow-ups); fofo lead page re-selects every chosen status so Download sends the statuses the table was loaded with.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JjVXnkrysiXEa2YYRn4BqL |
|
| 37793 |
8 d 13 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. |
|
| 37781 |
11 d 16 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. |
|
| 37772 |
12 d 10 h |
amit |
/trunk/profitmandi-web/src/main/java/com/spice/profitmandi/web/v2/controller/fofo/ |
fix(supplier): V2 supplier save derives state from GSTIN |
|
| 37766 |
12 d 11 h |
amit |
/trunk/profitmandi-web/src/main/java/com/spice/profitmandi/web/v2/controller/fofo/ |
feat(store-closure): V2 closeStore goes through StoreClosureService; access checks read StoreAccess
Same rules as the fofo portal (dao r37764): reason, remark and approval mail required; mohit.gulati
removed from extend billing. |
|
| 37759 |
13 d 9 h |
vikas |
/trunk/ |
LMS Call for App |
|
| 37742 |
15 d 12 h |
amit |
/trunk/profitmandi-web/src/main/java/com/spice/profitmandi/web/v2/exception/ |
v2: silence client disconnects and demote 4xx, matching the v1 taxonomy
r37728 added a client-disconnect handler to the v1 GlobalExceptionHandler and the
flood did not stop: #39 went on adding events, and every one of a 50-event sample
was from a /v2/ URL. V2GlobalExceptionHandler is scoped
basePackages="com.spice.profitmandi.web.v2", so v2 responses never reached the v1
handler at all and fell through to its own handleGenericException at ERROR --
~19,900 events, the second largest source on the board.
Worth recording because the first diagnosis was wrong. The theory was that an
exception raised while the response body is written cannot reach an @ExceptionHandler
because the response is already committed. It can: DispatcherServlet catches it out of
ha.handle() and still runs the resolvers. What it cannot do afterwards is WRITE the
substitute response. The handler runs and logs either way, which is precisely why
patching the wrong advice class changed nothing.
Adds @ExceptionHandler(IOException.class) here, delegating to the same
isClientDisconnect test the v1 handler uses -- duplicated rather than shared because
catalina is provided by the container and is not on this module's compile classpath,
so ClientAbortException cannot be imported and is matched on simple name plus the
messages a dead peer actually produces. A genuine IOException (full disk, a broken
pipe to something that is not the client) matches none of them and keeps its ERROR
and its 500. Returns null for a disconnect: the connection that would carry a body is
already gone, and writing to it is what raised this.
Also demotes five sibling handlers in the same file that were logging caller mistakes
at ERROR, which v1 has logged at WARN since the taxonomy work: business validation,
missing parameter, type mismatch, malformed body, method not supported, media type
not supported. v2 never received that pass. The business line also stops attaching a
stack trace -- a validation has nothing useful in one.
Left at ERROR deliberately, because they are real defects: IllegalArgument, NPE,
genuine IO failure, and the two catch-alls. |
|
| 37733 |
15 d 16 h |
amit |
/trunk/profitmandi-web/src/main/ |
Remove dead third-party integrations: web
- PayU: /payu-pay and the payu-pay-response/cancelled handlers (bodies were
already commented out), PayuHandler, payment/ helpers, PayU POJOs, and the
PAYU PAY option served by checkout/payment-options.
PayuPayController stays: it holds the live CCAvenue, Razorpay and Pine Labs
callbacks.
- SmartPing: GET /click2call/{toMobile} (v1 + v2) and the caller-ID hook
/smartping/receipt + /v2/hookCallerID, which only logged
- HyperTrack: device location, partner location and geofence endpoints plus
HyperTrackService. The attendance endpoints (/getPunchHistory,
/employee/attendance) never called HyperTrack and are kept, moved to
EmployeeAttendanceController / V2EmployeeAttendanceController.
- Affiliate click-outs and live pricing (Amazon/Flipkart/Snapdeal via OMG):
ClicksController, LivePricingController and v2 wrappers. Live pricing was
already returning an empty list.
- Thriwe controller (fully commented out), SpiceMoney and FundFina v2 controllers
- Aramex branch of /track; tofee.* keys in dev/prod properties |
|
| 37729 |
15 d 16 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. |
|
| 37728 |
15 d 16 h |
amit |
/trunk/ |
logging: stop three non-defects reporting to the error board as ERROR
log4j2.xml ships ERROR and above to GlitchTip, so the log level IS the filter.
Three sources of ordinary, expected behaviour were logging at ERROR and between
them accounted for 51,244 events -- 43% of the whole board -- none of them a bug.
GlobalExceptionHandler already logs ProfitMandiBusinessException at WARN and is
unchanged; every leak below bypassed it.
1. Session state (27,302 events, 23% of the board). CookiesProcessor and the two
interceptors logged a missing or expired cookie at ERROR. A logged-out or
anonymous visitor is the normal case -- the code already handles it by
redirecting to /login or returning 403 -- and each of these sits in a class
whose surrounding lines already log at DEBUG. These never reach the handler,
which is why its WARN-level treatment never applied. Now DEBUG.
2. Client disconnect (19,349 events). There was no handler for it, so a client
hanging up mid-response fell through to @ExceptionHandler(Exception.class) and
was recorded as an unhandled server bug. Adds an IOException handler to both
GlobalExceptionHandlers that logs a disconnect at DEBUG and everything else at
ERROR with its 500 intact.
⚠ ClientAbortException CANNOT be imported here: catalina is provided by the
container and is not on either module's compileClasspath (verified against
the configuration, not assumed). The match is therefore on the simple class
name plus the "broken pipe"/"connection reset" messages, walked down the cause
chain. A genuine IOException matches none of those and keeps its ERROR.
The handler returns null for a disconnect rather than a body: the connection
that would carry it is already closed, and writing to it is what raised the
exception. Null is the supported way to say "handled, no body" --
HttpEntityMethodProcessor marks the request handled before reading the value.
3. Business exceptions re-logged at ERROR (11 sites). Each catches a
ProfitMandiBusinessException, logs it, and CONTINUES with a fallback -- a
missing wallet history defaults to an empty list, a user not found by primary
email is retried against the secondary. That is an expected outcome being
reported as a failure, and logging it at ERROR pre-empted the handler that
would have logged it at WARN. Now WARN.
Selenium/WebDriver (58,776 events) is deliberately untouched: that is a genuinely
broken ChromeDriver, and muting it would hide Oppo/Realme IMEI activation failing.
It looks like noise only because one broken thing repeated 52,000 times. |
|
| 37715 |
19 d 0 h |
amit |
/trunk/ |
SD credit: stop read paths writing utilized_limit - the SD Credit admin page and V2 getLoans mutated managed SDCreditRequirement entities to show recomputed utilization, so Hibernate dirty-checking flushed an UPDATE per partner at commit; fofo now feeds the view from display maps and getLoans detaches before the display write, leaving output identical |
|
| 37706 |
19 d 0 h |
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. |
|
| 37698 |
19 d 6 h |
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. |
|
| 37681 |
19 d 12 h |
amit |
/trunk/profitmandi-web/src/main/java/com/spice/profitmandi/web/v2/controller/fofo/ |
feat(warehouse): resolve the logged-in user on V2 PO creation and GRN mismatch resolution
V2 mirror of r37680, keeping the dormant V2 controllers in step with the fofo MVC ones.
- createPurchaseOrder resolves the logged-in user via getEmailId + authRepository and sets createdBy
- resolvedMismatchRequest passes the resolver through
Pairs with r37679. |
|
| 37676 |
19 d 13 h |
ranu |
/trunk/profitmandi-web/src/main/java/com/spice/profitmandi/web/controller/ |
v2 version some fixes |
|