fix(review): #90 thickness band omitted entirely (not read-only)
This commit is contained in:
@@ -45,6 +45,21 @@ def env(monkeypatch):
|
||||
conn.execute("INSERT INTO woo_categories(id,name,slug,parent_id,woo_id) VALUES (2,'Live edge wood','live-edge-wood',NULL,101)")
|
||||
conn.execute("INSERT INTO woo_attributes(id,name,slug,woo_id) VALUES (8,'Edge Type','edge-type',200)")
|
||||
conn.execute("INSERT INTO woo_attribute_terms(id,attribute_id,name,slug,woo_id) VALUES (1,8,'Flat','flat',300)")
|
||||
# Thickness bands, names as synced (the derivation parses them) — QUALITY-95.
|
||||
conn.execute("INSERT INTO woo_attributes(id,name,slug,woo_id) VALUES (9,'Thickness','pa_thk',201)")
|
||||
for tid, name, slug in [
|
||||
(347, 'Under 1/2"', 'xthin'), (348, '1/2" - 3/4"', '2-3qtr'),
|
||||
(349, '3/4" - 1"', '3-4qtr'), (350, '1"- 1 1/2"', '4-6qtr'),
|
||||
(351, '1 1/2" - 2"', '6-8qtr'), (352, '2" - 3"', '8-12qtr'),
|
||||
(353, 'Over 3"', '12qtr'),
|
||||
]:
|
||||
conn.execute(
|
||||
"INSERT INTO woo_attribute_terms(id,attribute_id,name,slug,woo_id) VALUES (?,9,?,?,?)",
|
||||
(tid, name, slug, tid),
|
||||
)
|
||||
# Moisture keeps its own attribute so the five locked attributes stay distinct.
|
||||
conn.execute("INSERT INTO woo_attributes(id,name,slug,woo_id) VALUES (10,'Moisture','pa_mst',202)")
|
||||
conn.execute("INSERT INTO woo_attribute_terms(id,attribute_id,name,slug,woo_id) VALUES (326,10,'Kiln dried','kd',326)")
|
||||
# creds
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('woo_wp_username','admin',NULL)")
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('woo_app_password',NULL,?)", (encrypt("secret"),))
|
||||
@@ -119,6 +134,62 @@ def test_publish_success(env, monkeypatch):
|
||||
assert conn.execute("SELECT processed_blob FROM slab_photos WHERE id='p1'").fetchone()["processed_blob"] is None
|
||||
|
||||
|
||||
def test_publish_sends_derived_thickness_band(env, monkeypatch):
|
||||
"""#90 / QUALITY-95: the band is off the Review screen but must still reach Woo.
|
||||
|
||||
The owner has no thickness-band control, so nothing client-side carries it: the
|
||||
server derives it from the measurement on create/update, and publish must send the
|
||||
live synced term name on the product payload. This pins that chain end to end.
|
||||
"""
|
||||
client, conn = env
|
||||
create = client.post("/api/v1/slabs", json={
|
||||
"id": "s1", "sku": "SLAB-BAND", "length_in": 114.0, "thickness_in": 1.75,
|
||||
"sqft": 30.0, "bdft": 18.4, "width_min_in": 18.0, "width_max_in": 24.3, "width_avg_in": 24.3,
|
||||
})
|
||||
assert create.status_code == 201, create.text
|
||||
# 1.75 in bands into the live '1 1/2" - 2"' term — never sent by the client.
|
||||
assert create.json()["thickness_term_id"] == 351
|
||||
|
||||
put = client.put("/api/v1/slabs/s1", json={
|
||||
"client_rev": create.json()["server_rev"], "species_id": 1, "wood_category_ids": [2],
|
||||
"edge_type_term_id": 1, "figure_term_ids": [1], "grade_term_id": 1, "moisture_term_id": 326,
|
||||
"price": 184.0, "price_source": "recommendation", "title": "T", "short_description": "S",
|
||||
"description": "D", "content_source": "manual",
|
||||
})
|
||||
assert put.status_code == 200, put.text
|
||||
conn.execute("UPDATE slabs SET status='ready' WHERE id='s1'")
|
||||
conn.execute("INSERT INTO slab_photos(id,slab_id,seq,is_calibration,processed_blob,created_at) VALUES ('p1','s1',1,1,X'89504E47','t')")
|
||||
conn.commit()
|
||||
|
||||
products = []
|
||||
|
||||
def fake_request(url, method, data, headers):
|
||||
if "/products?sku=" in url:
|
||||
return 200, {}, b"[]"
|
||||
if url.endswith("/wp-json/wp/v2/media") and method == "POST":
|
||||
return 201, {}, json.dumps({"id": 42}).encode()
|
||||
if url.endswith("/wp-json/wc/v3/products") and method == "POST":
|
||||
products.append(json.loads(data))
|
||||
return 201, {}, json.dumps({"id": 999}).encode()
|
||||
return 200, {}, b"[]"
|
||||
|
||||
from backend.woo import WooClient
|
||||
|
||||
orig_init = WooClient.__init__
|
||||
|
||||
def patched_init(self, *a, **k):
|
||||
orig_init(self, *a, **k)
|
||||
self._request = fake_request
|
||||
|
||||
monkeypatch.setattr(WooClient, "__init__", patched_init)
|
||||
r = client.post("/api/v1/slabs/s1/publish")
|
||||
assert r.status_code == 200, r.text
|
||||
assert len(products) == 1, "expected exactly one product create"
|
||||
attrs = {a["id"]: a for a in products[0].get("attributes", [])}
|
||||
assert 9 in attrs, f"Thickness attribute missing from the Woo payload: {products[0].get('attributes')}"
|
||||
assert attrs[9]["options"] == ['1 1/2" - 2"']
|
||||
|
||||
|
||||
def test_publish_woo_image_order_by_role(env, monkeypatch):
|
||||
# PUBLISH-45: Woo order is clean cal, dimension-overlay, then batch — by
|
||||
# role, not by stored seq (the Review strip stores the owner's keep order).
|
||||
|
||||
@@ -121,9 +121,10 @@ hard stop. Switch to a supported browser.
|
||||
3. **Wood categories.** Pick at least one from the synced list.
|
||||
4. **Edge type, figure, grade.** Pick from the synced lists. One edge type, at least one figure,
|
||||
exactly one grade.
|
||||
5. **Thickness band.** Derived, not picked: the server bands the measured thickness up into
|
||||
the synced Woo band and Review shows it read-only. Verify the shown band is the one the
|
||||
captured thickness rounds up into (`QUALITY-95`).
|
||||
5. **Thickness band.** Not on the screen and not picked: the server bands the measured thickness
|
||||
up into the synced Woo band, stores it, and publish sends it to the store. Verify after publish
|
||||
that the Woo product's Thickness attribute is the band the captured thickness rounds up into
|
||||
(`QUALITY-95`).
|
||||
6. **Moisture.** Keep the default (kiln-dried) or change it.
|
||||
7. **fig-* tags.** Optional; pick any from the synced list.
|
||||
8. **feat-* tags.** Zero or more. Only pick existing `feat-*` tags.
|
||||
|
||||
@@ -59,9 +59,9 @@ Cites REVIEW-5, REVIEW-15, REVIEW-20, METADATA-45.
|
||||
optional; price; title, short_description, and description.
|
||||
- **Primary category.** The owner declares one primary wood category and any secondary ones; the
|
||||
default is the first selected.
|
||||
- **Thickness band is derived, not picked.** The measured thickness bands up into the synced Woo
|
||||
band server-side (`METADATA-15`, `QUALITY-95`); Review shows the assigned band read-only and
|
||||
offers no owner control for it.
|
||||
- **Thickness band is derived and not shown.** The measured thickness bands up into the synced Woo
|
||||
band server-side (`METADATA-15`, `QUALITY-95`); Review carries no thickness-band control and no
|
||||
read-only echo of it. The derived band is stored on the slab and sent to the store at publish.
|
||||
- **Inline nudges.** Every empty required field shows an inline nudge as the owner works; there is
|
||||
no single submit-time gap list.
|
||||
- **Calculated fields.** Widths, sqft, and bdft are read-only and not overridable.
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
let edgeOpts: Opt[] = [];
|
||||
let figureOpts: Opt[] = [];
|
||||
let gradeOpts: Opt[] = [];
|
||||
let thicknessOpts: Opt[] = [];
|
||||
let moistureOpts: Opt[] = [];
|
||||
let figTagOpts: Opt[] = [];
|
||||
let featTagOpts: Opt[] = [];
|
||||
@@ -76,7 +75,8 @@
|
||||
edgeOpts = termsOf('edge type');
|
||||
figureOpts = termsOf('figure');
|
||||
gradeOpts = termsOf('grade');
|
||||
thicknessOpts = termsOf('thickness');
|
||||
// No thickness options: the band is derived server-side and never shown here
|
||||
// (METADATA-15 / QUALITY-95).
|
||||
moistureOpts = termsOf('moisture');
|
||||
// fig-* / feat-* product tags (METADATA-30): pulled from the live synced set,
|
||||
// filtered by the family the backend keeps. Never invent a prefix — if the
|
||||
@@ -346,11 +346,9 @@
|
||||
.map((o) => ({ ...o, ai: aiRecommendations.includes(o.id) }));
|
||||
|
||||
// METADATA-15 / QUALITY-95: the thickness band is derived server-side from the
|
||||
// measured thickness against the live synced Woo bands. There is no owner
|
||||
// control for it — this is the read-only display of the band the server assigned.
|
||||
$: thicknessBandName =
|
||||
thicknessOpts.find((o) => o.id === (slab?.thickness_term_id ?? -1))?.name ?? '';
|
||||
|
||||
// measured thickness against the live synced Woo bands. It is deliberately absent
|
||||
// from this screen — no owner control, no read-only echo. The only thickness the
|
||||
// owner sees here is the measured value in the summary above.
|
||||
const aiIn = (ids: number[]) => (id: number) => ids.includes(id);
|
||||
// Persist taxonomy/price/sku edits to the slab (PUT), then recompute ready.
|
||||
async function commitReview(): Promise<void> {
|
||||
@@ -730,12 +728,6 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="lbl">Thickness band <span class="sug">set from the measurement</span></label>
|
||||
<p class="ro" data-testid="thickness-band">
|
||||
{#if thicknessBandName}{thicknessBandName}{:else}— not yet resolved{/if}
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="lbl" for="moisture">Moisture</label>
|
||||
<select id="moisture" class="sel" data-testid="moisture" bind:value={moistureId} on:change={commitReview}>
|
||||
@@ -862,9 +854,6 @@
|
||||
.field { margin-bottom: 12px; }
|
||||
.nudge { color: #e8a; font-size: .74rem; margin: 4px 0 0; }
|
||||
.sug { color: #aaa; font-size: .74rem; font-weight: 400; margin-left: 6px; }
|
||||
/* Read-only derived value (thickness band): reads as data, not as a control. */
|
||||
.ro { margin: 0; padding: 9px; background: #161616; border: 1px solid #2f2f2f; border-radius: 3px;
|
||||
color: #cfcfcf; font-size: .9rem; font-variant-numeric: tabular-nums; }
|
||||
.gen { margin-top: 8px; } .gen-btn { padding: 8px 12px; border: 0; border-radius: 3px; background: #00e5ff; color: #002; font-weight: 600; font-size: .82rem; }
|
||||
/* Lock chrome matches agreed #90 wireframe: muted dark green, not jarring neon. */
|
||||
.lockbox { margin: 14px 0; padding: 12px; border: 1px solid #2a3a2a; border-radius: 3px; background: #141a14; }
|
||||
|
||||
Reference in New Issue
Block a user