backbone-mail 0.2.32

Odoo mail core port — message/notification/followers/activity/alias/sms queue (schema: messaging)
# =============================================================================
# Module: backbone-mail — Area: ACTIVITY (M17/M19/M20/M21).
# Ported from docs/odoo/messaging/mail: addons/mail/models/
# {mail_activity,mail_activity_type,mail_activity_plan,mail_activity_plan_
# template}.py (docs/odoo/messaging/mail/schema/models/thread-activity.model.yaml).
#
# PORT DECISIONS:
#  - MailActivity.state is a PROJECTION, not stored: done is computed from
#    active=False (archive == done — Odoo's done marks the row archived, the
#    kept row is 'the activity was done'), overdue/today/planned from
#    date_deadline vs today. Declared lifecycle shape: projection (MAIL-M17).
#  - The m2m template_ids on mail.activity.type (used_by) and plan-template
#    composition are modeled structurally: plan → templates via
#    MailActivityPlanTemplate.plan_id (the template rows ARE the join).
#  - res_model/res_id polymorphic, logical columns (no FK).
# =============================================================================

models:

  # ===========================================================================
  # mail.activity — THE scheduled to-do on a record (M17).
  # ===========================================================================
  - name: MailActivity
    collection: mail_activities
    description: "mail.activity — THE scheduled to-do on a record: a dated user task attached to a (model,res_id) document with a user_id owner (M17). Activities are the calendar-backed work queue. state is NOT stored — done is the archived row (active=False set on done_action; the archived row IS the completion record) and overdue/today/planned derive from date_deadline vs today (a PROJECTION lifecycle). Chaining: on done, the next activity in the plan/template chain is created (or a suggested dialog offered)."
    fields:
      id:
        type: uuid
        attributes: ["@id", "@default(uuid)"]
        description: "Primary key"
      res_model:
        type: string
        attributes: ["@required", "@indexed"]
        description: "The document model the activity is attached to (polymorphic, logical — no FK)."
      res_id:
        type: uuid
        attributes: ["@required", "@exclude_from_foreign_key_check"]
        description: "The document id the activity is attached to (polymorphic, logical — no FK). G-MAIL-3: CHECK (res_model IS NULL OR res_id IS NOT NULL) — an activity attached to a model MUST have a res_id."
      res_model_id:
        type: uuid?
        attributes: ["@exclude_from_foreign_key_check"]
        description: "Logical ref to the model-registry entry for res_model (ir.model in Odoo — framework, not ported) # no FK"
      activity_type_id:
        type: uuid?
        attributes: ["@foreign_key(MailActivityType.activities)", "@exclude_from_foreign_key_check"]
        description: "The activity type (category, default duration, chaining behavior) # logical FK to MailActivityType.id"
      summary:
        type: string?
        description: "Short task summary (defaults to the type's summary)."
      note:
        type: string?
        description: "Task detail / instructions (rich text in Odoo)."
      date_deadline:
        type: date
        attributes: ["@indexed"]
        description: "Due date — THE sort/filter key of the activity queue; drives the state projection (overdue/today/planned)."
      user_id:
        type: uuid
        attributes: ["@required", "@foreign_key(sapiens.User.id)", "@exclude_from_foreign_key_check", "@indexed"]
        description: "The assigned user (queue owner) # logical FK to sapiens.User.id. G-MAIL-4: CHECK (res_model IS NULL OR user_id IS NOT NULL) — an activity attached to a model MUST have an assignee."
      requested_user_id:
        type: uuid?
        attributes: ["@foreign_key(sapiens.User.id)", "@exclude_from_foreign_key_check"]
        description: "The user who requested the activity be assigned to user_id (delegation) # logical FK to sapiens.User.id"
      state:
        type: MailActivityState
        attributes: ["@default(today)"]
        lifecycle: projection
        description: "PROJECTION (MAIL-M17) — NOT stored in Odoo. done is derived from active=False (the archived row IS the completion record: done_action archives); overdue = date_deadline < today; today = date_deadline == today; planned = date_deadline > today. Stored default kept only so the column exists for read-model materialization."
      active:
        type: boolean
        attributes: ["@default(true)"]
        description: "Archive flag — False means the activity WAS DONE (the projection's driver). Not a soft-delete: the archived row is the completion record."
      has_recommended_activities:
        type: boolean
        attributes: ["@default(false)"]
        description: "Whether this activity's type has recommended next activities (drives the done-dialog suggestions in Odoo)."
      chained_next_activity:
        type: json?
        description: "Reserved for the chaining payload when done triggers the next template (trigger mode): snapshot of the template fields used to mint the successor activity."
      calendar_event_id:
        type: uuid?
        attributes: ["@exclude_from_foreign_key_check"]
        description: "The calendar event minted for meeting-category activities # logical FK to a calendar module's Event.id (cross-module, no FK)"
      metadata:
        type: Metadata
        attributes: ["@audit_metadata"]
        description: "Audit metadata (created_at, updated_at, deleted_at, created_by, updated_by, deleted_by)"
    indexes:
      - type: index
        fields: [user_id, date_deadline]
        name: mail_activity_user_deadline_idx
      - type: index
        fields: [res_model, res_id]
        name: mail_activity_document_idx

  # ===========================================================================
  # mail.activity.type — the kind of to-do (M19). State-ish default-ish
  # fields live here (chaining, delays) — the queue's vocabulary.
  # ===========================================================================
  - name: MailActivityType
    collection: mail_activity_types
    description: "mail.activity.type — the kind of to-do (M19): category (upload_invoice/phonecall/meeting/none), default summary/note, delay/count defaults, and chaining behavior (suggest/trigger). The initial delay (delay_count/delay_unit/delay_from) computes date_deadline when an activity of this type is created."
    fields:
      id:
        type: uuid
        attributes: ["@id", "@default(uuid)"]
        description: "Primary key"
      name:
        type: string
        attributes: ["@required"]
        description: "Type name (e.g. Call, Todo, Meeting)."
      summary:
        type: string?
        description: "Default summary copied onto new activities of this type."
      note:
        type: string?
        description: "Default note/instructions copied onto new activities."
      category:
        type: MailActivityCategory?
        description: "Uplift category: upload_invoice (asks for an upload), phonecall, meeting (mints a calendar event); null = plain todo."
      chaining_type:
        type: MailActivityChainingType
        attributes: ["@required", "@default(suggest)"]
        lifecycle:
          shape: hand_set
          state_machine: MailActivityTypeHooks
          display_labels:
            suggest: "Suggest next activities"
            trigger: "Trigger next activity"
        description: "What happens on done (M19): suggest = the done dialog offers recommended follow-ups; trigger = the next activity in the chain is created AUTOMATICALLY. Immutable in effect post-use (Odoo hides it once activities exist); hand-set."
      delay_count:
        type: integer
        attributes: ["@default(0)"]
        description: "Default delay before due (number of delay_units)."
      delay_unit:
        type: MailActivityDelayUnit
        attributes: ["@default(days)"]
        description: "Unit for delay_count (days/weeks/months)."
      delay_from:
        type: MailActivityDelayFrom
        attributes: ["@default(previous_activity)"]
        description: "Anchor for the delay computation (M20 chain semantics): plan_date = from the plan's date field; previous_activity = from the previous activity's deadline (sequential scheduling)."
      default_user_id:
        type: uuid?
        attributes: ["@foreign_key(sapiens.User.id)", "@exclude_from_foreign_key_check"]
        description: "Default assignee for activities of this type # logical FK to sapiens.User.id"
      default_note:
        type: string?
        description: "Backwards-compat default note (older deployments write here)."
      icon:
        type: string?
        description: "Icon identifier for the type (UI)."
      res_model:
        type: string?
        description: "Model this type is specific to (null = available on all models)."
      res_model_id:
        type: uuid?
        attributes: ["@exclude_from_foreign_key_check"]
        description: "Logical ref to the model-registry entry for res_model # no FK"
      active:
        type: boolean
        attributes: ["@default(true)"]
        description: "Archive flag — archived types stop appearing in the schedule dialog."
      metadata:
        type: Metadata
        attributes: ["@audit_metadata"]
        description: "Audit metadata (created_at, updated_at, deleted_at, created_by, updated_by, deleted_by)"
    indexes:
      - type: index
        fields: [res_model]
        name: mail_activity_type_model_idx

  # ===========================================================================
  # mail.activity.plan — a bundle of activity templates to schedule at once
  # (M20).
  # ===========================================================================
  - name: MailActivityPlan
    collection: mail_activity_plans
    description: "mail.activity.plan — a named bundle of activity templates (M20): 'Onboarding plan' = N templated activities scheduled in one action. A plan optionally applies to a specific res_model and can pull a date field (res_model_id.field_on_the_fly → the delay_from='plan_date' anchor). PORT NOTE: Odoo scopes plans by company; that column is DROPPED here (company_fence: none, ADR-0014)."
    fields:
      id:
        type: uuid
        attributes: ["@id", "@default(uuid)"]
        description: "Primary key"
      name:
        type: string
        attributes: ["@required"]
        description: "Plan name (shown in the Schedule activity by plan dialog)."
      res_model:
        type: string?
        description: "Model this plan applies to (null = all models)."
      res_model_id:
        type: uuid?
        attributes: ["@exclude_from_foreign_key_check"]
        description: "Logical ref to the model-registry entry for res_model # no FK"
      active:
        type: boolean
        attributes: ["@default(true)"]
        description: "Archive flag for plans."
      metadata:
        type: Metadata
        attributes: ["@audit_metadata"]
        description: "Audit metadata (created_at, updated_at, deleted_at, created_by, updated_by, deleted_by)"

  # ===========================================================================
  # mail.activity.plan.template — the plan's template rows (M21). The
  # template rows ARE the plan→type join (with per-slot overrides).
  # ===========================================================================
  - name: MailActivityPlanTemplate
    collection: mail_activity_plan_templates
    description: "mail.activity.plan.template — one slot of a plan: the activity type to schedule, its default assignee/summary/note, and per-slot delay overrides (M21). The plan's rows ARE the plan→type composition (no separate m2m table); unique(plan_id, activity_type_id) keeps a type at most once per plan."
    fields:
      id:
        type: uuid
        attributes: ["@id", "@default(uuid)"]
        description: "Primary key"
      plan_id:
        type: uuid
        attributes: ["@required", "@foreign_key(MailActivityPlan.templates)", "@exclude_from_foreign_key_check", "@indexed"]
        description: "The owning plan (cascade) # logical FK to MailActivityPlan.id"
      activity_type_id:
        type: uuid
        attributes: ["@required", "@foreign_key(MailActivityType.plan_templates)", "@exclude_from_foreign_key_check"]
        description: "The activity type this slot schedules (unique per plan) # logical FK to MailActivityType.id"
      summary:
        type: string?
        description: "Slot summary override (falls back to the type's summary)."
      note:
        type: string?
        description: "Slot note override (falls back to the type's note)."
      delay_count:
        type: integer?
        description: "Slot delay override (falls back to the type's delay_count)."
      delay_unit:
        type: MailActivityDelayUnit?
        description: "Slot delay-unit override."
      user_id:
        type: uuid?
        attributes: ["@foreign_key(sapiens.User.id)", "@exclude_from_foreign_key_check"]
        description: "Slot assignee override # logical FK to sapiens.User.id"
      metadata:
        type: Metadata
        attributes: ["@audit_metadata"]
        description: "Audit metadata (created_at, updated_at, deleted_at, created_by, updated_by, deleted_by)"
    indexes:
      - type: unique
        fields: [plan_id, activity_type_id]
        name: mail_activity_plan_template_uniq

