Subversion Repositories SmartDukaan

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
37640 amit 1
-- ============================================================================
2
--  Hot Deal brand - DATA MIGRATION                                2026-09-15
3
--
4
--  Spec : docs/superpowers/specs/2026-09-10-hot-deal-brand-design.md
5
--  Plan : docs/superpowers/plans/2026-09-11-hot-deal-brand.md
6
--
7
--  Run AFTER hot_deal_brand_schema.sql PASS 1, BEFORE PASS 2.
8
--  Idempotent - every mutating step guards on brand <> 'Hot Deal', so a second
9
--  run matches nothing and cannot double-prefix a model name.
10
--
11
--  WHAT IT DOES
12
--    1. freezes the current brand / model_name of every in-scope row
13
--    2. folds the OEM brand into model_name, then sets brand = 'Hot Deal'
14
--    3. ensures every in-scope catalog has a model_hot_deal attributes row
15
--       carrying oem_brand
16
--
17
--  WHY model_name CHANGES
18
--    Description is DERIVED, never stored: both Catalog.getDescription() and
19
--    Item.getItemDescriptionNoColor() build  brand + model_name + model_number
20
--    at read time. Overwriting brand alone would erase the OEM from every
21
--    label, so it is folded into model_name:
22
--
23
--      brand        'Oneplus'  ->  'Hot Deal'
24
--      model_name   ''         ->  'Oneplus'
25
--      model_number unchanged
26
--      => "Hot Deal Oneplus Y1S Edge 32inch 32HD2A01"
27
--
28
--    model_name is empty on most in-scope rows (the marketing text lives in
29
--    model_number), so the prefix usually lands in a free slot.
30
--
31
--  ALREADY-BILLED DOCUMENTS ARE NOT TOUCHED
32
--    transaction.lineitem and fofo.fofo_order_item snapshot brand/model_name
33
--    at order time, so invoices keep printing the OEM brand. 2,507 in-scope
34
--    lines sit on invoices filed at NIC with an IRN; rewriting the snapshot
35
--    would make a reprint disagree with the PrdDesc held against that IRN.
36
--    That is deliberate - see spec section 8.
37
-- ============================================================================
38
 
39
 
40
-- ---------------------------------------------------------------------------
41
--  SCOPE - FROZEN ID LIST, derived once on prod (hadb1) 2026-09-15
42
--
43
--  Selection rule that produced this list:
44
--      a known hot deal  AND  an active listing
45
--        known hot deal = a model_hot_deal row (expiry irrelevant - brand
46
--                         drives membership now, there are no windows)
47
--                       UNION tag_listing.hot_deals = 1 AND tag_id = 4
48
--                         (the 31 legacy rows from the old
49
--                          /v2/fofo/indent/confirm-hotdeals-pause screen)
50
--        active listing = tag_listing.tag_id = 4 AND active = 1
51
--
52
--  Result: 69 items across 31 catalogs - 27 of the 28 model_hot_deal catalogs
53
--  (one has no active listing) plus 4 legacy-only Ai+ catalogs.
54
--
55
--  ⚠ The ids are FROZEN ON PURPOSE. Do not re-derive the predicate per
56
--    environment. The localhost dev copy holds 26 model_hot_deal rows against
57
--    prod's 29, and the set moved from 69/31 to 68/30 over four days in
58
--    September as listings were reactivated. Pinning the ids is what makes the
59
--    same SKUs migrate in every environment.
60
-- ---------------------------------------------------------------------------
61
 
62
CREATE TABLE IF NOT EXISTS catalog._hot_deal_brand_scope (
63
  item_id    INT NOT NULL PRIMARY KEY,
64
  catalog_id INT NOT NULL,
65
  KEY idx_hd_scope_catalog (catalog_id)
66
) ENGINE=InnoDB;
67
 
68
INSERT IGNORE INTO catalog._hot_deal_brand_scope (item_id, catalog_id) VALUES
69
  (37876,1025252),(37877,1025252),(37878,1025252),(37879,1025253),(37880,1025253),
70
  (37881,1025253),(37882,1025253),(37919,1025253),(37920,1025252),(39579,1026010),
71
  (39759,1026085),(39941,1026085),(39942,1026085),(39943,1026085),(39944,1026085),
72
  (40199,1026268),(40251,1026302),(40412,1026360),(40413,1026360),(40483,1026403),
73
  (40484,1026403),(40485,1026403),(40486,1026403),(40487,1026403),(40488,1026403),
74
  (40497,1026408),(40498,1026408),(40499,1026408),(40500,1026408),(40581,1026443),
75
  (40582,1026444),(40583,1026445),(40584,1026446),(40585,1026447),(40649,1026470),
76
  (40650,1026470),(40651,1026470),(40654,1026473),(40655,1026473),(40656,1026473),
