Fix test failures and workload/websocket behavior
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, computed_field, model_validator
|
||||
from typing import Optional
|
||||
from datetime import datetime, date
|
||||
from decimal import Decimal
|
||||
@@ -19,9 +19,22 @@ class ProjectBase(BaseModel):
|
||||
end_date: Optional[date] = None
|
||||
security_level: SecurityLevel = SecurityLevel.DEPARTMENT
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def apply_name_alias(cls, values):
|
||||
if isinstance(values, dict) and not values.get("title") and values.get("name"):
|
||||
values["title"] = values["name"]
|
||||
return values
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self.title
|
||||
|
||||
|
||||
class ProjectCreate(ProjectBase):
|
||||
department_id: Optional[str] = None
|
||||
template_id: Optional[str] = None
|
||||
|
||||
|
||||
class ProjectUpdate(BaseModel):
|
||||
@@ -34,6 +47,13 @@ class ProjectUpdate(BaseModel):
|
||||
status: Optional[str] = Field(None, max_length=50)
|
||||
department_id: Optional[str] = None
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def apply_name_alias(cls, values):
|
||||
if isinstance(values, dict) and not values.get("title") and values.get("name"):
|
||||
values["title"] = values["name"]
|
||||
return values
|
||||
|
||||
|
||||
class ProjectResponse(ProjectBase):
|
||||
id: str
|
||||
|
||||
@@ -48,3 +48,12 @@ class GenerateReportResponse(BaseModel):
|
||||
message: str
|
||||
report_id: str
|
||||
summary: ReportSummary
|
||||
|
||||
|
||||
class WeeklyReportSubscription(BaseModel):
|
||||
is_active: bool
|
||||
last_sent_at: Optional[datetime] = None
|
||||
|
||||
|
||||
class WeeklyReportSubscriptionUpdate(BaseModel):
|
||||
is_active: bool
|
||||
|
||||
@@ -35,6 +35,15 @@ class TaskBase(BaseModel):
|
||||
start_date: Optional[datetime] = None
|
||||
due_date: Optional[datetime] = None
|
||||
|
||||
@field_validator("title")
|
||||
@classmethod
|
||||
def title_not_blank(cls, value: str) -> str:
|
||||
if value is None:
|
||||
return value
|
||||
if value.strip() == "":
|
||||
raise ValueError("Title cannot be blank or whitespace")
|
||||
return value
|
||||
|
||||
|
||||
class TaskCreate(TaskBase):
|
||||
parent_task_id: Optional[str] = None
|
||||
@@ -57,6 +66,15 @@ class TaskUpdate(BaseModel):
|
||||
custom_values: Optional[List[CustomValueInput]] = None
|
||||
version: Optional[int] = Field(None, ge=1, description="Version for optimistic locking")
|
||||
|
||||
@field_validator("title")
|
||||
@classmethod
|
||||
def title_not_blank(cls, value: Optional[str]) -> Optional[str]:
|
||||
if value is None:
|
||||
return value
|
||||
if value.strip() == "":
|
||||
raise ValueError("Title cannot be blank or whitespace")
|
||||
return value
|
||||
|
||||
|
||||
class TaskStatusUpdate(BaseModel):
|
||||
status_id: str
|
||||
@@ -131,3 +149,8 @@ class TaskDeleteResponse(BaseModel):
|
||||
task: TaskResponse
|
||||
blockers_resolved: int = 0
|
||||
force_deleted: bool = False
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def id(self) -> str:
|
||||
return self.task.id
|
||||
|
||||
@@ -5,9 +5,18 @@ from pydantic import BaseModel, Field
|
||||
|
||||
class FieldChangeCondition(BaseModel):
|
||||
"""Condition for field_change triggers."""
|
||||
field: str = Field(..., description="Field to check: status_id, assignee_id, priority")
|
||||
operator: str = Field(..., description="Operator: equals, not_equals, changed_to, changed_from")
|
||||
value: str = Field(..., description="Value to compare against")
|
||||
field: str = Field(..., description="Field to check: status_id, assignee_id, priority, start_date, due_date, custom_fields")
|
||||
operator: str = Field(..., description="Operator: equals, not_equals, changed_to, changed_from, before, after, in")
|
||||
value: Any = Field(..., description="Value to compare against")
|
||||
field_id: Optional[str] = Field(None, description="Custom field ID when field is custom_fields")
|
||||
|
||||
|
||||
class TriggerRule(BaseModel):
|
||||
"""Rule for composite field_change triggers."""
|
||||
field: str = Field(..., description="Field to check: status_id, assignee_id, priority, start_date, due_date, custom_fields")
|
||||
operator: str = Field(..., description="Operator: equals, not_equals, changed_to, changed_from, before, after, in")
|
||||
value: Any = Field(..., description="Value to compare against")
|
||||
field_id: Optional[str] = Field(None, description="Custom field ID when field is custom_fields")
|
||||
|
||||
|
||||
class ScheduleCondition(BaseModel):
|
||||
@@ -19,9 +28,12 @@ class ScheduleCondition(BaseModel):
|
||||
class TriggerCondition(BaseModel):
|
||||
"""Union condition that supports both field_change and schedule triggers."""
|
||||
# Field change conditions
|
||||
field: Optional[str] = Field(None, description="Field to check: status_id, assignee_id, priority")
|
||||
operator: Optional[str] = Field(None, description="Operator: equals, not_equals, changed_to, changed_from")
|
||||
value: Optional[str] = Field(None, description="Value to compare against")
|
||||
field: Optional[str] = Field(None, description="Field to check: status_id, assignee_id, priority, start_date, due_date, custom_fields")
|
||||
operator: Optional[str] = Field(None, description="Operator: equals, not_equals, changed_to, changed_from, before, after, in")
|
||||
value: Optional[Any] = Field(None, description="Value to compare against")
|
||||
field_id: Optional[str] = Field(None, description="Custom field ID when field is custom_fields")
|
||||
logic: Optional[str] = Field(None, description="Composite logic: and")
|
||||
rules: Optional[List[TriggerRule]] = None
|
||||
# Schedule conditions
|
||||
cron_expression: Optional[str] = Field(None, description="Cron expression for schedule triggers")
|
||||
deadline_reminder_days: Optional[int] = Field(None, ge=1, le=365, description="Days before due date to send reminder")
|
||||
@@ -37,7 +49,7 @@ class TriggerAction(BaseModel):
|
||||
"""
|
||||
type: str = Field(..., description="Action type: notify, update_field, auto_assign")
|
||||
# Notify action fields
|
||||
target: Optional[str] = Field(None, description="Target: assignee, creator, project_owner, user:<id>")
|
||||
target: Optional[str] = Field(None, description="Target: assignee, creator, project_owner, project_members, department:<id>, role:<name>, user:<id>")
|
||||
template: Optional[str] = Field(None, description="Message template with variables")
|
||||
# update_field action fields (FEAT-014)
|
||||
field: Optional[str] = Field(None, description="Field to update: priority, status_id, due_date")
|
||||
|
||||
Reference in New Issue
Block a user