fsanyoto commited on
Commit
bb16423
·
verified ·
1 Parent(s): dfd512f

Deploy AIOS web (React glide grid + FastAPI slice)

Browse files
RELEASES.json CHANGED
@@ -1,5 +1,5 @@
1
  {
2
- "current": "76fb302",
3
  "releases": [
4
  {
5
  "version": "v53",
 
1
  {
2
+ "current": "4089c02",
3
  "releases": [
4
  {
5
  "version": "v53",
VERSION CHANGED
@@ -1 +1 @@
1
- 76fb302
 
1
+ 4089c02
api/routes_products.py CHANGED
@@ -309,6 +309,21 @@ def _measure_condition_sets(views, offer, rows_src, team_id, today):
309
  for rid, codes in sets.items()}
310
 
311
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  def product_assembly(session: Session, scope: str = "product", storage_key: str = "",
313
  consume_corrections: bool = True):
314
  """The product topic's mirror of `routes_customers.grid_assembly` — SAME g-dict keys, so
@@ -422,6 +437,10 @@ def product_assembly(session: Session, scope: str = "product", storage_key: str
422
 
423
  from core import user_tables as _zones
424
  fields = _zones.with_zone_field("product_data", fields, st=session.runtime)
 
 
 
 
425
  return {"rows_src": rows_src, "pids": pids, "ws": ws, "workspace": workspace,
426
  "fields": fields, "views": views, "lists": lists,
427
  "derived": derived,
 
309
  for rid, codes in sets.items()}
310
 
311
 
312
+ def _hydrate_effective_choice_options(fields, rows_src, overlays, derived):
313
+ """Project selectable values from this caller's effective Product rows.
314
+
315
+ The Product table applies supplier-master defaults after overlay rows are built. Choice
316
+ hydration must use that same effective value, otherwise the picker misses historic master
317
+ data even while the cell visibly contains it.
318
+ """
319
+ import aios_grid
320
+
321
+ effective_rows = _seed_shared(
322
+ aios_grid.rows_from_pool(rows_src, fields, overlays, derived=derived), rows_src, fields)
323
+ return aios_grid.hydrate_choice_options_from_rows(
324
+ fields, effective_rows, ignored_by_field={"supplier": {"(none)"}})
325
+
326
+
327
  def product_assembly(session: Session, scope: str = "product", storage_key: str = "",
328
  consume_corrections: bool = True):
329
  """The product topic's mirror of `routes_customers.grid_assembly` — SAME g-dict keys, so
 
437
 
438
  from core import user_tables as _zones
439
  fields = _zones.with_zone_field("product_data", fields, st=session.runtime)
440
+ # Existing supplier-master defaults and imported overlay cells predate W43's write-time
441
+ # choice hydration. Build their effective, already-walled row values before returning the
442
+ # field contract so a populated select never opens as "no choices yet".
443
+ fields = _hydrate_effective_choice_options(fields, rows_src, ws.get("overlays"), derived)
444
  return {"rows_src": rows_src, "pids": pids, "ws": ws, "workspace": workspace,
445
  "fields": fields, "views": views, "lists": lists,
446
  "derived": derived,
platform/aios_grid.py CHANGED
@@ -257,6 +257,49 @@ def merge_choice_options(*sources):
257
  return _clean_options(merged)
258
 
259
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  def apply_choice_overrides(fields, overrides):
261
  """Return field copies with durable select-family option overrides applied.
262
 
 
257
  return _clean_options(merged)
258
 
259
 
260
+ def _choice_values_from_cell(field, value):
261
+ """The individual labels represented by one select-family cell value."""
262
+ if not isinstance(field, dict) or field.get("type") not in ("select", "multiselect"):
263
+ return []
264
+ raw_values = (str(value or "").split(",")
265
+ if field.get("type") == "multiselect" else [value])
266
+ return _clean_options(raw_values)
267
+
268
+
269
+ def hydrate_choice_options_from_rows(fields, rows, ignored_by_field=None):
270
+ """Return field copies whose choice vocabularies include their served cell values.
271
+
272
+ A field may already contain real values from an import or a read-time source default before
273
+ anybody edits it through ``grid_events``. Those values are still choices: omitting them
274
+ makes the picker claim that a populated select has no options. This is a read projection,
275
+ deliberately; it cannot write a value a caller is not permitted to read into tenant schema.
276
+ """
277
+ row_maps = [row for row in (rows or ()) if isinstance(row, dict)]
278
+ ignored = {}
279
+ for key, raw_values in (ignored_by_field or {}).items():
280
+ values = [raw_values] if isinstance(raw_values, (str, int, float)) else raw_values
281
+ ignored[str(key)] = {str(value).strip().casefold() for value in (values or ())
282
+ if str(value).strip()}
283
+
284
+ out = []
285
+ for raw in fields or ():
286
+ field = dict(raw) if isinstance(raw, dict) else raw
287
+ if not isinstance(field, dict) or field.get("type") not in ("select", "multiselect"):
288
+ out.append(field)
289
+ continue
290
+ key = str(field.get("key") or "")
291
+ values = []
292
+ for row in row_maps:
293
+ if key not in row:
294
+ continue
295
+ values.extend(value for value in _choice_values_from_cell(field, row.get(key))
296
+ if value.casefold() not in ignored.get(key, set()))
297
+ if values:
298
+ field["options"] = merge_choice_options(field.get("options"), values)
299
+ out.append(field)
300
+ return out
301
+
302
+
303
  def apply_choice_overrides(fields, overrides):
304
  """Return field copies with durable select-family option overrides applied.
305
 
platform/aios_grid_fields.json CHANGED
@@ -359,7 +359,7 @@
359
  "type": "multiselect",
360
  "source": "overlay",
361
  "default": true,
362
- "description": "Who makes it. Editable here and shared with everyone in the workspace; seeded from the inventory mastersheet.",
363
  "shared": true
364
  },
365
  {
@@ -368,7 +368,7 @@
368
  "type": "text",
369
  "source": "overlay",
370
  "default": false,
371
- "description": "Country of origin. Editable here and shared with everyone; seeded from the inventory mastersheet.",
372
  "shared": true
373
  },
374
  {
@@ -377,7 +377,7 @@
377
  "type": "int",
378
  "source": "overlay",
379
  "default": true,
380
- "description": "Order-to-arrival days for this supplier. Drives the buy signal. Editable and shared with everyone.",
381
  "shared": true
382
  },
383
  {
@@ -386,7 +386,7 @@
386
  "type": "currency",
387
  "source": "overlay",
388
  "default": false,
389
- "description": "Quoted unit cost at origin, before freight and duty. Editable and shared with everyone.",
390
  "shared": true
391
  },
392
  {
@@ -403,7 +403,7 @@
403
  "type": "text",
404
  "source": "odoo",
405
  "default": false,
406
- "description": "Package label for Price 1. Blank means Odoo has no package reference for that price."
407
  },
408
  {
409
  "key": "price_2",
@@ -671,7 +671,7 @@
671
  "label": "Days of supply",
672
  "type": "int",
673
  "source": "odoo",
674
- "description": "Days of supply at the trailing twelve month rate, counting ON HAND PLUS INBOUND units as stock. A blank means there is no honest number to print, and Stock status beside it says which of two reasons applies: 'No recent sales' is stock on the shelf that sold nothing in the window, so there is no rate to divide by; 'No stock record' is a product the inventory read returned no row for, so its shelf is unknown rather than empty. A product that sold nothing and holds nothing reads 0, not blank. Same stock figure as the cover gap columns, so the two cannot disagree about how much you have. They still divide by different rates: this one is the trailing average, the cover gap uses the forward 8 month forecast, so a seasonal SKU reads differently in each. A business unit caller gets this column too, re-scoped: the shelf is the whole warehouse's, the rate is that unit's."
675
  },
676
  {
677
  "key": "cover_gap_d",
@@ -687,7 +687,7 @@
687
  "type": "int",
688
  "source": "odoo",
689
  "default": false,
690
- "description": "The recommended reorder quantity: units of forecast demand over the lead time that on-hand plus inbound does not cover. POSITIVE means buy this many; negative is surplus units; blank means we cannot say, because the SKU has no lead time on file or no forecast demand. Rounded AWAY from zero, so a real shortfall never rounds down to nothing."
691
  },
692
  {
693
  "key": "demand_fwd",
@@ -732,7 +732,7 @@
732
  "Yes"
733
  ],
734
  "shared": true,
735
- "description": "Team-maintained. A SKU carries “Yes” when it appears on the NEEDS PRICING tab of the 2027 catalog workbook; no value means it is not on that list. Shared with everyone in the workspace — the whole point of R8 is that the TEAM's work lands in the system, not one importer's private column."
736
  },
737
  {
738
  "key": "march_pricelist",
@@ -744,7 +744,7 @@
744
  "Yes"
745
  ],
746
  "shared": true,
747
- "description": "Team-maintained. A SKU carries “Yes” when it appears on the March Pricelist tab of the 2027 catalog workbook; no value means it is not on that list. Shared with everyone in the workspace — the whole point of R8 is that the TEAM's work lands in the system, not one importer's private column."
748
  },
749
  {
750
  "key": "price_changes",
@@ -756,7 +756,7 @@
756
  "Yes"
757
  ],
758
  "shared": true,
759
- "description": "Team-maintained. A SKU carries “Yes” when it appears on the Price Changes tab of the 2027 catalog workbook; no value means it is not on that list. Shared with everyone in the workspace — the whole point of R8 is that the TEAM's work lands in the system, not one importer's private column."
760
  },
761
  {
762
  "key": "closeouts",
@@ -768,7 +768,7 @@
768
  "Yes"
769
  ],
770
  "shared": true,
771
- "description": "Team-maintained. A SKU carries “Yes” when it appears on the Closeouts tab of the 2027 catalog workbook; no value means it is not on that list. Shared with everyone in the workspace — the whole point of R8 is that the TEAM's work lands in the system, not one importer's private column."
772
  },
773
  {
774
  "key": "notes",
@@ -777,7 +777,7 @@
777
  "source": "overlay",
778
  "default": false,
779
  "shared": true,
780
- "description": "Team-maintained. The 2027 workbook's own non-Odoo columns (Product Description, Packing) folded into one field. Shared with everyone in the workspace."
781
  },
782
  {
783
  "key": "sub_category",
@@ -791,7 +791,7 @@
791
  "permissions": {
792
  "edit": "collaborative"
793
  },
794
- "description": "Fine grained product class from the Daily Mastersheet. Owned by the Administration account and shared with everyone in the workspace. Blank means the mastersheet carries no value for this SKU."
795
  },
796
  {
797
  "key": "main_category",
@@ -805,7 +805,7 @@
805
  "permissions": {
806
  "edit": "collaborative"
807
  },
808
- "description": "Top level product class from the Daily Mastersheet. Owned by the Administration account and shared with everyone in the workspace. Blank means the mastersheet carries no value for this SKU."
809
  },
810
  {
811
  "key": "color",
@@ -819,7 +819,7 @@
819
  "permissions": {
820
  "edit": "collaborative"
821
  },
822
- "description": "Colors this SKU is offered in, from the Daily Mastersheet. Multi-select, because the sheet stores several colors in one comma separated cell and each one becomes its own value. Owned by the Administration account and shared with everyone in the workspace."
823
  },
824
  {
825
  "key": "shape_style",
@@ -833,7 +833,7 @@
833
  "permissions": {
834
  "edit": "collaborative"
835
  },
836
- "description": "Shape or styling of the piece, from the Daily Mastersheet. Owned by the Administration account and shared with everyone in the workspace. Blank means the mastersheet carries no value for this SKU."
837
  },
838
  {
839
  "key": "material",
@@ -847,7 +847,7 @@
847
  "permissions": {
848
  "edit": "collaborative"
849
  },
850
- "description": "What the piece is made of, from the Daily Mastersheet. Owned by the Administration account and shared with everyone in the workspace. Blank means the mastersheet carries no value for this SKU."
851
  },
852
  {
853
  "key": "finish",
@@ -861,7 +861,7 @@
861
  "permissions": {
862
  "edit": "collaborative"
863
  },
864
- "description": "Surface finish of the piece, from the Daily Mastersheet. Owned by the Administration account and shared with everyone in the workspace. Blank means the mastersheet carries no value for this SKU."
865
  },
866
  {
867
  "key": "occassion",
@@ -875,7 +875,7 @@
875
  "permissions": {
876
  "edit": "collaborative"
877
  },
878
- "description": "Occasions this SKU is bought for, from the Daily Mastersheet. Multi-select, because the sheet stores several occasions in one comma separated cell and each one becomes its own value. The label keeps the mastersheet's own spelling. Owned by the Administration account and shared with everyone in the workspace."
879
  },
880
  {
881
  "key": "collection",
@@ -889,7 +889,7 @@
889
  "permissions": {
890
  "edit": "collaborative"
891
  },
892
- "description": "The merchandising collection this SKU belongs to, from the Daily Mastersheet. Owned by the Administration account and shared with everyone in the workspace. Blank means the mastersheet carries no value for this SKU."
893
  },
894
  {
895
  "key": "size",
@@ -903,7 +903,7 @@
903
  "permissions": {
904
  "edit": "collaborative"
905
  },
906
- "description": "Size band from the Daily Mastersheet. Owned by the Administration account and shared with everyone in the workspace. Blank means the mastersheet carries no value for this SKU."
907
  },
908
  {
909
  "key": "upc",
@@ -911,7 +911,7 @@
911
  "type": "text",
912
  "source": "odoo",
913
  "default": false,
914
- "description": "The SKU's barcode, read live from Odoo as product.product.barcode. Blank when Odoo carries no barcode for this product. The Daily Mastersheet has a UPC column of its own and it is deliberately not used: three quarters of it is a placeholder rather than a number."
915
  }
916
  ]
