首次提交by MimoCode

This commit is contained in:
tang1219
2026-06-15 14:50:15 +08:00
parent 584b31da50
commit ead13f863c
166 changed files with 14908 additions and 1 deletions
View File
+47
View File
@@ -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
)
+37
View File
@@ -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
}
+89
View File
@@ -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
+56
View File
@@ -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
+64
View File
@@ -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)
+39
View File
@@ -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)
+60
View File
@@ -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