Feature: Sale Category also shows the products in it to make it easier to check for errors.
This commit is contained in:
@ -1,7 +1,7 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
from typing import List
|
from typing import List, Optional
|
||||||
|
|
||||||
import barker.schemas.product as schemas
|
import barker.schemas.product as schemas
|
||||||
|
|
||||||
@ -299,45 +299,106 @@ def product_list(date_: date, db: Session) -> List[schemas.Product]:
|
|||||||
|
|
||||||
@router.get("/query")
|
@router.get("/query")
|
||||||
async def show_term(
|
async def show_term(
|
||||||
mc: uuid.UUID = None,
|
mc: Optional[uuid.UUID] = None,
|
||||||
|
sc: Optional[uuid.UUID] = None,
|
||||||
date_: date = Depends(effective_date),
|
date_: date = Depends(effective_date),
|
||||||
current_user: UserToken = Depends(get_user),
|
current_user: UserToken = Depends(get_user),
|
||||||
):
|
):
|
||||||
list_ = []
|
list_ = []
|
||||||
|
query = select(ProductVersion)
|
||||||
|
if sc is not None:
|
||||||
|
query = query.join(ProductVersion.menu_category)
|
||||||
|
if mc is not None:
|
||||||
|
query = query.join(ProductVersion.sale_category).join(SaleCategory.tax)
|
||||||
|
query = query.where(
|
||||||
|
and_(
|
||||||
|
or_(
|
||||||
|
ProductVersion.valid_from == None, # noqa: E711
|
||||||
|
ProductVersion.valid_from <= date_,
|
||||||
|
),
|
||||||
|
or_(
|
||||||
|
ProductVersion.valid_till == None, # noqa: E711
|
||||||
|
ProductVersion.valid_till >= date_,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if mc is not None:
|
||||||
|
query = query.where(ProductVersion.menu_category_id == mc).order_by(
|
||||||
|
ProductVersion.sort_order, ProductVersion.name
|
||||||
|
)
|
||||||
|
if sc is not None:
|
||||||
|
query = query.where(ProductVersion.sale_category_id == sc).order_by(
|
||||||
|
MenuCategory.sort_order, ProductVersion.sort_order, ProductVersion.name
|
||||||
|
)
|
||||||
|
|
||||||
|
if mc is not None:
|
||||||
|
query = query.options(
|
||||||
|
joinedload(ProductVersion.sale_category, innerjoin=True),
|
||||||
|
joinedload(ProductVersion.sale_category, SaleCategory.tax, innerjoin=True),
|
||||||
|
contains_eager(ProductVersion.sale_category),
|
||||||
|
contains_eager(ProductVersion.sale_category, SaleCategory.tax),
|
||||||
|
)
|
||||||
|
|
||||||
|
if sc is not None:
|
||||||
|
query = query.options(
|
||||||
|
joinedload(ProductVersion.menu_category, innerjoin=True),
|
||||||
|
contains_eager(ProductVersion.menu_category),
|
||||||
|
)
|
||||||
|
|
||||||
with SessionFuture() as db:
|
with SessionFuture() as db:
|
||||||
for item in (
|
for item in db.execute(query).scalars().all():
|
||||||
db.execute(
|
if sc is not None:
|
||||||
select(ProductVersion)
|
list_.append(
|
||||||
.join(ProductVersion.sale_category)
|
{
|
||||||
.join(SaleCategory.tax)
|
"id": item.product_id,
|
||||||
.where(
|
"name": item.full_name,
|
||||||
and_(
|
"menuCategory": {
|
||||||
ProductVersion.menu_category_id == mc,
|
"id": item.menu_category_id,
|
||||||
or_(
|
"name": item.menu_category.name,
|
||||||
ProductVersion.valid_from == None, # noqa: E711
|
},
|
||||||
ProductVersion.valid_from <= date_,
|
"price": item.price,
|
||||||
),
|
}
|
||||||
or_(
|
|
||||||
ProductVersion.valid_till == None, # noqa: E711
|
|
||||||
ProductVersion.valid_till >= date_,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.order_by(ProductVersion.sort_order, ProductVersion.name)
|
if mc is not None:
|
||||||
.options(
|
list_.append(query_product_info(item, False))
|
||||||
joinedload(ProductVersion.sale_category, innerjoin=True),
|
if item.has_happy_hour:
|
||||||
joinedload(ProductVersion.sale_category, SaleCategory.tax, innerjoin=True),
|
list_.append(query_product_info(item, True))
|
||||||
contains_eager(ProductVersion.sale_category),
|
return list_
|
||||||
contains_eager(ProductVersion.sale_category, SaleCategory.tax),
|
|
||||||
|
|
||||||
|
def product_list_of_sale_category(date_: date, db: Session) -> List[schemas.Product]:
|
||||||
|
return [
|
||||||
|
product_info(item)
|
||||||
|
for item in db.execute(
|
||||||
|
select(ProductVersion)
|
||||||
|
.join(ProductVersion.menu_category)
|
||||||
|
.join(ProductVersion.sale_category)
|
||||||
|
.where(
|
||||||
|
and_(
|
||||||
|
or_(
|
||||||
|
ProductVersion.valid_from == None, # noqa: E711
|
||||||
|
ProductVersion.valid_from <= date_,
|
||||||
|
),
|
||||||
|
or_(
|
||||||
|
ProductVersion.valid_till == None, # noqa: E711
|
||||||
|
ProductVersion.valid_till >= date_,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.scalars()
|
.order_by(MenuCategory.sort_order)
|
||||||
.all()
|
.order_by(MenuCategory.name)
|
||||||
):
|
.order_by(ProductVersion.sort_order)
|
||||||
list_.append(query_product_info(item, False))
|
.order_by(ProductVersion.name)
|
||||||
if item.has_happy_hour:
|
.options(
|
||||||
list_.append(query_product_info(item, True))
|
joinedload(ProductVersion.menu_category, innerjoin=True),
|
||||||
return list_
|
joinedload(ProductVersion.sale_category, innerjoin=True),
|
||||||
|
contains_eager(ProductVersion.menu_category),
|
||||||
|
contains_eager(ProductVersion.sale_category),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.scalars()
|
||||||
|
.all()
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{id_}", response_model=schemas.Product)
|
@router.get("/{id_}", response_model=schemas.Product)
|
||||||
|
|||||||
@ -30,6 +30,15 @@ export class ProductService {
|
|||||||
.pipe(catchError(this.log.handleError(serviceName, 'list'))) as Observable<Product[]>;
|
.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[]> {
|
listIsActiveOfCategory(id: string): Observable<Product[]> {
|
||||||
const options = { params: new HttpParams().set('mc', id) };
|
const options = { params: new HttpParams().set('mc', id) };
|
||||||
return this.http
|
return this.http
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
|
||||||
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
|
import { Product } from '../core/product';
|
||||||
|
import { ProductService } from '../product/product.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class ProductListResolver implements Resolve<Product[]> {
|
||||||
|
constructor(private ser: ProductService) {}
|
||||||
|
|
||||||
|
resolve(route: ActivatedRouteSnapshot): Observable<Product[]> {
|
||||||
|
const id = route.paramMap.get('id');
|
||||||
|
if (id === null) {
|
||||||
|
return observableOf([]);
|
||||||
|
}
|
||||||
|
return this.ser.listOfSaleCategory(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import { RouterModule, Routes } from '@angular/router';
|
|||||||
import { AuthGuard } from '../auth/auth-guard.service';
|
import { AuthGuard } from '../auth/auth-guard.service';
|
||||||
import { TaxListResolver } from '../taxes/tax-list-resolver.service';
|
import { TaxListResolver } from '../taxes/tax-list-resolver.service';
|
||||||
|
|
||||||
|
import { ProductListResolver } from './product-list-resolver.service';
|
||||||
import { SaleCategoryDetailComponent } from './sale-category-detail/sale-category-detail.component';
|
import { SaleCategoryDetailComponent } from './sale-category-detail/sale-category-detail.component';
|
||||||
import { SaleCategoryListResolver } from './sale-category-list-resolver.service';
|
import { SaleCategoryListResolver } from './sale-category-list-resolver.service';
|
||||||
import { SaleCategoryListComponent } from './sale-category-list/sale-category-list.component';
|
import { SaleCategoryListComponent } from './sale-category-list/sale-category-list.component';
|
||||||
@ -32,6 +33,7 @@ const saleCategoriesRoutes: Routes = [
|
|||||||
resolve: {
|
resolve: {
|
||||||
item: SaleCategoryResolver,
|
item: SaleCategoryResolver,
|
||||||
taxes: TaxListResolver,
|
taxes: TaxListResolver,
|
||||||
|
products: ProductListResolver,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -44,6 +46,7 @@ const saleCategoriesRoutes: Routes = [
|
|||||||
resolve: {
|
resolve: {
|
||||||
item: SaleCategoryResolver,
|
item: SaleCategoryResolver,
|
||||||
taxes: TaxListResolver,
|
taxes: TaxListResolver,
|
||||||
|
products: ProductListResolver,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
import { DataSource } from '@angular/cdk/collections';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { tap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { Product } from '../../core/product';
|
||||||
|
|
||||||
|
export class SaleCategoryDetailDatasource extends DataSource<Product> {
|
||||||
|
private data: Product[] = [];
|
||||||
|
|
||||||
|
constructor(private readonly dataObs: Observable<Product[]>) {
|
||||||
|
super();
|
||||||
|
this.dataObs = dataObs.pipe(
|
||||||
|
tap((x) => {
|
||||||
|
this.data = x;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(): Observable<Product[]> {
|
||||||
|
return this.dataObs;
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {}
|
||||||
|
}
|
||||||
@ -1,64 +1,101 @@
|
|||||||
<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>Sale Category</mat-card-title>
|
<mat-card-title-group>
|
||||||
</mat-card-title-group>
|
<mat-card-title>Sale Category</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>Name</mat-label>
|
<mat-form-field fxFlex>
|
||||||
<input matInput #nameElement placeholder="Name" formControlName="name" />
|
<mat-label>Name</mat-label>
|
||||||
</mat-form-field>
|
<input matInput #nameElement placeholder="Name" formControlName="name" />
|
||||||
</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>Discount Limit</mat-label>
|
<mat-form-field fxFlex>
|
||||||
<input
|
<mat-label>Discount Limit</mat-label>
|
||||||
matInput
|
<input
|
||||||
type="number"
|
matInput
|
||||||
placeholder="Discount Limit"
|
type="number"
|
||||||
formControlName="discountLimit"
|
placeholder="Discount Limit"
|
||||||
class="right-align"
|
formControlName="discountLimit"
|
||||||
/>
|
class="right-align"
|
||||||
<span matSuffix>%</span>
|
/>
|
||||||
</mat-form-field>
|
<span matSuffix>%</span>
|
||||||
</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>Tax</mat-label>
|
<mat-form-field fxFlex>
|
||||||
<mat-select placeholder="Tax" formControlName="tax">
|
<mat-label>Tax</mat-label>
|
||||||
<mat-option *ngFor="let t of taxes" [value]="t.id">
|
<mat-select placeholder="Tax" formControlName="tax">
|
||||||
{{ t.name }}
|
<mat-option *ngFor="let t of taxes" [value]="t.id">
|
||||||
</mat-option>
|
{{ t.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">
|
||||||
|
<mat-card fxFlex>
|
||||||
|
<mat-card-title-group>
|
||||||
|
<mat-card-title>Products</mat-card-title>
|
||||||
|
</mat-card-title-group>
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-table [dataSource]="dataSource" aria-label="Elements">
|
||||||
|
<!-- Name Column -->
|
||||||
|
<ng-container matColumnDef="name">
|
||||||
|
<mat-header-cell *matHeaderCellDef>Name</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let row"
|
||||||
|
><a [routerLink]="['/products', row.id]">{{ row.name }}</a></mat-cell
|
||||||
|
>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Price Column -->
|
||||||
|
<ng-container matColumnDef="price">
|
||||||
|
<mat-header-cell *matHeaderCellDef class="right">Price</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let row" class="right">{{
|
||||||
|
row.price | currency: 'INR'
|
||||||
|
}}</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Menu Category Column -->
|
||||||
|
<ng-container matColumnDef="menuCategory">
|
||||||
|
<mat-header-cell *matHeaderCellDef>Menu Category</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let row">{{ row.menuCategory.name }}</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||||
|
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
||||||
|
</mat-table>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,13 +3,17 @@ import { FormBuilder, FormGroup } from '@angular/forms';
|
|||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { round } from 'mathjs';
|
import { round } from 'mathjs';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
|
import { Product } from '../../core/product';
|
||||||
import { SaleCategory } from '../../core/sale-category';
|
import { SaleCategory } from '../../core/sale-category';
|
||||||
import { Tax } from '../../core/tax';
|
import { Tax } from '../../core/tax';
|
||||||
import { ToasterService } from '../../core/toaster.service';
|
import { ToasterService } from '../../core/toaster.service';
|
||||||
import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
|
import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
|
||||||
import { SaleCategoryService } from '../sale-category.service';
|
import { SaleCategoryService } from '../sale-category.service';
|
||||||
|
|
||||||
|
import { SaleCategoryDetailDatasource } from './sale-category-detail-datasource';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-sale-category-detail',
|
selector: 'app-sale-category-detail',
|
||||||
templateUrl: './sale-category-detail.component.html',
|
templateUrl: './sale-category-detail.component.html',
|
||||||
@ -20,6 +24,9 @@ export class SaleCategoryDetailComponent implements OnInit, AfterViewInit {
|
|||||||
form: FormGroup;
|
form: FormGroup;
|
||||||
taxes: Tax[] = [];
|
taxes: Tax[] = [];
|
||||||
item: SaleCategory = new SaleCategory();
|
item: SaleCategory = new SaleCategory();
|
||||||
|
products: BehaviorSubject<Product[]> = new BehaviorSubject<Product[]>([]);
|
||||||
|
dataSource: SaleCategoryDetailDatasource = new SaleCategoryDetailDatasource(this.products);
|
||||||
|
displayedColumns = ['name', 'price', 'menuCategory'];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
@ -39,9 +46,10 @@ export class SaleCategoryDetailComponent implements OnInit, AfterViewInit {
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.route.data.subscribe((value) => {
|
this.route.data.subscribe((value) => {
|
||||||
const data = value as { item: SaleCategory; taxes: Tax[] };
|
const data = value as { item: SaleCategory; taxes: Tax[]; products: Product[] };
|
||||||
this.showItem(data.item);
|
this.showItem(data.item);
|
||||||
this.taxes = data.taxes;
|
this.taxes = data.taxes;
|
||||||
|
this.products.next(data.products);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { MatTable } from '@angular/material/table';
|
|||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
|
import { Product } from '../../core/product';
|
||||||
import { SaleCategory } from '../../core/sale-category';
|
import { SaleCategory } from '../../core/sale-category';
|
||||||
import { ToasterService } from '../../core/toaster.service';
|
import { ToasterService } from '../../core/toaster.service';
|
||||||
import { SaleCategoryService } from '../sale-category.service';
|
import { SaleCategoryService } from '../sale-category.service';
|
||||||
|
|||||||
Reference in New Issue
Block a user