917
  }
 
359
  "type": "multiselect",
360
  "source": "overlay",
361
  "default": true,
362
+ "description": "Supplier or manufacturer for this product.",
363
  "shared": true
364
  },
365
  {
 
368
  "type": "text",
369
  "source": "overlay",
370
  "default": false,
371
+ "description": "Country where this product was made.",
372
  "shared": true
373
  },
374
  {
 
377
  "type": "int",
378
  "source": "overlay",
379
  "default": true,
380
+ "description": "Days from placing an order to receiving it.",
381
  "shared": true
382
  },
383
  {
 
386
  "type": "currency",
387
  "source": "overlay",
388
  "default": false,
389
+ "description": "Quoted unit cost before freight and duty.",
390
  "shared": true
391
  },
392
  {
 
403
  "type": "text",
404
  "source": "odoo",
405
  "default": false,
406
+ "description": "Package label for Price 1."
407
  },
408
  {
409
  "key": "price_2",
 
671
  "label": "Days of supply",
672
  "type": "int",
673
  "source": "odoo",
674
+ "description": "Estimated days of supply at the trailing twelve-month sales rate."
675
  },
676
  {
677
  "key": "cover_gap_d",
 
687
  "type": "int",
688
  "source": "odoo",
689
  "default": false,
690
+ "description": "Recommended reorder quantity after on-hand and inbound stock."
691
  },