77
  (40657,1026473),(40658,1026473),(40659,1026474),(40660,1026474),(40661,1026474),
78
  (40662,1026474),(40663,1026474),(40664,1026475),(40665,1026475),(40666,1026475),
79
  (40667,1026475),(40668,1026475),(40698,1026488),(40699,1026488),(40700,1026488),
80
  (40701,1026488),(40702,1026488),(40733,1026506),(40753,1026526),(40754,1026527),
81
  (40755,1026528),(40756,1026529),(40826,1026562),(40828,1026564),(40829,1026565),
82
  (40830,1026566),(40860,1026583),(40861,1026584),(40863,1026586);
83
 
84
-- Guard: the list must load whole. 69 items / 31 catalogs.
85
-- MySQL cannot reference the same TEMPORARY table twice in one statement, so
86
-- these are two plain statements against a normal table.
87
SELECT COUNT(*) AS scope_items, COUNT(DISTINCT catalog_id) AS scope_catalogs
88
FROM catalog._hot_deal_brand_scope;
89
 
90
 
91
-- ---------------------------------------------------------------------------
92
--  FREEZE - must run before any UPDATE.
93
--
94
--  This table is the ONLY record of the pre-move state: after the UPDATE the
95
--  old brand cannot be re-derived, because brand now reads 'Hot Deal' and the
96
--  OEM survives only as a prefix inside model_name, which is not safely
97
--  parseable back out.
98
--
99
--  INSERT IGNORE + the brand guard means a re-run will not overwrite the
100
--  original values with already-migrated ones.
101
-- ---------------------------------------------------------------------------
102
 
103
CREATE TABLE IF NOT EXISTS catalog._hot_deal_brand_freeze (
104
  level          VARCHAR(8)   NOT NULL COMMENT 'catalog | item',
105
  row_id         INT          NOT NULL,
106
  old_brand_id   INT          NULL     COMMENT 'catalog level only; item has no brand_id',
107
  old_brand      VARCHAR(64)  NULL,
108
  old_model_name VARCHAR(255) NULL,
109
  run_date       DATE         NOT NULL,
110
  PRIMARY KEY (level, row_id)
111
) ENGINE=InnoDB;
112
 
113
INSERT IGNORE INTO catalog._hot_deal_brand_freeze
114
      (level, row_id, old_brand_id, old_brand, old_model_name, run_date)
115
SELECT 'catalog', c.id, c.brand_id, c.brand, c.model_name, CURDATE()
116
FROM catalog.catalog c
117
JOIN (SELECT DISTINCT catalog_id FROM catalog._hot_deal_brand_scope) s
118
     ON s.catalog_id = c.id
119
WHERE c.brand <> 'Hot Deal';
120
 
121
INSERT IGNORE INTO catalog._hot_deal_brand_freeze
122
      (level, row_id, old_brand_id, old_brand, old_model_name, run_date)
123
SELECT 'item', i.id, NULL, i.brand, i.model_name, CURDATE()
124
FROM catalog.item i
125
JOIN catalog._hot_deal_brand_scope s ON s.item_id = i.id
126
WHERE i.brand <> 'Hot Deal';
127
 
128
 
129
-- ---------------------------------------------------------------------------
130
--  MOVE - prefix the OEM brand into model_name, then overwrite brand.
131
--
132
--  The  brand <> 'Hot Deal'  predicate is what makes this idempotent: on a
133
--  second run nothing matches, so no row can become "Samsung Samsung ...".
134
--  COALESCE guards the NULL model_name rows; TRIM collapses the stray space
135
--  those leave behind.
136
-- ---------------------------------------------------------------------------
137
 
138
UPDATE catalog.catalog c
139
JOIN (SELECT DISTINCT catalog_id FROM catalog._hot_deal_brand_scope) s
140
     ON s.catalog_id = c.id
141
JOIN catalog.brand b ON b.name = 'Hot Deal'
142
SET c.model_name = TRIM(CONCAT(c.brand, ' ', COALESCE(c.model_name, ''))),
143
    c.brand      = 'Hot Deal',
144
    c.brand_id   = b.id
145
WHERE c.brand <> 'Hot Deal';
146
 
147
UPDATE catalog.item i
148
JOIN catalog._hot_deal_brand_scope s ON s.item_id = i.id
149
SET i.model_name = TRIM(CONCAT(i.brand, ' ', COALESCE(i.model_name, ''))),
150
    i.brand      = 'Hot Deal'
151
WHERE i.brand <> 'Hot Deal';
152
 
153
 
154
-- ---------------------------------------------------------------------------
155
--  ATTRIBUTES - every in-scope catalog needs exactly one model_hot_deal row.
156
--
157
--  Catalogs that arrived via the legacy-flag source have no row at all. They
158
--  get one with oem_brand set and the five pill attributes left at defaults,
159
--  so the fofo admin lists them as incomplete and ops can fill them in. A SKU
160
--  with the Hot Deal brand but no pills is valid, not an error - the badge
161
--  comes from the brand, the pills from this row.
162
--
163
--  start_date / end_date are still NOT NULL at this point (they are dropped in
164
--  schema PASS 3), so sentinel values are supplied.
165
-- ---------------------------------------------------------------------------
166
 