# =============================================================================
# ENUMS (activity)
# =============================================================================
enums:
  - name: MailActivityState
    description: "mail.activity.state — PROJECTION (not stored in Odoo): done = archived row; overdue/today/planned from date_deadline vs today (M17)."
    variants:
      - name: overdue
        description: "date_deadline < today"
      - name: today
        description: "date_deadline == today"
        default: true
      - name: planned
        description: "date_deadline > today"
      - name: done
        description: "active=False — the archived row IS the completion record"

  - name: MailActivityChainingType
    description: "mail.activity.type.chaining_type — what happens on done (M19)."
    variants:
      - name: suggest
        description: "The done dialog suggests recommended next activities"
        default: true
      - name: trigger
        description: "The next activity in the chain is created automatically"

  - name: MailActivityCategory
    description: "mail.activity.type.category — the UI/behavioral uplift category."
    variants:
      - name: upload_invoice
        description: "Asks the vendor to upload an invoice (vendor portal flow)"
      - name: phonecall
        description: "Phone call"
      - name: meeting
        description: "Meeting (mints a calendar event)"
        default: true

  - name: MailActivityDelayUnit
    description: "Unit for activity delay_count (type default or plan-slot override)."
    variants:
      - name: days
        description: "Delay in days"
        default: true
      - name: weeks
        description: "Delay in weeks"
      - name: months
        description: "Delay in months"

  - name: MailActivityDelayFrom
    description: "Anchor for the delay computation (chain semantics, M20)."
    variants:
      - name: plan_date
        description: "From the plan's date field"
      - name: previous_activity
        description: "From the previous activity's deadline (sequential)"
        default: true