692
  {
693
  "key": "demand_fwd",
 
732
  "Yes"
733
  ],
734
  "shared": true,
735
+ "description": "Marks products that need pricing."
736
  },
737
  {
738
  "key": "march_pricelist",
 
744
  "Yes"
745
  ],
746
  "shared": true,
747
+ "description": "Marks products included in the March pricelist."
748
  },
749
  {
750
  "key": "price_changes",
 
756
  "Yes"
757
  ],
758
  "shared": true,
759
+ "description": "Marks products with planned price changes."
760
  },
761
  {
762
  "key": "closeouts",
 
768
  "Yes"
769
  ],
770
  "shared": true,
771
+ "description": "Marks products designated for closeout."
772
  },
773
  {
774
  "key": "notes",
 
777
  "source": "overlay",
778
  "default": false,
779
  "shared": true,
780
+ "description": "Team notes about this product."
781
  },
782
  {
783
  "key": "sub_category",
 
791
  "permissions": {
792
  "edit": "collaborative"
793
  },
794
+ "description": "Detailed product classification."
795
  },
796
  {
797
  "key": "main_category",
 
805
  "permissions": {
806
  "edit": "collaborative"
807
  },
808
+ "description": "Top-level product classification."
809
  },
810
  {
811
  "key": "color",
 
819
  "permissions": {
820
  "edit": "collaborative"
821
  },
822
+ "description": "Colors available for this product."
823
  },
