Skip to content

AI Agent Context: Action Reasons

High-density technical context for AI agents working on the Action Reasons feature.

Quick Facts

  • Purpose: Admin-managed lookup table for predefined reasons in contract workflows
  • Location: services/partner-service/src/modules/action-reasons/
  • Route prefix: /api/partners/admin/action-reasons
  • Database table: action_reasons
  • Status: Active, production-ready

Entry Points

Type Path
Controller src/modules/action-reasons/action-reasons.controller.ts
Service src/modules/action-reasons/action-reasons.service.ts
Entity src/modules/action-reasons/entities/action-reason.entity.ts
DTOs src/modules/action-reasons/dto/
DB Schema src/common/database/database.service.ts (lines 812-865)

Database Schema

CREATE TABLE IF NOT EXISTS action_reasons (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  context VARCHAR(100) NOT NULL,
  slug VARCHAR(100) NOT NULL,
  name VARCHAR(255) NOT NULL,
  description TEXT,
  display_order INTEGER NOT NULL DEFAULT 0,
  enabled BOOLEAN NOT NULL DEFAULT true,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  CONSTRAINT uq_action_reasons_context_slug UNIQUE (context, slug)
);

-- Indexes
CREATE INDEX idx_action_reasons_context ON action_reasons(context);
CREATE INDEX idx_action_reasons_enabled ON action_reasons(enabled);

-- FK columns on partner_contracts
ALTER TABLE partner_contracts ADD COLUMN void_reason_id UUID;
ALTER TABLE partner_contracts ADD COLUMN changes_requested_reason_id UUID;
-- FKs reference action_reasons(id) ON DELETE SET NULL

API Endpoints

Method Path Handler Purpose
GET / list() List by context, optionally include disabled
GET /:id getById() Single reason lookup
POST / create() Create new reason
PATCH /:id update() Partial update
DELETE /:id delete() Hard delete
PUT /reorder reorder() Bulk update display_order

Context Constants

export const ACTION_REASON_CONTEXTS = {
  CONTRACT_VOID: 'contract_void',
  CONTRACT_CHANGES_REQUESTED: 'contract_changes_requested',
} as const;

Service Methods

Method Parameters Returns Notes
listByContext(context) context: string ActionReason[] Enabled only, sorted by display_order
listAllByContext(context) context: string ActionReason[] All reasons, for admin UI
getById(id) id: string ActionReason | null Single lookup
create(dto) CreateActionReasonDto ActionReason Auto-increments display_order
update(id, dto) id, UpdateActionReasonDto ActionReason Throws NotFoundException
delete(id) id: string void Hard delete, throws NotFoundException
reorder(context, ids) context, string[] ActionReason[] Updates display_order for all

DTO Definitions

CreateActionReasonDto

{
  context: string;      // @IsString @MinLength(1) @MaxLength(100)
  slug: string;         // @IsString @MinLength(1) @MaxLength(100) @Matches(/^[a-z0-9_-]+$/)
  name: string;         // @IsString @MinLength(1) @MaxLength(255)
  description?: string; // @IsOptional @IsString
  display_order?: number; // @IsOptional @IsInt @Min(0)
}

UpdateActionReasonDto

{
  name?: string;        // @IsOptional @IsString @MinLength(1) @MaxLength(255)
  description?: string; // @IsOptional @IsString
  display_order?: number; // @IsOptional @IsInt @Min(0)
  enabled?: boolean;    // @IsOptional @IsBoolean
}

ReorderActionReasonsDto

{
  context: string;      // @IsString @MinLength(1)
  ids: string[];        // @IsArray @IsUUID("4", { each: true })
}

Frontend Files

Component Path Purpose
Settings Page admin-console/src/pages/SettingsPage/ActionReasonsPage.tsx CRUD UI with drag-drop
Service admin-console/src/services/partner.service.ts API client methods
Contract List admin-console/src/components/organisms/PartnerManagement/PartnerContractList.tsx Reason selection dropdown

