Deploy AIOS web (React glide grid + FastAPI slice)
Browse files- RELEASES.json +1 -1
- VERSION +1 -1
- api/routes_products.py +19 -0
- platform/aios_grid.py +43 -0
- platform/aios_grid_fields.json +22 -22
- platform/model/topics/odoo_products.yml +4 -4
RELEASES.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
{
|
| 2 |
-
"current": "
|
| 3 |
"releases": [
|
| 4 |
{
|
| 5 |
"version": "v53",
|
|
|
|
| 1 |
{
|
| 2 |
+
"current": "4089c02",
|
| 3 |
"releases": [
|
| 4 |
{
|
| 5 |
"version": "v53",
|
VERSION
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
|
|
|
|
| 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": "
|
| 363 |
"shared": true
|
| 364 |
},
|
| 365 |
{
|
|
@@ -368,7 +368,7 @@
|
|
| 368 |
"type": "text",
|
| 369 |
"source": "overlay",
|
| 370 |
"default": false,
|
| 371 |
-
"description": "Country
|
| 372 |
"shared": true
|
| 373 |
},
|
| 374 |
{
|
|
@@ -377,7 +377,7 @@
|
|
| 377 |
"type": "int",
|
| 378 |
"source": "overlay",
|
| 379 |
"default": true,
|
| 380 |
-
"description": "
|
| 381 |
"shared": true
|
| 382 |
},
|
| 383 |
{
|
|
@@ -386,7 +386,7 @@
|
|
| 386 |
"type": "currency",
|
| 387 |
"source": "overlay",
|
| 388 |
"default": false,
|
| 389 |
-
"description": "Quoted unit cost
|
| 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.
|
| 407 |
},
|
| 408 |
{
|
| 409 |
"key": "price_2",
|
|
@@ -671,7 +671,7 @@
|
|
| 671 |
"label": "Days of supply",
|
| 672 |
"type": "int",
|
| 673 |
"source": "odoo",
|
| 674 |
-
"description": "
|
| 675 |
},
|
| 676 |
{
|
| 677 |
"key": "cover_gap_d",
|
|
@@ -687,7 +687,7 @@
|
|
| 687 |
"type": "int",
|
| 688 |
"source": "odoo",
|
| 689 |
"default": false,
|
| 690 |
-
"description": "
|
| 691 |
},
|
| 692 |
{
|
| 693 |
"key": "demand_fwd",
|
|
@@ -732,7 +732,7 @@
|
|
| 732 |
"Yes"
|
| 733 |
],
|
| 734 |
"shared": true,
|
| 735 |
-
"description": "
|
| 736 |
},
|
| 737 |
{
|
| 738 |
"key": "march_pricelist",
|
|
@@ -744,7 +744,7 @@
|
|
| 744 |
"Yes"
|
| 745 |
],
|
| 746 |
"shared": true,
|
| 747 |
-
"description": "
|
| 748 |
},
|
| 749 |
{
|
| 750 |
"key": "price_changes",
|
|
@@ -756,7 +756,7 @@
|
|
| 756 |
"Yes"
|
| 757 |
],
|
| 758 |
"shared": true,
|
| 759 |
-
"description": "
|
| 760 |
},
|
| 761 |
{
|
| 762 |
"key": "closeouts",
|
|
@@ -768,7 +768,7 @@
|
|
| 768 |
"Yes"
|
| 769 |
],
|
| 770 |
"shared": true,
|
| 771 |
-
"description": "
|
| 772 |
},
|
| 773 |
{
|
| 774 |
"key": "notes",
|
|
@@ -777,7 +777,7 @@
|
|
| 777 |
"source": "overlay",
|
| 778 |
"default": false,
|
| 779 |
"shared": true,
|
| 780 |
-
"description": "Team
|
| 781 |
},
|
| 782 |
{
|
| 783 |
"key": "sub_category",
|
|
@@ -791,7 +791,7 @@
|
|
| 791 |
"permissions": {
|
| 792 |
"edit": "collaborative"
|
| 793 |
},
|
| 794 |
-
"description": "
|
| 795 |
},
|
| 796 |
{
|
| 797 |
"key": "main_category",
|
|
@@ -805,7 +805,7 @@
|
|
| 805 |
"permissions": {
|
| 806 |
"edit": "collaborative"
|
| 807 |
},
|
| 808 |
-
"description": "Top
|
| 809 |
},
|
| 810 |
{
|
| 811 |
"key": "color",
|
|
@@ -819,7 +819,7 @@
|
|
| 819 |
"permissions": {
|
| 820 |
"edit": "collaborative"
|
| 821 |
},
|
| 822 |
-
"description": "Colors
|
| 823 |
},
|
| 824 |
{
|
| 825 |
"key": "shape_style",
|
|
@@ -833,7 +833,7 @@
|
|
| 833 |
"permissions": {
|
| 834 |
"edit": "collaborative"
|
| 835 |
},
|
| 836 |
-
"description": "Shape or styling of
|
| 837 |
},
|
| 838 |
{
|
| 839 |
"key": "material",
|
|
@@ -847,7 +847,7 @@
|
|
| 847 |
"permissions": {
|
| 848 |
"edit": "collaborative"
|
| 849 |
},
|
| 850 |
-
"description": "
|
| 851 |
},
|
| 852 |
{
|
| 853 |
"key": "finish",
|
|
@@ -861,7 +861,7 @@
|
|
| 861 |
"permissions": {
|
| 862 |
"edit": "collaborative"
|
| 863 |
},
|
| 864 |
-
"description": "Surface finish of
|
| 865 |
},
|
| 866 |
{
|
| 867 |
"key": "occassion",
|
|
@@ -875,7 +875,7 @@
|
|
| 875 |
"permissions": {
|
| 876 |
"edit": "collaborative"
|
| 877 |
},
|
| 878 |
-
"description": "Occasions this
|
| 879 |
},
|
| 880 |
{
|
| 881 |
"key": "collection",
|
|
@@ -889,7 +889,7 @@
|
|
| 889 |
"permissions": {
|
| 890 |
"edit": "collaborative"
|
| 891 |
},
|
| 892 |
-
"description": "
|
| 893 |
},
|
| 894 |
{
|
| 895 |
"key": "size",
|
|
@@ -903,7 +903,7 @@
|
|
| 903 |
"permissions": {
|
| 904 |
"edit": "collaborative"
|
| 905 |
},
|
| 906 |
-
"description": "Size band
|
| 907 |
},
|
| 908 |
{
|
| 909 |
"key": "upc",
|
|
@@ -911,7 +911,7 @@
|
|
| 911 |
"type": "text",
|
| 912 |
"source": "odoo",
|
| 913 |
"default": false,
|
| 914 |
-
"description": "
|
| 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: "
|
| 178 |
- key: origin_country
|
| 179 |
label: "Country"
|
| 180 |
type: text
|
| 181 |
kind: data
|
| 182 |
-
means: "Country
|
| 183 |
- key: lead_days
|
| 184 |
label: "Lead time (days)"
|
| 185 |
type: int
|
| 186 |
kind: data
|
| 187 |
-
means: "
|
| 188 |
- key: first_cost
|
| 189 |
label: "First cost"
|
| 190 |
type: currency
|
| 191 |
kind: data
|
| 192 |
-
means: "Quoted unit cost
|
| 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
|