167
-- existing rows: backfill oem_brand from the freeze table
168
UPDATE catalog.model_hot_deal mhd
169
JOIN catalog._hot_deal_brand_freeze f
170
     ON f.level = 'catalog' AND f.row_id = mhd.catalog_item_id
171
SET mhd.oem_brand = f.old_brand
172
WHERE mhd.oem_brand IS NULL;
173
 
174
-- missing rows: create them
175
INSERT INTO catalog.model_hot_deal
176
      (brand_id, catalog_item_id, start_date, end_date, oem_brand,
177
       warranty_months, item_condition, finance_mapping, fresh, affordability,
178
       created_on, created_by)
179
SELECT (SELECT id FROM catalog.brand WHERE name = 'Hot Deal'),
180
       f.row_id, '2000-01-01', '2099-12-31', f.old_brand,
181
       0, 'NEW', 0, 0, 0, NOW(), 'hot_deal_brand_migration'
182
FROM catalog._hot_deal_brand_freeze f
183
WHERE f.level = 'catalog'
184
  AND NOT EXISTS (SELECT 1 FROM catalog.model_hot_deal m
185
                   WHERE m.catalog_item_id = f.row_id);
186
 
187
 
188
-- ---------------------------------------------------------------------------
189
--  VERIFY - all three must come back clean before running schema PASS 2.
190
-- ---------------------------------------------------------------------------
191
 
192
-- 1. every scope row now carries the brand: expect 69 and 31
193
SELECT COUNT(*) AS items_on_brand
194
FROM catalog.item i JOIN catalog._hot_deal_brand_scope s ON s.item_id = i.id
195
WHERE i.brand = 'Hot Deal';
196
 
197
SELECT COUNT(DISTINCT c.id) AS catalogs_on_brand
198
FROM catalog.catalog c
199
JOIN (SELECT DISTINCT catalog_id FROM catalog._hot_deal_brand_scope) s ON s.catalog_id = c.id
200
WHERE c.brand = 'Hot Deal';
201
 
202
-- 2. no catalog is missing its attributes row or its oem_brand: expect 0
203
SELECT COUNT(*) AS catalogs_missing_attributes
204
FROM (SELECT DISTINCT catalog_id FROM catalog._hot_deal_brand_scope) s
205
LEFT JOIN catalog.model_hot_deal m ON m.catalog_item_id = s.catalog_id
206
WHERE m.id IS NULL OR m.oem_brand IS NULL;
207
 
208
-- 3. no duplicate attributes rows, which would break schema PASS 2: expect 0
209
SELECT COUNT(*) AS duplicate_attribute_rows FROM (
210
  SELECT catalog_item_id FROM catalog.model_hot_deal
211
  GROUP BY catalog_item_id HAVING COUNT(*) > 1
212
) d;
213
 
214
-- 4. eyeball the derived descriptions
215
SELECT c.id, c.brand, c.model_name, c.model_number,
216
       TRIM(CONCAT(c.brand, ' ', c.model_name, ' ', c.model_number)) AS description
217
FROM catalog.catalog c
218
JOIN (SELECT DISTINCT catalog_id FROM catalog._hot_deal_brand_scope) s ON s.catalog_id = c.id
219
ORDER BY c.id LIMIT 10;
220
 
221
 
222
-- ============================  ROLLBACK  ====================================
223
--  Restores brand, brand_id and model_name from the freeze table, then removes
224
--  the rows this migration created. Run a FULL Solr reindex afterwards.
225
--
226
--    UPDATE catalog.catalog c
227
--      JOIN catalog._hot_deal_brand_freeze f
228
--        ON f.level = 'catalog' AND f.row_id = c.id
229
--    SET c.brand      = f.old_brand,
230
--        c.brand_id   = f.old_brand_id,
231
--        c.model_name = f.old_model_name;
232
--
233
--    UPDATE catalog.item i
234
--      JOIN catalog._hot_deal_brand_freeze f
235
--        ON f.level = 'item' AND f.row_id = i.id
236
--    SET i.brand      = f.old_brand,
237
--        i.model_name = f.old_model_name;
238
--
239
--    DELETE FROM catalog.model_hot_deal WHERE created_by = 'hot_deal_brand_migration';
240
--    UPDATE catalog.model_hot_deal SET oem_brand = NULL;
241
--
242
--  Keep catalog._hot_deal_brand_freeze and catalog._hot_deal_brand_scope as
243
--  the audit record of the run; they are what make it reversible at all.
244
-- ============================================================================