Frontend Service Methods

// partner.service.ts
listActionReasons(context: string, includeDisabled?: boolean): Promise<ActionReason[]>
getActionReason(id: string): Promise<ActionReason | null>
createActionReason(data: CreateActionReasonDto): Promise<ActionReason>
updateActionReason(id: string, data: UpdateActionReasonDto): Promise<ActionReason>
deleteActionReason(id: string): Promise<void>
reorderActionReasons(context: string, ids: string[]): Promise<ActionReason[]>

Translation Keys

Path: admin-console/src/i18n/locales/en.json

settings.actionReasons.title
settings.actionReasons.subtitle
settings.actionReasons.contractVoid
settings.actionReasons.contractChanges
settings.actionReasons.voidReasons
settings.actionReasons.changesReasons
settings.actionReasons.addReason
settings.actionReasons.editReason
settings.actionReasons.deleteTitle
settings.actionReasons.deleteConfirm
settings.actionReasons.slug
settings.actionReasons.slugHelp
settings.actionReasons.name
settings.actionReasons.description
settings.actionReasons.enabled
settings.actionReasons.noReasons
settings.actionReasons.createFirst
settings.actionReasons.loadError
settings.actionReasons.saveError
settings.actionReasons.createSuccess
settings.actionReasons.updateSuccess
settings.actionReasons.deleteSuccess
settings.actionReasons.deleteError
settings.actionReasons.toggleError
settings.actionReasons.reorderSuccess
settings.actionReasons.reorderError
settings.actionReasons.disabled
settings.actionReasons.errors.slugRequired
settings.actionReasons.errors.slugFormat
settings.actionReasons.errors.slugExists
settings.actionReasons.errors.nameRequired

Common Operations

Add new reason via API

curl -X POST http://localhost:3003/api/partners/admin/action-reasons \
  -H "Content-Type: application/json" \
  -d '{"context":"contract_void","slug":"test-reason","name":"Test Reason"}'

List reasons for a context

curl "http://localhost:3003/api/partners/admin/action-reasons?context=contract_void"

List all including disabled

curl "http://localhost:3003/api/partners/admin/action-reasons?context=contract_void&includeDisabled=true"

Code Patterns

Creating a reason with auto display_order

// If display_order not provided, service auto-calculates next value
const reason = await actionReasonsService.create({
  context: 'contract_void',
  slug: 'new-reason',
  name: 'New Reason',
});
// display_order will be MAX(display_order) + 1 for context

Checking slug uniqueness

// Done automatically in create() - throws ConflictException if duplicate
// Constraint: uq_action_reasons_context_slug UNIQUE (context, slug)

Integration with contract void

// In partner-contracts.service.ts voidContract method:
// Store void_reason_id alongside existing void_reason text field
await this.databaseService.query(
  `UPDATE partner_contracts 
   SET status = 'VOID', void_reason = $2, void_reason_id = $3 
   WHERE id = $1`,
  [contractId, reasonText, reasonId]
);

Relationships

Dependencies:
  → DatabaseService (PostgreSQL queries)

Dependents:
  ← PartnerContractsService (reason ID storage)
  ← Admin Console Settings Page (CRUD UI)
  ← Admin Console Contract List (reason selection)

Troubleshooting

Issue Cause Fix
ConflictException on create Duplicate slug in context Change slug or use different context
NotFoundException on update/delete Invalid ID Verify ID exists in database
Empty list on GET No enabled reasons Add includeDisabled=true or create reasons
Reorder fails ID not in specified context Ensure all IDs belong to the context

Extension Points

To add a new context:

  1. Add constant to ACTION_REASON_CONTEXTS in entity
  2. Add constant to frontend partner.service.ts
  3. Add tab in ActionReasonsPage.tsx
  4. Add FK column to relevant table
  5. Update workflow service to use reason_id