Product Detail is neater.
Recipe list fixed. Recipe xlsx not working.
This commit is contained in:
@ -119,7 +119,7 @@ def save(
|
|||||||
|
|
||||||
def date_range(
|
def date_range(
|
||||||
start: date, stop: date, step: timedelta = timedelta(days=1), inclusive: bool = False
|
start: date, stop: date, step: timedelta = timedelta(days=1), inclusive: bool = False
|
||||||
) -> Generator[date, None, None]:
|
) -> Generator[date]:
|
||||||
# inclusive=False to behave like range by default
|
# inclusive=False to behave like range by default
|
||||||
if step.days > 0:
|
if step.days > 0:
|
||||||
while start < stop:
|
while start < stop:
|
||||||
|
|||||||
@ -125,7 +125,9 @@ async def update_route(
|
|||||||
item.quantity = round(new_item.quantity, 2)
|
item.quantity = round(new_item.quantity, 2)
|
||||||
item.description = new_item.description
|
item.description = new_item.description
|
||||||
else:
|
else:
|
||||||
|
db.delete(item)
|
||||||
recipe.items.remove(item)
|
recipe.items.remove(item)
|
||||||
|
db.flush()
|
||||||
|
|
||||||
for d_item in data.items:
|
for d_item in data.items:
|
||||||
product = db.execute(select(Product).where(Product.id == d_item.product.id_)).scalar_one()
|
product = db.execute(select(Product).where(Product.id == d_item.product.id_)).scalar_one()
|
||||||
@ -152,9 +154,16 @@ def check_recursion(product: uuid.UUID, visited: set[uuid.UUID], db: Session) ->
|
|||||||
detail="Recipe recursion. Some ingredient recipe contains parent recipe.",
|
detail="Recipe recursion. Some ingredient recipe contains parent recipe.",
|
||||||
)
|
)
|
||||||
recipe = (
|
recipe = (
|
||||||
db.execute(select(Recipe).join(Recipe.items).join(Recipe.sku).where(StockKeepingUnit.product_id == product))
|
db.execute(
|
||||||
|
select(Recipe)
|
||||||
|
.join(Recipe.items)
|
||||||
|
.join(Recipe.sku)
|
||||||
|
.options(contains_eager(Recipe.items))
|
||||||
|
.where(StockKeepingUnit.product_id == product)
|
||||||
|
)
|
||||||
.unique()
|
.unique()
|
||||||
.scalar_one_or_none()
|
.scalars()
|
||||||
|
.first()
|
||||||
)
|
)
|
||||||
if recipe is None:
|
if recipe is None:
|
||||||
return
|
return
|
||||||
@ -239,14 +248,15 @@ async def show_list(
|
|||||||
.join(ProductVersion, onclause=product_version_onclause)
|
.join(ProductVersion, onclause=product_version_onclause)
|
||||||
.join(ProductVersion.product_group)
|
.join(ProductVersion.product_group)
|
||||||
.options(
|
.options(
|
||||||
|
contains_eager(Recipe.sku).contains_eager(StockKeepingUnit.versions),
|
||||||
contains_eager(Recipe.sku)
|
contains_eager(Recipe.sku)
|
||||||
.contains_eager(StockKeepingUnit.versions)
|
|
||||||
.contains_eager(StockKeepingUnit.product)
|
.contains_eager(StockKeepingUnit.product)
|
||||||
.contains_eager(Product.versions)
|
.contains_eager(Product.versions)
|
||||||
.contains_eager(ProductVersion.product_group)
|
.contains_eager(ProductVersion.product_group),
|
||||||
)
|
)
|
||||||
.order_by(ProductVersion.name)
|
.order_by(ProductVersion.name)
|
||||||
)
|
)
|
||||||
|
.unique()
|
||||||
.scalars()
|
.scalars()
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
@ -302,15 +312,15 @@ def show_pdf(
|
|||||||
@router.get("/xlsx", response_class=StreamingResponse)
|
@router.get("/xlsx", response_class=StreamingResponse)
|
||||||
def get_report(
|
def get_report(
|
||||||
p: uuid.UUID,
|
p: uuid.UUID,
|
||||||
t: uuid.UUID | None = None,
|
pg: uuid.UUID | None = None,
|
||||||
) -> StreamingResponse:
|
) -> StreamingResponse:
|
||||||
with SessionFuture() as db:
|
with SessionFuture() as db:
|
||||||
calculate_prices(t, db)
|
calculate_prices(p, db)
|
||||||
db.commit()
|
db.commit()
|
||||||
prices: list[tuple[str, str, Decimal]] = []
|
prices: list[tuple[str, str, Decimal]] = []
|
||||||
with SessionFuture() as db:
|
with SessionFuture() as db:
|
||||||
pq = (
|
pq = (
|
||||||
db.execute(select(Price).where(Price.period_id == t).options(joinedload(Price.product, innerjoin=True)))
|
db.execute(select(Price).where(Price.period_id == p).options(joinedload(Price.product, innerjoin=True)))
|
||||||
.unique()
|
.unique()
|
||||||
.scalars()
|
.scalars()
|
||||||
.all()
|
.all()
|
||||||
@ -318,6 +328,7 @@ def get_report(
|
|||||||
prices = [(i.product.versions[-1].name, i.product.versions[-1].fraction_units, i.price) for i in pq]
|
prices = [(i.product.versions[-1].name, i.product.versions[-1].fraction_units, i.price) for i in pq]
|
||||||
|
|
||||||
list_: Sequence[Recipe] = []
|
list_: Sequence[Recipe] = []
|
||||||
|
print("test2")
|
||||||
with SessionFuture() as db:
|
with SessionFuture() as db:
|
||||||
RecipeProductVersion = aliased(ProductVersion, name="recipe_product_version")
|
RecipeProductVersion = aliased(ProductVersion, name="recipe_product_version")
|
||||||
ItemProductVersion = aliased(ProductVersion, name="item_product_version")
|
ItemProductVersion = aliased(ProductVersion, name="item_product_version")
|
||||||
@ -384,8 +395,8 @@ def get_report(
|
|||||||
contains_eager(Recipe.sku).contains_eager(StockKeepingUnit.versions, alias=CurrentSkuVersion),
|
contains_eager(Recipe.sku).contains_eager(StockKeepingUnit.versions, alias=CurrentSkuVersion),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if p is not None:
|
if pg is not None:
|
||||||
q = q.where(RecipeProductVersion.product_group_id == p)
|
q = q.where(RecipeProductVersion.product_group_id == pg)
|
||||||
list_ = db.execute(q).unique().scalars().all()
|
list_ = db.execute(q).unique().scalars().all()
|
||||||
e = excel(prices, sorted(list_, key=lambda r: r.sku.product.versions[0].name))
|
e = excel(prices, sorted(list_, key=lambda r: r.sku.product.versions[0].name))
|
||||||
e.seek(0)
|
e.seek(0)
|
||||||
@ -569,7 +580,8 @@ def recipe_info(recipe: Recipe) -> schemas.Recipe:
|
|||||||
id_=item.id,
|
id_=item.id,
|
||||||
product=schemas.ProductLink(
|
product=schemas.ProductLink(
|
||||||
id_=item.product.id,
|
id_=item.product.id,
|
||||||
name=f"{item.product.versions[0].name} ({item.product.versions[0].fraction_units})",
|
name=item.product.versions[0].name,
|
||||||
|
fraction_units=item.product.versions[0].fraction_units,
|
||||||
),
|
),
|
||||||
quantity=round(item.quantity, 2),
|
quantity=round(item.quantity, 2),
|
||||||
description=item.description,
|
description=item.description,
|
||||||
|
|||||||
@ -12,6 +12,7 @@ from .stock_keeping_unit import StockKeepingUnit
|
|||||||
class ProductLink(BaseModel):
|
class ProductLink(BaseModel):
|
||||||
id_: uuid.UUID = Field(...)
|
id_: uuid.UUID = Field(...)
|
||||||
name: str | None = None
|
name: str | None = None
|
||||||
|
fraction_units: str | None = None
|
||||||
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
.nutrition-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-items: stretch;
|
||||||
|
}
|
||||||
|
.nutrition-grid > * {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box; /* helps with padding */
|
||||||
|
}
|
||||||
|
|||||||
@ -47,7 +47,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@if (item.productGroup?.nutritional ?? false) {
|
@if (item.productGroup?.nutritional ?? false) {
|
||||||
<h2>Nutritional Information</h2>
|
<h2>Nutritional Information</h2>
|
||||||
<div class="row-container">
|
<div class="nutrition-grid">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-label>Protein</mat-label>
|
<mat-label>Protein</mat-label>
|
||||||
<input matInput formControlName="protein" />
|
<input matInput formControlName="protein" />
|
||||||
@ -108,7 +108,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<h2>Stock Keeping Units</h2>
|
<h2>Stock Keeping Units</h2>
|
||||||
<div formGroupName="addRow" class="row-container space-between">
|
<div formGroupName="addRow" class="nutrition-grid">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-label>Units</mat-label>
|
<mat-label>Units</mat-label>
|
||||||
<input matInput formControlName="units" />
|
<input matInput formControlName="units" />
|
||||||
@ -132,7 +132,7 @@
|
|||||||
<button mat-raised-button color="primary" (click)="addRow()" class="flex-auto">Add</button>
|
<button mat-raised-button color="primary" (click)="addRow()" class="flex-auto">Add</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div class="row-container">
|
<div class="row-container wrapped">
|
||||||
<mat-table [dataSource]="dataSource" aria-label="Elements" class="flex-auto">
|
<mat-table [dataSource]="dataSource" aria-label="Elements" class="flex-auto">
|
||||||
<!-- Units Column -->
|
<!-- Units Column -->
|
||||||
<ng-container matColumnDef="units">
|
<ng-container matColumnDef="units">
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<h2>Recipe Detail</h2>
|
<h2>Recipe Detail</h2>
|
||||||
|
|
||||||
<form [formGroup]="form" class="flex-col">
|
<form [formGroup]="form" class="flex-col wrapped">
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-label>Date</mat-label>
|
<mat-label>Date</mat-label>
|
||||||
@ -83,9 +83,13 @@
|
|||||||
<!-- Quantity Column -->
|
<!-- Quantity Column -->
|
||||||
<ng-container matColumnDef="quantity">
|
<ng-container matColumnDef="quantity">
|
||||||
<mat-header-cell *matHeaderCellDef class="right">Quantity</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef class="right">Quantity</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row" class="right"
|
<mat-cell *matCellDef="let row" class="right">{{ row.quantity | number: '1.2-2' }}</mat-cell>
|
||||||
>{{ row.quantity | number: '1.2-2' }} {{ row.product.fractionUnits }}</mat-cell
|
</ng-container>
|
||||||
>
|
|
||||||
|
<!-- Units Column -->
|
||||||
|
<ng-container matColumnDef="units">
|
||||||
|
<mat-header-cell *matHeaderCellDef>Units</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let row">{{ row.product.fractionUnits }}</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Action Column -->
|
<!-- Action Column -->
|
||||||
@ -101,18 +105,24 @@
|
|||||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||||
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
||||||
</mat-table>
|
</mat-table>
|
||||||
<mat-form-field class="flex-auto">
|
<div class="row-container">
|
||||||
<mat-label>Instructions</mat-label>
|
<mat-form-field class="flex-auto">
|
||||||
<textarea matInput matAutosizeMinRows="5" formControlName="instructions"></textarea>
|
<mat-label>Instructions</mat-label>
|
||||||
</mat-form-field>
|
<textarea matInput matAutosizeMinRows="5" formControlName="instructions"></textarea>
|
||||||
<mat-form-field class="flex-auto">
|
</mat-form-field>
|
||||||
<mat-label>Garnishing</mat-label>
|
</div>
|
||||||
<textarea matInput matAutosizeMinRows="5" formControlName="garnishing"></textarea>
|
<div class="row-container">
|
||||||
</mat-form-field>
|
<mat-form-field class="flex-auto">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-label>Garnishing</mat-label>
|
||||||
<mat-label>Plating</mat-label>
|
<textarea matInput matAutosizeMinRows="5" formControlName="garnishing"></textarea>
|
||||||
<textarea matInput matAutosizeMinRows="5" formControlName="plating"></textarea>
|
</mat-form-field>
|
||||||
</mat-form-field>
|
</div>
|
||||||
|
<div class="row-container">
|
||||||
|
<mat-form-field class="flex-auto">
|
||||||
|
<mat-label>Plating</mat-label>
|
||||||
|
<textarea matInput matAutosizeMinRows="5" formControlName="plating"></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
|
|||||||
@ -82,7 +82,7 @@ export class RecipeDetailComponent implements OnInit, AfterViewInit {
|
|||||||
ingredients: Observable<ProductSku[]>;
|
ingredients: Observable<ProductSku[]>;
|
||||||
item: Recipe = new Recipe();
|
item: Recipe = new Recipe();
|
||||||
|
|
||||||
displayedColumns = ['product', 'quantity', 'action'];
|
displayedColumns = ['product', 'quantity', 'units', 'action'];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.product = null;
|
this.product = null;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<h2 class="row-container space-between">
|
<h2 class="row-container space-between">
|
||||||
<span>Recipes</span>
|
<span>Recipes</span>
|
||||||
<a mat-icon-button [href]="'/api/recipes/xlsx?t=' + period.id">
|
<a mat-icon-button [href]="'/api/recipes/xlsx?p=' + period.id">
|
||||||
<mat-icon>save_alt</mat-icon>
|
<mat-icon>save_alt</mat-icon>
|
||||||
</a>
|
</a>
|
||||||
<a mat-icon-button href="/api/recipes/nutrition">
|
<a mat-icon-button href="/api/recipes/nutrition">
|
||||||
@ -16,7 +16,7 @@
|
|||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-select formControlName="period">
|
<mat-select formControlName="period">
|
||||||
@for (p of periods; track p) {
|
@for (p of periods; track p.id) {
|
||||||
<mat-option [value]="p"> {{ p.validFrom }} to {{ p.validTill }} </mat-option>
|
<mat-option [value]="p"> {{ p.validFrom }} to {{ p.validTill }} </mat-option>
|
||||||
}
|
}
|
||||||
</mat-select>
|
</mat-select>
|
||||||
@ -25,7 +25,7 @@
|
|||||||
<mat-label>Product Type</mat-label>
|
<mat-label>Product Type</mat-label>
|
||||||
<mat-select formControlName="productGroup" (selectionChange)="filterProductGroup($event.value)">
|
<mat-select formControlName="productGroup" (selectionChange)="filterProductGroup($event.value)">
|
||||||
<mat-option>-- All Products --</mat-option>
|
<mat-option>-- All Products --</mat-option>
|
||||||
@for (mc of productGroups; track mc) {
|
@for (mc of productGroups; track mc.id) {
|
||||||
<mat-option [value]="mc.id">
|
<mat-option [value]="mc.id">
|
||||||
{{ mc.name }}
|
{{ mc.name }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
|
|||||||
@ -69,12 +69,13 @@ export class RecipeListComponent implements OnInit {
|
|||||||
period: new FormControl(new Period(), { nonNullable: true }),
|
period: new FormControl(new Period(), { nonNullable: true }),
|
||||||
productGroup: new FormControl<ProductGroup | string | null>(null),
|
productGroup: new FormControl<ProductGroup | string | null>(null),
|
||||||
});
|
});
|
||||||
// Listen to Payment Account Change
|
// Listen to Period Change
|
||||||
this.form.controls.period.valueChanges.subscribe((x) => {
|
this.form.controls.period.valueChanges.subscribe((x) => {
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
relativeTo: this.route,
|
relativeTo: this.route,
|
||||||
queryParams: { p: x.id },
|
queryParams: { p: x.id },
|
||||||
replaceUrl: true,
|
replaceUrl: true,
|
||||||
|
queryParamsHandling: 'merge',
|
||||||
});
|
});
|
||||||
this.period = x;
|
this.period = x;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -81,6 +81,13 @@
|
|||||||
.center {
|
.center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.right {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
// .right {
|
||||||
|
// text-align: right;
|
||||||
|
// }
|
||||||
|
|
||||||
.warn {
|
.warn {
|
||||||
background-color: red;
|
background-color: red;
|
||||||
|
|||||||
Reference in New Issue
Block a user