dingdanquanliucheng/backend/app/services/product_service.py

374 lines
18 KiB
Python
Raw Normal View History

from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from backend.app.core.error_codes import ErrorCode
from backend.app.core.exceptions import AppException
from backend.app.repositories.product_repository import ProductRepository
2026-05-14 13:51:06 +08:00
class ProductService:
def __init__(self) -> None:
self.repository = ProductRepository()
def list_categories(self, session: Session | None = None, filters: dict | None = None) -> dict:
if session is not None:
try:
categories = self.repository.list_categories(session, filters or {})
return {
"total": len(categories),
"page_no": 1,
"page_size": len(categories) or 20,
"list": [
{
"category_id": category.id,
"category_name": category.category_name,
"category_code": category.category_code,
"sort_no": category.sort_no,
"status": category.status,
"remark": category.remark,
}
for category in categories
],
}
except SQLAlchemyError:
pass
2026-05-14 13:51:06 +08:00
return {
"total": 2,
"page_no": 1,
"page_size": 20,
"list": [
{"category_id": 1, "category_name": "工业品", "category_code": "industry", "status": 1},
{"category_id": 2, "category_name": "日用品", "category_code": "daily", "status": 1},
],
}
def create_category(self, payload: dict, session: Session | None = None) -> dict:
if session is not None:
try:
if not payload["category_name"].strip():
raise AppException(code=ErrorCode.PARAM_ERROR, message="分类名称不能为空", status_code=400)
if not payload["category_code"].strip():
raise AppException(code=ErrorCode.PARAM_ERROR, message="分类编码不能为空", status_code=400)
existed = self.repository.get_category_by_code(session, payload["category_code"])
if existed is not None:
raise AppException(code=ErrorCode.DUPLICATE, message="分类编码已存在", status_code=400)
category = self.repository.create_category(session, payload)
session.commit()
return {
"category_id": category.id,
"category_name": category.category_name,
"category_code": category.category_code,
"status": category.status,
}
except AppException:
session.rollback()
raise
except SQLAlchemyError:
session.rollback()
2026-05-14 13:51:06 +08:00
return {
"category_id": 3,
"category_name": payload["category_name"],
"category_code": payload["category_code"],
"status": payload.get("status", 1),
2026-05-14 13:51:06 +08:00
}
def update_category(self, category_id: int, payload: dict, session: Session | None = None) -> dict:
if session is not None:
try:
if not payload["category_name"].strip():
raise AppException(code=ErrorCode.PARAM_ERROR, message="分类名称不能为空", status_code=400)
if not payload["category_code"].strip():
raise AppException(code=ErrorCode.PARAM_ERROR, message="分类编码不能为空", status_code=400)
category = self.repository.get_category(session, category_id)
if category is None:
raise AppException(code=ErrorCode.NOT_FOUND, message="产品分类不存在", status_code=404)
existed = self.repository.get_category_by_code(session, payload["category_code"])
if existed is not None and existed.id != category_id:
raise AppException(code=ErrorCode.DUPLICATE, message="分类编码已存在", status_code=400)
self.repository.update_category(session, category, payload)
session.commit()
return {"category_id": category_id, "updated": True}
except AppException:
session.rollback()
raise
except SQLAlchemyError:
session.rollback()
2026-05-14 13:51:06 +08:00
return {"category_id": category_id, "updated": True}
2026-05-21 14:59:38 +08:00
def _group_products(self, products: list, categories: dict[int, object]) -> list[dict]:
grouped: dict[str, dict] = {}
for product in products:
key = product.product_name
category_name = categories[product.category_id].category_name if product.category_id in categories else (product.category or "")
entry = grouped.setdefault(
key,
{
"product_name": product.product_name,
"product_id": product.id,
"category_id": product.category_id,
"category_name": category_name,
"status": product.status,
"remark": product.remark,
"specifications": [],
},
)
entry["specifications"].append(
{
"product_id": product.id,
"specification": product.specification,
"unit": product.unit,
"cost_price": float(product.cost_price or 0),
"sale_price": float(product.sale_price or 0),
"status": product.status,
"remark": product.remark,
"is_default": bool(getattr(product, "is_default", 0)),
}
)
for entry in grouped.values():
if entry["specifications"] and not any(spec["is_default"] for spec in entry["specifications"]):
entry["specifications"][0]["is_default"] = True
return list(grouped.values())
def list_products(self, filters: dict | None = None, session: Session | None = None) -> dict:
if session is not None:
try:
products = self.repository.list_products(session, filters or {})
2026-05-21 14:59:38 +08:00
categories = {category.id: category for category in self.repository.list_categories(session, {})}
grouped_list = self._group_products(products, categories)
return {
2026-05-21 14:59:38 +08:00
"total": len(grouped_list),
"page_no": 1,
2026-05-21 14:59:38 +08:00
"page_size": len(grouped_list) or 20,
"list": grouped_list,
}
except SQLAlchemyError:
pass
product_list = [
{
"product_id": 2001,
"product_name": "演示产品A",
"specification": "10kg",
"unit": "",
"category_id": 1,
"category_name": "工业品",
"cost_price": 60,
"sale_price": 100,
"status": 1,
},
{
"product_id": 2002,
"product_name": "演示产品B",
"specification": "20kg",
"unit": "",
"category_id": 1,
"category_name": "工业品",
"cost_price": 120,
"sale_price": 180,
"status": 1,
},
]
if filters and filters.get("product_name"):
product_list = [item for item in product_list if filters["product_name"] in item["product_name"]]
2026-05-14 13:51:06 +08:00
return {
"total": len(product_list),
2026-05-14 13:51:06 +08:00
"page_no": 1,
"page_size": 20,
"list": product_list,
2026-05-14 13:51:06 +08:00
}
2026-05-21 14:59:38 +08:00
def update_product(self, product_id: int, payload: dict, session: Session | None = None) -> dict:
if session is not None:
try:
2026-05-21 14:59:38 +08:00
product = self.repository.get_product(session, product_id)
if product is None:
raise AppException(code=ErrorCode.NOT_FOUND, message="产品主档不存在", status_code=404)
if not payload["product_name"].strip():
raise AppException(code=ErrorCode.PARAM_ERROR, message="产品名称不能为空", status_code=400)
2026-05-21 14:59:38 +08:00
category = None
if payload.get("category_id") is not None:
category = self.repository.get_category(session, payload["category_id"])
if category is None or category.status != 1:
raise AppException(code=ErrorCode.NOT_FOUND, message="产品分类不存在或已停用", status_code=404)
product.product_name = payload["product_name"]
product.category_id = payload.get("category_id")
product.category = category.category_name if category else None
product.status = payload.get("status", product.status)
product.remark = payload.get("remark", product.remark)
session.add(product)
session.commit()
return {"product_id": product.id, "product_name": product.product_name, "updated": True}
except AppException:
session.rollback()
raise
except SQLAlchemyError:
session.rollback()
return {"product_id": product_id, **payload}
def update_product_specification(self, product_id: int, payload: dict, session: Session | None = None) -> dict:
if session is not None:
try:
product = self.repository.get_product(session, product_id)
if product is None:
raise AppException(code=ErrorCode.NOT_FOUND, message="产品规格不存在", status_code=404)
if not payload["specification"].strip():
raise AppException(code=ErrorCode.PARAM_ERROR, message="产品规格不能为空", status_code=400)
if not payload["unit"].strip():
raise AppException(code=ErrorCode.PARAM_ERROR, message="产品单位不能为空", status_code=400)
2026-05-21 14:59:38 +08:00
existed = self.repository.get_product_by_name_and_specification(session, product.product_name, payload["specification"])
if existed is not None and existed.id != product_id:
raise AppException(code=ErrorCode.DUPLICATE, message="同一产品下的规格不能重复", status_code=400)
category = None
if payload.get("category_id") is not None:
category = self.repository.get_category(session, payload["category_id"])
if category is None or category.status != 1:
raise AppException(code=ErrorCode.NOT_FOUND, message="产品分类不存在或已停用", status_code=404)
product.specification = payload["specification"]
product.unit = payload["unit"]
product.category_id = payload.get("category_id")
product.category = category.category_name if category else product.category
product.cost_price = payload.get("cost_price", product.cost_price)
product.sale_price = payload.get("sale_price", product.sale_price)
product.status = payload.get("status", product.status)
product.remark = payload.get("remark", product.remark)
session.add(product)
session.commit()
return {"product_id": product.id, "product_name": product.product_name, "updated": True}
except AppException:
session.rollback()
raise
except SQLAlchemyError:
session.rollback()
return {"product_id": product_id, **payload}
2026-05-21 14:59:38 +08:00
def set_default_specification(self, product_id: int, session: Session | None = None) -> dict:
if session is not None:
try:
product = self.repository.get_product(session, product_id)
if product is None:
raise AppException(code=ErrorCode.NOT_FOUND, message="产品规格不存在", status_code=404)
siblings = self.repository.list_products(session, {"product_name": product.product_name})
for sibling in siblings:
sibling.is_default = 1 if sibling.id == product.id else 0
session.add(sibling)
session.flush()
session.commit()
return {"product_id": product.id, "product_name": product.product_name, "is_default": True}
except AppException:
session.rollback()
raise
except SQLAlchemyError:
session.rollback()
return {"product_id": product_id, "is_default": True}
def create_product(self, payload: dict, session: Session | None = None) -> dict:
if session is not None:
try:
if not payload["product_name"].strip():
raise AppException(code=ErrorCode.PARAM_ERROR, message="产品名称不能为空", status_code=400)
specifications = payload.get("specifications") or []
if not specifications:
raise AppException(code=ErrorCode.PARAM_ERROR, message="请至少填写一个规格明细", status_code=400)
category = None
if payload.get("category_id") is not None:
category = self.repository.get_category(session, payload["category_id"])
if category is None or category.status != 1:
raise AppException(code=ErrorCode.NOT_FOUND, message="产品分类不存在或已停用", status_code=404)
2026-05-21 14:59:38 +08:00
created_specs = []
for spec in specifications:
if not spec["specification"].strip():
raise AppException(code=ErrorCode.PARAM_ERROR, message="产品规格不能为空", status_code=400)
if not spec["unit"].strip():
raise AppException(code=ErrorCode.PARAM_ERROR, message="产品单位不能为空", status_code=400)
existed = self.repository.get_product_by_name_and_specification(
session,
payload["product_name"],
spec["specification"],
)
if existed is not None:
raise AppException(code=ErrorCode.DUPLICATE, message="同一产品下的规格不能重复", status_code=400)
product = self.repository.create_product(
session,
{
"product_name": payload["product_name"],
"specification": spec["specification"],
"unit": spec["unit"],
"category_id": spec.get("category_id") or payload.get("category_id"),
"category": category.category_name if category else None,
"cost_price": spec.get("cost_price", 0),
"sale_price": spec.get("sale_price", 0),
"status": spec.get("status", payload.get("status", 1)),
"remark": spec.get("remark") or payload.get("remark"),
},
)
created_specs.append(product)
session.commit()
return {
2026-05-21 14:59:38 +08:00
"product_name": payload["product_name"],
"product_id": created_specs[0].id if created_specs else None,
"specifications": [
{
"product_id": item.id,
"specification": item.specification,
"unit": item.unit,
"cost_price": float(item.cost_price or 0),
"sale_price": float(item.sale_price or 0),
"status": item.status,
"remark": item.remark,
"is_default": index == 0,
}
for index, item in enumerate(created_specs)
],
}
except AppException:
session.rollback()
raise
except SQLAlchemyError:
session.rollback()
2026-05-14 13:51:06 +08:00
return {
"product_id": 2003,
"product_name": payload["product_name"],
2026-05-21 14:59:38 +08:00
"specifications": payload.get("specifications", []),
2026-05-14 13:51:06 +08:00
}
def get_product(self, product_id: int, session: Session | None = None) -> dict:
if session is not None:
try:
product = self.repository.get_product(session, product_id)
if product is not None:
2026-05-21 14:59:38 +08:00
products = self.repository.list_products(session, {"product_name": product.product_name})
categories = {category.id: category for category in self.repository.list_categories(session, {})}
grouped = self._group_products(products, categories)
if grouped:
return grouped[0]
except SQLAlchemyError:
pass
2026-05-14 13:51:06 +08:00
return {
"product_name": "演示产品A",
2026-05-14 13:51:06 +08:00
"category_name": "工业品",
2026-05-21 14:59:38 +08:00
"specifications": [
{
"product_id": product_id,
"specification": "10kg",
"unit": "",
"cost_price": 60,
"sale_price": 100,
"status": 1,
"remark": "-",
"is_default": True,
}
],
2026-05-14 13:51:06 +08:00
}
product_service = ProductService()