Fix: Repint was creating new bills. Rounding caused it to think that the amounts had changed.

Made the inventory amount expression round to even and the inventory schema to validate to round the inputs
This commit is contained in:
Amritanshu Agrawal 2023-07-26 13:00:01 +05:30
parent 26310dfc42
commit 96e54e2a0a
5 changed files with 58 additions and 56 deletions

View File

@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, List
from barker.models.product import Product from barker.models.product import Product
from sqlalchemy import ( from sqlalchemy import (
Boolean, Boolean,
ColumnElement,
ForeignKey, ForeignKey,
Integer, Integer,
Numeric, Numeric,
@ -13,6 +14,7 @@ from sqlalchemy import (
case, case,
func, func,
text, text,
type_coerce,
) )
from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.hybrid import hybrid_property
@ -86,37 +88,44 @@ class Inventory:
self.tax = tax self.tax = tax
@hybrid_property @hybrid_property
def effective_price(self): def effective_price(self) -> Decimal:
return 0 if self.is_happy_hour else self.price return 0 if self.is_happy_hour else self.price
@effective_price.inplace.expression @effective_price.inplace.expression
@classmethod @classmethod
def _effective_price_expression(cls): def _effective_price_expression(cls) -> ColumnElement[Decimal]:
return case((cls.is_happy_hour == True, 0), else_=cls.price) # noqa: E712 return type_coerce(
case((cls.is_happy_hour == True, 0), else_=cls.price), Numeric(precision=15, scale=2) # noqa: E712
).label("effective_price")
@hybrid_property @hybrid_property
def net(self): def net(self) -> Decimal:
return self.effective_price * self.quantity * (1 - self.discount) return self.effective_price * self.quantity * (1 - self.discount)
@net.inplace.expression @net.inplace.expression
@classmethod @classmethod
def _net_expression(cls): def _net_expression(cls) -> ColumnElement[Decimal]:
return cls.effective_price * cls.quantity * (1 - cls.discount) return type_coerce(
cls.effective_price * cls.quantity * (1 - cls.discount), Numeric(precision=15, scale=2)
).label("net")
@hybrid_property @hybrid_property
def tax_amount(self): def tax_amount(self) -> Decimal:
return self.net * self.tax_rate return self.net * self.tax_rate
@tax_amount.inplace.expression @tax_amount.inplace.expression
@classmethod @classmethod
def _tax_amount_expression(cls): def _tax_amount_expression(cls) -> ColumnElement[Decimal]:
return cls.net * cls.tax_rate return type_coerce(cls.net * cls.tax_rate, Numeric(precision=15, scale=2)).label("tax_amount")
@hybrid_property @hybrid_property
def amount(self): def amount(self) -> Decimal:
return round(Decimal(self.net * (1 + self.tax_rate)), 2) return round(Decimal(self.net * (1 + self.tax_rate)), 2)
@amount.inplace.expression @amount.inplace.expression
@classmethod @classmethod
def _amount_expression(cls): def _amount_expression(cls) -> ColumnElement[Decimal]:
return func.round(cls.net * (1 + cls.tax_rate), 2) # func.round(cls.net * (1 + cls.tax_rate), 2)
return type_coerce(
func.round(cls.net * (1 + cls.tax_rate) / 0.02, 0) * 0.02, Numeric(precision=15, scale=2)
).label("amount")

View File

@ -39,8 +39,8 @@ class User:
return self._password return self._password
@password.inplace.setter @password.inplace.setter
def _password_setter(self, password): def _password_setter(self, value: str) -> None:
self._password = encrypt(password) self._password = encrypt(value)
def __init__(self, name=None, password=None, locked_out=None, id_=None): def __init__(self, name=None, password=None, locked_out=None, id_=None):
self.name = name self.name = name

View File

@ -1,9 +1,6 @@
import uuid import uuid
from datetime import date, datetime from pydantic import BaseModel, ConfigDict, Field
from decimal import Decimal
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from . import to_camel from . import to_camel
@ -26,43 +23,6 @@ class Account(AccountIn):
is_fixture: bool is_fixture: bool
class EmployeeIn(AccountBase):
designation: str
salary: int = Field(ge=0)
points: Decimal = Field(ge=0, lt=1000)
joining_date: date
leaving_date: date | None = None
@field_validator("joining_date", mode="before")
@classmethod
def parse_joining_date(cls, value):
return datetime.strptime(value, "%d-%b-%Y").date()
@field_validator("leaving_date", mode="before")
@classmethod
def parse_leaving_date(cls, value):
if value is None or value == "":
return None
else:
return datetime.strptime(value, "%d-%b-%Y").date()
@model_validator(mode="after")
def leaving_date_more_than_joining_date(self) -> "EmployeeIn":
if self.is_active:
self.leaving_date = None
if (not self.is_active) and (self.leaving_date is None):
raise ValueError("Need leaving date for employee")
if self.leaving_date < self.joining_date:
raise ValueError("Leaving Date cannot be less than Joining Date")
return self
class Employee(EmployeeIn):
id_: uuid.UUID
code: int
is_fixture: bool
class DbSetting(BaseModel): class DbSetting(BaseModel):
id_: uuid.UUID id_: uuid.UUID
name: str name: str

View File

@ -2,7 +2,7 @@ import uuid
from decimal import Decimal from decimal import Decimal
from pydantic import BaseModel, ConfigDict, Field, model_validator from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from . import to_camel from . import to_camel
from .customer import CustomerLink from .customer import CustomerLink
@ -25,6 +25,34 @@ class Inventory(BaseModel):
amount: Decimal | None = None amount: Decimal | None = None
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True) model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
@field_validator("quantity")
@classmethod
def _quantity(cls, value: Decimal | None) -> Decimal:
if value is None:
return Decimal(0)
return round(value, 2)
@field_validator("price")
@classmethod
def _price(cls, value: Decimal | None) -> Decimal:
if value is None:
return Decimal(0)
return round(value, 2)
@field_validator("tax_rate")
@classmethod
def _tax_rate(cls, value: Decimal | None) -> Decimal:
if value is None:
return Decimal(0)
return round(value, 5)
@field_validator("discount")
@classmethod
def _discount(cls, value: Decimal | None) -> Decimal:
if value is None:
return Decimal(0)
return round(value, 5)
@model_validator(mode="after") @model_validator(mode="after")
def calculate_amount(self) -> "Inventory": def calculate_amount(self) -> "Inventory":
self.amount = round( self.amount = round(

View File

@ -39,6 +39,11 @@ tomli = "^2.0.1"
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
[tool.ruff]
line-length = 120
# Assume Python 3.11.
target-version = "py311"
[tool.isort] [tool.isort]
profile = "black" profile = "black"
atomic = true atomic = true