Feature: Sale Category also shows the products in it to make it easier to check for errors.

This commit is contained in:
2021-08-06 08:23:36 +05:30
parent 0b3ec5da16
commit 2f440a4127
8 changed files with 260 additions and 96 deletions

View File

@ -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)

View File

@ -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

View File

@ -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);
}
}

View File

@ -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,
}, },
}, },
]; ];

View File

@ -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() {}
}

View File

@ -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>

View File

@ -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);
}); });
} }

View File

@ -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';