diff --git a/brewman/alembic/versions/a1372ed99c45_recipe_upgrade.py b/brewman/alembic/versions/a1372ed99c45_recipe_upgrade.py index a82fad68..ebc52ec0 100644 --- a/brewman/alembic/versions/a1372ed99c45_recipe_upgrade.py +++ b/brewman/alembic/versions/a1372ed99c45_recipe_upgrade.py @@ -61,10 +61,10 @@ def upgrade(): op.add_column("recipes", sa.Column("instructions", sa.Text(), nullable=False, server_default="")) op.add_column("recipes", sa.Column("garnishing", sa.Text(), nullable=False, server_default="")) op.add_column("recipes", sa.Column("plating", sa.Text(), nullable=False, server_default="")) + op.drop_constraint(op.f("uq_recipes_sku_id"), "recipes", type_="unique") op.alter_column("recipes", "notes", existing_type=sa.VARCHAR(length=255),type=sa.Text(), nullable=False) op.create_unique_constraint(op.f('uq_recipes_sku_id'), 'recipes', ['sku_id', 'date']) op.create_index(op.f("ix_recipes_date"), "recipes", ["date"], unique=False) - op.drop_constraint(op.f("uq_recipes_sku_id"), "recipes", type_="unique") op.drop_constraint("fk_recipes_period_id_periods", "recipes", type_="foreignkey") op.drop_column("recipes", "period_id") op.drop_column('recipes', 'sale_price') diff --git a/brewman/brewman/models/inventory.py b/brewman/brewman/models/inventory.py index 44156280..858b7d9d 100644 --- a/brewman/brewman/models/inventory.py +++ b/brewman/brewman/models/inventory.py @@ -70,4 +70,6 @@ class Inventory: @amount.inplace.expression @classmethod def _amount_expression(cls) -> ColumnElement[Decimal]: - return type_coerce(cls.quantity * cls.rate * (1 + cls.tax) * (1 - cls.discount), Decimal) + return type_coerce( + cls.quantity * cls.rate * (1 + cls.tax) * (1 - cls.discount), Numeric(precision=15, scale=2) + ).label("amount") diff --git a/brewman/brewman/models/journal.py b/brewman/brewman/models/journal.py index e3687304..e3d078c2 100644 --- a/brewman/brewman/models/journal.py +++ b/brewman/brewman/models/journal.py @@ -51,7 +51,7 @@ class Journal: @signed_amount.inplace.expression @classmethod def _signed_amount_expression(cls) -> ColumnElement[Decimal]: - return type_coerce(cls.debit * cls.amount, Decimal) + return type_coerce(cls.debit * cls.amount, Numeric(precision=15, scale=2)).label("signed_amount") def __init__( self, diff --git a/brewman/brewman/routers/account.py b/brewman/brewman/routers/account.py index 1685d940..0ed1c2bb 100644 --- a/brewman/brewman/routers/account.py +++ b/brewman/brewman/routers/account.py @@ -226,6 +226,7 @@ def delete_with_data(account: Account, db: Session) -> None: .options(joinedload(Voucher.journals, innerjoin=True).joinedload(Journal.account, innerjoin=True)) .where(Voucher.journals.any(Journal.account_id == account.id)) ) + .unique() .scalars() .all() ) diff --git a/brewman/brewman/routers/reports/product_ledger.py b/brewman/brewman/routers/reports/product_ledger.py index f9faaa4a..f8766a11 100644 --- a/brewman/brewman/routers/reports/product_ledger.py +++ b/brewman/brewman/routers/reports/product_ledger.py @@ -72,7 +72,7 @@ def build_report( running_total_q, running_total_a, opening = opening_balance(product_id, start_date, db) body = opening - query = db.execute( + query: list[tuple[Voucher, Inventory, Journal, StockKeepingUnit]] = db.execute( select(Voucher, Inventory, Journal, StockKeepingUnit) .join(Voucher.journals) .join(Voucher.inventories) @@ -112,14 +112,14 @@ def build_report( body.append( schemas.ProductLedgerItem( id_=voucher.id, - date_=voucher.date, + date_=voucher.date_, name=name, url=[ "/", voucher.voucher_type.name.replace("_", "-").lower(), str(voucher.id) if voucher.voucher_type != VoucherType.CLOSING_STOCK - else voucher.date.strftime("%d-%b-%Y"), + else voucher.date_.strftime("%d-%b-%Y"), ], type_=voucher.voucher_type.name.replace("_", " ").title(), narration=voucher.narration, diff --git a/brewman/brewman/schemas/closing_stock.py b/brewman/brewman/schemas/closing_stock.py index bb5c30bd..25f3b01c 100644 --- a/brewman/brewman/schemas/closing_stock.py +++ b/brewman/brewman/schemas/closing_stock.py @@ -27,6 +27,27 @@ class ClosingStockItem(BaseModel): cost_centre: CostCentreLink | None = None model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True) + @field_validator("quantity", mode="before") + @classmethod + def parse_quantity(cls, value: Decimal | float) -> Decimal: + if isinstance(value, float): + return Decimal(round(value, 2)) + return round(value, 2) + + @field_validator("amount", mode="before") + @classmethod + def parse_amount(cls, value: Decimal | float) -> Decimal: + if isinstance(value, float): + return Decimal(round(value, 2)) + return round(value, 2) + + @field_validator("physical", mode="before") + @classmethod + def parse_physical(cls, value: Decimal | float) -> Decimal: + if isinstance(value, float): + return Decimal(round(value, 2)) + return round(value, 2) + class ClosingStock(BaseModel): date_: date diff --git a/brewman/brewman/schemas/voucher.py b/brewman/brewman/schemas/voucher.py index 14d41754..02617a0e 100644 --- a/brewman/brewman/schemas/voucher.py +++ b/brewman/brewman/schemas/voucher.py @@ -94,8 +94,8 @@ class Voucher(VoucherIn): return datetime.strptime(value, "%d-%b-%Y %H:%M") @field_serializer("creation_date") - def serialize_creation_date(self, value: datetime, info: FieldSerializationInfo) -> str | None: - return value.strftime("%d-%b-%Y %H:%M") + def serialize_creation_date(self, value: datetime | None, info: FieldSerializationInfo) -> str | None: + return None if value is None else value.strftime("%d-%b-%Y %H:%M") @field_validator("last_edit_date", mode="before") @classmethod @@ -107,5 +107,5 @@ class Voucher(VoucherIn): return datetime.strptime(value, "%d-%b-%Y %H:%M") @field_serializer("last_edit_date") - def serialize_last_edit_date(self, value: datetime, info: FieldSerializationInfo) -> str: - return value.strftime("%d-%b-%Y %H:%M") + def serialize_last_edit_date(self, value: datetime, info: FieldSerializationInfo) -> str | None: + return None if value is None else value.strftime("%d-%b-%Y %H:%M")