Add per-project Department management
Departments are now a customizable per-project list (standard + custom), matching the existing custom task type/status pattern, instead of a fixed 7-value enum. They're usable directly on tasks (new field, independent of assignee) and continue to drive team member department roles.
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Pydantic schemas for department management
|
||||
"""
|
||||
from pydantic import BaseModel, Field, validator
|
||||
from typing import List
|
||||
import re
|
||||
|
||||
|
||||
class CustomDepartmentCreate(BaseModel):
|
||||
"""Schema for creating a new custom department"""
|
||||
department: str = Field(..., min_length=2, max_length=50, description="Department name")
|
||||
|
||||
@validator('department')
|
||||
def validate_department_name(cls, v):
|
||||
"""Validate department name format"""
|
||||
if not re.match(r'^[a-z0-9_]{2,50}$', v):
|
||||
raise ValueError(
|
||||
'Department name must be 2-50 characters, lowercase alphanumeric with underscores only'
|
||||
)
|
||||
return v
|
||||
|
||||
|
||||
class CustomDepartmentUpdate(BaseModel):
|
||||
"""Schema for updating a custom department name"""
|
||||
old_name: str = Field(..., description="Current department name")
|
||||
new_name: str = Field(..., min_length=2, max_length=50, description="New department name")
|
||||
|
||||
@validator('new_name')
|
||||
def validate_department_name(cls, v):
|
||||
"""Validate department name format"""
|
||||
if not re.match(r'^[a-z0-9_]{2,50}$', v):
|
||||
raise ValueError(
|
||||
'Department name must be 2-50 characters, lowercase alphanumeric with underscores only'
|
||||
)
|
||||
return v
|
||||
|
||||
|
||||
class AllDepartmentsResponse(BaseModel):
|
||||
"""Schema for response containing all departments (standard + custom)"""
|
||||
departments: List[str] = Field(..., description="All departments")
|
||||
standard_departments: List[str] = Field(..., description="Standard departments (read-only)")
|
||||
custom_departments: List[str] = Field(..., description="Custom departments")
|
||||
|
||||
|
||||
class DepartmentInUseError(BaseModel):
|
||||
"""Schema for error when trying to delete a department in use"""
|
||||
error: str = Field(..., description="Error message")
|
||||
department: str = Field(..., description="Department that is in use")
|
||||
member_count: int = Field(..., description="Number of project members using this department")
|
||||
task_count: int = Field(..., description="Number of tasks using this department")
|
||||
@@ -5,7 +5,6 @@ from enum import Enum
|
||||
import re
|
||||
|
||||
from models.project import ProjectStatus, ProjectType
|
||||
from models.user import DepartmentRole
|
||||
|
||||
|
||||
# Technical Specifications Schemas
|
||||
@@ -67,16 +66,6 @@ class ProjectTechnicalSpecs(BaseModel):
|
||||
raise ValueError('Delivery image resolution must be in format "WIDTHxHEIGHT" (e.g., "1920x1080")')
|
||||
return v
|
||||
|
||||
@validator('delivery_movie_specs_by_department')
|
||||
def validate_delivery_movie_specs_by_department(cls, v):
|
||||
if v is None:
|
||||
return {}
|
||||
allowed_departments = ['layout', 'animation', 'lighting', 'composite', 'modeling', 'rigging', 'surfacing']
|
||||
for dept in v.keys():
|
||||
if dept not in allowed_departments:
|
||||
raise ValueError(f'Department must be one of: {", ".join(allowed_departments)}')
|
||||
return v
|
||||
|
||||
|
||||
# Default delivery movie specifications per department
|
||||
DEFAULT_DELIVERY_MOVIE_SPECS = {
|
||||
@@ -135,17 +124,6 @@ class ProjectBase(BaseModel):
|
||||
raise ValueError('Delivery image resolution must be in format "WIDTHxHEIGHT" (e.g., "1920x1080")')
|
||||
return v
|
||||
|
||||
@validator('delivery_movie_specs_by_department')
|
||||
def validate_delivery_movie_specs_by_department(cls, v):
|
||||
if v is None:
|
||||
return {}
|
||||
allowed_departments = ['layout', 'animation', 'lighting', 'composite', 'modeling', 'rigging', 'surfacing']
|
||||
for dept in v.keys():
|
||||
if dept not in allowed_departments:
|
||||
raise ValueError(f'Department must be one of: {", ".join(allowed_departments)}')
|
||||
return v
|
||||
|
||||
|
||||
class ProjectCreate(ProjectBase):
|
||||
pass
|
||||
|
||||
@@ -178,20 +156,9 @@ class ProjectUpdate(BaseModel):
|
||||
raise ValueError('Delivery image resolution must be in format "WIDTHxHEIGHT" (e.g., "1920x1080")')
|
||||
return v
|
||||
|
||||
@validator('delivery_movie_specs_by_department')
|
||||
def validate_delivery_movie_specs_by_department(cls, v):
|
||||
if v is None:
|
||||
return v
|
||||
allowed_departments = ['layout', 'animation', 'lighting', 'composite', 'modeling', 'rigging', 'surfacing']
|
||||
for dept in v.keys():
|
||||
if dept not in allowed_departments:
|
||||
raise ValueError(f'Department must be one of: {", ".join(allowed_departments)}')
|
||||
return v
|
||||
|
||||
|
||||
class ProjectMemberBase(BaseModel):
|
||||
user_id: int
|
||||
department_role: Optional[DepartmentRole] = None
|
||||
department_role: Optional[str] = None
|
||||
|
||||
|
||||
class ProjectMemberCreate(ProjectMemberBase):
|
||||
@@ -199,7 +166,7 @@ class ProjectMemberCreate(ProjectMemberBase):
|
||||
|
||||
|
||||
class ProjectMemberUpdate(BaseModel):
|
||||
department_role: Optional[DepartmentRole] = None
|
||||
department_role: Optional[str] = None
|
||||
|
||||
|
||||
class ProjectMemberResponse(ProjectMemberBase):
|
||||
|
||||
@@ -4,7 +4,6 @@ from datetime import date, datetime
|
||||
from enum import Enum
|
||||
|
||||
from models.task import TaskType, TaskStatus, ReviewDecision, AttachmentType, NoteType
|
||||
from models.user import DepartmentRole
|
||||
|
||||
|
||||
class TaskBase(BaseModel):
|
||||
@@ -14,6 +13,7 @@ class TaskBase(BaseModel):
|
||||
start_date: Optional[date] = None
|
||||
deadline: Optional[date] = None
|
||||
status: str = "not_started" # Changed from TaskStatus enum to str to support custom statuses
|
||||
department: Optional[str] = None # Standard or project-custom department, independent of assignee
|
||||
|
||||
|
||||
class TaskCreate(TaskBase):
|
||||
@@ -31,6 +31,7 @@ class TaskUpdate(BaseModel):
|
||||
start_date: Optional[date] = None
|
||||
deadline: Optional[date] = None
|
||||
status: Optional[str] = None # Changed from TaskStatus enum to str to support custom statuses
|
||||
department: Optional[str] = None # Standard or project-custom department, independent of assignee
|
||||
assigned_user_id: Optional[int] = None
|
||||
|
||||
|
||||
@@ -70,6 +71,7 @@ class TaskListResponse(BaseModel):
|
||||
name: str
|
||||
task_type: str # Changed from TaskType enum to str to support custom task types
|
||||
status: str # Changed from TaskStatus enum to str to support custom statuses
|
||||
department: Optional[str] = None # Standard or project-custom department, independent of assignee
|
||||
start_date: Optional[date] = None
|
||||
deadline: Optional[date] = None
|
||||
project_id: int
|
||||
|
||||
Reference in New Issue
Block a user