feat: bump client-services (accumulated feature work + deploy fixes)
Snapshot of in-progress work across local_backend, manager_dashboard, and waiter_pwa (pricing, chat, fiscal, prep zones, recovery codes, CRM, inventory, permissions), plus the nginx/docker-compose deploy fixes for the Unraid + NPM reverse-proxy setup. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,24 @@ class ParentGeneralReorderItem(BaseModel):
|
||||
general_sort_order: int
|
||||
|
||||
|
||||
# ── Modifier Groups ───────────────────────────────────────────────────────────
|
||||
|
||||
class ModifierGroupCreate(BaseModel):
|
||||
modifier_type: str # "option" | "ingredient" | "preference"
|
||||
name: str
|
||||
sort_order: int = 0
|
||||
|
||||
|
||||
class ModifierGroupOut(BaseModel):
|
||||
id: int
|
||||
product_id: int
|
||||
modifier_type: str
|
||||
name: str
|
||||
sort_order: int = 0
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
# ── Quick Options ─────────────────────────────────────────────────────────────
|
||||
|
||||
class ProductQuickOptionCreate(BaseModel):
|
||||
@@ -84,14 +102,23 @@ class OptionSubChoice(BaseModel):
|
||||
name: str
|
||||
extra_cost: float = 0.0
|
||||
is_default: bool = False
|
||||
# When True waiter can add more than 1 of this sub-choice
|
||||
allow_multiple: bool = False
|
||||
# When True renders as half-width card on the PWA
|
||||
is_compact: bool = False
|
||||
|
||||
|
||||
class ProductOptionBase(BaseModel):
|
||||
name: str
|
||||
extra_cost: float = 0.0
|
||||
allow_multiple: bool = False
|
||||
# When True waiter can select more than one sub-choice at once
|
||||
multi_select: bool = False
|
||||
is_favorite: bool = False
|
||||
favorite_sort_order: int = 0
|
||||
# When True renders the option itself as half-width card on the PWA
|
||||
is_compact: bool = False
|
||||
group_id: Optional[int] = None
|
||||
|
||||
|
||||
class ProductOptionCreate(ProductOptionBase):
|
||||
@@ -117,9 +144,12 @@ class ProductOptionOut(ProductOptionBase):
|
||||
'name': data.name,
|
||||
'extra_cost': data.extra_cost,
|
||||
'allow_multiple': getattr(data, 'allow_multiple', False) or False,
|
||||
'multi_select': getattr(data, 'multi_select', False) or False,
|
||||
'sub_choices': parsed,
|
||||
'is_favorite': getattr(data, 'is_favorite', False) or False,
|
||||
'favorite_sort_order': getattr(data, 'favorite_sort_order', 0) or 0,
|
||||
'is_compact': getattr(data, 'is_compact', False) or False,
|
||||
'group_id': getattr(data, 'group_id', None),
|
||||
}
|
||||
return data
|
||||
|
||||
@@ -131,6 +161,9 @@ class ProductIngredientBase(BaseModel):
|
||||
extra_cost: float = 0.0
|
||||
is_favorite: bool = False
|
||||
favorite_sort_order: int = 0
|
||||
# When True renders as half-width card on the PWA
|
||||
is_compact: bool = False
|
||||
group_id: Optional[int] = None
|
||||
|
||||
|
||||
class ProductIngredientCreate(ProductIngredientBase):
|
||||
@@ -150,6 +183,7 @@ class SubChoice(BaseModel):
|
||||
name: str
|
||||
extra_cost: float = 0.0
|
||||
is_default: bool = False
|
||||
is_compact: bool = False
|
||||
|
||||
|
||||
# ── Shared subset (set-level, shown for all non-disabling choices) ─────────────
|
||||
@@ -158,6 +192,7 @@ class SharedSubsetChoice(BaseModel):
|
||||
name: str
|
||||
extra_cost: float = 0.0
|
||||
is_default: bool = False
|
||||
is_compact: bool = False
|
||||
|
||||
|
||||
class SharedSubset(BaseModel):
|
||||
@@ -172,6 +207,8 @@ class PreferenceChoiceCreate(BaseModel):
|
||||
extra_cost: float = 0.0
|
||||
sub_choices: List[SubChoice] = []
|
||||
disables_subset: bool = False
|
||||
# When True renders as half-width card on the PWA
|
||||
is_compact: bool = False
|
||||
|
||||
|
||||
class PreferenceChoiceOut(BaseModel):
|
||||
@@ -181,6 +218,7 @@ class PreferenceChoiceOut(BaseModel):
|
||||
extra_cost: float = 0.0
|
||||
sub_choices: List[SubChoice] = []
|
||||
disables_subset: bool = False
|
||||
is_compact: bool = False
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
@@ -203,6 +241,7 @@ class PreferenceChoiceOut(BaseModel):
|
||||
'extra_cost': data.extra_cost,
|
||||
'sub_choices': parsed,
|
||||
'disables_subset': data.disables_subset or False,
|
||||
'is_compact': getattr(data, 'is_compact', False) or False,
|
||||
}
|
||||
return data
|
||||
|
||||
@@ -214,6 +253,9 @@ class PreferenceSetCreate(BaseModel):
|
||||
shared_subset: Optional[SharedSubset] = None
|
||||
is_favorite: bool = False
|
||||
favorite_sort_order: int = 0
|
||||
group_id: Optional[int] = None
|
||||
allow_multi_select: bool = False
|
||||
allow_choice_quantity: bool = False
|
||||
|
||||
|
||||
class PreferenceSetOut(BaseModel):
|
||||
@@ -225,6 +267,9 @@ class PreferenceSetOut(BaseModel):
|
||||
shared_subset: Optional[SharedSubset] = None
|
||||
is_favorite: bool = False
|
||||
favorite_sort_order: int = 0
|
||||
group_id: Optional[int] = None
|
||||
allow_multi_select: bool = False
|
||||
allow_choice_quantity: bool = False
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
@@ -249,6 +294,9 @@ class PreferenceSetOut(BaseModel):
|
||||
'shared_subset': parsed,
|
||||
'is_favorite': getattr(data, 'is_favorite', False) or False,
|
||||
'favorite_sort_order': getattr(data, 'favorite_sort_order', 0) or 0,
|
||||
'group_id': getattr(data, 'group_id', None),
|
||||
'allow_multi_select': getattr(data, 'allow_multi_select', False) or False,
|
||||
'allow_choice_quantity': getattr(data, 'allow_choice_quantity', False) or False,
|
||||
}
|
||||
return data
|
||||
|
||||
@@ -265,9 +313,12 @@ class ProductBase(BaseModel):
|
||||
description: Optional[str] = None
|
||||
category_id: Optional[int] = None
|
||||
base_price: float
|
||||
# piece | portion | kg | liter | gram | ml
|
||||
unit_type: str = "piece"
|
||||
is_available: bool = True
|
||||
lifecycle_status: str = "active"
|
||||
printer_zone_id: Optional[int] = None
|
||||
printer_zone_id: Optional[int] = None # kept for backward compat; use prep_zone_ids instead
|
||||
prep_zone_ids: List[int] = []
|
||||
sort_order: int = 0
|
||||
# Xenia Connect — digital menu overrides
|
||||
digital_visible: bool = True
|
||||
@@ -280,6 +331,15 @@ class ProductBase(BaseModel):
|
||||
# Phase 2A — cost tracking
|
||||
cost_simple: Optional[float] = None
|
||||
cost_breakdown: Optional[List[CostBreakdownItem]] = None
|
||||
# Quick Add
|
||||
quick_add_enabled: bool = True
|
||||
# Tags
|
||||
tags: Optional[List[str]] = None
|
||||
# Service item flag
|
||||
is_service_item: bool = False
|
||||
# Fiscal printer (ΦΗΜ)
|
||||
fiscal_name: Optional[str] = None
|
||||
fiscal_vat_group_id: Optional[int] = None
|
||||
|
||||
|
||||
class ProductCreate(ProductBase):
|
||||
@@ -287,6 +347,8 @@ class ProductCreate(ProductBase):
|
||||
options: List[ProductOptionCreate] = []
|
||||
ingredients: List[ProductIngredientCreate] = []
|
||||
preference_sets: List[PreferenceSetCreate] = []
|
||||
# group_id on options/ingredients/preference_sets is an index into this list
|
||||
modifier_groups: List[ModifierGroupCreate] = []
|
||||
|
||||
|
||||
class ProductUpdate(BaseModel):
|
||||
@@ -294,14 +356,17 @@ class ProductUpdate(BaseModel):
|
||||
description: Optional[str] = None
|
||||
category_id: Optional[int] = None
|
||||
base_price: Optional[float] = None
|
||||
unit_type: Optional[str] = None
|
||||
is_available: Optional[bool] = None
|
||||
lifecycle_status: Optional[str] = None
|
||||
printer_zone_id: Optional[int] = None
|
||||
prep_zone_ids: Optional[List[int]] = None
|
||||
sort_order: Optional[int] = None
|
||||
quick_options: Optional[List[ProductQuickOptionCreate]] = None
|
||||
options: Optional[List[ProductOptionCreate]] = None
|
||||
ingredients: Optional[List[ProductIngredientCreate]] = None
|
||||
preference_sets: Optional[List[PreferenceSetCreate]] = None
|
||||
modifier_groups: Optional[List[ModifierGroupCreate]] = None
|
||||
# Xenia Connect — digital menu overrides
|
||||
digital_visible: Optional[bool] = None
|
||||
digital_available: Optional[bool] = None
|
||||
@@ -313,6 +378,15 @@ class ProductUpdate(BaseModel):
|
||||
# Phase 2A — cost tracking
|
||||
cost_simple: Optional[float] = None
|
||||
cost_breakdown: Optional[List[CostBreakdownItem]] = None
|
||||
# Quick Add
|
||||
quick_add_enabled: Optional[bool] = None
|
||||
# Tags
|
||||
tags: Optional[List[str]] = None
|
||||
# Service item flag
|
||||
is_service_item: Optional[bool] = None
|
||||
# Fiscal printer (ΦΗΜ)
|
||||
fiscal_name: Optional[str] = None
|
||||
fiscal_vat_group_id: Optional[int] = None
|
||||
|
||||
|
||||
class ProductReorderItem(BaseModel):
|
||||
@@ -326,6 +400,7 @@ class ProductOut(ProductBase):
|
||||
options: List[ProductOptionOut] = []
|
||||
ingredients: List[ProductIngredientOut] = []
|
||||
preference_sets: List[PreferenceSetOut] = []
|
||||
modifier_groups: List[ModifierGroupOut] = []
|
||||
image_url: Optional[str] = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
@@ -348,9 +423,11 @@ class ProductOut(ProductBase):
|
||||
'description': data.description,
|
||||
'category_id': data.category_id,
|
||||
'base_price': data.base_price,
|
||||
'unit_type': getattr(data, 'unit_type', 'piece') or 'piece',
|
||||
'is_available': data.is_available,
|
||||
'lifecycle_status': data.lifecycle_status,
|
||||
'printer_zone_id': data.printer_zone_id,
|
||||
'prep_zone_ids': [z.id for z in data.prep_zones] if hasattr(data, 'prep_zones') else [],
|
||||
'sort_order': data.sort_order,
|
||||
'image_url': data.image_url,
|
||||
'digital_visible': bool(data.digital_visible),
|
||||
@@ -362,9 +439,15 @@ class ProductOut(ProductBase):
|
||||
'digital_image_url': data.digital_image_url,
|
||||
'cost_simple': data.cost_simple,
|
||||
'cost_breakdown': parsed,
|
||||
'quick_add_enabled': getattr(data, 'quick_add_enabled', True),
|
||||
'tags': json.loads(data.tags) if isinstance(getattr(data, 'tags', None), str) else (getattr(data, 'tags', None) or []),
|
||||
'is_service_item': bool(getattr(data, 'is_service_item', False)),
|
||||
'fiscal_name': getattr(data, 'fiscal_name', None),
|
||||
'fiscal_vat_group_id': getattr(data, 'fiscal_vat_group_id', None),
|
||||
'quick_options': list(data.quick_options),
|
||||
'options': list(data.options),
|
||||
'ingredients': list(data.ingredients),
|
||||
'preference_sets': list(data.preference_sets),
|
||||
'modifier_groups': list(data.modifier_groups) if hasattr(data, 'modifier_groups') else [],
|
||||
}
|
||||
return data
|
||||
|
||||
Reference in New Issue
Block a user