from app.api.v1.module_system.auth.schema import AuthSchema from app.core.exceptions import CustomException from app.core.logger import log from .crud import PointsCRUD, PointsRecordCRUD from .enums import PointsChangeTypeEnum, BusinessTypeEnum from .schema import PointsCreateSchema, PointsRecordOutSchema, PointsUpdateSchema, PointsOutSchema, PointsChangeSchema class PointsService: """积分服务层""" @classmethod async def create_points_service( cls, auth: AuthSchema, data: PointsCreateSchema ) -> PointsOutSchema: """ 创建积分账户 """ crud = PointsCRUD(auth) # 检查租户是否已存在积分账户 existing_points = await crud.get_by_tenant_id(data.tenant_id) if existing_points: raise CustomException(msg="租户已存在积分账户") points = await crud.create(data.model_dump(exclude_unset=True)) return PointsOutSchema.model_validate(points) @classmethod async def get_points_service(cls, auth: AuthSchema) -> PointsOutSchema: """ 查询租户积分 """ crud = PointsCRUD(auth) points = await crud.get_by_tenant_id(auth.tenant_id) if not points: raise CustomException(msg="积分账户不存在") return PointsOutSchema.model_validate(points) @classmethod async def update_points_service( cls, auth: AuthSchema, data: PointsUpdateSchema ) -> PointsOutSchema: """ 更新积分 """ change_data = PointsChangeSchema( points=data.points, change_type=PointsChangeTypeEnum.ADD.value, business_type=BusinessTypeEnum.RECHARGE.value, remark="后台充值" ) return await cls.change_points_service(auth, change_data) @classmethod async def add_points_service( cls, auth: AuthSchema, data: PointsChangeSchema ) -> PointsOutSchema: """ 增加积分 """ crud = PointsCRUD(auth) points = await crud.get_by_tenant_id(auth.tenant_id) if not points: raise CustomException(msg="积分账户不存在") # 计算新的积分余额 new_points = points.points + data.points # 更新积分 points.points = new_points await auth.db.flush() await auth.db.refresh(points) # 记录积分变动 record_crud = PointsRecordCRUD(auth) await record_crud.create({ "tenant_id": auth.tenant_id, "enterprise_id": data.enterprise_id, "employee_id": data.employee_id, "points": data.points, "balance": new_points, "type": PointsChangeTypeEnum.ADD.value, "business_type": data.business_type, "business_id": data.business_id, "remark": data.remark }) log.info(f"增加积分成功: 租户={auth.tenant_id}, 增加={data.points}, 余额={new_points}") return PointsOutSchema.model_validate(points) @classmethod async def deduct_points_service( cls, auth: AuthSchema, data: PointsChangeSchema ) -> PointsOutSchema: """ 扣除积分 """ crud = PointsCRUD(auth) points = await crud.get_by_tenant_id(auth.tenant_id) if not points: raise CustomException(msg="积分账户不存在") if points.points < data.points: raise CustomException(msg="积分不足") # 计算新的积分余额 new_points = points.points - data.points # 更新积分 points.points = new_points await auth.db.flush() await auth.db.refresh(points) # 记录积分变动 record_crud = PointsRecordCRUD(auth) await record_crud.create({ "tenant_id": auth.tenant_id, "enterprise_id": data.enterprise_id, "employee_id": data.employee_id, "points": data.points, "balance": new_points, "type": PointsChangeTypeEnum.DEDUCT.value, "business_type": data.business_type, "business_id": data.business_id, "remark": data.remark }) log.info(f"扣除积分成功: 租户={auth.tenant_id}, 扣除={data.points}, 余额={new_points}") return PointsOutSchema.model_validate(points) @classmethod async def check_points_service(cls, auth: AuthSchema, change_data: PointsChangeSchema) -> bool: """ 检查积分余额 """ crud = PointsCRUD(auth) points = await crud.get_by_tenant_id(auth.tenant_id) return points.points >= change_data.points @classmethod async def change_points_service( cls, auth: AuthSchema, data: PointsChangeSchema ) -> PointsOutSchema: """ 通用积分变动 """ if data.change_type == PointsChangeTypeEnum.ADD.value: return await cls.add_points_service(auth, data) elif data.change_type == PointsChangeTypeEnum.DEDUCT.value: return await cls.deduct_points_service(auth, data) else: raise CustomException(msg="无效的积分变动类型") @classmethod async def list_record_service( cls, auth: AuthSchema, page_no: int = 1, page_size: int = 20, search: dict | None = None, ) -> dict: """ 查询积分记录 """ crud = PointsRecordCRUD(auth) offset = (page_no - 1) * page_size return await crud.page( offset=offset, limit=page_size, order_by=[{"id": "desc"}], search=search or {}, out_schema=PointsRecordOutSchema )