首次提交by MimoCode
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
from typing import Optional, List
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.repositories.audit_log import AuditLogRepository
|
||||
|
||||
|
||||
class AuditService:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
self.repo = AuditLogRepository(db)
|
||||
|
||||
async def log_action(
|
||||
self,
|
||||
medicine_id: int,
|
||||
batch_id: Optional[int],
|
||||
user_id: Optional[int],
|
||||
action: str,
|
||||
quantity_change: int,
|
||||
quantity_after: int,
|
||||
remark: Optional[str] = None
|
||||
):
|
||||
data = {
|
||||
"medicine_id": medicine_id,
|
||||
"batch_id": batch_id,
|
||||
"user_id": user_id,
|
||||
"action": action,
|
||||
"quantity_change": quantity_change,
|
||||
"quantity_after": quantity_after,
|
||||
"remark": remark
|
||||
}
|
||||
return await self.repo.create(data)
|
||||
|
||||
async def get_audit_logs(
|
||||
self,
|
||||
medicine_id: Optional[int] = None,
|
||||
user_id: Optional[int] = None,
|
||||
action: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20
|
||||
) -> List:
|
||||
return await self.repo.get_list(
|
||||
medicine_id=medicine_id,
|
||||
user_id=user_id,
|
||||
action=action,
|
||||
page=page,
|
||||
page_size=page_size
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
from typing import Optional
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.user import User
|
||||
from app.services.user import UserService
|
||||
from app.core.security import create_access_token
|
||||
|
||||
|
||||
class AuthService:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
self.user_service = UserService(db)
|
||||
|
||||
async def login(self, username: str, password: str) -> Optional[dict]:
|
||||
user = await self.user_service.authenticate(username, password)
|
||||
if not user:
|
||||
return None
|
||||
|
||||
access_token = create_access_token(data={"sub": str(user.id)})
|
||||
return {
|
||||
"access_token": access_token,
|
||||
"token_type": "bearer",
|
||||
"user": user
|
||||
}
|
||||
|
||||
async def register(self, data: dict) -> Optional[dict]:
|
||||
existing_user = await self.user_service.get_user_by_username(data["username"])
|
||||
if existing_user:
|
||||
return None
|
||||
|
||||
user = await self.user_service.create_user(data)
|
||||
access_token = create_access_token(data={"sub": str(user.id)})
|
||||
return {
|
||||
"access_token": access_token,
|
||||
"token_type": "bearer",
|
||||
"user": user
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
from typing import List, Optional
|
||||
from datetime import date, timedelta
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.batch import Batch
|
||||
from app.schemas.batch import BatchCreate, BatchUpdate
|
||||
from app.repositories.batch import BatchRepository
|
||||
from app.services.audit import AuditService
|
||||
|
||||
|
||||
class BatchService:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
self.repo = BatchRepository(db)
|
||||
self.audit_service = AuditService(db)
|
||||
|
||||
async def get_batches_by_medicine(self, medicine_id: int) -> List[Batch]:
|
||||
return await self.repo.get_by_medicine_id(medicine_id)
|
||||
|
||||
async def get_batch(self, batch_id: int) -> Optional[Batch]:
|
||||
return await self.repo.get_by_id(batch_id)
|
||||
|
||||
async def create_batch(self, medicine_id: int, data: BatchCreate) -> Batch:
|
||||
batch_data = data.model_dump()
|
||||
batch_data['medicine_id'] = medicine_id
|
||||
return await self.repo.create(batch_data)
|
||||
|
||||
async def update_batch(self, batch_id: int, data: BatchUpdate) -> Optional[Batch]:
|
||||
update_data = data.model_dump(exclude_unset=True)
|
||||
return await self.repo.update(batch_id, update_data)
|
||||
|
||||
async def delete_batch(self, batch_id: int) -> bool:
|
||||
return await self.repo.delete(batch_id)
|
||||
|
||||
async def dispense(self, batch_id: int, quantity: int, user_id: int) -> Optional[Batch]:
|
||||
batch = await self.repo.get_by_id(batch_id)
|
||||
if not batch:
|
||||
raise ValueError("批次不存在")
|
||||
|
||||
if batch.quantity < quantity:
|
||||
raise ValueError("库存不足")
|
||||
|
||||
await self.audit_service.log_action(
|
||||
medicine_id=batch.medicine_id,
|
||||
batch_id=batch_id,
|
||||
user_id=user_id,
|
||||
action="dispense",
|
||||
quantity_change=-quantity,
|
||||
quantity_after=batch.quantity - quantity
|
||||
)
|
||||
|
||||
batch.quantity -= quantity
|
||||
await self.db.commit()
|
||||
|
||||
return batch
|
||||
|
||||
async def add_stock(self, batch_id: int, quantity: int, user_id: int) -> Optional[Batch]:
|
||||
batch = await self.repo.get_by_id(batch_id)
|
||||
if not batch:
|
||||
raise ValueError("批次不存在")
|
||||
|
||||
await self.audit_service.log_action(
|
||||
medicine_id=batch.medicine_id,
|
||||
batch_id=batch_id,
|
||||
user_id=user_id,
|
||||
action="add_stock",
|
||||
quantity_change=quantity,
|
||||
quantity_after=batch.quantity + quantity
|
||||
)
|
||||
|
||||
batch.quantity += quantity
|
||||
await self.db.commit()
|
||||
|
||||
return batch
|
||||
|
||||
async def check_expiring_batches(self, warning_days: List[int]) -> List[dict]:
|
||||
expiring = []
|
||||
today = date.today()
|
||||
|
||||
for days in warning_days:
|
||||
target_date = today + timedelta(days=days)
|
||||
batches = await self.repo.get_expiring_before(target_date)
|
||||
for batch in batches:
|
||||
expiring.append({
|
||||
'batch': batch,
|
||||
'days_until_expiry': days
|
||||
})
|
||||
|
||||
return expiring
|
||||
@@ -0,0 +1,56 @@
|
||||
from typing import Optional, List
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.category import Category
|
||||
from app.repositories.category import CategoryRepository
|
||||
|
||||
|
||||
class CategoryService:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
self.repo = CategoryRepository(db)
|
||||
|
||||
async def get_categories(self, level: Optional[int] = None, parent_id: Optional[int] = None) -> List[Category]:
|
||||
return await self.repo.get_all(level=level, parent_id=parent_id)
|
||||
|
||||
async def get_category(self, category_id: int) -> Optional[Category]:
|
||||
return await self.repo.get_by_id(category_id)
|
||||
|
||||
async def get_category_with_children(self, category_id: int) -> Optional[Category]:
|
||||
category = await self.repo.get_by_id(category_id)
|
||||
if category:
|
||||
category.children = await self.repo.get_children(category_id)
|
||||
return category
|
||||
|
||||
async def create_category(self, data: dict) -> Category:
|
||||
return await self.repo.create(data)
|
||||
|
||||
async def update_category(self, category_id: int, data: dict) -> Optional[Category]:
|
||||
return await self.repo.update(category_id, data)
|
||||
|
||||
async def delete_category(self, category_id: int) -> bool:
|
||||
return await self.repo.delete(category_id)
|
||||
|
||||
async def get_category_tree(self) -> List[dict]:
|
||||
root_categories = await self.repo.get_all(level=1)
|
||||
tree = []
|
||||
for category in root_categories:
|
||||
children = await self.repo.get_children(category.id)
|
||||
tree.append({
|
||||
"id": category.id,
|
||||
"name": category.name,
|
||||
"level": category.level,
|
||||
"icon": category.icon,
|
||||
"sort_order": category.sort_order,
|
||||
"children": [
|
||||
{
|
||||
"id": child.id,
|
||||
"name": child.name,
|
||||
"level": child.level,
|
||||
"icon": child.icon,
|
||||
"sort_order": child.sort_order
|
||||
}
|
||||
for child in children
|
||||
]
|
||||
})
|
||||
return tree
|
||||
@@ -0,0 +1,64 @@
|
||||
from typing import List, Optional, Tuple
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.medicine import Medicine
|
||||
from app.schemas.medicine import MedicineCreate, MedicineUpdate, MedicineWithStock
|
||||
from app.repositories.medicine import MedicineRepository
|
||||
|
||||
|
||||
class MedicineService:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
self.repo = MedicineRepository(db)
|
||||
|
||||
async def get_medicines(
|
||||
self,
|
||||
category_id: Optional[int] = None,
|
||||
search: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20
|
||||
) -> Tuple[List[MedicineWithStock], int]:
|
||||
medicines, total = await self.repo.get_list(
|
||||
category_id=category_id,
|
||||
search=search,
|
||||
page=page,
|
||||
page_size=page_size
|
||||
)
|
||||
|
||||
result = []
|
||||
for medicine in medicines:
|
||||
total_quantity = sum(b.quantity for b in medicine.batches if not b.is_expired)
|
||||
|
||||
nearest_expiry = None
|
||||
for batch in medicine.batches:
|
||||
if not batch.is_expired:
|
||||
if nearest_expiry is None or batch.expiry_date < nearest_expiry:
|
||||
nearest_expiry = batch.expiry_date
|
||||
|
||||
medicine_with_stock = MedicineWithStock(
|
||||
**medicine.__dict__,
|
||||
total_quantity=total_quantity,
|
||||
nearest_expiry_date=nearest_expiry,
|
||||
batch_count=len([b for b in medicine.batches if not b.is_expired])
|
||||
)
|
||||
result.append(medicine_with_stock)
|
||||
|
||||
return result, total
|
||||
|
||||
async def get_medicine(self, medicine_id: int) -> Optional[Medicine]:
|
||||
return await self.repo.get_by_id(medicine_id)
|
||||
|
||||
async def create_medicine(self, data: MedicineCreate, user_id: int) -> Medicine:
|
||||
medicine_data = data.model_dump()
|
||||
medicine_data['created_by'] = user_id
|
||||
return await self.repo.create(medicine_data)
|
||||
|
||||
async def update_medicine(self, medicine_id: int, data: MedicineUpdate) -> Optional[Medicine]:
|
||||
update_data = data.model_dump(exclude_unset=True)
|
||||
return await self.repo.update(medicine_id, update_data)
|
||||
|
||||
async def delete_medicine(self, medicine_id: int) -> bool:
|
||||
return await self.repo.delete(medicine_id)
|
||||
|
||||
async def search_medicines(self, query: str) -> List[Medicine]:
|
||||
return await self.repo.search(query)
|
||||
@@ -0,0 +1,39 @@
|
||||
from typing import Optional, List
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.notification import Notification
|
||||
from app.repositories.notification import NotificationRepository
|
||||
|
||||
|
||||
class NotificationService:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
self.repo = NotificationRepository(db)
|
||||
|
||||
async def get_notifications(
|
||||
self,
|
||||
user_id: Optional[int] = None,
|
||||
is_read: Optional[bool] = None,
|
||||
type: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20
|
||||
) -> List[Notification]:
|
||||
return await self.repo.get_list(
|
||||
user_id=user_id,
|
||||
is_read=is_read,
|
||||
type=type,
|
||||
page=page,
|
||||
page_size=page_size
|
||||
)
|
||||
|
||||
async def create_notification(self, data: dict) -> Notification:
|
||||
return await self.repo.create(data)
|
||||
|
||||
async def mark_as_read(self, notification_id: int) -> bool:
|
||||
return await self.repo.mark_as_read(notification_id)
|
||||
|
||||
async def mark_all_as_read(self, user_id: int) -> int:
|
||||
return await self.repo.mark_all_as_read(user_id)
|
||||
|
||||
async def delete_notification(self, notification_id: int) -> bool:
|
||||
return await self.repo.delete(notification_id)
|
||||
@@ -0,0 +1,60 @@
|
||||
from typing import Optional, List
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.user import User
|
||||
from app.repositories.user import UserRepository
|
||||
from app.core.security import get_password_hash, verify_password
|
||||
|
||||
|
||||
class UserService:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
self.repo = UserRepository(db)
|
||||
|
||||
async def get_user(self, user_id: int) -> Optional[User]:
|
||||
return await self.repo.get_by_id(user_id)
|
||||
|
||||
async def get_user_by_username(self, username: str) -> Optional[User]:
|
||||
return await self.repo.get_by_username(username)
|
||||
|
||||
async def get_all_users(self) -> List[User]:
|
||||
return await self.repo.get_all()
|
||||
|
||||
async def create_user(self, data: dict) -> User:
|
||||
if "password" in data:
|
||||
data["password_hash"] = get_password_hash(data.pop("password"))
|
||||
return await self.repo.create(data)
|
||||
|
||||
async def update_user(self, user_id: int, data: dict) -> Optional[User]:
|
||||
if "password" in data:
|
||||
data["password_hash"] = get_password_hash(data.pop("password"))
|
||||
return await self.repo.update(user_id, data)
|
||||
|
||||
async def delete_user(self, user_id: int) -> bool:
|
||||
return await self.repo.delete(user_id)
|
||||
|
||||
async def authenticate(self, username: str, password: str) -> Optional[User]:
|
||||
user = await self.repo.get_by_username(username)
|
||||
if not user:
|
||||
return None
|
||||
if not verify_password(password, user.password_hash):
|
||||
return None
|
||||
if not user.is_active:
|
||||
return None
|
||||
return user
|
||||
|
||||
async def change_password(self, user_id: int, old_password: str, new_password: str) -> bool:
|
||||
user = await self.repo.get_by_id(user_id)
|
||||
if not user:
|
||||
return False
|
||||
if not verify_password(old_password, user.password_hash):
|
||||
return False
|
||||
await self.repo.update(user_id, {"password_hash": get_password_hash(new_password)})
|
||||
return True
|
||||
|
||||
async def reset_password(self, user_id: int, new_password: str) -> bool:
|
||||
user = await self.repo.get_by_id(user_id)
|
||||
if not user:
|
||||
return False
|
||||
await self.repo.update(user_id, {"password_hash": get_password_hash(new_password)})
|
||||
return True
|
||||
Reference in New Issue
Block a user