Allow editing of product version with the right permission.

This commit is contained in:
Amritanshu Agrawal 2021-09-04 12:52:05 +05:30
parent f929a731cb
commit 4a12ee0834
11 changed files with 370 additions and 196 deletions

View File

@ -0,0 +1,27 @@
"""temporal products editing
Revision ID: c123dbf9c659
Revises: e5e8acfc6495
Create Date: 2021-08-06 10:15:57.719313
"""
from alembic import op
# revision identifiers, used by Alembic.
from barker.models.permission import Permission
revision = "c123dbf9c659"
down_revision = "e5e8acfc6495"
branch_labels = None
depends_on = None
def upgrade():
op.execute(
Permission.__table__.insert().values(id="eb604d72-8cbc-4fcb-979c-4713eaf34e56", name="Temporal Products")
)
def downgrade():
pass

View File

@ -6,7 +6,7 @@ from typing import List, Optional
import barker.schemas.product as schemas import barker.schemas.product as schemas
from fastapi import APIRouter, Depends, HTTPException, Security, status from fastapi import APIRouter, Depends, HTTPException, Security, status
from sqlalchemy import and_, insert, or_, select, update from sqlalchemy import and_, delete, insert, or_, select, update
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session, contains_eager, joinedload from sqlalchemy.orm import Session, contains_eager, joinedload
from sqlalchemy.sql.functions import count from sqlalchemy.sql.functions import count
@ -71,7 +71,7 @@ def save(
data: schemas.ProductIn, data: schemas.ProductIn,
date_: date = Depends(effective_date), date_: date = Depends(effective_date),
user: UserToken = Security(get_user, scopes=["products"]), user: UserToken = Security(get_user, scopes=["products"]),
): ) -> None:
try: try:
with SessionFuture() as db: with SessionFuture() as db:
item = Product() item = Product()
@ -93,7 +93,7 @@ def save(
db.add(product_version) db.add(product_version)
add_modifiers(item.id, product_version.menu_category_id, date_, db) add_modifiers(item.id, product_version.menu_category_id, date_, db)
db.commit() db.commit()
return product_info(product_version) return
except SQLAlchemyError as e: except SQLAlchemyError as e:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@ -143,16 +143,18 @@ def add_modifiers(product_id: uuid.UUID, menu_category_id: uuid.UUID, date_: dat
db.execute(insert(modifier_categories_products).values(product_id=product_id, modifier_category_id=mc)) db.execute(insert(modifier_categories_products).values(product_id=product_id, modifier_category_id=mc))
@router.put("/{id_}", response_model=schemas.Product) @router.put("/{version_id}", response_model=schemas.Product)
def update_route( def update_route(
id_: uuid.UUID, version_id: uuid.UUID,
data: schemas.ProductIn, data: schemas.ProductIn,
date_: date = Depends(effective_date), date_: date = Depends(effective_date),
user: UserToken = Security(get_user, scopes=["products"]), user: UserToken = Security(get_user, scopes=["products"]),
) -> schemas.Product: ) -> None:
try: try:
with SessionFuture() as db: with SessionFuture() as db:
item: ProductVersion = db.execute( old: ProductVersion = db.execute(select(ProductVersion).where(ProductVersion.id == version_id)).scalar_one()
id_ = old.product_id
latest: ProductVersion = db.execute(
select(ProductVersion) select(ProductVersion)
.join(ProductVersion.menu_category) .join(ProductVersion.menu_category)
.where( .where(
@ -169,42 +171,65 @@ def update_route(
) )
) )
).scalar_one() ).scalar_one()
if item.valid_till is not None: if version_id != latest.id and "temporal-products" not in user.permissions:
# This should not happen as only someone with this permission should reach here
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Permission error, you cannot edit this product version.",
)
if version_id != latest.id:
# Update the old product update by temporal product editor
old.name = data.name
old.units = data.units
old.menu_category_id = data.menu_category.id_
old.sale_category_id = data.sale_category.id_
old.price = data.price
old.has_happy_hour = data.has_happy_hour
old.is_not_available = data.is_not_available
old.quantity = data.quantity
db.commit()
return
if latest.valid_till is not None:
# Allow adding a product here splitting the valid from and to, but not implemented right now # Allow adding a product here splitting the valid from and to, but not implemented right now
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Product has been invalidated", detail="Product has been invalidated",
) )
if item.valid_from == date_: # Update the product as valid from the the same
item.name = data.name if latest.valid_from == date_:
item.units = data.units # Update the product as it is valid from the the same
item.menu_category_id = data.menu_category.id_ latest.name = data.name
item.sale_category_id = data.sale_category.id_ latest.units = data.units
item.price = data.price latest.menu_category_id = data.menu_category.id_
item.has_happy_hour = data.has_happy_hour latest.sale_category_id = data.sale_category.id_
item.is_not_available = data.is_not_available latest.price = data.price
item.quantity = data.quantity latest.has_happy_hour = data.has_happy_hour
latest.is_not_available = data.is_not_available
latest.quantity = data.quantity
db.commit() db.commit()
return product_info(item) return
else: # Create a new version of the product from the new details
item.valid_till = date_ - timedelta(days=1) # Create a new product version
product_version = ProductVersion( latest.valid_till = date_ - timedelta(days=1)
product_id=item.product_id, product_version = ProductVersion(
name=data.name, product_id=id_,
units=data.units, name=data.name,
menu_category_id=data.menu_category.id_, units=data.units,
sale_category_id=data.sale_category.id_, menu_category_id=data.menu_category.id_,
price=data.price, sale_category_id=data.sale_category.id_,
has_happy_hour=data.has_happy_hour, price=data.price,
is_not_available=data.is_not_available, has_happy_hour=data.has_happy_hour,
quantity=data.quantity, is_not_available=data.is_not_available,
valid_from=date_, quantity=data.quantity,
valid_till=None, valid_from=date_,
sort_order=item.sort_order, valid_till=None,
) sort_order=latest.sort_order,
db.add(product_version) )
db.commit() db.add(product_version)
return product_info(product_version) db.commit()
return
except SQLAlchemyError as e: except SQLAlchemyError as e:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@ -212,15 +237,19 @@ def update_route(
) )
@router.delete("/{id_}") @router.delete("/{version_id}")
def delete_route( def delete_route(
id_: uuid.UUID, version_id: uuid.UUID,
date_: date = Depends(effective_date), date_: date = Depends(effective_date),
user: UserToken = Security(get_user, scopes=["products"]), user: UserToken = Security(get_user, scopes=["products"]),
): ) -> None:
with SessionFuture() as db: with SessionFuture() as db:
item: ProductVersion = db.execute( old: ProductVersion = db.execute(select(ProductVersion).where(ProductVersion.id == version_id)).scalar_one()
select(ProductVersion).where( id_ = old.product_id
latest: ProductVersion = db.execute(
select(ProductVersion)
.join(ProductVersion.menu_category)
.where(
and_( and_(
ProductVersion.product_id == id_, ProductVersion.product_id == id_,
or_( or_(
@ -234,26 +263,82 @@ def delete_route(
) )
) )
).scalar_one() ).scalar_one()
if item.valid_from == date_: if version_id != latest.id and "temporal-products" not in user.permissions:
db.delete(item) # This should not happen as only someone with this permission should reach here
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Permission error, you cannot delete this product.",
)
if version_id != latest.id:
# Delete old product, but make sure that no gaps remain
if old.valid_from is not None:
# Set the previous version valid till to item's valid till
db.execute(
delete(ProductVersion)
.where(ProductVersion.id == version_id)
.execution_options(synchronize_session=False)
)
id_to_update = (
select(ProductVersion.id)
.where(
ProductVersion.product_id == id_,
ProductVersion.valid_till == old.valid_from - timedelta(days=1),
)
.scalar_subquery()
)
db.execute(
update(ProductVersion)
.where(ProductVersion.id == id_to_update)
.values(valid_till=old.valid_till)
.execution_options(synchronize_session=False)
)
else:
# Set the next version valid from to item's valid from which is None
db.execute(
delete(ProductVersion)
.where(ProductVersion.id == version_id)
.execution_options(synchronize_session=False)
)
id_to_update = (
select(ProductVersion.id)
.where(
ProductVersion.product_id == id_,
ProductVersion.valid_from == old.valid_till + timedelta(days=1),
)
.scalar_subquery()
)
db.execute(
update(ProductVersion)
.where(ProductVersion.id == id_to_update)
.values(valid_till=old.valid_from)
.execution_options(synchronize_session=False)
)
db.commit()
return
if latest.valid_from == date_:
db.delete(latest)
else: else:
item.valid_till = date_ - timedelta(days=1) latest.valid_till = date_ - timedelta(days=1)
db.commit() db.commit()
return
@router.get("", response_model=schemas.ProductBlank) @router.get("", response_model=List[schemas.ProductBlank])
def show_blank( def show_blank(
user: UserToken = Security(get_user, scopes=["products"]), user: UserToken = Security(get_user, scopes=["products"]),
) -> schemas.ProductBlank: ) -> List[schemas.ProductBlank]:
return schemas.ProductBlank( return [
name="", schemas.ProductBlank(
units="", name="",
price=0, units="",
hasHappyHour=False, price=0,
isNotAvailable=False, hasHappyHour=False,
isActive=True, isNotAvailable=False,
sortOrder=0, isActive=True,
) sortOrder=0,
)
]
@router.get("/list", response_model=List[schemas.Product]) @router.get("/list", response_model=List[schemas.Product])
@ -401,39 +486,44 @@ def product_list_of_sale_category(date_: date, db: Session) -> List[schemas.Prod
] ]
@router.get("/{id_}", response_model=schemas.Product) @router.get("/{id_}", response_model=List[schemas.Product])
def show_id( def show_id(
id_: uuid.UUID, id_: uuid.UUID,
date_: date = Depends(effective_date), date_: date = Depends(effective_date),
user: UserToken = Security(get_user, scopes=["products"]), user: UserToken = Security(get_user, scopes=["products"]),
) -> schemas.Product: ) -> List[schemas.Product]:
query = (
select(ProductVersion)
.join(ProductVersion.sale_category)
.join(SaleCategory.tax)
.where(ProductVersion.product_id == id_)
)
if "temporal-products" not in user.permissions:
query = query.where(
or_(
ProductVersion.valid_from == None, # noqa: E711
ProductVersion.valid_from <= date_,
),
or_(
ProductVersion.valid_till == None, # noqa: E711
ProductVersion.valid_till >= date_,
),
)
with SessionFuture() as db: with SessionFuture() as db:
item: ProductVersion = db.execute( items = [
select(ProductVersion) product_info(item)
.join(ProductVersion.sale_category) for item in db.execute(
.join(SaleCategory.tax) query.order_by(ProductVersion.valid_till).options(
.where( joinedload(ProductVersion.sale_category, innerjoin=True),
and_( joinedload(ProductVersion.sale_category, SaleCategory.tax, innerjoin=True),
ProductVersion.product_id == id_, contains_eager(ProductVersion.sale_category),
or_( contains_eager(ProductVersion.sale_category, SaleCategory.tax),
ProductVersion.valid_from == None, # noqa: E711
ProductVersion.valid_from <= date_,
),
or_(
ProductVersion.valid_till == None, # noqa: E711
ProductVersion.valid_till >= date_,
),
) )
) )
.order_by(ProductVersion.sort_order, ProductVersion.name) .scalars()
.options( .all()
joinedload(ProductVersion.sale_category, innerjoin=True), ]
joinedload(ProductVersion.sale_category, SaleCategory.tax, innerjoin=True), return items
contains_eager(ProductVersion.sale_category),
contains_eager(ProductVersion.sale_category, SaleCategory.tax),
)
).scalar_one()
return product_info(item)
def query_product_info(item: ProductVersion, happy_hour: bool): def query_product_info(item: ProductVersion, happy_hour: bool):
@ -459,6 +549,7 @@ def query_product_info(item: ProductVersion, happy_hour: bool):
def product_info(item: ProductVersion) -> schemas.Product: def product_info(item: ProductVersion) -> schemas.Product:
return schemas.Product( return schemas.Product(
id=item.product_id, id=item.product_id,
versionId=item.id,
name=item.name, name=item.name,
units=item.units, units=item.units,
menuCategory=schemas.MenuCategoryLink(id=item.menu_category_id, name=item.menu_category.name, products=[]), menuCategory=schemas.MenuCategoryLink(id=item.menu_category_id, name=item.menu_category.name, products=[]),
@ -472,4 +563,6 @@ def product_info(item: ProductVersion) -> schemas.Product:
quantity=item.quantity, quantity=item.quantity,
isActive=True, isActive=True,
sortOrder=item.sort_order, sortOrder=item.sort_order,
validFrom=item.valid_from,
validTill=item.valid_till,
) )

View File

@ -1,9 +1,10 @@
import uuid import uuid
from datetime import date, datetime
from decimal import Decimal from decimal import Decimal
from typing import Optional from typing import Optional
from pydantic import BaseModel, Field from pydantic import BaseModel, Field, validator
from . import to_camel from . import to_camel
from .menu_category import MenuCategoryLink from .menu_category import MenuCategoryLink
@ -29,10 +30,30 @@ class ProductIn(BaseModel):
class Product(ProductIn): class Product(ProductIn):
id_: uuid.UUID id_: uuid.UUID
version_id: uuid.UUID
valid_from: Optional[date]
valid_till: Optional[date]
class Config: class Config:
anystr_strip_whitespace = True anystr_strip_whitespace = True
alias_generator = to_camel alias_generator = to_camel
json_encoders = {date: lambda v: v.strftime("%d-%b-%Y")}
@validator("valid_from", pre=True)
def parse_valid_from(cls, value):
if value is None:
return None
if isinstance(value, date):
return value
return datetime.strptime(value, "%d-%b-%Y").date()
@validator("valid_till", pre=True)
def parse_valid_till(cls, value):
if value is None:
return None
if isinstance(value, date):
return value
return datetime.strptime(value, "%d-%b-%Y").date()
class ProductBlank(ProductIn): class ProductBlank(ProductIn):

View File

@ -5,6 +5,7 @@ import { Tax } from './tax';
export class Product { export class Product {
id: string | undefined; id: string | undefined;
versionId?: string;
code: number; code: number;
name: string; name: string;
units: string; units: string;
@ -20,6 +21,9 @@ export class Product {
enabled: boolean; enabled: boolean;
tax: Tax; tax: Tax;
validFrom?: string;
validTill?: string;
public constructor(init?: Partial<Product>) { public constructor(init?: Partial<Product>) {
this.id = undefined; this.id = undefined;
this.code = 0; this.code = 0;

View File

@ -0,0 +1,3 @@
.mat-radio-button ~ .mat-radio-button {
margin-left: 16px;
}

View File

@ -1,95 +1,111 @@
<div fxLayout="row" fxFlex="50%" fxLayoutAlign="space-around center" class="example-card"> <div fxLayout="column">
<mat-card fxFlex> <div fxLayout="row" fxFlex="50%" fxLayoutAlign="space-around center" class="example-card">
<mat-card-title-group> <mat-card fxFlex>
<mat-card-title>Product</mat-card-title> <mat-card-title-group>
</mat-card-title-group> <mat-card-title>Product</mat-card-title>
<mat-card-content> </mat-card-title-group>
<form [formGroup]="form" fxLayout="column"> <mat-card-content>
<div <form [formGroup]="form" fxLayout="column">
fxLayout="row" <div
fxLayoutAlign="space-around start" fxLayout="row"
fxLayout.lt-md="column" fxLayoutAlign="space-around start"
fxLayoutGap="20px" fxLayout.lt-md="column"
fxLayoutGap.lt-md="0px" fxLayoutGap="20px"
> fxLayoutGap.lt-md="0px"
<mat-form-field fxFlex> >
<mat-label>Code</mat-label> <mat-form-field fxFlex>
<input matInput placeholder="Code" formControlName="code" /> <mat-label>Code</mat-label>
</mat-form-field> <input matInput placeholder="Code" formControlName="code" />
</div> </mat-form-field>
<div </div>
fxLayout="row" <div
fxLayoutAlign="space-around start" fxLayout="row"
fxLayout.lt-md="column" fxLayoutAlign="space-around start"
fxLayoutGap="20px" fxLayout.lt-md="column"
fxLayoutGap.lt-md="0px" fxLayoutGap="20px"
> fxLayoutGap.lt-md="0px"
<mat-form-field fxFlex="75"> >
<mat-label>Name</mat-label> <mat-form-field fxFlex="75">
<input matInput #name placeholder="Name" formControlName="name" /> <mat-label>Name</mat-label>
</mat-form-field> <input matInput #name placeholder="Name" formControlName="name" />
<mat-form-field fxFlex="25"> </mat-form-field>
<mat-label>Units</mat-label> <mat-form-field fxFlex="25">
<input matInput placeholder="Units" formControlName="units" /> <mat-label>Units</mat-label>
</mat-form-field> <input matInput placeholder="Units" formControlName="units" />
</div> </mat-form-field>
<div </div>
fxLayout="row" <div
fxLayoutAlign="space-around start" fxLayout="row"
fxLayout.lt-md="column" fxLayoutAlign="space-around start"
fxLayoutGap="20px" fxLayout.lt-md="column"
fxLayoutGap.lt-md="0px" fxLayoutGap="20px"
> fxLayoutGap.lt-md="0px"
<mat-form-field fxFlex> >
<mat-label>Price</mat-label> <mat-form-field fxFlex>
<input matInput type="number" placeholder="Price" formControlName="price" /> <mat-label>Price</mat-label>
</mat-form-field> <input matInput type="number" placeholder="Price" formControlName="price" />
<mat-form-field fxFlex> </mat-form-field>
<mat-label>Quantity</mat-label> <mat-form-field fxFlex>
<input matInput type="number" placeholder="Quantity" formControlName="quantity" /> <mat-label>Quantity</mat-label>
</mat-form-field> <input matInput type="number" placeholder="Quantity" formControlName="quantity" />
</div> </mat-form-field>
<div </div>
fxLayout="row" <div
fxLayoutAlign="space-around start" fxLayout="row"
fxLayout.lt-md="column" fxLayoutAlign="space-around start"
fxLayoutGap="20px" fxLayout.lt-md="column"
fxLayoutGap.lt-md="0px" fxLayoutGap="20px"
> fxLayoutGap.lt-md="0px"
<mat-checkbox formControlName="hasHappyHour">Has Happy Hour?</mat-checkbox> >
<mat-checkbox formControlName="isNotAvailable">Is Not Available?</mat-checkbox> <mat-checkbox formControlName="hasHappyHour">Has Happy Hour?</mat-checkbox>
</div> <mat-checkbox formControlName="isNotAvailable">Is Not Available?</mat-checkbox>
<div </div>
fxLayout="row" <div
fxLayoutAlign="space-around start" fxLayout="row"
fxLayout.lt-md="column" fxLayoutAlign="space-around start"
fxLayoutGap="20px" fxLayout.lt-md="column"
fxLayoutGap.lt-md="0px" fxLayoutGap="20px"
> fxLayoutGap.lt-md="0px"
<mat-form-field fxFlex> >
<mat-label>Menu Category</mat-label> <mat-form-field fxFlex>
<mat-select placeholder="Menu Category" formControlName="menuCategory"> <mat-label>Menu Category</mat-label>
<mat-option *ngFor="let mc of menuCategories" [value]="mc.id"> <mat-select placeholder="Menu Category" formControlName="menuCategory">
{{ mc.name }} <mat-option *ngFor="let mc of menuCategories" [value]="mc.id">
</mat-option> {{ mc.name }}
</mat-select> </mat-option>
</mat-form-field> </mat-select>
<mat-form-field fxFlex> </mat-form-field>
<mat-label>Sale Category</mat-label> <mat-form-field fxFlex>
<mat-select placeholder="Sale Category" formControlName="saleCategory"> <mat-label>Sale Category</mat-label>
<mat-option *ngFor="let sc of saleCategories" [value]="sc.id"> <mat-select placeholder="Sale Category" formControlName="saleCategory">
{{ sc.name }} <mat-option *ngFor="let sc of saleCategories" [value]="sc.id">
</mat-option> {{ sc.name }}
</mat-select> </mat-option>
</mat-form-field> </mat-select>
</div> </mat-form-field>
</form> </div>
</mat-card-content> </form>
<mat-card-actions> </mat-card-content>
<button mat-raised-button color="primary" (click)="save()">Save</button> <mat-card-actions>
<button mat-raised-button color="warn" (click)="confirmDelete()" *ngIf="!!item.id"> <button mat-raised-button color="primary" (click)="save()">Save</button>
Delete <button mat-raised-button color="warn" (click)="confirmDelete()" *ngIf="!!item.id">
</button> Delete
</mat-card-actions> </button>
</mat-card> </mat-card-actions>
</mat-card>
</div>
<div fxLayout="row" fxFlex="50%" fxLayoutAlign="space-around center" class="example-card">
<mat-radio-group [hidden]="this.list.length === 1">
<mat-radio-button
class="example-radio-button"
*ngFor="let product of list"
[value]="this.product.versionId"
(change)="loadProduct($event)"
[checked]="this.item.versionId === product.versionId"
>
{{ !!product.validFrom ? product.validFrom : '\u221E' }} -
{{ !!product.validTill ? product.validTill : '\u221E' }}
</mat-radio-button>
</mat-radio-group>
</div>
</div> </div>

View File

@ -1,6 +1,7 @@
import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { MatRadioChange } from '@angular/material/radio';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { MenuCategory } from '../../core/menu-category'; import { MenuCategory } from '../../core/menu-category';
@ -21,6 +22,7 @@ export class ProductDetailComponent implements OnInit, AfterViewInit {
menuCategories: MenuCategory[] = []; menuCategories: MenuCategory[] = [];
saleCategories: SaleCategory[] = []; saleCategories: SaleCategory[] = [];
item: Product = new Product(); item: Product = new Product();
list: Product[] = [];
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
@ -47,13 +49,14 @@ export class ProductDetailComponent implements OnInit, AfterViewInit {
ngOnInit() { ngOnInit() {
this.route.data.subscribe((value) => { this.route.data.subscribe((value) => {
const data = value as { const data = value as {
item: Product; items: Product[];
menuCategories: MenuCategory[]; menuCategories: MenuCategory[];
saleCategories: SaleCategory[]; saleCategories: SaleCategory[];
}; };
this.menuCategories = data.menuCategories; this.menuCategories = data.menuCategories;
this.saleCategories = data.saleCategories; this.saleCategories = data.saleCategories;
this.showItem(data.item); this.list = data.items;
this.showItem(this.list[this.list.length - 1]);
}); });
} }
@ -93,7 +96,7 @@ export class ProductDetailComponent implements OnInit, AfterViewInit {
} }
delete() { delete() {
this.ser.delete(this.item.id as string).subscribe( this.ser.delete(this.item.versionId as string).subscribe(
() => { () => {
this.toaster.show('Success', ''); this.toaster.show('Success', '');
this.router.navigateByUrl('/products'); this.router.navigateByUrl('/products');
@ -135,4 +138,9 @@ export class ProductDetailComponent implements OnInit, AfterViewInit {
this.item.quantity = +formModel.quantity; this.item.quantity = +formModel.quantity;
return this.item; return this.item;
} }
loadProduct($event: MatRadioChange) {
const product = this.list.find((x) => x.versionId === $event.value);
this.showItem(product as Product);
}
} }

View File

@ -9,10 +9,10 @@ import { ProductService } from './product.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class ProductResolver implements Resolve<Product> { export class ProductResolver implements Resolve<Product[]> {
constructor(private ser: ProductService) {} constructor(private ser: ProductService) {}
resolve(route: ActivatedRouteSnapshot): Observable<Product> { resolve(route: ActivatedRouteSnapshot): Observable<Product[]> {
const id = route.paramMap.get('id'); const id = route.paramMap.get('id');
return this.ser.get(id); return this.ser.get(id);
} }

View File

@ -17,11 +17,11 @@ const serviceName = 'ProductService';
export class ProductService { export class ProductService {
constructor(private http: HttpClient, private log: ErrorLoggerService) {} constructor(private http: HttpClient, private log: ErrorLoggerService) {}
get(id: string | null): Observable<Product> { get(id: string | null): Observable<Product[]> {
const getUrl: string = id === null ? `${url}` : `${url}/${id}`; const getUrl: string = id === null ? `${url}` : `${url}/${id}`;
return this.http return this.http
.get<Product>(getUrl) .get<Product[]>(getUrl)
.pipe(catchError(this.log.handleError(serviceName, `get id=${id}`))) as Observable<Product>; .pipe(catchError(this.log.handleError(serviceName, `get id=${id}`))) as Observable<Product[]>;
} }
list(): Observable<Product[]> { list(): Observable<Product[]> {
@ -48,16 +48,16 @@ export class ProductService {
>; >;
} }
save(product: Product): Observable<Product> { save(product: Product): Observable<void> {
return this.http return this.http
.post<Product>(`${url}`, product, httpOptions) .post<Product>(`${url}`, product, httpOptions)
.pipe(catchError(this.log.handleError(serviceName, 'save'))) as Observable<Product>; .pipe(catchError(this.log.handleError(serviceName, 'save'))) as Observable<void>;
} }
update(product: Product): Observable<Product> { update(product: Product): Observable<void> {
return this.http return this.http
.put<Product>(`${url}/${product.id}`, product, httpOptions) .put<Product>(`${url}/${product.versionId}`, product, httpOptions)
.pipe(catchError(this.log.handleError(serviceName, 'update'))) as Observable<Product>; .pipe(catchError(this.log.handleError(serviceName, 'update'))) as Observable<void>;
} }
updateSortOrder(list: Product[]): Observable<Product[]> { updateSortOrder(list: Product[]): Observable<Product[]> {
@ -68,17 +68,17 @@ export class ProductService {
>; >;
} }
saveOrUpdate(product: Product): Observable<Product> { saveOrUpdate(product: Product): Observable<void> {
if (!product.id) { if (!product.versionId) {
return this.save(product); return this.save(product);
} }
return this.update(product); return this.update(product);
} }
delete(id: string): Observable<Product> { delete(id: string): Observable<void> {
return this.http return this.http
.delete<Product>(`${url}/${id}`, httpOptions) .delete<Product>(`${url}/${id}`, httpOptions)
.pipe(catchError(this.log.handleError(serviceName, 'delete'))) as Observable<Product>; .pipe(catchError(this.log.handleError(serviceName, 'delete'))) as Observable<void>;
} }
balance(id: string, date: string): Observable<number> { balance(id: string, date: string): Observable<number> {

View File

@ -32,7 +32,7 @@ const productsRoutes: Routes = [
permission: 'Products', permission: 'Products',
}, },
resolve: { resolve: {
item: ProductResolver, items: ProductResolver,
menuCategories: MenuCategoryListResolver, menuCategories: MenuCategoryListResolver,
saleCategories: SaleCategoryListResolver, saleCategories: SaleCategoryListResolver,
}, },
@ -45,7 +45,7 @@ const productsRoutes: Routes = [
permission: 'Products', permission: 'Products',
}, },
resolve: { resolve: {
item: ProductResolver, items: ProductResolver,
menuCategories: MenuCategoryListResolver, menuCategories: MenuCategoryListResolver,
saleCategories: SaleCategoryListResolver, saleCategories: SaleCategoryListResolver,
}, },

View File

@ -11,6 +11,7 @@ import { MatOptionModule } from '@angular/material/core';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
@ -35,6 +36,7 @@ import { ProductsRoutingModule } from './products-routing.module';
MatCheckboxModule, MatCheckboxModule,
ReactiveFormsModule, ReactiveFormsModule,
ProductsRoutingModule, ProductsRoutingModule,
MatRadioModule,
], ],
declarations: [ProductListComponent, ProductDetailComponent], declarations: [ProductListComponent, ProductDetailComponent],
}) })