Compare commits

..
11 Commits
22 changed files with 108 additions and 57 deletions
@@ -0,0 +1,24 @@
"""FP
Revision ID: 48af31eb6f3f
Revises: 12262aadbc08
Create Date: 2023-08-07 13:01:05.401492
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '48af31eb6f3f'
down_revision = '12262aadbc08'
branch_labels = None
depends_on = None
def upgrade():
op.create_unique_constraint(op.f('uq_fingerprints_date'), 'fingerprints', ['date', 'employee_id'])
def downgrade():
op.drop_constraint(op.f('uq_fingerprints_date'), 'fingerprints', type_='unique')
+1 -1
View File
@@ -1 +1 @@
__version__ = "11.1.6"
__version__ = "11.2.1"
+2 -1
View File
@@ -3,7 +3,7 @@ import uuid
from datetime import datetime
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKey, Uuid
from sqlalchemy import DateTime, ForeignKey, UniqueConstraint, Uuid
from sqlalchemy.orm import Mapped, mapped_column, relationship
from ..db.base_class import reg
@@ -16,6 +16,7 @@ if TYPE_CHECKING:
@reg.mapped_as_dataclass(unsafe_hash=True)
class Fingerprint:
__tablename__ = "fingerprints"
__table_args__ = (UniqueConstraint("date", "employee_id"),)
id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, insert_default=uuid.uuid4)
employee_id: Mapped[uuid.UUID] = mapped_column(Uuid, ForeignKey("employees.id"), nullable=False)
+27
View File
@@ -40,3 +40,30 @@ class Recipe:
tags: Mapped[list["Tag"]] = relationship(
"Tag", secondary=RecipeTag.__table__, order_by="Tag.name", back_populates="recipes"
)
def __init__(
self,
date_: date,
source: str,
instructions: str,
garnishing: str,
plating: str,
notes: str,
recipe_yield: Decimal,
sku_id: uuid.UUID | None = None,
sku: "StockKeepingUnit" | None = None,
id_: uuid.UUID | None = None,
):
self.date_ = date_
self.source = source
self.instructions = instructions
self.garnishing = garnishing
self.plating = plating
self.notes = notes
self.recipe_yield = recipe_yield
if sku_id is not None:
self.sku_id = sku_id
if sku is not None:
self.sku = sku
if id_ is not None:
self.id = id_
+23
View File
@@ -29,3 +29,26 @@ class RecipeItem:
recipe: Mapped["Recipe"] = relationship("Recipe", back_populates="items")
product: Mapped["Product"] = relationship("Product")
def __init__(
self,
quantity: Decimal,
description: str = "",
recipe_id: uuid.UUID | None = None,
product_id: uuid.UUID | None = None,
recipe: "Recipe" | None = None,
product: "Product" | None = None,
id_: uuid.UUID | None = None,
):
self.quantity = quantity
self.description = description
if recipe_id is not None:
self.recipe_id = recipe_id
if product_id is not None:
self.product_id = product_id
if recipe is not None:
self.recipe = recipe
if product is not None:
self.product = product
if id_ is not None:
self.id = id_
+5 -14
View File
@@ -8,7 +8,7 @@ from io import StringIO
import brewman.schemas.fingerprint as schemas
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
from sqlalchemy import bindparam, select
from sqlalchemy import select
from sqlalchemy.dialects.postgresql import insert as pg_insert
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
@@ -36,22 +36,13 @@ def upload_prints(
for id_, code in db.execute(select(Employee.id, Employee.code)).all():
employees[code] = id_
file_data = read_file(fingerprints)
prints = [d for d in fp(file_data, employees) if start <= d.date_.date() <= finish]
prints = [d.model_dump() for d in fp(file_data, employees) if start <= d.date_.date() <= finish]
for p in prints:
p["id"] = p.pop("id_")
paged_data = [prints[i : i + 100] for i in range(0, len(prints), 100)]
for i, page in enumerate(paged_data):
print(f"Processing page {i} of {len(paged_data)}")
db.execute(
pg_insert(Fingerprint)
.values(
{
"id": bindparam("id"),
"employee_id": bindparam("employee_id"),
"date": bindparam("date"),
}
)
.on_conflict_do_nothing(),
[p.dict() for p in page],
)
db.execute(pg_insert(Fingerprint).on_conflict_do_nothing(), page)
db.commit()
except SQLAlchemyError as e:
raise HTTPException(
+3 -1
View File
@@ -58,6 +58,7 @@ def save(
instructions=data.instructions,
garnishing=data.garnishing,
plating=data.plating,
notes=data.notes,
sku=recipe_sku,
recipe_yield=round(data.recipe_yield, 2),
)
@@ -71,7 +72,8 @@ def save(
r_item.recipe_id = recipe.id
db.add(r_item)
check_recursion(set([recipe_sku.product_id]), set(), recipe, db)
# TODO: Check recursion
# check_recursion(set([recipe_sku.product_id]), set(), recipe, db)
db.commit()
return recipe_info(recipe)
except SQLAlchemyError as e:
+5 -3
View File
@@ -44,11 +44,13 @@ class ClientList(Client):
@field_validator("last_date", mode="before")
@classmethod
def parse_last_date(cls, value: datetime | str) -> datetime | None:
def parse_last_date(cls, value: datetime | str | None) -> datetime | None:
if value is None or value == "":
return None
if isinstance(value, datetime):
return value
return datetime.strptime(value, "%d-%b-%Y %H:%M")
@field_serializer("last_date")
def serialize_last_date(self, value: datetime, info: FieldSerializationInfo) -> str:
return value.strftime("%d-%b-%Y %H:%M")
def serialize_last_date(self, value: datetime | None, info: FieldSerializationInfo) -> str | None:
return None if value is None else value.strftime("%d-%b-%Y %H:%M")
+4 -4
View File
@@ -20,11 +20,11 @@ class Fingerprint(BaseModel):
@field_validator("date_", mode="before")
@classmethod
def parse_date(cls, value: date | str) -> date:
if isinstance(value, date):
def parse_date(cls, value: datetime | str) -> datetime:
if isinstance(value, datetime):
return value
return datetime.strptime(value, "%d-%b-%Y").date()
return datetime.strptime(value, "%d-%b-%Y %H:%M")
@field_serializer("date_")
def serialize_date(self, value: date, info: FieldSerializationInfo) -> str:
return value.strftime("%d-%b-%Y")
return value.strftime("%d-%b-%Y %H:%M")
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "brewman"
version = "11.1.6"
version = "11.2.1"
description = "Accounting plus inventory management for a restaurant."
authors = ["tanshu <git@tanshu.com>"]
+1 -1
View File
@@ -32,5 +32,5 @@ else
echo "No version bump"
fi
cd "$parent_path/docker" || exit
docker save brewman:latest | bzip2 | pv | ssh beacon 'bunzip2 | sudo docker load'
docker save brewman:latest | bzip2 | pv | ssh gondor 'bunzip2 | sudo docker load'
ansible-playbook --inventory hosts playbook.yml
-2
View File
@@ -1,7 +1,5 @@
---
name: acc
# host_directory: "brewman-{{ name }}"
# db_name: "brewman_{{ name }}"
http_host: "acc.hopsngrains.com"
http_conf: "acc.hopsngrains.com.conf"
-2
View File
@@ -1,7 +1,5 @@
---
name: exp
# host_directory: "brewman-{{ name }}"
# db_name: "brewman_{{ name }}"
http_host: "exp.tanshu.com"
http_conf: "exp.tanshu.com.conf"
-2
View File
@@ -1,7 +1,5 @@
---
name: hinchco
# host_directory: "brewman-{{ name }}"
# db_name: "brewman_{{ name }}"
http_host: "acc.hinchco.in"
http_conf: "acc.hinchco.in.conf"
-2
View File
@@ -1,7 +1,5 @@
---
name: hops
# host_directory: "brewman-{{ name }}"
# db_name: "brewman_{{ name }}"
http_host: "hops.hopsngrains.com"
http_conf: "hops.hopsngrains.com.conf"
-2
View File
@@ -1,7 +1,5 @@
---
name: mhl
# host_directory: "brewman-{{ name }}"
# db_name: "brewman_{{ name }}"
http_host: "mhl.hopsngrains.com"
http_conf: "mhl.hopsngrains.com.conf"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "overlord",
"version": "11.1.6",
"version": "11.2.1",
"scripts": {
"ng": "ng",
"start": "ng serve",
+4 -4
View File
@@ -193,10 +193,10 @@ export class IssueComponent implements OnInit, AfterViewInit, OnDestroy {
this.voucher.inventories.push(
new Inventory({
quantity,
rate: this.batch.rate,
tax: this.batch.tax,
discount: this.batch.discount,
amount: quantity * this.batch.rate * (1 + this.batch.tax) * (1 - this.batch.discount),
rate: +this.batch.rate,
tax: +this.batch.tax,
discount: +this.batch.discount,
amount: quantity * +this.batch.rate * (1 + +this.batch.tax) * (1 - +this.batch.discount),
batch: this.batch,
}),
);
@@ -24,7 +24,7 @@
</mat-form-field>
</div>
<div class="flex flex-row justify-around content-start items-start sm:max-lg:flex-col">
<mat-form-field class="flex-auto basis-3/5 mr-5">
<mat-form-field class="flex-auto basis-4/5 mr-5">
<mat-label>Product</mat-label>
<input
type="text"
@@ -43,7 +43,7 @@
<mat-option *ngFor="let product of products | async" [value]="product">{{ product.name }}</mat-option>
</mat-autocomplete>
</mat-form-field>
<mat-form-field class="flex-auto basis-1/10 mr-5">
<mat-form-field class="flex-auto basis-1/5">
<mat-label>Yield</mat-label>
<input type="text" matInput formControlName="recipeYield" autocomplete="off" />
</mat-form-field>
@@ -74,17 +74,12 @@
<mat-label>Quantity</mat-label>
<input type="text" matInput formControlName="quantity" autocomplete="off" />
</mat-form-field>
<mat-form-field class="flex-auto basis-1/5 mr-5">
<mat-form-field class="flex-auto basis-[30%] mr-5">
<mat-label>Description</mat-label>
<input type="text" matInput formControlName="description" autocomplete="off" />
</mat-form-field>
<mat-form-field class="flex-auto basis-1/10 mr-5">
<mat-label>Rate</mat-label>
<input type="text" matInput formControlName="rate" autocomplete="off" />
<span matTextPrefix>&nbsp;</span>
</mat-form-field>
<button mat-raised-button color="primary" (click)="addRow()" class="flex-auto basis-1/10">Add</button>
<button mat-raised-button color="primary" (click)="addRow()" class="flex-auto basis-[10%]">Add</button>
</div>
<mat-table #table [dataSource]="dataSource" matSort aria-label="Elements">
<!-- Ingredient Column -->
@@ -39,7 +39,6 @@ export class RecipeDetailComponent implements OnInit, AfterViewInit {
ingredient: FormControl<string | null>;
quantity: FormControl<string>;
description: FormControl<string>;
rate: FormControl<string>;
}>;
instructions: FormControl<string>;
garnishing: FormControl<string>;
@@ -75,7 +74,6 @@ export class RecipeDetailComponent implements OnInit, AfterViewInit {
ingredient: new FormControl(''),
quantity: new FormControl('', { nonNullable: true }),
description: new FormControl('', { nonNullable: true }),
rate: new FormControl('', { nonNullable: true }),
}),
instructions: new FormControl('', { nonNullable: true }),
garnishing: new FormControl('', { nonNullable: true }),
@@ -118,7 +116,6 @@ export class RecipeDetailComponent implements OnInit, AfterViewInit {
ingredient: null,
quantity: '',
description: '',
rate: '',
},
instructions: item.instructions,
garnishing: item.garnishing,
@@ -160,8 +157,7 @@ export class RecipeDetailComponent implements OnInit, AfterViewInit {
return;
}
const quantity = this.math.parseAmount(formValue.quantity, 2);
const rate = this.math.parseAmount(formValue.rate, 2);
if (this.ingredient === null || quantity <= 0 || rate <= 0) {
if (this.ingredient === null || quantity <= 0) {
return;
}
const oldFiltered = this.item.items.filter((x) => x.product.id === (this.ingredient as ProductSku).id);
@@ -2,5 +2,5 @@ export const environment = {
production: true,
// eslint-disable-next-line @typescript-eslint/naming-convention
ACCESS_TOKEN_REFRESH_MINUTES: 10, // refresh token 10 minutes before expiry
version: '11.1.6',
version: '11.2.1',
};
+1 -1
View File
@@ -6,7 +6,7 @@ export const environment = {
production: false,
// eslint-disable-next-line @typescript-eslint/naming-convention
ACCESS_TOKEN_REFRESH_MINUTES: 10, // refresh token 10 minutes before expiry
version: '11.1.6',
version: '11.2.1',
};
/*