824
  {
825
  "key": "shape_style",
 
833
  "permissions": {
834
  "edit": "collaborative"
835
  },
836
+ "description": "Shape or styling of this product."
837
  },
838
  {
839
  "key": "material",
 
847
  "permissions": {
848
  "edit": "collaborative"
849
  },
850
+ "description": "Material this product is made from."
851
  },
852
  {
853
  "key": "finish",
 
861
  "permissions": {
862
  "edit": "collaborative"
863
  },
864
+ "description": "Surface finish of this product."
865
  },
866
  {
867
  "key": "occassion",
 
875
  "permissions": {
876
  "edit": "collaborative"
877
  },
878
+ "description": "Occasions this product is bought for."
879
  },
880
  {
881
  "key": "collection",
 
889
  "permissions": {
890
  "edit": "collaborative"
891
  },
892
+ "description": "Merchandising collection this product belongs to."
893
  },
894
  {
895
  "key": "size",
 
903
  "permissions": {
904
  "edit": "collaborative"
905
  },
906
+ "description": "Size band for this product."
907
  },
908
  {
909
  "key": "upc",
 
911
  "type": "text",
912
  "source": "odoo",
913
  "default": false,
914
+ "description": "Barcode for this product."
915
  }
916
  ]
917
  }
platform/model/topics/odoo_products.yml CHANGED
@@ -174,22 +174,22 @@ fields:
174
  label: "Supplier"
