| 37563 |
amit |
1 |
-- ============================================================================
|
| 37566 |
amit |
2 |
-- Delist logic v3 - rolling 24-month, house-wide 2026-09-10
|
| 37563 |
amit |
3 |
--
|
|
|
4 |
-- Supersedes samsung_delist_pre2025_no_stock_20260909.sql, which was one
|
|
|
5 |
-- brand with a frozen id list. This runs across EVERY brand and EVERY
|
|
|
6 |
-- category (mobile and non-mobile) on a rolling 24-month window, so it is
|
|
|
7 |
-- safe to schedule daily.
|
|
|
8 |
--
|
| 37566 |
amit |
9 |
-- Changes over v1:
|
| 37563 |
amit |
10 |
-- (NEW 0) rolling 24-month window instead of a hardcoded 2025-01-01, and
|
|
|
11 |
-- no brand / category restriction.
|
|
|
12 |
-- (NEW 1) an item with an OUTSTANDING VENDOR PO is never delisted. It is
|
|
|
13 |
-- skipped, not excluded -- the next run re-evaluates it, so it
|
|
|
14 |
-- delists by itself once the PO closes.
|
|
|
15 |
-- (NEW 2) a model that goes FULLY DARK (no active listing left on any of
|
|
|
16 |
-- its colours) has its movement categorisation moved to OTHER, so
|
|
|
17 |
-- it stops being offered as a live model.
|
| 37566 |
amit |
18 |
-- Added in v3 (2026-09-10):
|
|
|
19 |
-- (NEW 3) NOT MOVED FOR A MONTH on either side. Zero stock alone was too
|
|
|
20 |
-- weak: it fired on SKUs partners were still transacting, where the
|
|
|
21 |
-- recent movement was often the very sale that emptied the stock.
|
|
|
22 |
-- (NEW 4) the internal pseudo-brands (Dummy, FOC, FOC HANDSET, Live Demo)
|
|
|
23 |
-- are no longer excluded; they are swept like anything else.
|
|
|
24 |
-- (NEW 5) vendor-PO guard narrowed to status = 1; status 2 is unreachable.
|
| 37563 |
amit |
25 |
--
|
| 37566 |
amit |
26 |
-- ⚠ The 2026-09-10 run of v2 predates NEW 3, so it delisted 24 listings /
|
|
|
27 |
-- 21 models that had partner movement inside the month. Reverted by
|
|
|
28 |
-- rollback_delist_recent_movement_20260910.sql.
|
|
|
29 |
--
|
| 37563 |
amit |
30 |
-- Written to be run repeatedly (daily). Every step is idempotent.
|
|
|
31 |
-- ============================================================================
|
|
|
32 |
|
|
|
33 |
-- ---------------------------------------------------------------------------
|
|
|
34 |
-- WHAT "OUTSTANDING VENDOR PO" MEANS (NEW 1)
|
|
|
35 |
--
|
|
|
36 |
-- warehouse.purchaseorder.status is an ORDINAL enum (in.shop2020.purchase.POStatus):
|
|
|
37 |
-- 0 INIT 1 READY 2 PARTIALLY_FULFILLED 3 PRECLOSED 4 CLOSED
|
|
|
38 |
--
|
| 37566 |
amit |
39 |
-- Outstanding = status = 1 AND supplier.internal = 0
|
| 37563 |
amit |
40 |
-- AND lineitem.unfulfilledQuantity > 0
|
|
|
41 |
--
|
| 37566 |
amit |
42 |
-- status 1 (READY) is the house definition of an open PO -- the existing
|
|
|
43 |
-- named query warehouse.selectOpenPo uses exactly
|
|
|
44 |
-- "po.status = 1 and s.internal = false".
|
| 37563 |
amit |
45 |
--
|
| 37566 |
amit |
46 |
-- PARTIALLY_FULFILLED (2) is NOT included, and this is not an oversight.
|
|
|
47 |
-- It is UNREACHABLE, not merely rare -- verified 2026-09-10:
|
|
|
48 |
-- * all 8 PO-status writes in the codebase set INIT, READY, PRECLOSED or
|
|
|
49 |
-- CLOSED (PurchaseOrderServiceImpl 399/479/913, GrnController:495,
|
|
|
50 |
-- V2FofoGrnController:441, PurchaseOrderController:188,
|
|
|
51 |
-- V2FofoPurchaseOrderController:139, POScheduler:46). None sets it.
|
|
|
52 |
-- * no native SQL writes purchaseorder.status.
|
|
|
53 |
-- * SELECT COUNT(*) ... WHERE status = 2 -> 0, across 51,593+ POs to 2011.
|
|
|
54 |
-- Its only two code references (InvoiceServiceImpl:248, POScheduler:32) are
|
|
|
55 |
-- defensive READS. Partial receipt is modelled per LINE
|
|
|
56 |
-- (unfulfilledQuantity / fulfilled -- 9,153 lines are genuinely part
|
|
|
57 |
-- received), never rolled up to a header status, so a part-received PO sits
|
|
|
58 |
-- in READY and is already caught by "status = 1 AND unfulfilled > 0".
|
|
|
59 |
-- Do not "restore" status 2 here thinking it closes a gap.
|
|
|
60 |
--
|
|
|
61 |
-- NOTE the guard is effectively a 6-DAY GRACE, not an indefinite hold:
|
|
|
62 |
-- POScheduler.autoClosePurchaseOrders (ScheduledSkeleton:747, daily 01:00)
|
|
|
63 |
-- force-CLOSES vendor POs 6 days after creation (internal: 4) regardless of
|
|
|
64 |
-- whether goods arrived. That is why every READY PO on prod is <= 6 days
|
|
|
65 |
-- old, and it guarantees a deferral always resolves.
|
|
|
66 |
--
|
| 37563 |
amit |
67 |
-- INIT (0) is DELIBERATELY EXCLUDED. Measured on prod 2026-09-09:
|
|
|
68 |
-- status 0 -> 376 unfulfilled lines spanning 2024-04-02 .. 2026-09-03
|
|
|
69 |
-- status 1 -> 97 unfulfilled lines spanning 2026-09-01 .. 2026-09-09
|
|
|
70 |
-- INIT is a drawer of abandoned drafts, 2.5 years deep. Nothing ever closes
|
|
|
71 |
-- them, so counting INIT as "outstanding" would defer those items forever
|
|
|
72 |
-- instead of for a day. On the 738 rows of the 2026-09-09 Samsung batch,
|
|
|
73 |
-- INIT would have blocked 13 listings / 10 models, every one a stale draft.
|
|
|
74 |
--
|
|
|
75 |
-- s.internal = 0 is what makes it a VENDOR po rather than an internal
|
|
|
76 |
-- stock transfer between our own warehouses.
|
|
|
77 |
--
|
|
|
78 |
-- COLUMN-NAME TRAP in warehouse.lineitem: the entity field `quantity` maps to
|
|
|
79 |
-- DB column `initial_qty`, and entity `amendedQty` maps to DB column
|
|
|
80 |
-- `quantity`. So in raw SQL `quantity` is the AMENDED qty, not the ordered
|
|
|
81 |
-- one. Use unfulfilledQuantity / fulfilled and do not reach for `quantity`.
|
|
|
82 |
-- ---------------------------------------------------------------------------
|
|
|
83 |
|
|
|
84 |
-- ===========================================================================
|
|
|
85 |
-- STEP 0 - persistent audit. Created once, appended to by every run.
|
|
|
86 |
-- Without this a run is IRREVERSIBLE: after step 3 the rows read active=0,
|
|
|
87 |
-- so the selection predicate (which requires active=1) can no longer
|
|
|
88 |
-- re-derive what it touched. Same pattern as the freeze table kept for the
|
|
|
89 |
-- 2026-09-09 Samsung batch.
|
|
|
90 |
--
|
|
|
91 |
-- ROLLBACK for a given run_date:
|
|
|
92 |
-- UPDATE catalog.tag_listing tl
|
|
|
93 |
-- JOIN catalog._delist_audit a ON a.tag_listing_id = tl.id
|
|
|
94 |
-- SET tl.active = 1, tl.eol_date = NULL
|
|
|
95 |
-- WHERE a.run_date = '<date>';
|
|
|
96 |
-- -- and restore categorisation:
|
|
|
97 |
-- DELETE cc FROM catalog.catagoriesd_catalog cc
|
|
|
98 |
-- JOIN catalog._delist_cc_audit x ON x.catalog_id = cc.catalog_id
|
|
|
99 |
-- WHERE x.run_date = '<date>' AND cc.status = 'OTHER' AND cc.end_date IS NULL;
|
|
|
100 |
-- UPDATE catalog.catagoriesd_catalog cc
|
|
|
101 |
-- JOIN catalog._delist_cc_audit x ON x.cc_id = cc.id
|
|
|
102 |
-- SET cc.end_date = NULL
|
|
|
103 |
-- WHERE x.run_date = '<date>';
|
|
|
104 |
-- ===========================================================================
|
|
|
105 |
CREATE TABLE IF NOT EXISTS catalog._delist_audit (
|
|
|
106 |
tag_listing_id INT NOT NULL,
|
|
|
107 |
item_id INT,
|
|
|
108 |
run_date DATE NOT NULL,
|
|
|
109 |
PRIMARY KEY (tag_listing_id, run_date)
|
|
|
110 |
);
|
|
|
111 |
|
|
|
112 |
CREATE TABLE IF NOT EXISTS catalog._delist_cc_audit (
|
|
|
113 |
cc_id INT NOT NULL,
|
|
|
114 |
catalog_id INT NOT NULL,
|
|
|
115 |
prev_status VARCHAR(299),
|
|
|
116 |
run_date DATE NOT NULL,
|
|
|
117 |
PRIMARY KEY (cc_id, run_date)
|
|
|
118 |
);
|
|
|
119 |
|
|
|
120 |
-- ===========================================================================
|
|
|
121 |
-- STEP 1 - build the candidate set
|
|
|
122 |
-- ===========================================================================
|
|
|
123 |
DROP TEMPORARY TABLE IF EXISTS _delist_candidate;
|
|
|
124 |
CREATE TEMPORARY TABLE _delist_candidate (
|
|
|
125 |
tag_listing_id INT PRIMARY KEY,
|
|
|
126 |
item_id INT,
|
|
|
127 |
catalog_item_id INT,
|
|
|
128 |
KEY idx_item (item_id),
|
|
|
129 |
KEY idx_cat (catalog_item_id)
|
|
|
130 |
);
|
|
|
131 |
|
|
|
132 |
-- ROLLING 24-MONTH WINDOW, EVERY BRAND, EVERY CATEGORY (mobile and non-mobile).
|
|
|
133 |
-- No brand or category predicate: the cron evaluates any itemId.
|
|
|
134 |
INSERT INTO _delist_candidate (tag_listing_id, item_id, catalog_item_id)
|
|
|
135 |
SELECT tl.id, tl.item_id, i.catalog_item_id
|
|
|
136 |
FROM catalog.tag_listing tl
|
|
|
137 |
JOIN catalog.item i ON i.id = tl.item_id
|
|
|
138 |
WHERE tl.tag_id = 4 -- default_fofo; tag 7 'test' has no rows
|
|
|
139 |
AND tl.active = 1
|
|
|
140 |
AND tl.start_date < CURDATE() - INTERVAL 24 MONTH
|
| 37566 |
amit |
141 |
-- No brand exclusion. The internal pseudo-brands (Dummy, FOC, FOC HANDSET,
|
|
|
142 |
-- Live Demo) are swept like anything else, by instruction 2026-09-10.
|
| 37563 |
amit |
143 |
-- no SmartDukaan stock
|
|
|
144 |
AND NOT EXISTS (
|
|
|
145 |
SELECT 1 FROM warehouse.view_availability va
|
|
|
146 |
WHERE va.item_id = tl.item_id AND va.total > 0)
|
|
|
147 |
-- no stock at any live partner
|
|
|
148 |
AND NOT EXISTS (
|
|
|
149 |
SELECT 1
|
|
|
150 |
FROM fofo.inventory_item ii
|
|
|
151 |
JOIN fofo.fofo_store fs ON fs.id = ii.fofo_id
|
|
|
152 |
WHERE ii.item_id = tl.item_id AND ii.good_quantity > 0
|
|
|
153 |
AND fs.active = 1 AND fs.closed = 0)
|
|
|
154 |
-- nothing already on order from the partner side
|
|
|
155 |
AND NOT EXISTS (
|
|
|
156 |
SELECT 1 FROM warehouse.view_cis vc
|
| 37566 |
amit |
157 |
WHERE vc.item_id = tl.item_id AND vc.indent > 0)
|
|
|
158 |
-- ---------------------------------------------------------------------
|
|
|
159 |
-- NOT MOVED FOR A MONTH, EITHER SIDE.
|
|
|
160 |
--
|
|
|
161 |
-- The stock guards above are point-in-time: they say "zero right now", not
|
|
|
162 |
-- "zero for a while". Nothing records when stock hit zero
|
|
|
163 |
-- (view_availability.updated_at is the materialized-view refresh clock, it
|
|
|
164 |
-- ticks continuously and cannot be used for this), so recent MOVEMENT is
|
|
|
165 |
-- used as the proxy for "still trading".
|
|
|
166 |
--
|
|
|
167 |
-- Warehouse side: warehouse.scanNew (3.4M rows, live) via inventoryItem.
|
|
|
168 |
-- warehouse.scan is a dead Saholic relic - 2,476 rows, last write 2012.
|
|
|
169 |
-- Partner side: fofo.scan_record via fofo.inventory_item. Any type counts
|
|
|
170 |
-- (PURCHASE / SALE / returns) - the question is whether the SKU is moving
|
|
|
171 |
-- at all, not which direction.
|
|
|
172 |
--
|
|
|
173 |
-- Both are NOT EXISTS rather than MAX(...) comparisons so they short-circuit
|
|
|
174 |
-- on the first recent row and use the (inventoryItemId, scannedAt) and
|
|
|
175 |
-- (inventory_item_id, create_timestamp) indexes. An item that has NEVER
|
|
|
176 |
-- moved qualifies for delisting, which is correct.
|
|
|
177 |
AND NOT EXISTS (
|
|
|
178 |
SELECT 1
|
|
|
179 |
FROM warehouse.inventoryItem wii
|
|
|
180 |
JOIN warehouse.scanNew sn ON sn.inventoryItemId = wii.id
|
|
|
181 |
WHERE wii.itemId = tl.item_id
|
|
|
182 |
AND sn.scannedAt >= CURDATE() - INTERVAL 1 MONTH)
|
|
|
183 |
AND NOT EXISTS (
|
|
|
184 |
SELECT 1
|
|
|
185 |
FROM fofo.inventory_item fii
|
|
|
186 |
JOIN fofo.scan_record sr ON sr.inventory_item_id = fii.id
|
|
|
187 |
WHERE fii.item_id = tl.item_id
|
|
|
188 |
AND sr.create_timestamp >= CURDATE() - INTERVAL 1 MONTH);
|
| 37563 |
amit |
189 |
|
|
|
190 |
-- ===========================================================================
|
|
|
191 |
-- STEP 2 (NEW 1) - drop anything with an outstanding vendor PO.
|
|
|
192 |
-- These are SKIPPED, not excluded: tomorrow's run re-evaluates them.
|
|
|
193 |
-- ===========================================================================
|
|
|
194 |
DROP TEMPORARY TABLE IF EXISTS _deferred_vendor_po;
|
|
|
195 |
CREATE TEMPORARY TABLE _deferred_vendor_po (
|
|
|
196 |
tag_listing_id INT PRIMARY KEY
|
|
|
197 |
);
|
|
|
198 |
|
|
|
199 |
INSERT INTO _deferred_vendor_po (tag_listing_id)
|
|
|
200 |
SELECT DISTINCT c.tag_listing_id
|
|
|
201 |
FROM _delist_candidate c
|
|
|
202 |
JOIN warehouse.lineitem li ON li.itemId = c.item_id
|
|
|
203 |
JOIN warehouse.purchaseorder po ON po.id = li.purchaseOrder_id
|
|
|
204 |
JOIN warehouse.supplier s ON s.id = po.supplierId
|
|
|
205 |
WHERE s.internal = 0
|
| 37566 |
amit |
206 |
AND po.status = 1 -- READY only; status 2 is unreachable (see header)
|
| 37563 |
amit |
207 |
AND li.unfulfilledQuantity > 0;
|
|
|
208 |
|
|
|
209 |
-- Report before writing anything.
|
|
|
210 |
-- NOTE: MySQL cannot reference the same TEMPORARY table twice in one
|
|
|
211 |
-- statement ("Can't reopen table"), so these counts are deliberately kept as
|
|
|
212 |
-- separate statements rather than one combined SELECT.
|
|
|
213 |
SELECT COUNT(*) AS deferred_open_vendor_po FROM _deferred_vendor_po;
|
|
|
214 |
|
|
|
215 |
DELETE c FROM _delist_candidate c
|
|
|
216 |
JOIN _deferred_vendor_po d ON d.tag_listing_id = c.tag_listing_id;
|
|
|
217 |
|
|
|
218 |
SELECT COUNT(*) AS listings_to_delist FROM _delist_candidate;
|
|
|
219 |
SELECT COUNT(DISTINCT catalog_item_id) AS models_touched FROM _delist_candidate;
|
|
|
220 |
|
|
|
221 |
-- ===========================================================================
|
|
|
222 |
-- STEP 3 - the delist itself
|
|
|
223 |
-- ===========================================================================
|
|
|
224 |
INSERT IGNORE INTO catalog._delist_audit (tag_listing_id, item_id, run_date)
|
|
|
225 |
SELECT c.tag_listing_id, c.item_id, CURDATE() FROM _delist_candidate c;
|
|
|
226 |
|
|
|
227 |
UPDATE catalog.tag_listing tl
|
|
|
228 |
JOIN _delist_candidate c ON c.tag_listing_id = tl.id
|
|
|
229 |
SET tl.active = 0,
|
|
|
230 |
tl.eol_date = CURDATE()
|
|
|
231 |
WHERE tl.active = 1;
|
|
|
232 |
|
|
|
233 |
-- ===========================================================================
|
|
|
234 |
-- STEP 4 (NEW 2) - models that just went FULLY DARK -> categorisation OTHER
|
|
|
235 |
--
|
|
|
236 |
-- "Fully dark" = zero active listings left across every colour of that
|
|
|
237 |
-- catalog_item_id. A model that keeps even one active colour is NOT touched.
|
|
|
238 |
-- (Real example from the 2026-09-09 batch: catalog 1024414, Samsung S24
|
|
|
239 |
-- 8GB/256GB, had 3 colours delisted but 2 still active -- it correctly
|
|
|
240 |
-- stays RUNNING.)
|
|
|
241 |
--
|
|
|
242 |
-- WHY 'OTHER' AND NOT NULL: catagoriesd_catalog.status is
|
|
|
243 |
-- `varchar(299) NOT NULL`, so a literal NULL needs a schema change. It is
|
|
|
244 |
-- also unnecessary -- CatalogMovingEnum.OTHER is defined as "not found", and
|
|
|
245 |
-- every reader already treats OTHER as "not a live model":
|
|
|
246 |
-- * /indent/getOutOfStockDetails keeps only {HID, FASTMOVING, RUNNING}
|
|
|
247 |
-- -> OTHER drops out of the Suggested-PO out-of-stock list
|
|
|
248 |
-- * Catalog.findAllWithEOLWithOutStock / findAllWithNoGoodStock accept
|
|
|
249 |
-- (status IS NULL OR 'OTHER' OR 'SLOWMOVING') -> the model becomes
|
|
|
250 |
-- eligible for Solr eol_no_stock_b and hides from partner browse
|
|
|
251 |
-- * Catalog.selectAllStatusAndBrandWise matches only requested statuses
|
|
|
252 |
--
|
|
|
253 |
-- WHY WE INSERT A NEW ROW RATHER THAN JUST END-DATING: the table is a
|
|
|
254 |
-- slowly-changing dimension (open row = end_date IS NULL), but the native
|
|
|
255 |
-- query CatagorisedCatalog.getBrandWiseCatalogMovement picks the latest row
|
|
|
256 |
-- per catalog via MAX(COALESCE(end_date, CURDATE())) and has NO end_date
|
|
|
257 |
-- filter. An orphaned end-dated RUNNING row would still come back as
|
|
|
258 |
-- "latest" and still read RUNNING. Closing the row is not enough -- a new
|
|
|
259 |
-- open OTHER row must outrank it.
|
|
|
260 |
--
|
|
|
261 |
-- SCOPE: MOBILE ONLY (category 10006, ProfitMandiConstants.MOBILE_CATEGORY_ID).
|
|
|
262 |
-- Movement classification is a mobile-catalogue concept -- accessories, TVs
|
|
|
263 |
-- and the rest are never categorised, so writing OTHER rows for them would
|
|
|
264 |
-- invent data no reader consults. Delisting itself (steps 1-3) stays
|
|
|
265 |
-- house-wide; only this re-categorisation is narrowed.
|
|
|
266 |
-- NOTE: the EOL readers Catalog.findAllWithEOLWithOutStock /
|
|
|
267 |
-- findAllWithNoGoodStock actually span categories (10006, 10009, 10010). If
|
|
|
268 |
-- 10009/10010 should be re-categorised too, widen the IN list below.
|
|
|
269 |
--
|
|
|
270 |
-- We only NORMALISE models that already have an open row -- i.e. models
|
|
|
271 |
-- whose categorisation is currently non-null. We do not manufacture rows for
|
|
|
272 |
-- models that never had one: every reader treats a missing row exactly like
|
|
|
273 |
-- OTHER, so inserting would add noise and change no behaviour. (2026-09-09 batch: 170 fully dark -> 139 have no open row
|
|
|
274 |
-- and are left alone, 31 SLOWMOVING are normalised.)
|
|
|
275 |
-- ===========================================================================
|
|
|
276 |
DROP TEMPORARY TABLE IF EXISTS _dark_model;
|
|
|
277 |
CREATE TEMPORARY TABLE _dark_model (catalog_item_id INT PRIMARY KEY);
|
|
|
278 |
|
|
|
279 |
-- SELF-HEALING, NOT RUN-SCOPED. This deliberately looks at EVERY fully-dark
|
|
|
280 |
-- mobile model, not just the ones this run delisted. Scoping it to the
|
|
|
281 |
-- current candidate set would leave models that went dark before the cron
|
|
|
282 |
-- existed (e.g. the 2026-09-09 Samsung batch) carrying a stale RUNNING /
|
|
|
283 |
-- SLOWMOVING categorisation forever, because no later run would revisit them.
|
|
|
284 |
-- Run house-wide it converges instead: once every dark model reads OTHER,
|
|
|
285 |
-- subsequent runs are no-ops. It also removes the need for a separate
|
|
|
286 |
-- Samsung backfill.
|
|
|
287 |
INSERT INTO _dark_model (catalog_item_id)
|
|
|
288 |
SELECT cc.catalog_id
|
|
|
289 |
FROM catalog.catagoriesd_catalog cc
|
|
|
290 |
WHERE cc.end_date IS NULL -- has an open categorisation row ...
|
|
|
291 |
AND cc.status <> 'OTHER' -- ... that is not already OTHER
|
|
|
292 |
AND EXISTS ( -- MOBILE ONLY (see note above)
|
|
|
293 |
SELECT 1 FROM catalog.item im
|
|
|
294 |
WHERE im.catalog_item_id = cc.catalog_id
|
|
|
295 |
AND im.category = 10006)
|
|
|
296 |
AND NOT EXISTS ( -- fully dark: no active colour left
|
|
|
297 |
SELECT 1
|
|
|
298 |
FROM catalog.tag_listing t2
|
|
|
299 |
JOIN catalog.item i2 ON i2.id = t2.item_id
|
|
|
300 |
WHERE i2.catalog_item_id = cc.catalog_id
|
|
|
301 |
AND t2.active = 1);
|
|
|
302 |
|
|
|
303 |
-- 4a. close the open row (only where it is not already OTHER)
|
|
|
304 |
INSERT IGNORE INTO catalog._delist_cc_audit (cc_id, catalog_id, prev_status, run_date)
|
|
|
305 |
SELECT cc.id, cc.catalog_id, cc.status, CURDATE()
|
|
|
306 |
FROM catalog.catagoriesd_catalog cc
|
|
|
307 |
JOIN _dark_model d ON d.catalog_item_id = cc.catalog_id
|
|
|
308 |
WHERE cc.end_date IS NULL AND cc.status <> 'OTHER';
|
|
|
309 |
|
|
|
310 |
UPDATE catalog.catagoriesd_catalog cc
|
|
|
311 |
JOIN _dark_model d ON d.catalog_item_id = cc.catalog_id
|
|
|
312 |
SET cc.end_date = CURDATE() - INTERVAL 1 DAY
|
|
|
313 |
WHERE cc.end_date IS NULL
|
|
|
314 |
AND cc.status <> 'OTHER';
|
|
|
315 |
|
|
|
316 |
-- 4b. open a new OTHER row so it outranks the closed one in
|
|
|
317 |
-- getBrandWiseCatalogMovement's MAX(COALESCE(end_date, CURDATE()))
|
|
|
318 |
INSERT INTO catalog.catagoriesd_catalog (catalog_id, status, start_date, end_date)
|
|
|
319 |
SELECT d.catalog_item_id, 'OTHER', CURDATE(), NULL
|
|
|
320 |
FROM _dark_model d
|
|
|
321 |
WHERE EXISTS ( -- had an open row we just closed
|
|
|
322 |
SELECT 1 FROM catalog.catagoriesd_catalog x
|
|
|
323 |
WHERE x.catalog_id = d.catalog_item_id
|
|
|
324 |
AND x.end_date = CURDATE() - INTERVAL 1 DAY)
|
|
|
325 |
AND NOT EXISTS ( -- idempotency guard
|
|
|
326 |
SELECT 1 FROM catalog.catagoriesd_catalog y
|
|
|
327 |
WHERE y.catalog_id = d.catalog_item_id
|
|
|
328 |
AND y.end_date IS NULL);
|
|
|
329 |
|
|
|
330 |
-- ===========================================================================
|
|
|
331 |
-- STEP 5 - Solr does not see any of this.
|
|
|
332 |
-- A raw UPDATE publishes no TagListingChangeListener event. The portal
|
|
|
333 |
-- catches up at the 06:00/18:00 full reindex (Listing.scheduledPushDataToSolr),
|
|
|
334 |
-- or run the cron jar with --pushDataToSolr to force it.
|
|
|
335 |
-- ===========================================================================
|