Modifier Category Working
This commit is contained in:
@@ -14,6 +14,8 @@ from ..models.menu_category import MenuCategory
|
||||
from ..models.modifier_category import ModifierCategory
|
||||
from ..models.product import Product
|
||||
from ..models.product_version import ProductVersion
|
||||
from ..models.sku_version import SkuVersion
|
||||
from ..models.stock_keeping_unit import StockKeepingUnit
|
||||
from ..schemas import modifier_category as schemas
|
||||
from ..schemas.menu_category import MenuCategoryLink
|
||||
from ..schemas.modifier_category_for_product import ModifierCategoryForProduct, ModifierLink
|
||||
@@ -41,7 +43,7 @@ def save(
|
||||
is_active=data.is_active,
|
||||
)
|
||||
db.add(item)
|
||||
add_products(item, data.menu_categories, db)
|
||||
add_skus(item, data.menu_categories, db)
|
||||
db.commit()
|
||||
return modifier_category_info(item, date_, db=db)
|
||||
except SQLAlchemyError as e:
|
||||
@@ -65,7 +67,7 @@ def update_route(
|
||||
item.minimum = data.minimum
|
||||
item.maximum = None if data.maximum == 0 else data.maximum
|
||||
item.is_active = data.is_active
|
||||
add_products(item, data.menu_categories, db)
|
||||
add_skus(item, data.menu_categories, db)
|
||||
db.commit()
|
||||
return modifier_category_info(item, date_, db=db)
|
||||
except SQLAlchemyError as e:
|
||||
@@ -109,7 +111,7 @@ def show_list(
|
||||
) -> list[schemas.ModifierCategory]:
|
||||
with SessionFuture() as db:
|
||||
product_version_onclause = and_(
|
||||
ProductVersion.menu_category_id == MenuCategory.id,
|
||||
ProductVersion.product_id == Product.id,
|
||||
or_(
|
||||
ProductVersion.valid_from == None, # noqa: E711
|
||||
ProductVersion.valid_from <= date_,
|
||||
@@ -119,6 +121,22 @@ def show_list(
|
||||
ProductVersion.valid_till >= date_,
|
||||
),
|
||||
)
|
||||
sku_version_onclause = and_(
|
||||
SkuVersion.sku_id == StockKeepingUnit.id,
|
||||
or_(SkuVersion.valid_from == None, SkuVersion.valid_from <= date_), # noqa: E711
|
||||
or_(SkuVersion.valid_till == None, SkuVersion.valid_till >= date_), # noqa: E711
|
||||
)
|
||||
sku_version_menu_category_onclause = and_(
|
||||
SkuVersion.menu_category_id == MenuCategory.id,
|
||||
or_(
|
||||
SkuVersion.valid_from == None, # noqa: E711
|
||||
SkuVersion.valid_from <= date_,
|
||||
),
|
||||
or_(
|
||||
SkuVersion.valid_till == None, # noqa: E711
|
||||
SkuVersion.valid_till >= date_,
|
||||
),
|
||||
)
|
||||
list_ = (
|
||||
db.execute(select(ModifierCategory).order_by(ModifierCategory.sort_order).order_by(ModifierCategory.name))
|
||||
.scalars()
|
||||
@@ -127,9 +145,17 @@ def show_list(
|
||||
menu_categories = (
|
||||
db.execute(
|
||||
select(MenuCategory)
|
||||
.join(SkuVersion, onclause=sku_version_menu_category_onclause)
|
||||
.join(StockKeepingUnit, onclause=sku_version_onclause)
|
||||
.join(StockKeepingUnit.product)
|
||||
.join(ProductVersion, onclause=product_version_onclause)
|
||||
.order_by(MenuCategory.sort_order, ProductVersion.sort_order, ProductVersion.name)
|
||||
.options(contains_eager(MenuCategory.products))
|
||||
.order_by(MenuCategory.sort_order, StockKeepingUnit.sort_order, ProductVersion.name)
|
||||
.options(
|
||||
contains_eager(MenuCategory.skus)
|
||||
.contains_eager(SkuVersion.sku)
|
||||
.contains_eager(StockKeepingUnit.product)
|
||||
.contains_eager(Product.versions)
|
||||
)
|
||||
)
|
||||
.unique()
|
||||
.scalars()
|
||||
@@ -149,13 +175,17 @@ def show_list(
|
||||
schemas.MenuCategoryLink(
|
||||
id_=mc.id,
|
||||
name=mc.name,
|
||||
enabled=reduce(lambda x, y: x and (y.product_id in products), mc.products, True),
|
||||
products=[ProductLink(id_=p.id, name=p.name) for p in mc.products if p.product_id in products],
|
||||
enabled=reduce(lambda x, y: x and (y.sku.product_id in products), mc.skus, True),
|
||||
skus=[
|
||||
ProductLink(id_=p.sku.product_id, name=f"{p.sku.product.versions[0].name} ({p.units})")
|
||||
for p in mc.skus
|
||||
if p.sku.product_id in products
|
||||
],
|
||||
)
|
||||
for mc in menu_categories
|
||||
],
|
||||
)
|
||||
modifier_category.menu_categories = [i for i in modifier_category.menu_categories if len(i.products) > 0]
|
||||
modifier_category.menu_categories = [i for i in modifier_category.menu_categories if len(i.skus) > 0]
|
||||
modifier_categories.append(modifier_category)
|
||||
return modifier_categories
|
||||
|
||||
@@ -198,7 +228,7 @@ def show_id(
|
||||
|
||||
def modifier_category_info(item: ModifierCategory, date_: date, db: Session) -> schemas.ModifierCategory:
|
||||
product_version_onclause = and_(
|
||||
ProductVersion.menu_category_id == MenuCategory.id,
|
||||
ProductVersion.product_id == Product.id,
|
||||
or_(
|
||||
ProductVersion.valid_from == None, # noqa: E711
|
||||
ProductVersion.valid_from <= date_,
|
||||
@@ -208,18 +238,42 @@ def modifier_category_info(item: ModifierCategory, date_: date, db: Session) ->
|
||||
ProductVersion.valid_till >= date_,
|
||||
),
|
||||
)
|
||||
sku_version_onclause = and_(
|
||||
SkuVersion.sku_id == StockKeepingUnit.id,
|
||||
or_(SkuVersion.valid_from == None, SkuVersion.valid_from <= date_), # noqa: E711
|
||||
or_(SkuVersion.valid_till == None, SkuVersion.valid_till >= date_), # noqa: E711
|
||||
)
|
||||
sku_version_menu_category_onclause = and_(
|
||||
SkuVersion.menu_category_id == MenuCategory.id,
|
||||
or_(
|
||||
SkuVersion.valid_from == None, # noqa: E711
|
||||
SkuVersion.valid_from <= date_,
|
||||
),
|
||||
or_(
|
||||
SkuVersion.valid_till == None, # noqa: E711
|
||||
SkuVersion.valid_till >= date_,
|
||||
),
|
||||
)
|
||||
menu_categories = (
|
||||
db.execute(
|
||||
select(MenuCategory)
|
||||
.join(SkuVersion, onclause=sku_version_menu_category_onclause)
|
||||
.join(StockKeepingUnit, onclause=sku_version_onclause)
|
||||
.join(StockKeepingUnit.product)
|
||||
.join(ProductVersion, onclause=product_version_onclause)
|
||||
.order_by(MenuCategory.sort_order, ProductVersion.sort_order, ProductVersion.name)
|
||||
.options(contains_eager(MenuCategory.products))
|
||||
.order_by(MenuCategory.sort_order, StockKeepingUnit.sort_order, ProductVersion.name)
|
||||
.options(
|
||||
contains_eager(MenuCategory.skus)
|
||||
.contains_eager(SkuVersion.sku)
|
||||
.contains_eager(StockKeepingUnit.product)
|
||||
.contains_eager(Product.versions)
|
||||
)
|
||||
)
|
||||
.unique()
|
||||
.scalars()
|
||||
.all()
|
||||
)
|
||||
products = [p.id for p in item.products]
|
||||
products = [x.id for x in item.products]
|
||||
return schemas.ModifierCategory(
|
||||
id_=item.id,
|
||||
name=item.name,
|
||||
@@ -230,13 +284,13 @@ def modifier_category_info(item: ModifierCategory, date_: date, db: Session) ->
|
||||
schemas.MenuCategoryLink(
|
||||
id_=mc.id,
|
||||
name=mc.name,
|
||||
products=[
|
||||
ProductLinkSchema(
|
||||
id_=p.product_id,
|
||||
name=p.name,
|
||||
enabled=p.product_id in products,
|
||||
skus=[
|
||||
ProductLink(
|
||||
id_=p.sku.product_id,
|
||||
name=f"{p.sku.product.versions[0].name} ({p.units})",
|
||||
enabled=p.sku.product_id in products,
|
||||
)
|
||||
for p in mc.products
|
||||
for p in mc.skus
|
||||
],
|
||||
)
|
||||
for mc in menu_categories
|
||||
@@ -247,7 +301,7 @@ def modifier_category_info(item: ModifierCategory, date_: date, db: Session) ->
|
||||
|
||||
def modifier_category_blank(date_: date, db: Session) -> schemas.ModifierCategoryBlank:
|
||||
product_version_onclause = and_(
|
||||
ProductVersion.menu_category_id == MenuCategory.id,
|
||||
ProductVersion.product_id == Product.id,
|
||||
or_(
|
||||
ProductVersion.valid_from == None, # noqa: E711
|
||||
ProductVersion.valid_from <= date_,
|
||||
@@ -257,12 +311,36 @@ def modifier_category_blank(date_: date, db: Session) -> schemas.ModifierCategor
|
||||
ProductVersion.valid_till >= date_,
|
||||
),
|
||||
)
|
||||
sku_version_onclause = and_(
|
||||
SkuVersion.sku_id == StockKeepingUnit.id,
|
||||
or_(SkuVersion.valid_from == None, SkuVersion.valid_from <= date_), # noqa: E711
|
||||
or_(SkuVersion.valid_till == None, SkuVersion.valid_till >= date_), # noqa: E711
|
||||
)
|
||||
sku_version_menu_category_onclause = and_(
|
||||
SkuVersion.menu_category_id == MenuCategory.id,
|
||||
or_(
|
||||
SkuVersion.valid_from == None, # noqa: E711
|
||||
SkuVersion.valid_from <= date_,
|
||||
),
|
||||
or_(
|
||||
SkuVersion.valid_till == None, # noqa: E711
|
||||
SkuVersion.valid_till >= date_,
|
||||
),
|
||||
)
|
||||
menu_categories = (
|
||||
db.execute(
|
||||
select(MenuCategory)
|
||||
.join(SkuVersion, onclause=sku_version_menu_category_onclause)
|
||||
.join(StockKeepingUnit, onclause=sku_version_onclause)
|
||||
.join(StockKeepingUnit.product)
|
||||
.join(ProductVersion, onclause=product_version_onclause)
|
||||
.order_by(MenuCategory.sort_order, ProductVersion.sort_order, ProductVersion.name)
|
||||
.options(contains_eager(MenuCategory.products))
|
||||
.order_by(MenuCategory.sort_order, StockKeepingUnit.sort_order, ProductVersion.name)
|
||||
.options(
|
||||
contains_eager(MenuCategory.skus)
|
||||
.contains_eager(SkuVersion.sku)
|
||||
.contains_eager(StockKeepingUnit.product)
|
||||
.contains_eager(Product.versions)
|
||||
)
|
||||
)
|
||||
.unique()
|
||||
.scalars()
|
||||
@@ -277,16 +355,21 @@ def modifier_category_blank(date_: date, db: Session) -> schemas.ModifierCategor
|
||||
schemas.MenuCategoryLink(
|
||||
id_=mc.id,
|
||||
name=mc.name,
|
||||
products=[ProductLinkSchema(id_=p.product_id, name=p.name, enabled=False) for p in mc.products],
|
||||
skus=[
|
||||
ProductLinkSchema(
|
||||
id_=p.sku.product_id, name=f"{p.sku.product.versions[0].name} ({p.units})", enabled=False
|
||||
)
|
||||
for p in mc.skus
|
||||
],
|
||||
)
|
||||
for mc in menu_categories
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def add_products(modifier_category: ModifierCategory, menu_categories: list[MenuCategoryLink], db: Session) -> None:
|
||||
def add_skus(modifier_category: ModifierCategory, menu_categories: list[MenuCategoryLink], db: Session) -> None:
|
||||
for mc in menu_categories:
|
||||
for p in mc.products:
|
||||
for p in mc.skus:
|
||||
old = next((x for x in modifier_category.products if x.id == p.id_), None)
|
||||
if p.enabled and old is None:
|
||||
product_object = db.execute(select(Product).where(Product.id == p.id_)).scalar_one()
|
||||
|
||||
@@ -28,5 +28,5 @@ class MenuCategoryLink(BaseModel):
|
||||
id_: uuid.UUID = Field(...)
|
||||
name: str | None = None
|
||||
enabled: bool | None = None
|
||||
products: list[ProductLink]
|
||||
skus: list[ProductLink]
|
||||
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { StockKeepingUnit as Product } from './stock-keeping-unit';
|
||||
import { StockKeepingUnit } from './product';
|
||||
|
||||
export class MenuCategory {
|
||||
id: string | undefined;
|
||||
@@ -6,7 +6,7 @@ export class MenuCategory {
|
||||
isActive: boolean;
|
||||
isFixture: boolean;
|
||||
sortOrder: number;
|
||||
products: Product[];
|
||||
skus: StockKeepingUnit[];
|
||||
enabled?: boolean;
|
||||
|
||||
public constructor(init?: Partial<MenuCategory>) {
|
||||
@@ -15,7 +15,7 @@ export class MenuCategory {
|
||||
this.isActive = true;
|
||||
this.isFixture = false;
|
||||
this.sortOrder = 0;
|
||||
this.products = [];
|
||||
this.skus = [];
|
||||
Object.assign(this, init);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,32 @@
|
||||
import { MenuCategory } from './menu-category';
|
||||
import { Modifier } from './modifier';
|
||||
|
||||
export class StockKeepingUnitLink {
|
||||
id: string | undefined;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
|
||||
public constructor(init?: Partial<StockKeepingUnitLink>) {
|
||||
this.name = '';
|
||||
this.enabled = true;
|
||||
Object.assign(this, init);
|
||||
}
|
||||
}
|
||||
|
||||
export class MenuCategory {
|
||||
id: string | undefined;
|
||||
name: string;
|
||||
skus: StockKeepingUnitLink[];
|
||||
enabled?: boolean;
|
||||
|
||||
public constructor(init?: Partial<MenuCategory>) {
|
||||
this.id = undefined;
|
||||
this.name = '';
|
||||
this.enabled = true;
|
||||
this.skus = [];
|
||||
Object.assign(this, init);
|
||||
}
|
||||
}
|
||||
|
||||
export class ModifierCategory {
|
||||
id: string | undefined;
|
||||
name: string;
|
||||
|
||||
+14
-16
@@ -12,9 +12,7 @@ import { MatTreeModule, MatTreeNestedDataSource } from '@angular/material/tree';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { MenuCategory } from '../../core/menu-category';
|
||||
import { ModifierCategory } from '../../core/modifier-category';
|
||||
import { StockKeepingUnit as Product } from '../../core/stock-keeping-unit';
|
||||
import { ModifierCategory, MenuCategory, StockKeepingUnitLink } from '../../core/modifier-category';
|
||||
import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
|
||||
import { ModifierCategoryService } from '../modifier-category.service';
|
||||
import { NodeItem } from '../node-item';
|
||||
@@ -35,7 +33,6 @@ interface ModifierCategoryNode {
|
||||
styleUrls: ['./modifier-category-detail.component.css'],
|
||||
imports: [
|
||||
MatButtonModule,
|
||||
|
||||
MatCheckboxModule,
|
||||
MatDividerModule,
|
||||
MatFormFieldModule,
|
||||
@@ -65,8 +62,8 @@ export class ModifierCategoryDetailComponent implements OnInit, AfterViewInit {
|
||||
|
||||
item: ModifierCategory = new ModifierCategory();
|
||||
dataSource = new MatTreeNestedDataSource<NodeItem>();
|
||||
products: Map<string, Product> = new Map<string, Product>();
|
||||
productsOfMenuCategory: Map<string, Product[]> = new Map<string, Product[]>();
|
||||
products: Map<string, StockKeepingUnitLink> = new Map<string, StockKeepingUnitLink>();
|
||||
productsOfMenuCategory: Map<string, StockKeepingUnitLink[]> = new Map<string, StockKeepingUnitLink[]>();
|
||||
|
||||
constructor() {
|
||||
// Create form
|
||||
@@ -87,7 +84,7 @@ export class ModifierCategoryDetailComponent implements OnInit, AfterViewInit {
|
||||
const tree: NodeItem[] = data.item.menuCategories.map((x: MenuCategory) => ({
|
||||
id: x.id as string,
|
||||
name: x.name,
|
||||
children: x.products.map((y: Product) => ({ id: y.id as string, name: y.name })),
|
||||
children: x.skus.map((y: StockKeepingUnitLink) => ({ id: y.id as string, name: y.name })),
|
||||
}));
|
||||
return { item: data.item, tree };
|
||||
}),
|
||||
@@ -109,12 +106,12 @@ export class ModifierCategoryDetailComponent implements OnInit, AfterViewInit {
|
||||
this.products = new Map();
|
||||
this.productsOfMenuCategory = new Map();
|
||||
this.item.menuCategories.forEach((mc: MenuCategory) => {
|
||||
mc.products.forEach((p: Product) => {
|
||||
mc.skus.forEach((p: StockKeepingUnitLink) => {
|
||||
this.products.set(p.id as string, p);
|
||||
if (!this.productsOfMenuCategory.has(mc.id as string)) {
|
||||
this.productsOfMenuCategory.set(mc.id as string, []);
|
||||
}
|
||||
(this.productsOfMenuCategory.get(mc.id as string) as Product[]).push(p);
|
||||
(this.productsOfMenuCategory.get(mc.id as string) as StockKeepingUnitLink[]).push(p);
|
||||
});
|
||||
});
|
||||
// this.setMenuCategories();
|
||||
@@ -180,19 +177,19 @@ export class ModifierCategoryDetailComponent implements OnInit, AfterViewInit {
|
||||
}
|
||||
|
||||
isProductSelected(node: NodeItem) {
|
||||
return (this.products.get(node.id) as Product).enabled;
|
||||
return (this.products.get(node.id) as StockKeepingUnitLink).enabled;
|
||||
}
|
||||
|
||||
isMenuCategorySelected(node: NodeItem) {
|
||||
return (this.productsOfMenuCategory.get(node.id) as Product[]).reduce(
|
||||
(acc: boolean, current: Product) => acc && current.enabled,
|
||||
return (this.productsOfMenuCategory.get(node.id) as StockKeepingUnitLink[]).reduce(
|
||||
(acc: boolean, current: StockKeepingUnitLink) => acc && current.enabled,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
isMenuCategoryPartiallySelected(node: NodeItem) {
|
||||
const total = (this.productsOfMenuCategory.get(node.id) as Product[]).length;
|
||||
const ticked = (this.productsOfMenuCategory.get(node.id) as Product[]).reduce((acc, current) => {
|
||||
const total = (this.productsOfMenuCategory.get(node.id) as StockKeepingUnitLink[]).length;
|
||||
const ticked = (this.productsOfMenuCategory.get(node.id) as StockKeepingUnitLink[]).reduce((acc, current) => {
|
||||
if (current.enabled) {
|
||||
acc += 1;
|
||||
}
|
||||
@@ -202,12 +199,13 @@ export class ModifierCategoryDetailComponent implements OnInit, AfterViewInit {
|
||||
}
|
||||
|
||||
toggleProductSelection(node: NodeItem) {
|
||||
(this.products.get(node.id) as Product).enabled = !(this.products.get(node.id) as Product).enabled;
|
||||
(this.products.get(node.id) as StockKeepingUnitLink).enabled = !(this.products.get(node.id) as StockKeepingUnitLink)
|
||||
.enabled;
|
||||
}
|
||||
|
||||
toggleMenuCategorySelection(node: NodeItem) {
|
||||
const sel = !this.isMenuCategorySelected(node);
|
||||
(this.productsOfMenuCategory.get(node.id as string) as Product[]).forEach((p) => {
|
||||
(this.productsOfMenuCategory.get(node.id as string) as StockKeepingUnitLink[]).forEach((p) => {
|
||||
p.enabled = sel;
|
||||
});
|
||||
return sel;
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@
|
||||
{{ mc.name }}
|
||||
@if (!mc.enabled) {
|
||||
<ul>
|
||||
@for (p of mc.products; track p) {
|
||||
@for (p of mc.skus; track p) {
|
||||
<li>{{ p.name }}</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
@@ -63,13 +63,13 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-checkbox formControlName="hasHappyHour" class="flex-auto">Has Happy Hour?</mat-checkbox>
|
||||
<mat-checkbox formControlName="isNotAvailable" class="flex-auto">Not Available?</mat-checkbox>
|
||||
<mat-checkbox formControlName="hasHappyHour" class="flex-auto">Has Happy Hour?</mat-checkbox>
|
||||
<mat-checkbox formControlName="isNotAvailable" class="flex-auto">Not Available?</mat-checkbox>
|
||||
|
||||
<button mat-raised-button color="primary" (click)="addRow()" class="flex-auto">Add</button>
|
||||
|
||||
</div></form>
|
||||
<!-- SKU table -->
|
||||
<button mat-raised-button color="primary" (click)="addRow()" class="flex-auto">Add</button>
|
||||
</div>
|
||||
</form>
|
||||
<!-- SKU table -->
|
||||
<div class="row-container wrapped">
|
||||
<mat-table [dataSource]="dataSource" aria-label="Stock Keeping Units" class="flex-auto">
|
||||
<!-- Units Column -->
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CurrencyPipe } from '@angular/common';
|
||||
import { AfterViewInit, Component, ElementRef, OnInit, ViewChild, inject } from '@angular/core';
|
||||
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -5,21 +6,20 @@ import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { MenuCategory } from '../../core/menu-category';
|
||||
import { Product, StockKeepingUnit } from '../../core/product';
|
||||
import { SaleCategory } from '../../core/sale-category';
|
||||
import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
|
||||
import { ProductService } from '../product.service';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { ProductDetailDatasource } from './product-detail-datasource';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { CurrencyPipe } from '@angular/common';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
|
||||
@Component({
|
||||
selector: 'app-product-detail',
|
||||
@@ -46,7 +46,7 @@ export class ProductDetailComponent implements OnInit, AfterViewInit {
|
||||
private ser = inject(ProductService);
|
||||
|
||||
@ViewChild('name', { static: true }) nameElement?: ElementRef;
|
||||
form: FormGroup<{
|
||||
form: FormGroup<{
|
||||
// Product fields
|
||||
name: FormControl<string>;
|
||||
fractionUnits: FormControl<string>;
|
||||
@@ -66,18 +66,27 @@ export class ProductDetailComponent implements OnInit, AfterViewInit {
|
||||
}>;
|
||||
}>;
|
||||
|
||||
|
||||
menuCategories: MenuCategory[] = [];
|
||||
saleCategories: SaleCategory[] = [];
|
||||
public skus = new BehaviorSubject<StockKeepingUnit[]>([]);
|
||||
dataSource: ProductDetailDatasource = new ProductDetailDatasource(this.skus);
|
||||
item: Product = new Product();
|
||||
|
||||
displayedColumns = ['units', 'fraction', 'productYield', 'costPrice', 'salePrice', 'menuCategory', 'hasHappyHour', 'isNotAvailable', 'action'];
|
||||
displayedColumns = [
|
||||
'units',
|
||||
'fraction',
|
||||
'productYield',
|
||||
'costPrice',
|
||||
'salePrice',
|
||||
'menuCategory',
|
||||
'hasHappyHour',
|
||||
'isNotAvailable',
|
||||
'action',
|
||||
];
|
||||
|
||||
constructor() {
|
||||
// Create form
|
||||
this.form = new FormGroup({
|
||||
this.form = new FormGroup({
|
||||
name: new FormControl<string>('', { nonNullable: true }),
|
||||
fractionUnits: new FormControl<string>('', { nonNullable: true }),
|
||||
saleCategory: new FormControl<string>('', { nonNullable: true }),
|
||||
@@ -174,13 +183,16 @@ export class ProductDetailComponent implements OnInit, AfterViewInit {
|
||||
this.snackBar.open('Menu Category is required', 'Error');
|
||||
return;
|
||||
}
|
||||
const sku = new StockKeepingUnit({
|
||||
const sku = new StockKeepingUnit({
|
||||
units,
|
||||
fraction,
|
||||
productYield,
|
||||
costPrice,
|
||||
salePrice,
|
||||
menuCategory: new MenuCategory({ id: menuCategoryId, name: this.menuCategories.find((x) => x.id === menuCategoryId)?.name ?? '' }),
|
||||
menuCategory: new MenuCategory({
|
||||
id: menuCategoryId,
|
||||
name: this.menuCategories.find((x) => x.id === menuCategoryId)?.name ?? '',
|
||||
}),
|
||||
hasHappyHour: formValue.hasHappyHour ?? false,
|
||||
isNotAvailable: formValue.isNotAvailable ?? false,
|
||||
});
|
||||
@@ -203,7 +215,6 @@ export class ProductDetailComponent implements OnInit, AfterViewInit {
|
||||
// isPurchased: this.item.isPurchased,
|
||||
// },
|
||||
// });
|
||||
|
||||
// dialogRef.afterClosed().subscribe((result: boolean | StockKeepingUnit) => {
|
||||
// if (!result) {
|
||||
// return;
|
||||
@@ -280,14 +291,12 @@ export class ProductDetailComponent implements OnInit, AfterViewInit {
|
||||
}
|
||||
}
|
||||
|
||||
// if (this.item.menuCategory === null || this.item.menuCategory === undefined) {
|
||||
// this.item.menuCategory = new MenuCategory();
|
||||
// }
|
||||
// this.item.menuCategory.id = formModel.menuCategory;
|
||||
|
||||
|
||||
// if (this.item.menuCategory === null || this.item.menuCategory === undefined) {
|
||||
// this.item.menuCategory = new MenuCategory();
|
||||
// }
|
||||
// this.item.menuCategory.id = formModel.menuCategory;
|
||||
|
||||
// this.item.price = formModel.price ?? 0;
|
||||
// this.item.hasHappyHour = formModel.hasHappyHour ?? false;
|
||||
// this.item.isNotAvailable = formModel.isNotAvailable ?? false;
|
||||
// this.item.quantity = formModel.quantity ?? 0;
|
||||
// this.item.price = formModel.price ?? 0;
|
||||
// this.item.hasHappyHour = formModel.hasHappyHour ?? false;
|
||||
// this.item.isNotAvailable = formModel.isNotAvailable ?? false;
|
||||
// this.item.quantity = formModel.quantity ?? 0;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { ResolveFn } from '@angular/router';
|
||||
|
||||
import { StockKeepingUnit as Product } from '../core/stock-keeping-unit';
|
||||
import { Product } from '../core/product';
|
||||
import { ProductService } from './product.service';
|
||||
|
||||
export const productListResolver: ResolveFn<Product[]> = () => {
|
||||
|
||||
@@ -2,7 +2,6 @@ import { DataSource } from '@angular/cdk/collections';
|
||||
import { merge, Observable } from 'rxjs';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
|
||||
import { MenuCategory } from '../../core/menu-category';
|
||||
import { Product } from '../../core/product';
|
||||
|
||||
export class ProductListDataSource extends DataSource<Product> {
|
||||
|
||||
@@ -50,8 +50,7 @@
|
||||
<ul>
|
||||
@for (sku of row.skus; track sku) {
|
||||
<li>
|
||||
<a [routerLink]="['/products', row.id]"
|
||||
>{{ row.name }} ({{ sku.units }})</a>
|
||||
<a [routerLink]="['/products', row.id]">{{ row.name }} ({{ sku.units }})</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
@@ -99,19 +98,19 @@
|
||||
<ul>
|
||||
@for (sku of row.skus; track sku) {
|
||||
<li layout="row">
|
||||
<div>
|
||||
<mat-icon>
|
||||
{{ sku.hasHappyHour ? 'sentiment_satisfied_alt' : 'sentiment_dissatisfied' }}
|
||||
</mat-icon>
|
||||
<b> {{ sku.hasHappyHour ? 'Happy Hour' : 'Regular' }}</b>
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon>
|
||||
{{ row.isNotAvailable ? 'pause' : 'play_arrow' }}
|
||||
</mat-icon>
|
||||
<b> {{ row.isNotAvailable ? 'Not Available' : 'Available' }}</b>
|
||||
</div>
|
||||
</li>
|
||||
<div>
|
||||
<mat-icon>
|
||||
{{ sku.hasHappyHour ? 'sentiment_satisfied_alt' : 'sentiment_dissatisfied' }}
|
||||
</mat-icon>
|
||||
<b> {{ sku.hasHappyHour ? 'Happy Hour' : 'Regular' }}</b>
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon>
|
||||
{{ row.isNotAvailable ? 'pause' : 'play_arrow' }}
|
||||
</mat-icon>
|
||||
<b> {{ row.isNotAvailable ? 'Not Available' : 'Available' }}</b>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</mat-cell>
|
||||
@@ -121,15 +120,11 @@
|
||||
<ng-container matColumnDef="quantity">
|
||||
<mat-header-cell *matHeaderCellDef class="right">Quantity</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row" class="right">
|
||||
<ul>
|
||||
<ul>
|
||||
@for (sku of row.skus; track sku) {
|
||||
<li>
|
||||
{{ sku.fraction | number: '1.2-2' }}{{ row.fractionUnits }}
|
||||
</li>
|
||||
<li>{{ sku.fraction | number: '1.2-2' }}{{ row.fractionUnits }}</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CdkDragDrop, moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
|
||||
import { CdkDragDrop, DragDropModule } from '@angular/cdk/drag-drop';
|
||||
import { AsyncPipe, DecimalPipe, CurrencyPipe } from '@angular/common';
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
import { ErrorLoggerService } from '../core/error-logger.service';
|
||||
import { StockKeepingUnit as Product } from '../core/stock-keeping-unit';
|
||||
|
||||
const httpOptions = {
|
||||
headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
|
||||
};
|
||||
|
||||
const url = '/api/products';
|
||||
const serviceName = 'ProductService';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ProductService {
|
||||
private http = inject(HttpClient);
|
||||
private log = inject(ErrorLoggerService);
|
||||
|
||||
get(id: string | null): Observable<Product> {
|
||||
const getUrl: string = id === null ? `${url}` : `${url}/${id}`;
|
||||
return this.http
|
||||
.get<Product>(getUrl)
|
||||
.pipe(catchError(this.log.handleError(serviceName, `get id=${id}`))) as Observable<Product>;
|
||||
}
|
||||
|
||||
list(): Observable<Product[]> {
|
||||
return this.http
|
||||
.get<Product[]>(`${url}/list`)
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'list'))) as Observable<Product[]>;
|
||||
}
|
||||
|
||||
listOfSaleCategory(id: string): Observable<Product[]> {
|
||||
const options = { params: new HttpParams().set('sc', id) };
|
||||
return this.http
|
||||
.get<Product[]>(`${url}/query`, options)
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'listOfSaleCategory'))) as Observable<Product[]>;
|
||||
}
|
||||
|
||||
listIsActiveOfCategory(id: string): Observable<Product[]> {
|
||||
const options = { params: new HttpParams().set('mc', id) };
|
||||
return this.http
|
||||
.get<Product[]>(`${url}/query`, options)
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'listIsActiveOfCategory'))) as Observable<Product[]>;
|
||||
}
|
||||
|
||||
save(product: Product): Observable<void> {
|
||||
return this.http
|
||||
.post<Product>(`${url}`, product, httpOptions)
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'save'))) as Observable<void>;
|
||||
}
|
||||
|
||||
update(product: Product): Observable<void> {
|
||||
return this.http
|
||||
.put<Product>(`${url}/${product.id}`, product, httpOptions)
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'update'))) as Observable<void>;
|
||||
}
|
||||
|
||||
updateSortOrder(list: Product[]): Observable<Product[]> {
|
||||
return this.http
|
||||
.post<Product[]>(`${url}/list`, list, httpOptions)
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'updateSortOrder'))) as Observable<Product[]>;
|
||||
}
|
||||
|
||||
saveOrUpdate(product: Product): Observable<void> {
|
||||
if (!product.versionId) {
|
||||
return this.save(product);
|
||||
}
|
||||
return this.update(product);
|
||||
}
|
||||
|
||||
delete(id: string): Observable<void> {
|
||||
return this.http
|
||||
.delete<Product>(`${url}/${id}`, httpOptions)
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'delete'))) as Observable<void>;
|
||||
}
|
||||
|
||||
balance(id: string, date: string): Observable<number> {
|
||||
const options = { params: new HttpParams().set('d', date) };
|
||||
return this.http
|
||||
.get<number>(`${url}/balance`, options)
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'balance'))) as Observable<number>;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { ResolveFn } from '@angular/router';
|
||||
|
||||
import { StockKeepingUnit as Product } from '../core/stock-keeping-unit';
|
||||
import { Product } from '../core/product';
|
||||
import { ProductService } from './product.service';
|
||||
|
||||
export const productResolver: ResolveFn<Product> = (route) => {
|
||||
|
||||
@@ -4,8 +4,7 @@ import { Observable } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
import { ErrorLoggerService } from '../core/error-logger.service';
|
||||
import { StockKeepingUnit as Product } from '../core/stock-keeping-unit';
|
||||
import { Product as ProductNew } from '../core/product';
|
||||
import { Product } from '../core/product';
|
||||
|
||||
const httpOptions = {
|
||||
headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
|
||||
@@ -46,25 +45,25 @@ export class ProductService {
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'listIsActiveOfCategory'))) as Observable<Product[]>;
|
||||
}
|
||||
|
||||
save(product: ProductNew): Observable<void> {
|
||||
save(product: Product): Observable<void> {
|
||||
return this.http
|
||||
.post<ProductNew>(`${url}`, product, httpOptions)
|
||||
.post<Product>(`${url}`, product, httpOptions)
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'save'))) as Observable<void>;
|
||||
}
|
||||
|
||||
update(product: ProductNew): Observable<void> {
|
||||
update(product: Product): Observable<void> {
|
||||
return this.http
|
||||
.put<ProductNew>(`${url}/${product.id}`, product, httpOptions)
|
||||
.put<Product>(`${url}/${product.id}`, product, httpOptions)
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'update'))) as Observable<void>;
|
||||
}
|
||||
|
||||
updateSortOrder(list: ProductNew[]): Observable<ProductNew[]> {
|
||||
updateSortOrder(list: Product[]): Observable<Product[]> {
|
||||
return this.http
|
||||
.post<ProductNew[]>(`${url}/list`, list, httpOptions)
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'updateSortOrder'))) as Observable<ProductNew[]>;
|
||||
.post<Product[]>(`${url}/list`, list, httpOptions)
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'updateSortOrder'))) as Observable<Product[]>;
|
||||
}
|
||||
|
||||
saveOrUpdate(product: ProductNew): Observable<void> {
|
||||
saveOrUpdate(product: Product): Observable<void> {
|
||||
if (!product.versionId) {
|
||||
return this.save(product);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { inject } from '@angular/core';
|
||||
import { ResolveFn } from '@angular/router';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
|
||||
import { StockKeepingUnit as Product } from '../core/stock-keeping-unit';
|
||||
import { Product } from '../core/product';
|
||||
import { ProductService } from '../product/product.service';
|
||||
|
||||
export const productListResolver: ResolveFn<Product[]> = (route) => {
|
||||
|
||||
@@ -14,8 +14,8 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
import { round } from 'mathjs';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { StockKeepingUnit as Product } from '../../core/stock-keeping-unit';
|
||||
import { SaleCategory } from '../../core/sale-category';
|
||||
import { StockKeepingUnit as Product } from '../../core/stock-keeping-unit';
|
||||
import { Tax } from '../../core/tax';
|
||||
import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
|
||||
import { SaleCategoryService } from '../sale-category.service';
|
||||
|
||||
@@ -7,9 +7,9 @@ import { tap } from 'rxjs/operators';
|
||||
|
||||
import { BillViewItem } from '../core/bill-view-item';
|
||||
import { ModifierCategory } from '../core/modifier-category';
|
||||
import { StockKeepingUnit } from '../core/stock-keeping-unit';
|
||||
import { ReceivePaymentItem } from '../core/receive-payment-item';
|
||||
import { SaleCategory } from '../core/sale-category';
|
||||
import { StockKeepingUnit } from '../core/stock-keeping-unit';
|
||||
import { Table } from '../core/table';
|
||||
import { ModifierCategoryService } from '../modifier-categories/modifier-category.service';
|
||||
import { MathService } from '../shared/math.service';
|
||||
@@ -104,10 +104,7 @@ export class BillService {
|
||||
minimum(skuId: string, happyHour: boolean): number {
|
||||
return this.bill.kots.reduce(
|
||||
(t, k) =>
|
||||
k.inventories.reduce(
|
||||
(a, c) => (c.sku.id === skuId && c.isHappyHour === happyHour ? a + c.quantity : a),
|
||||
0,
|
||||
) + t,
|
||||
k.inventories.reduce((a, c) => (c.sku.id === skuId && c.isHappyHour === happyHour ? a + c.quantity : a), 0) + t,
|
||||
0,
|
||||
);
|
||||
}
|
||||
@@ -264,10 +261,7 @@ export class BillService {
|
||||
|
||||
printBill(guestBookId: string | null, voucherType: VoucherType): Observable<boolean> {
|
||||
const item = JSON.parse(JSON.stringify(this.bill));
|
||||
const skus = item.kots.reduce(
|
||||
(p: number, k: Kot) => p + k.inventories.filter((i) => i.quantity !== 0).length,
|
||||
0,
|
||||
);
|
||||
const skus = item.kots.reduce((p: number, k: Kot) => p + k.inventories.filter((i) => i.quantity !== 0).length, 0);
|
||||
if (skus === 0) {
|
||||
return throwError(() => Error('Cannot print a blank Bill\nPlease add some products!'));
|
||||
}
|
||||
@@ -380,9 +374,7 @@ export class BillService {
|
||||
.filter((x) => x.isHappyHour)
|
||||
.map((x) => ({ id: x.sku.id as string, quantity: x.quantity }));
|
||||
for (const item of happyHourItems) {
|
||||
const q = kot.inventories.find(
|
||||
(x) => !x.isHappyHour && x.sku.id === item.id && x.quantity === item.quantity,
|
||||
);
|
||||
const q = kot.inventories.find((x) => !x.isHappyHour && x.sku.id === item.id && x.quantity === item.quantity);
|
||||
if (q === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MenuCategory } from './menu-category';
|
||||
import { SaleCategory } from './sale-category';
|
||||
import { Tax } from './tax';
|
||||
import { MenuCategory } from '../core/menu-category';
|
||||
import { SaleCategory } from '../core/sale-category';
|
||||
import { Tax } from '../core/tax';
|
||||
|
||||
export class Product {
|
||||
id: string | undefined;
|
||||
|
||||
@@ -4,8 +4,8 @@ import { MatCardModule } from '@angular/material/card';
|
||||
import { MatRippleModule } from '@angular/material/core';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
|
||||
import { StockKeepingUnit as Product } from '../../core/stock-keeping-unit';
|
||||
import { BillService } from '../bill.service';
|
||||
import { Product } from '../product';
|
||||
|
||||
@Component({
|
||||
selector: 'app-products',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { ResolveFn } from '@angular/router';
|
||||
|
||||
import { StockKeepingUnit as Product } from '../../core/stock-keeping-unit';
|
||||
import { Product } from '../../core/product';
|
||||
import { ProductService } from '../../product/product.service';
|
||||
|
||||
export const productsResolver: ResolveFn<Product[]> = (route) => {
|
||||
|
||||
+1
-1
@@ -13,8 +13,8 @@ import { ActivatedRoute, Router } from '@angular/router';
|
||||
import moment from 'moment';
|
||||
|
||||
import { MenuCategory } from '../../core/menu-category';
|
||||
import { StockKeepingUnit as Product } from '../../core/stock-keeping-unit';
|
||||
import { SaleCategory } from '../../core/sale-category';
|
||||
import { StockKeepingUnit as Product } from '../../core/stock-keeping-unit';
|
||||
import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
|
||||
import { TemporalProductService } from '../temporal-product.service';
|
||||
|
||||
|
||||
+1
-1
@@ -3,8 +3,8 @@ import { merge, Observable } from 'rxjs';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
|
||||
import { MenuCategory } from '../../core/menu-category';
|
||||
import { StockKeepingUnit as Product } from '../../core/stock-keeping-unit';
|
||||
import { SaleCategory } from '../../core/sale-category';
|
||||
import { StockKeepingUnit as Product } from '../../core/stock-keeping-unit';
|
||||
|
||||
export class TemporalProductListDatasource extends DataSource<Product> {
|
||||
public data: Product[][];
|
||||
|
||||
+1
-1
@@ -12,8 +12,8 @@ import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
||||
|
||||
import { MenuCategory } from '../../core/menu-category';
|
||||
import { StockKeepingUnit as Product } from '../../core/stock-keeping-unit';
|
||||
import { SaleCategory } from '../../core/sale-category';
|
||||
import { StockKeepingUnit as Product } from '../../core/stock-keeping-unit';
|
||||
import { TemporalProductListDatasource } from './temporal-product-list-datasource';
|
||||
|
||||
@Component({
|
||||
|
||||
Reference in New Issue
Block a user