175
  type: multiselect
176
  kind: data
177
- means: "Who makes it. Editable here and shared with everyone in the workspace; seeded from the inventory mastersheet."
178
  - key: origin_country
179
  label: "Country"
180
  type: text
181
  kind: data
182
- means: "Country of origin. Editable here and shared with everyone; seeded from the inventory mastersheet."
183
  - key: lead_days
184
  label: "Lead time (days)"
185
  type: int
186
  kind: data
187
- means: "Order-to-arrival days for this supplier. Drives the buy signal. Editable and shared with everyone."
188
  - key: first_cost
189
  label: "First cost"
190
  type: currency
191
  kind: data
192
- means: "Quoted unit cost at origin, before freight and duty. Editable and shared with everyone."
193
  - key: price_1
194
  label: "Price 1"
195
  type: currency
 
174
  label: "Supplier"
175
  type: multiselect
176
  kind: data
177
+ means: "Supplier or manufacturer for this product."
178
  - key: origin_country
179
  label: "Country"
180
  type: text
181
  kind: data
182
+ means: "Country where this product was made."
183
  - key: lead_days
184
  label: "Lead time (days)"
185
  type: int
186
  kind: data
187
+ means: "Days from placing an order to receiving it."
188
  - key: first_cost
189
  label: "First cost"
190
  type: currency
191
  kind: data
192
+ means: "Quoted unit cost before freight and duty."
193
  - key: price_1
194
  label: "Price 1"
195
  type: currency