DB Normalization: Moved fractionUnits back to Product from SKU as it is better suited there.
Feature: Created the ProductSku schema for the product/sku autocomplete
This commit is contained in:
50
brewman/alembic/versions/c39eb451a683_fraction_units.py
Normal file
50
brewman/alembic/versions/c39eb451a683_fraction_units.py
Normal file
@ -0,0 +1,50 @@
|
||||
"""fraction units
|
||||
|
||||
Revision ID: c39eb451a683
|
||||
Revises: 7ba0aff64237
|
||||
Create Date: 2021-11-01 10:05:46.057929
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
from sqlalchemy import Unicode, column, select, table
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
|
||||
revision = "c39eb451a683"
|
||||
down_revision = "7ba0aff64237"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column(
|
||||
"products",
|
||||
sa.Column("fraction_units", sa.Unicode(length=255), nullable=False, server_default=""),
|
||||
)
|
||||
product = table(
|
||||
"products",
|
||||
column("id", UUID(as_uuid=True)),
|
||||
column("fraction_units", Unicode(255)),
|
||||
)
|
||||
sku = table(
|
||||
"stock_keeping_units",
|
||||
column("product_id", UUID(as_uuid=True)),
|
||||
column("fraction_units", Unicode(255)),
|
||||
)
|
||||
op.execute(
|
||||
product.update().values(fraction_units=select(sku.c.fraction_units).where(sku.c.product_id == product.c.id))
|
||||
)
|
||||
|
||||
op.drop_index("unique_true_is_default", table_name="stock_keeping_units")
|
||||
op.drop_column("stock_keeping_units", "is_default")
|
||||
op.drop_column("stock_keeping_units", "fraction_units")
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
pass
|
||||
@ -1,6 +1,7 @@
|
||||
import uuid
|
||||
|
||||
from datetime import date
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import Column, Date, ForeignKey, Numeric, select
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
@ -51,7 +52,7 @@ class Batch(Base):
|
||||
return self.quantity_remaining * self.rate * (1 + self.tax) * (1 - self.discount)
|
||||
|
||||
@classmethod
|
||||
def list(cls, q: str, include_nil: bool, date_: date, db: Session):
|
||||
def list(cls, q: str, include_nil: bool, date_: Optional[date], db: Session):
|
||||
query = (
|
||||
select(cls)
|
||||
.join(cls.sku)
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, Unicode, desc, func, select
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, Unicode
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Session, relationship
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from .meta import Base
|
||||
from .stock_keeping_unit import StockKeepingUnit
|
||||
|
||||
|
||||
class Product(Base):
|
||||
@ -14,6 +13,7 @@ class Product(Base):
|
||||
id = Column("id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
code = Column("code", Integer, unique=True)
|
||||
name = Column("name", Unicode(255), nullable=False, unique=True)
|
||||
fraction_units = Column("fraction_units", Unicode(255), nullable=False)
|
||||
product_group_id = Column(
|
||||
"product_group_id",
|
||||
UUID(as_uuid=True),
|
||||
@ -26,15 +26,15 @@ class Product(Base):
|
||||
is_purchased = Column("is_purchased", Boolean, nullable=False)
|
||||
is_sold = Column("is_sold", Boolean, nullable=False)
|
||||
|
||||
skus = relationship("StockKeepingUnit", back_populates="product", order_by=desc(StockKeepingUnit.is_default))
|
||||
skus = relationship("StockKeepingUnit", back_populates="product")
|
||||
product_group = relationship("ProductGroup", back_populates="products")
|
||||
recipes = relationship("Recipe", back_populates="product")
|
||||
account = relationship("Account", primaryjoin="Account.id==Product.account_id", back_populates="products")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
code=None,
|
||||
name=None,
|
||||
fraction_units=None,
|
||||
product_group_id=None,
|
||||
account_id=None,
|
||||
is_active=None,
|
||||
@ -45,6 +45,7 @@ class Product(Base):
|
||||
):
|
||||
self.code = code
|
||||
self.name = name
|
||||
self.fraction_units = fraction_units
|
||||
self.product_group_id = product_group_id
|
||||
self.account_id = account_id
|
||||
self.is_active = is_active
|
||||
@ -53,11 +54,6 @@ class Product(Base):
|
||||
self.id = id_
|
||||
self.is_fixture = is_fixture
|
||||
|
||||
def create(self, db: Session):
|
||||
self.code = db.execute(select(func.coalesce(func.max(Product.code), 0) + 1)).scalar_one()
|
||||
db.add(self)
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def suspense(cls):
|
||||
return uuid.UUID("aa79a643-9ddc-4790-ac7f-a41f9efb4c15")
|
||||
|
||||
@ -1,15 +1,6 @@
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
Column,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Numeric,
|
||||
Unicode,
|
||||
UniqueConstraint,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy import Column, ForeignKey, Numeric, Unicode, UniqueConstraint
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
@ -18,50 +9,37 @@ from .meta import Base
|
||||
|
||||
class StockKeepingUnit(Base):
|
||||
__tablename__ = "stock_keeping_units"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("product_id", "units"),
|
||||
Index(
|
||||
"unique_true_is_default",
|
||||
"product_id",
|
||||
unique=True,
|
||||
postgresql_where=text("is_default = true"),
|
||||
),
|
||||
)
|
||||
__table_args__ = (UniqueConstraint("product_id", "units"),)
|
||||
|
||||
id = Column("id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
product_id = Column("product_id", UUID(as_uuid=True), ForeignKey("products.id"), nullable=False)
|
||||
is_default = Column("is_default", Boolean, nullable=False)
|
||||
units = Column("units", Unicode(255), nullable=False)
|
||||
fraction = Column("fraction", Numeric(precision=15, scale=5), nullable=False)
|
||||
fraction_units = Column("fraction_units", Unicode(255), nullable=False)
|
||||
product_yield = Column("product_yield", Numeric(precision=15, scale=5), nullable=False)
|
||||
price = Column("cost_price", Numeric(precision=15, scale=2), nullable=False)
|
||||
cost_price = Column("cost_price", Numeric(precision=15, scale=2), nullable=False)
|
||||
sale_price = Column("sale_price", Numeric(precision=15, scale=2), nullable=False)
|
||||
|
||||
product = relationship("Product", back_populates="skus")
|
||||
batches = relationship("Batch", back_populates="sku")
|
||||
recipes = relationship("Recipe", back_populates="sku")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
product_id=None,
|
||||
is_default=None,
|
||||
units=None,
|
||||
fraction=None,
|
||||
fraction_units=None,
|
||||
product_yield=None,
|
||||
price=None,
|
||||
cost_price=None,
|
||||
sale_price=None,
|
||||
id_=None,
|
||||
product=None,
|
||||
):
|
||||
if product_id is not None:
|
||||
self.product_id = product_id
|
||||
self.is_default = is_default
|
||||
self.units = units
|
||||
self.fraction = fraction
|
||||
self.fraction_units = fraction_units
|
||||
self.product_yield = product_yield
|
||||
self.price = price
|
||||
self.cost_price = cost_price
|
||||
self.sale_price = sale_price
|
||||
self.id = id_
|
||||
if product is not None:
|
||||
|
||||
@ -17,10 +17,10 @@ def batch_term(
|
||||
d: str = None,
|
||||
current_user: UserToken = Depends(get_user),
|
||||
):
|
||||
date = None if not d else datetime.datetime.strptime(d, "%d-%b-%Y")
|
||||
date_ = None if not d else datetime.datetime.strptime(d, "%d-%b-%Y")
|
||||
list_ = []
|
||||
with SessionFuture() as db:
|
||||
for item in Batch.list(q, include_nil=False, date_=date, db=db):
|
||||
for item in Batch.list(q, include_nil=False, date_=date_, db=db):
|
||||
text = (
|
||||
f"{item.sku.product.name} ({item.sku.units}) {item.quantity_remaining:.2f}@"
|
||||
f"{item.rate:.2f} from {item.name.strftime('%d-%b-%Y')}"
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import uuid
|
||||
|
||||
from datetime import date, datetime
|
||||
from typing import List
|
||||
|
||||
import brewman.schemas.employee_attendance as schemas
|
||||
|
||||
@ -41,27 +42,27 @@ def employee_attendance_report(
|
||||
s: str = None,
|
||||
f: str = None,
|
||||
user: UserToken = Security(get_user, scopes=["attendance"]),
|
||||
):
|
||||
) -> schemas.EmployeeAttendance:
|
||||
with SessionFuture() as db:
|
||||
employee: Employee = db.execute(select(Employee).where(Employee.id == id_)).scalar_one()
|
||||
start_date = s if s is not None else get_start_date(request.session)
|
||||
finish_date = f if f is not None else get_finish_date(request.session)
|
||||
info = {
|
||||
"startDate": start_date,
|
||||
"finishDate": finish_date,
|
||||
"employee": {"id": employee.id, "name": employee.name},
|
||||
}
|
||||
start_date = datetime.strptime(start_date, "%d-%b-%Y").date()
|
||||
finish_date = datetime.strptime(finish_date, "%d-%b-%Y").date()
|
||||
start_date = datetime.strptime(s or get_start_date(request.session), "%d-%b-%Y").date()
|
||||
finish_date = datetime.strptime(f or get_finish_date(request.session), "%d-%b-%Y").date()
|
||||
info = schemas.EmployeeAttendance(
|
||||
startDate=start_date,
|
||||
finishDate=finish_date,
|
||||
employee=schemas.AccountLink(id=employee.id, name=employee.name),
|
||||
)
|
||||
start_date = employee.joining_date if employee.joining_date > start_date else start_date
|
||||
finish_date = (
|
||||
employee.leaving_date if not employee.is_active and employee.leaving_date < finish_date else finish_date
|
||||
)
|
||||
info["body"] = employee_attendance(employee, start_date, finish_date, db)
|
||||
info.body = employee_attendance(employee, start_date, finish_date, db)
|
||||
return info
|
||||
|
||||
|
||||
def employee_attendance(employee: Employee, start_date: date, finish_date: date, db: Session):
|
||||
def employee_attendance(
|
||||
employee: Employee, start_date: date, finish_date: date, db: Session
|
||||
) -> List[schemas.EmployeeAttendanceItem]:
|
||||
list_ = []
|
||||
for item in date_range(start_date, finish_date, inclusive=True):
|
||||
att = (
|
||||
@ -95,16 +96,10 @@ def save_employee_attendance(
|
||||
id_: uuid.UUID,
|
||||
data: schemas.EmployeeAttendance,
|
||||
user: UserToken = Security(get_user, scopes=["attendance"]),
|
||||
):
|
||||
start_date = None
|
||||
finish_date = None
|
||||
) -> schemas.EmployeeAttendance:
|
||||
with SessionFuture() as db:
|
||||
employee: Employee = db.execute(select(Employee).where(Employee.id == id_)).scalar_one()
|
||||
for item in data.body:
|
||||
if start_date is None:
|
||||
start_date = item.date_
|
||||
finish_date = item.date_
|
||||
|
||||
attendance_type = item.attendance_type.id_
|
||||
if attendance_type != 0:
|
||||
attendance = Attendance(
|
||||
@ -115,9 +110,11 @@ def save_employee_attendance(
|
||||
)
|
||||
attendance.create(db)
|
||||
db.commit()
|
||||
return {
|
||||
"startDate": start_date.strftime("%d-%b-%Y"),
|
||||
"finishDate": finish_date.strftime("%d-%b-%Y"),
|
||||
"employee": {"id": employee.id, "name": employee.name},
|
||||
"body": employee_attendance(employee, start_date, finish_date, db),
|
||||
}
|
||||
start_date = min(i.date_ for i in data.body)
|
||||
finish_date = max(i.date_ for i in data.body)
|
||||
return schemas.EmployeeAttendance(
|
||||
startDate=start_date,
|
||||
finishDate=finish_date,
|
||||
employee={"id": employee.id, "name": employee.name},
|
||||
body=employee_attendance(employee, start_date, finish_date, db),
|
||||
)
|
||||
|
||||
@ -2,7 +2,7 @@ import uuid
|
||||
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import brewman.schemas.input as schema_in
|
||||
import brewman.schemas.voucher as output
|
||||
@ -67,7 +67,7 @@ def save_route(
|
||||
)
|
||||
|
||||
|
||||
def save(data: schema_in.IssueIn, user: UserToken, db: Session) -> (Voucher, Optional[bool]):
|
||||
def save(data: schema_in.IssueIn, user: UserToken, db: Session) -> Tuple[Voucher, Optional[bool]]:
|
||||
product_accounts = (
|
||||
select(Product.account_id)
|
||||
.join(Product.skus)
|
||||
@ -198,7 +198,9 @@ def update_route(
|
||||
)
|
||||
|
||||
|
||||
def update_voucher(id_: uuid.UUID, data: schema_in.IssueIn, user: UserToken, db: Session) -> (Voucher, Optional[bool]):
|
||||
def update_voucher(
|
||||
id_: uuid.UUID, data: schema_in.IssueIn, user: UserToken, db: Session
|
||||
) -> Tuple[Voucher, Optional[bool]]:
|
||||
voucher: Voucher = db.execute(select(Voucher).where(Voucher.id == id_)).scalar_one()
|
||||
product_accounts = (
|
||||
select(Product.account_id)
|
||||
|
||||
@ -9,7 +9,7 @@ import brewman.schemas.product as schemas
|
||||
from fastapi import APIRouter, Depends, HTTPException, Security, status
|
||||
from sqlalchemy import delete, desc, func, or_, select
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.orm import Session, contains_eager
|
||||
from sqlalchemy.orm import Session, contains_eager, joinedload
|
||||
|
||||
from ..core.security import get_current_active_user as get_user
|
||||
from ..db.session import SessionFuture
|
||||
@ -19,6 +19,7 @@ from ..models.product import Product
|
||||
from ..models.rate_contract import RateContract
|
||||
from ..models.rate_contract_item import RateContractItem
|
||||
from ..models.stock_keeping_unit import StockKeepingUnit
|
||||
from ..schemas.product_sku import ProductSku
|
||||
from ..schemas.user import UserToken
|
||||
|
||||
|
||||
@ -34,30 +35,23 @@ def save(
|
||||
with SessionFuture() as db:
|
||||
item = Product(
|
||||
name=data.name,
|
||||
fraction_units=data.fraction_units,
|
||||
product_group_id=data.product_group.id_,
|
||||
account_id=Account.all_purchases(),
|
||||
is_active=data.is_active,
|
||||
is_purchased=data.is_purchased,
|
||||
is_sold=data.is_sold,
|
||||
).create(db)
|
||||
if len([s for s in data.skus if s.is_default is True]) != 1:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Exactly one default sku is needed"
|
||||
)
|
||||
if len(set([s.fraction_units for s in data.skus])) != 1:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="All skus need to have the same fraction unit",
|
||||
)
|
||||
)
|
||||
item.code = db.execute(select(func.coalesce(func.max(Product.code), 0) + 1)).scalar_one()
|
||||
db.add(item)
|
||||
|
||||
for sku in data.skus:
|
||||
db.add(
|
||||
StockKeepingUnit(
|
||||
is_default=sku.is_default,
|
||||
units=sku.units,
|
||||
fraction=round(sku.fraction, 5),
|
||||
fraction_units=sku.fraction_units,
|
||||
product_yield=round(sku.product_yield, 5),
|
||||
price=round(sku.price, 2),
|
||||
cost_price=round(sku.cost_price, 2),
|
||||
sale_price=round(sku.sale_price, 2),
|
||||
product=item,
|
||||
)
|
||||
@ -86,33 +80,21 @@ def update_route(
|
||||
detail=f"{item.name} is a fixture and cannot be edited or deleted.",
|
||||
)
|
||||
item.name = data.name
|
||||
item.fraction_units = data.fraction_units
|
||||
item.product_group_id = data.product_group.id_
|
||||
item.account_id = Account.all_purchases()
|
||||
item.is_active = data.is_active
|
||||
item.is_purchased = data.is_purchased
|
||||
item.is_sold = data.is_sold
|
||||
if len([sku for sku in data.skus if sku.is_default is True]) != 1:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_423_LOCKED,
|
||||
detail="There needs to be exactly 1 default SKU",
|
||||
)
|
||||
if len(set([s.fraction_units for s in data.skus])) != 1:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="All skus need to have the same fraction unit",
|
||||
)
|
||||
default_sku = next(s.units for s in data.skus if s.is_default is True)
|
||||
for i in range(len(item.skus), 0, -1):
|
||||
sku = item.skus[i - 1]
|
||||
index = next((idx for (idx, d) in enumerate(data.skus) if d.id_ == sku.id), None)
|
||||
if index is not None:
|
||||
new_sku = data.skus.pop(index)
|
||||
sku.is_default = False
|
||||
sku.units = new_sku.units
|
||||
sku.fraction = round(new_sku.fraction, 5)
|
||||
sku.fraction_units = new_sku.fraction_units
|
||||
sku.product_yield = round(new_sku.product_yield, 5)
|
||||
sku.price = round(new_sku.price, 2)
|
||||
sku.cost_price = round(new_sku.cost_price, 2)
|
||||
sku.sale_price = round(new_sku.sale_price, 2)
|
||||
else:
|
||||
count: Decimal = db.execute(select(func.count()).where(Batch.sku_id == sku.id)).scalar_one()
|
||||
@ -125,20 +107,15 @@ def update_route(
|
||||
db.delete(sku)
|
||||
for sku in data.skus:
|
||||
new_sku = StockKeepingUnit(
|
||||
is_default=False,
|
||||
units=sku.units,
|
||||
fraction=round(sku.fraction, 5),
|
||||
fraction_units=sku.fraction_units,
|
||||
product_yield=round(sku.product_yield, 5),
|
||||
price=round(sku.price, 2),
|
||||
cost_price=round(sku.cost_price, 2),
|
||||
sale_price=round(sku.sale_price, 2),
|
||||
product=item,
|
||||
)
|
||||
db.add(new_sku)
|
||||
item.skus.append(new_sku)
|
||||
db.flush()
|
||||
default_sku = next(s for s in item.skus if s.units == default_sku)
|
||||
default_sku.is_default = True
|
||||
db.commit()
|
||||
return product_info(item)
|
||||
except SQLAlchemyError as e:
|
||||
@ -187,26 +164,33 @@ def show_list(user: UserToken = Depends(get_user)) -> List[schemas.Product]:
|
||||
product_info(item)
|
||||
for item in db.execute(
|
||||
select(Product)
|
||||
.join(Product.product_group)
|
||||
.join(Product.skus)
|
||||
.order_by(desc(Product.is_active))
|
||||
.order_by(Product.product_group_id)
|
||||
.order_by(Product.name)
|
||||
.options(
|
||||
joinedload(Product.skus, innerjoin=True),
|
||||
joinedload(Product.product_group, innerjoin=True),
|
||||
contains_eager(Product.skus),
|
||||
contains_eager(Product.product_group),
|
||||
)
|
||||
)
|
||||
.unique()
|
||||
.scalars()
|
||||
.all()
|
||||
]
|
||||
|
||||
|
||||
@router.get("/query")
|
||||
async def show_term(
|
||||
@router.get("/q-sku", response_model=List[ProductSku])
|
||||
async def show_term_sku(
|
||||
q: str = None, # Query
|
||||
a: bool = None, # Active
|
||||
p: bool = None, # Is Purchased?
|
||||
e: bool = False, # Extended
|
||||
s: bool = False, # List separate SKUs
|
||||
v: Optional[uuid.UUID] = None, # Vendor
|
||||
d: Optional[str] = None, # Date
|
||||
current_user: UserToken = Depends(get_user),
|
||||
):
|
||||
) -> List[ProductSku]:
|
||||
list_ = []
|
||||
with SessionFuture() as db:
|
||||
query_ = select(Product).join(Product.skus).options(contains_eager(Product.skus))
|
||||
@ -223,33 +207,59 @@ async def show_term(
|
||||
query_ = query_.order_by(Product.name)
|
||||
|
||||
for item in db.execute(query_).unique().scalars().all():
|
||||
skus = item.skus if s else item.skus[:1]
|
||||
for sku in skus:
|
||||
for sku in item.skus: # type: StockKeepingUnit
|
||||
rc_price = get_rc_price(item.id, d, v, db)
|
||||
list_.append(
|
||||
{
|
||||
"id": sku.id if s else item.id,
|
||||
"name": item.name,
|
||||
"price": sku.price if rc_price is None else rc_price,
|
||||
"units": sku.units if s else "",
|
||||
"fraction": sku.fraction,
|
||||
"fractionUnits": sku.fraction_units,
|
||||
"productYield": sku.product_yield,
|
||||
"isSold": item.is_sold,
|
||||
"salePrice": sku.sale_price,
|
||||
"isRateContracted": False if rc_price is None else True,
|
||||
}
|
||||
if e
|
||||
else {
|
||||
"id": sku.id if s else item.id,
|
||||
"name": f"{item.name} ({sku.units})" if s else item.name,
|
||||
"price": sku.price if rc_price is None else rc_price,
|
||||
"isRateContracted": False if rc_price is None else True,
|
||||
}
|
||||
ProductSku(
|
||||
id=sku.id,
|
||||
name=f"{item.name} ({sku.units})",
|
||||
fractionUnits=item.fraction_units,
|
||||
costPrice=sku.cost_price if rc_price is None else rc_price,
|
||||
salePrice=sku.sale_price,
|
||||
fraction=sku.fraction,
|
||||
productYield=sku.product_yield,
|
||||
isRateContracted=False if rc_price is None else True,
|
||||
)
|
||||
)
|
||||
return list_
|
||||
|
||||
|
||||
@router.get("/q-product", response_model=List[ProductSku])
|
||||
async def show_term_product(
|
||||
q: str = None, # Query
|
||||
a: bool = None, # Active
|
||||
p: bool = None, # Is Purchased?
|
||||
current_user: UserToken = Depends(get_user),
|
||||
) -> List[ProductSku]:
|
||||
list_ = []
|
||||
with SessionFuture() as db:
|
||||
query_ = select(Product)
|
||||
if a is not None:
|
||||
query_ = query_.filter(Product.is_active == a)
|
||||
if p is not None:
|
||||
query_ = query_.filter(Product.is_purchased == p)
|
||||
if q is not None:
|
||||
for item in q.split():
|
||||
if item.strip() != "":
|
||||
query_ = query_.filter(Product.name.ilike(f"%{item}%"))
|
||||
query_ = query_.order_by(Product.name)
|
||||
|
||||
for item in db.execute(query_).unique().scalars().all():
|
||||
list_.append(
|
||||
ProductSku(
|
||||
id=item.id,
|
||||
name=item.name,
|
||||
fractionUnits=item.fraction_units,
|
||||
costPrice=0,
|
||||
salePrice=0,
|
||||
fraction=1,
|
||||
productYield=1,
|
||||
isRateContracted=False,
|
||||
)
|
||||
)
|
||||
return list_
|
||||
|
||||
|
||||
@router.get("/{id_}", response_model=schemas.Product)
|
||||
def show_id(
|
||||
id_: uuid.UUID,
|
||||
@ -261,19 +271,18 @@ def show_id(
|
||||
|
||||
|
||||
def product_info(product: Product) -> schemas.Product:
|
||||
product = schemas.Product(
|
||||
return schemas.Product(
|
||||
id=product.id,
|
||||
code=product.code,
|
||||
name=product.name,
|
||||
fractionUnits=product.fraction_units,
|
||||
skus=[
|
||||
schemas.StockKeepingUnit(
|
||||
id=sku.id,
|
||||
isDefault=sku.is_default,
|
||||
units=sku.units,
|
||||
fraction=sku.fraction,
|
||||
fractionUnits=sku.fraction_units,
|
||||
productYield=sku.product_yield,
|
||||
price=sku.price,
|
||||
costPrice=sku.cost_price,
|
||||
salePrice=sku.sale_price,
|
||||
)
|
||||
for sku in product.skus
|
||||
@ -284,12 +293,12 @@ def product_info(product: Product) -> schemas.Product:
|
||||
isSold=product.is_sold,
|
||||
productGroup=schemas.ProductGroupLink(id=product.product_group.id, name=product.product_group.name),
|
||||
)
|
||||
return product
|
||||
|
||||
|
||||
def product_blank() -> schemas.ProductBlank:
|
||||
return schemas.ProductBlank(
|
||||
name="",
|
||||
fractionUnits="",
|
||||
skus=[],
|
||||
isActive=True,
|
||||
isPurchased=True,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from typing import List
|
||||
from typing import List, Tuple
|
||||
|
||||
import brewman.schemas.profit_loss as schemas
|
||||
|
||||
@ -59,7 +59,7 @@ def report_data(
|
||||
|
||||
def build_profit_loss(
|
||||
start_date: date, finish_date: date, db: Session
|
||||
) -> (List[schemas.ProfitLossItem], schemas.ProfitLossItem):
|
||||
) -> Tuple[List[schemas.ProfitLossItem], schemas.ProfitLossItem]:
|
||||
profit_type_list = (
|
||||
db.execute(select(AccountType.id).where(AccountType.balance_sheet == False)).scalars().all() # noqa: E712
|
||||
)
|
||||
|
||||
@ -19,6 +19,7 @@ class ProductLink(BaseModel):
|
||||
|
||||
class ProductIn(BaseModel):
|
||||
name: str = Field(..., min_length=1)
|
||||
fraction_units: str = Field(..., min_length=1)
|
||||
skus: List[StockKeepingUnit]
|
||||
product_group: ProductGroupLink = Field(...)
|
||||
is_active: bool
|
||||
@ -26,7 +27,6 @@ class ProductIn(BaseModel):
|
||||
is_sold: bool
|
||||
|
||||
class Config:
|
||||
fields = {"id_": "id"}
|
||||
anystr_strip_whitespace = True
|
||||
alias_generator = to_camel
|
||||
|
||||
@ -39,6 +39,7 @@ class Product(ProductIn):
|
||||
|
||||
class ProductBlank(ProductIn):
|
||||
name: str
|
||||
fraction_units: str
|
||||
skus: List[StockKeepingUnit]
|
||||
product_group: Optional[ProductGroupLink] # type: ignore[assignment]
|
||||
is_fixture: bool
|
||||
|
||||
20
brewman/brewman/schemas/product_sku.py
Normal file
20
brewman/brewman/schemas/product_sku.py
Normal file
@ -0,0 +1,20 @@
|
||||
import uuid
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from brewman.schemas import to_camel
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ProductSku(BaseModel):
|
||||
id_: uuid.UUID = Field(...)
|
||||
name: str
|
||||
fraction_units: str
|
||||
cost_price: Decimal
|
||||
sale_price: Decimal
|
||||
fraction: Decimal
|
||||
product_yield: Decimal
|
||||
is_rate_contracted: bool
|
||||
|
||||
class Config:
|
||||
alias_generator = to_camel
|
||||
@ -10,12 +10,10 @@ from . import to_camel
|
||||
|
||||
class StockKeepingUnit(BaseModel):
|
||||
id_: Optional[uuid.UUID]
|
||||
is_default: bool
|
||||
units: str = Field(..., min_length=1)
|
||||
fraction: Decimal = Field(ge=1, default=1)
|
||||
fraction_units: str
|
||||
product_yield: Decimal = Field(gt=0, le=1, default=1)
|
||||
price: Decimal = Field(ge=0, multiple_of=0.01, default=0)
|
||||
cost_price: Decimal = Field(ge=0, multiple_of=0.01, default=0)
|
||||
sale_price: Decimal = Field(ge=0, multiple_of=0.01, default=0)
|
||||
|
||||
class Config:
|
||||
|
||||
Reference in New Issue
Block a user