Phase 2: Tags UI, Custom Fields UI, Notifications Bell
- Tags UI: TagsPage (CRUD, color picker), TagBadge, TagSelector (multi-select, inline creation) - Custom Fields Backend: model, schema, service, routes, migration 0041 - Custom Fields Frontend: CustomFieldsPage (definitions CRUD), CustomFieldRenderer (dynamic field rendering) - Custom Fields: _collect_custom_field_definitions() extended to merge DB definitions with plugin definitions - Notifications Bell: NotificationBell (30s polling, unread badge), NotificationDropdown, NotificationItem - NotificationBell integrated into TopBar - Routes: /tags, /settings/custom-fields registered - Settings nav: Custom Fields entry added - Menu items: Tags added to automation plugin manifest
This commit is contained in:
@@ -21,6 +21,7 @@ from app.models.system_settings import SystemSettings
|
||||
from app.models.tax import TaxRate
|
||||
from app.models.tenant import Tenant
|
||||
from app.models.user import User, UserTenant
|
||||
from app.models.custom_field_definition import CustomFieldDefinition
|
||||
from app.models.workflow import Workflow, WorkflowInstance, WorkflowStepHistory
|
||||
|
||||
__all__ = [
|
||||
@@ -54,6 +55,7 @@ __all__ = [
|
||||
"PluginMigration",
|
||||
"AIConversation",
|
||||
"AIMessage",
|
||||
"CustomFieldDefinition",
|
||||
"Workflow",
|
||||
"WorkflowInstance",
|
||||
"WorkflowStepHistory",
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
"""CustomFieldDefinition model — user-defined custom fields stored in DB."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import Boolean, Integer, JSON, String, UniqueConstraint
|
||||
from sqlalchemy.dialects.postgresql import UUID as PGUUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.core.db import Base, TenantMixin
|
||||
|
||||
|
||||
class CustomFieldDefinition(Base, TenantMixin):
|
||||
"""User-defined custom field definition stored in the database.
|
||||
|
||||
These definitions are merged with plugin-provided custom fields
|
||||
at query time. Each definition is scoped to a tenant and entity type.
|
||||
"""
|
||||
|
||||
__tablename__ = "custom_field_definitions"
|
||||
__table_args__ = (
|
||||
UniqueConstraint(
|
||||
"tenant_id", "entity", "name",
|
||||
name="uq_custom_field_def_tenant_entity_name",
|
||||
),
|
||||
)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
||||
)
|
||||
|
||||
# ── Identity ──
|
||||
entity: Mapped[str] = mapped_column(String(50), nullable=False, index=True)
|
||||
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
label: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
|
||||
# ── Type & Options ──
|
||||
field_type: Mapped[str] = mapped_column(
|
||||
String(20), nullable=False, default="text"
|
||||
) # text, number, date, select, multiselect, boolean
|
||||
options: Mapped[list[str] | None] = mapped_column(JSON, nullable=True, default=list)
|
||||
default_value: Mapped[Any | None] = mapped_column(JSON, nullable=True, default=None)
|
||||
|
||||
# ── Behaviour ──
|
||||
required: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||||
sort_order: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
|
||||
# ── Audit ──
|
||||
created_by: Mapped[uuid.UUID | None] = mapped_column(
|
||||
PGUUID(as_uuid=True), nullable=True
|
||||
)
|
||||
updated_by: Mapped[uuid.UUID | None] = mapped_column(
|
||||
PGUUID(as_uuid=True), nullable=True
|
||||
)
|
||||
Reference in New Issue
Block a user