diff --git a/docs/implementation/task-f3-approval-workflow-builder-foundation.md b/docs/implementation/task-f3-approval-workflow-builder-foundation.md
new file mode 100644
index 0000000..f321df5
--- /dev/null
+++ b/docs/implementation/task-f3-approval-workflow-builder-foundation.md
@@ -0,0 +1,112 @@
+# Task F.3: Approval Workflow Builder Foundation
+
+## Summary
+
+This task upgrades the existing approval workflow settings surface into a safer workflow builder foundation. Admins can now manage workflow metadata and steps through dedicated APIs and a richer settings UI without editing seed data directly.
+
+## Delivered
+
+- Extended approval workflow schema in [src/db/schema.ts](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/db/schema.ts):
+ - workflow: `description`, `isSystem`, `createdBy`, `updatedBy`
+ - step: `approvalMode`
+- Added migration [drizzle/0003_workflow_builder_foundation.sql](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/drizzle/0003_workflow_builder_foundation.sql)
+- Added builder validation schemas:
+ - [src/features/crm/approval/schemas/approval-workflow.schema.ts](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/features/crm/approval/schemas/approval-workflow.schema.ts)
+ - [src/features/crm/approval/schemas/approval-step.schema.ts](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/features/crm/approval/schemas/approval-step.schema.ts)
+- Added builder server rules in [src/features/crm/approval/server/workflow-builder.service.ts](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/features/crm/approval/server/workflow-builder.service.ts)
+- Replaced the settings APIs with builder-aware behavior under:
+ - [src/app/api/crm/settings/approval-workflows/route.ts](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/approval-workflows/route.ts)
+ - [src/app/api/crm/settings/approval-workflows/[id]/route.ts](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/approval-workflows/[id]/route.ts)
+ - [src/app/api/crm/settings/approval-workflows/[id]/clone/route.ts](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/approval-workflows/[id]/clone/route.ts)
+ - [src/app/api/crm/settings/approval-workflows/[id]/activate/route.ts](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/approval-workflows/[id]/activate/route.ts)
+ - [src/app/api/crm/settings/approval-workflows/[id]/deactivate/route.ts](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/approval-workflows/[id]/deactivate/route.ts)
+ - [src/app/api/crm/settings/approval-workflows/[id]/steps/route.ts](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/approval-workflows/[id]/steps/route.ts)
+ - [src/app/api/crm/settings/approval-workflows/[id]/steps/[stepId]/route.ts](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/approval-workflows/[id]/steps/[stepId]/route.ts)
+ - [src/app/api/crm/settings/approval-workflows/[id]/steps/reorder/route.ts](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/approval-workflows/[id]/steps/reorder/route.ts)
+- Rebuilt workflow builder client contracts and UI in:
+ - [src/features/foundation/approval/types.ts](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/features/foundation/approval/types.ts)
+ - [src/features/foundation/approval/service.ts](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/features/foundation/approval/service.ts)
+ - [src/features/foundation/approval/queries.ts](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/features/foundation/approval/queries.ts)
+ - [src/features/foundation/approval/mutations.ts](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/features/foundation/approval/mutations.ts)
+ - [src/features/foundation/approval/components/approval-workflow-settings.tsx](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/features/foundation/approval/components/approval-workflow-settings.tsx)
+
+## Workflow Safety Strategy
+
+F.3 uses the recommended lock strategy from the task:
+
+- if a workflow has ever been used by any approval request, the workflow becomes locked for metadata edits
+- locked workflows also block step create, update, delete, and reorder
+- admins must clone a used workflow before changing its behavior
+
+This prevents historical approval requests from silently changing meaning after configuration edits.
+
+## Clone and Activation Rules
+
+- clone copies workflow metadata and all active steps
+- cloned workflows start inactive
+- deactivation is blocked when:
+ - active approval requests still use the workflow
+ - active approval matrices still point to the workflow
+- deletion is blocked when:
+ - any approval request has ever used the workflow
+ - active approval matrices still point to the workflow
+
+## Step Builder Rules
+
+- steps are managed individually instead of replacing the whole step list
+- create supports inserting at a target `stepNumber`
+- delete reorders remaining steps to continuous numbering
+- reorder uses explicit `orderedStepIds`
+- active workflows must retain at least one required step
+
+## Approval Mode Support
+
+Stored step modes:
+
+- `sequential`
+- `any_one`
+- `all_required`
+
+Current runtime still executes sequentially only. The builder UI marks non-sequential modes as stored for future use instead of pretending they already affect runtime behavior.
+
+## Permissions Added
+
+Extended in [src/lib/auth/rbac.ts](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/lib/auth/rbac.ts):
+
+- `crm.approval.workflow.clone`
+- `crm.approval.workflow.activate`
+- `crm.approval.workflow.deactivate`
+- `crm.approval.workflow.step.manage`
+
+Existing workflow read/create/update/delete permissions remain in use.
+
+## Audit Actions
+
+Builder routes now emit:
+
+- `create_approval_workflow`
+- `update_approval_workflow`
+- `delete_approval_workflow`
+- `clone_approval_workflow`
+- `activate_approval_workflow`
+- `deactivate_approval_workflow`
+- `create_approval_step`
+- `update_approval_step`
+- `delete_approval_step`
+- `reorder_approval_steps`
+
+## Matrix Integration Hook
+
+Workflow list API now accepts optional filters:
+
+- `entityType`
+- `activeOnly`
+
+This keeps Approval Matrix integration future-ready without requiring F.3 to build the matrix UI itself.
+
+## Known Limitations
+
+- approval runtime still executes sequentially even if a step stores `any_one` or `all_required`
+- no drag-and-drop builder is included; reorder uses simple up/down actions
+- no workflow version table exists yet; lock-and-clone is the current compatibility strategy
+- matrix admin UI is still outside this task scope
diff --git a/drizzle/0003_workflow_builder_foundation.sql b/drizzle/0003_workflow_builder_foundation.sql
new file mode 100644
index 0000000..a6b4efa
--- /dev/null
+++ b/drizzle/0003_workflow_builder_foundation.sql
@@ -0,0 +1,14 @@
+ALTER TABLE "crm_approval_workflows"
+ADD COLUMN "description" text;
+--> statement-breakpoint
+ALTER TABLE "crm_approval_workflows"
+ADD COLUMN "is_system" boolean DEFAULT false NOT NULL;
+--> statement-breakpoint
+ALTER TABLE "crm_approval_workflows"
+ADD COLUMN "created_by" text NOT NULL DEFAULT '';
+--> statement-breakpoint
+ALTER TABLE "crm_approval_workflows"
+ADD COLUMN "updated_by" text NOT NULL DEFAULT '';
+--> statement-breakpoint
+ALTER TABLE "crm_approval_steps"
+ADD COLUMN "approval_mode" text DEFAULT 'sequential' NOT NULL;
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index 0104412..3c37a29 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -22,6 +22,13 @@
"when": 1782604800000,
"tag": "0002_calm_approval_matrix",
"breakpoints": true
+ },
+ {
+ "idx": 3,
+ "version": "7",
+ "when": 1782691200000,
+ "tag": "0003_workflow_builder_foundation",
+ "breakpoints": true
}
]
}
diff --git a/next-env.d.ts b/next-env.d.ts
index 9edff1c..c4b7818 100644
--- a/next-env.d.ts
+++ b/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import "./.next/types/routes.d.ts";
+import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/plans/task-f.3.md b/plans/task-f.3.md
new file mode 100644
index 0000000..ae1dea0
--- /dev/null
+++ b/plans/task-f.3.md
@@ -0,0 +1,504 @@
+# Task F.3: Approval Workflow Builder Foundation
+
+## Status
+
+Planned
+
+## Objective
+
+Build the Approval Workflow Builder foundation so admins can manage approval workflows and workflow steps without changing code or seed data.
+
+This task extends the F.2 Approval Matrix Foundation by making workflow definitions editable and maintainable through service/API/UI foundations.
+
+The goal is to support:
+
+* workflow list
+* workflow create/edit
+* workflow clone
+* workflow activate/deactivate
+* step add/update/delete
+* step reorder
+* matrix-ready workflow selection
+* safe compatibility with existing approval requests
+
+This task does not implement Notification, Reminder, Escalation, or full parallel approval behavior yet.
+
+---
+
+## Mandatory Review
+
+Review before implementation:
+
+* `AGENTS.md`
+* `docs/standards/project-foundations.md`
+* `docs/standards/task-catalog.md`
+* `docs/implementation/task-f2-approval-matrix-foundation.md`
+* existing Approval implementation
+* existing Approval Matrix implementation
+* `src/db/schema.ts`
+* `src/features/crm/approval/**`
+* `src/features/foundation/approval/**`
+* `src/features/foundation/audit-log/**`
+* `src/lib/auth/rbac.ts`
+* `docs/security/crm-authorization-boundaries.md`
+
+---
+
+## Current Foundation
+
+Already available:
+
+```txt
+crm_approval_workflows
+crm_approval_steps
+crm_approval_requests
+crm_approval_actions
+crm_approval_matrices
+
+Approval matrix resolver
+Quotation submit approval flow
+Default quotation matrix
+Approval audit foundation
+```
+
+Current limitation:
+
+```txt
+Approval workflows and steps are mostly seed/config driven.
+```
+
+This task makes them manageable through Builder APIs and UI foundation.
+
+---
+
+## Business Requirement
+
+Admin must be able to configure approval workflows such as:
+
+```txt
+Quotation Standard Approval
+ 1. Sales Manager
+ 2. Department Manager
+ 3. CEO
+
+Quotation Crane Executive Approval
+ 1. Sales Manager
+ 2. Product Head
+ 3. Finance
+ 4. CEO
+```
+
+Approval Matrix from F.2 can then point to any active workflow.
+
+---
+
+## Scope F.3.1 Workflow Builder Domain Rules
+
+Workflow has:
+
+```txt
+code
+name
+entityType
+description
+isActive
+steps
+```
+
+Step has:
+
+```txt
+stepNumber
+roleCode
+roleName
+approvalMode
+isRequired
+```
+
+Recommended `approvalMode` values:
+
+```txt
+sequential
+any_one
+all_required
+```
+
+For F.3 behavior:
+
+* `sequential` remains the only fully executed runtime mode if current approval engine supports only sequential
+* `any_one` and `all_required` can be stored for future use if schema supports it
+* if runtime does not support non-sequential modes, UI/API must clearly mark them as future/disabled
+
+---
+
+## Scope F.3.2 Schema Review / Extension
+
+Review existing tables:
+
+```txt
+crm_approval_workflows
+crm_approval_steps
+```
+
+Add missing fields only if needed:
+
+Recommended workflow fields:
+
+```txt
+description text null
+is_system boolean default false
+created_by text
+updated_by text
+```
+
+Recommended step fields:
+
+```txt
+approval_mode text default 'sequential'
+approver_type text default 'role'
+approver_ref text null
+sort_order integer
+```
+
+Definitions:
+
+```txt
+approver_type = role | user | permission | manager
+approver_ref = role code, user id, permission code, or manager rule key
+```
+
+If current phase should stay simple, use only:
+
+```txt
+roleCode
+roleName
+stepNumber
+isRequired
+```
+
+and defer advanced approver resolution to later tasks.
+
+---
+
+## Scope F.3.3 Workflow CRUD API
+
+Create or extend settings APIs:
+
+```txt
+GET /api/crm/settings/approval-workflows
+POST /api/crm/settings/approval-workflows
+GET /api/crm/settings/approval-workflows/[id]
+PATCH /api/crm/settings/approval-workflows/[id]
+DELETE /api/crm/settings/approval-workflows/[id]
+POST /api/crm/settings/approval-workflows/[id]/clone
+POST /api/crm/settings/approval-workflows/[id]/activate
+POST /api/crm/settings/approval-workflows/[id]/deactivate
+```
+
+Rules:
+
+* delete should be soft delete
+* do not allow hard delete
+* do not allow deleting workflow used by an active approval request
+* deactivation allowed only if not used by active matrix or active approval request unless forced by admin policy
+* clone creates a new workflow and copies steps
+* code must be unique within organization
+
+---
+
+## Scope F.3.4 Step Management API
+
+Create:
+
+```txt
+GET /api/crm/settings/approval-workflows/[id]/steps
+POST /api/crm/settings/approval-workflows/[id]/steps
+PATCH /api/crm/settings/approval-workflows/[id]/steps/[stepId]
+DELETE /api/crm/settings/approval-workflows/[id]/steps/[stepId]
+POST /api/crm/settings/approval-workflows/[id]/steps/reorder
+```
+
+Rules:
+
+* step numbers must be continuous: 1, 2, 3...
+* deleting a step reorders remaining steps
+* reorder must be transactional
+* at least one required step must remain for active workflow
+* cannot modify steps of workflow with active approval requests unless versioning is implemented
+* F.3 may choose strict lock behavior:
+
+ * if workflow is already used by any approval request, require clone before edit
+
+Recommended:
+
+```txt
+Existing used workflow = locked
+Edit by clone only
+```
+
+This prevents historical approval requests from changing meaning retroactively.
+
+---
+
+## Scope F.3.5 Workflow Version Safety
+
+Implement one of these strategies:
+
+### Option A: Lock Used Workflows
+
+If workflow has ever been used in approval request:
+
+```txt
+workflow cannot be edited
+steps cannot be edited
+admin must clone workflow
+```
+
+This is recommended for F.3.
+
+### Option B: Versioned Workflows
+
+Create workflow versions.
+
+This is more powerful but larger scope.
+
+Recommendation:
+
+```txt
+Use Option A in F.3.
+Defer versioned workflows to future task if needed.
+```
+
+---
+
+## Scope F.3.6 Workflow Builder UI
+
+Create settings page:
+
+```txt
+/dashboard/crm/settings/approval-workflows
+```
+
+Required UI:
+
+* workflow list
+* create workflow
+* edit workflow metadata
+* clone workflow
+* activate/deactivate workflow
+* view steps
+* add step
+* edit step
+* delete step
+* reorder steps
+* usage warning if workflow is locked
+
+Use existing UI conventions:
+
+```txt
+shadcn/ui
+card layout
+dialog/sheet forms
+table
+bento/card section where useful
+design tokens only
+```
+
+No drag-and-drop is required in F.3. Simple up/down reorder is enough.
+
+---
+
+## Scope F.3.7 Matrix Integration UI Hook
+
+Approval Matrix settings must be able to select an active workflow.
+
+If F.2 has no matrix UI yet, expose workflow list API so future matrix UI can consume it.
+
+Workflow list API should support:
+
+```txt
+entityType filter
+active only filter
+```
+
+---
+
+## Scope F.3.8 Validation Rules
+
+Create schemas:
+
+```txt
+src/features/crm/approval/schemas/approval-workflow.schema.ts
+src/features/crm/approval/schemas/approval-step.schema.ts
+```
+
+Workflow validation:
+
+* code required
+* code normalized lowercase / snake_case
+* name required
+* entityType required
+* code unique per organization
+* cannot update code if workflow is used
+
+Step validation:
+
+* roleCode required
+* roleName required
+* stepNumber positive integer
+* approvalMode required
+* isRequired boolean
+* cannot create duplicate stepNumber
+* cannot leave active workflow with no required step
+
+---
+
+## Scope F.3.9 Audit Logging
+
+Audit actions:
+
+```txt
+create_approval_workflow
+update_approval_workflow
+delete_approval_workflow
+clone_approval_workflow
+activate_approval_workflow
+deactivate_approval_workflow
+
+create_approval_step
+update_approval_step
+delete_approval_step
+reorder_approval_steps
+```
+
+Audit must capture:
+
+```txt
+workflowId
+stepId
+before
+after
+actor
+timestamp
+```
+
+---
+
+## Scope F.3.10 Permissions
+
+Add or verify permissions:
+
+```txt
+crm.approval.workflow.read
+crm.approval.workflow.create
+crm.approval.workflow.update
+crm.approval.workflow.delete
+crm.approval.workflow.clone
+crm.approval.workflow.activate
+crm.approval.workflow.deactivate
+crm.approval.workflow.step.manage
+```
+
+Rules:
+
+* Admin/settings-only feature
+* route handlers must not check role strings directly
+* use existing CRM authorization foundation
+* do not expose builder to normal sales users
+
+---
+
+## Scope F.3.11 Documentation
+
+Create:
+
+```txt
+docs/implementation/task-f3-approval-workflow-builder-foundation.md
+```
+
+Document:
+
+```txt
+workflow schema
+step schema
+lock strategy
+clone behavior
+API routes
+permission map
+known limitations
+```
+
+---
+
+## Explicit Non-Scope
+
+Do not implement:
+
+* Notification Center
+* Email sending
+* LINE sending
+* approval reminder
+* approval escalation
+* delegation
+* vacation substitute approval
+* manager hierarchy approver lookup
+* full workflow versioning
+* drag-and-drop builder
+* advanced parallel approval runtime
+* approval dashboard
+
+These belong to later F tasks.
+
+---
+
+## Verification
+
+Run:
+
+```txt
+npm exec tsc --noEmit
+npm run build
+```
+
+Manual verification:
+
+```txt
+Create workflow
+Edit unused workflow
+Add steps
+Edit steps
+Delete step and verify reorder
+Reorder steps
+Clone workflow
+Deactivate workflow
+Activate workflow
+Cannot edit used workflow if lock strategy is applied
+Cannot delete workflow used by active approval request
+Approval Matrix can resolve to newly created active workflow
+Submit quotation approval still works
+Approve/reject existing flow still works
+Audit logs are created
+```
+
+---
+
+## Definition of Done
+
+Task is complete when:
+
+* Approval Workflow Builder APIs exist
+* Approval Workflow settings UI exists
+* Admin can create/clone/activate/deactivate workflow
+* Admin can add/edit/delete/reorder steps
+* Used workflow edit safety is enforced
+* Approval Matrix can select active workflows
+* Existing quotation approval submit/approve/reject flow remains operational
+* Audit logs capture workflow and step maintenance
+
+Result:
+
+```txt
+Approval Workflow Builder Foundation = Established
+Approval Matrix Configuration = Maintainable
+Ready for F.4 Notification Framework
+```
diff --git a/src/app/api/crm/settings/approval-workflows/[id]/activate/route.ts b/src/app/api/crm/settings/approval-workflows/[id]/activate/route.ts
new file mode 100644
index 0000000..2e681fb
--- /dev/null
+++ b/src/app/api/crm/settings/approval-workflows/[id]/activate/route.ts
@@ -0,0 +1,46 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { auditAction } from '@/features/foundation/audit-log/service';
+import {
+ activateApprovalWorkflow,
+ getApprovalWorkflow
+} from '@/features/crm/approval/server/workflow-builder.service';
+import { PERMISSIONS } from '@/lib/auth/rbac';
+import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
+
+type Params = {
+ params: Promise<{ id: string }>;
+};
+
+export async function POST(_request: NextRequest, { params }: Params) {
+ try {
+ const { id } = await params;
+ const { organization, session } = await requireOrganizationAccess({
+ permission: PERMISSIONS.crmApprovalWorkflowActivate
+ });
+ const before = await getApprovalWorkflow(id, organization.id);
+ const updated = await activateApprovalWorkflow(id, organization.id, session.user.id);
+
+ await auditAction({
+ organizationId: organization.id,
+ userId: session.user.id,
+ entityType: 'crm_approval_workflow',
+ entityId: id,
+ action: 'activate_approval_workflow',
+ beforeData: before,
+ afterData: updated
+ });
+
+ return NextResponse.json({
+ success: true,
+ message: 'Approval workflow activated successfully'
+ });
+ } catch (error) {
+ if (error instanceof AuthError) {
+ return NextResponse.json({ message: error.message }, { status: error.status });
+ }
+ if (error instanceof Error) {
+ return NextResponse.json({ message: error.message }, { status: 400 });
+ }
+ return NextResponse.json({ message: 'Unable activate approval workflow' }, { status: 500 });
+ }
+}
diff --git a/src/app/api/crm/settings/approval-workflows/[id]/clone/route.ts b/src/app/api/crm/settings/approval-workflows/[id]/clone/route.ts
new file mode 100644
index 0000000..f6745fe
--- /dev/null
+++ b/src/app/api/crm/settings/approval-workflows/[id]/clone/route.ts
@@ -0,0 +1,43 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { auditAction } from '@/features/foundation/audit-log/service';
+import { cloneApprovalWorkflow, getApprovalWorkflow } from '@/features/crm/approval/server/workflow-builder.service';
+import { PERMISSIONS } from '@/lib/auth/rbac';
+import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
+
+type Params = {
+ params: Promise<{ id: string }>;
+};
+
+export async function POST(_request: NextRequest, { params }: Params) {
+ try {
+ const { id } = await params;
+ const { organization, session } = await requireOrganizationAccess({
+ permission: PERMISSIONS.crmApprovalWorkflowClone
+ });
+ const before = await getApprovalWorkflow(id, organization.id);
+ const cloned = await cloneApprovalWorkflow(id, organization.id, session.user.id);
+
+ await auditAction({
+ organizationId: organization.id,
+ userId: session.user.id,
+ entityType: 'crm_approval_workflow',
+ entityId: cloned.id,
+ action: 'clone_approval_workflow',
+ beforeData: before,
+ afterData: cloned
+ });
+
+ return NextResponse.json({
+ success: true,
+ message: 'Approval workflow cloned successfully'
+ });
+ } catch (error) {
+ if (error instanceof AuthError) {
+ return NextResponse.json({ message: error.message }, { status: error.status });
+ }
+ if (error instanceof Error) {
+ return NextResponse.json({ message: error.message }, { status: 400 });
+ }
+ return NextResponse.json({ message: 'Unable clone approval workflow' }, { status: 500 });
+ }
+}
diff --git a/src/app/api/crm/settings/approval-workflows/[id]/deactivate/route.ts b/src/app/api/crm/settings/approval-workflows/[id]/deactivate/route.ts
new file mode 100644
index 0000000..4f0aa0f
--- /dev/null
+++ b/src/app/api/crm/settings/approval-workflows/[id]/deactivate/route.ts
@@ -0,0 +1,46 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { auditAction } from '@/features/foundation/audit-log/service';
+import {
+ deactivateApprovalWorkflow,
+ getApprovalWorkflow
+} from '@/features/crm/approval/server/workflow-builder.service';
+import { PERMISSIONS } from '@/lib/auth/rbac';
+import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
+
+type Params = {
+ params: Promise<{ id: string }>;
+};
+
+export async function POST(_request: NextRequest, { params }: Params) {
+ try {
+ const { id } = await params;
+ const { organization, session } = await requireOrganizationAccess({
+ permission: PERMISSIONS.crmApprovalWorkflowDeactivate
+ });
+ const before = await getApprovalWorkflow(id, organization.id);
+ const updated = await deactivateApprovalWorkflow(id, organization.id, session.user.id);
+
+ await auditAction({
+ organizationId: organization.id,
+ userId: session.user.id,
+ entityType: 'crm_approval_workflow',
+ entityId: id,
+ action: 'deactivate_approval_workflow',
+ beforeData: before,
+ afterData: updated
+ });
+
+ return NextResponse.json({
+ success: true,
+ message: 'Approval workflow deactivated successfully'
+ });
+ } catch (error) {
+ if (error instanceof AuthError) {
+ return NextResponse.json({ message: error.message }, { status: error.status });
+ }
+ if (error instanceof Error) {
+ return NextResponse.json({ message: error.message }, { status: 400 });
+ }
+ return NextResponse.json({ message: 'Unable deactivate approval workflow' }, { status: 500 });
+ }
+}
diff --git a/src/app/api/crm/settings/approval-workflows/[id]/route.ts b/src/app/api/crm/settings/approval-workflows/[id]/route.ts
index 1093103..7e730a8 100644
--- a/src/app/api/crm/settings/approval-workflows/[id]/route.ts
+++ b/src/app/api/crm/settings/approval-workflows/[id]/route.ts
@@ -1,11 +1,12 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
-import { auditDelete, auditUpdate } from '@/features/foundation/audit-log/service';
+import { auditAction } from '@/features/foundation/audit-log/service';
+import { updateApprovalWorkflowSchema } from '@/features/crm/approval/schemas/approval-workflow.schema';
import {
getApprovalWorkflow,
softDeleteApprovalWorkflow,
updateApprovalWorkflow
-} from '@/features/foundation/approval/server/service';
+} from '@/features/crm/approval/server/workflow-builder.service';
import { PERMISSIONS } from '@/lib/auth/rbac';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
@@ -13,13 +14,6 @@ type Params = {
params: Promise<{ id: string }>;
};
-const workflowSchema = z.object({
- code: z.string().min(1).optional(),
- name: z.string().min(1).optional(),
- entityType: z.string().min(1).optional(),
- isActive: z.boolean().optional()
-});
-
export async function GET(_request: NextRequest, { params }: Params) {
try {
const { id } = await params;
@@ -41,7 +35,7 @@ export async function GET(_request: NextRequest, { params }: Params) {
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
- return NextResponse.json({ message: 'Unable to load approval workflow' }, { status: 500 });
+ return NextResponse.json({ message: 'Unable load approval workflow' }, { status: 500 });
}
}
@@ -51,15 +45,16 @@ export async function PATCH(request: NextRequest, { params }: Params) {
const { organization, session } = await requireOrganizationAccess({
permission: PERMISSIONS.crmApprovalWorkflowUpdate
});
- const payload = workflowSchema.parse(await request.json());
+ const payload = updateApprovalWorkflowSchema.parse(await request.json());
const before = await getApprovalWorkflow(id, organization.id);
- const updated = await updateApprovalWorkflow(id, organization.id, payload);
+ const updated = await updateApprovalWorkflow(id, organization.id, session.user.id, payload);
- await auditUpdate({
+ await auditAction({
organizationId: organization.id,
userId: session.user.id,
entityType: 'crm_approval_workflow',
entityId: id,
+ action: 'update_approval_workflow',
beforeData: before,
afterData: updated
});
@@ -81,7 +76,7 @@ export async function PATCH(request: NextRequest, { params }: Params) {
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
- return NextResponse.json({ message: 'Unable to update approval workflow' }, { status: 500 });
+ return NextResponse.json({ message: 'Unable update approval workflow' }, { status: 500 });
}
}
@@ -91,13 +86,14 @@ export async function DELETE(_request: NextRequest, { params }: Params) {
const { organization, session } = await requireOrganizationAccess({
permission: PERMISSIONS.crmApprovalWorkflowDelete
});
- const result = await softDeleteApprovalWorkflow(id, organization.id);
+ const result = await softDeleteApprovalWorkflow(id, organization.id, session.user.id);
- await auditDelete({
+ await auditAction({
organizationId: organization.id,
userId: session.user.id,
entityType: 'crm_approval_workflow',
entityId: id,
+ action: 'delete_approval_workflow',
beforeData: result.before,
afterData: result.after
});
@@ -113,6 +109,6 @@ export async function DELETE(_request: NextRequest, { params }: Params) {
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
- return NextResponse.json({ message: 'Unable to delete approval workflow' }, { status: 500 });
+ return NextResponse.json({ message: 'Unable delete approval workflow' }, { status: 500 });
}
}
diff --git a/src/app/api/crm/settings/approval-workflows/[id]/steps/[stepId]/route.ts b/src/app/api/crm/settings/approval-workflows/[id]/steps/[stepId]/route.ts
new file mode 100644
index 0000000..50d11a3
--- /dev/null
+++ b/src/app/api/crm/settings/approval-workflows/[id]/steps/[stepId]/route.ts
@@ -0,0 +1,90 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { z } from 'zod';
+import { auditAction } from '@/features/foundation/audit-log/service';
+import { updateApprovalStepSchema } from '@/features/crm/approval/schemas/approval-step.schema';
+import {
+ deleteApprovalWorkflowStep,
+ getApprovalWorkflow,
+ updateApprovalWorkflowStep
+} from '@/features/crm/approval/server/workflow-builder.service';
+import { PERMISSIONS } from '@/lib/auth/rbac';
+import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
+
+type Params = {
+ params: Promise<{ id: string; stepId: string }>;
+};
+
+export async function PATCH(request: NextRequest, { params }: Params) {
+ try {
+ const { id, stepId } = await params;
+ const { organization, session } = await requireOrganizationAccess({
+ permission: PERMISSIONS.crmApprovalWorkflowStepManage
+ });
+ const payload = updateApprovalStepSchema.parse(await request.json());
+ const before = await getApprovalWorkflow(id, organization.id);
+ const updated = await updateApprovalWorkflowStep(stepId, organization.id, payload);
+
+ await auditAction({
+ organizationId: organization.id,
+ userId: session.user.id,
+ entityType: 'crm_approval_step',
+ entityId: stepId,
+ action: 'update_approval_step',
+ beforeData: before.steps,
+ afterData: updated
+ });
+
+ return NextResponse.json({
+ success: true,
+ message: 'Approval workflow step updated successfully'
+ });
+ } catch (error) {
+ if (error instanceof AuthError) {
+ return NextResponse.json({ message: error.message }, { status: error.status });
+ }
+ if (error instanceof z.ZodError) {
+ return NextResponse.json(
+ { message: error.issues[0]?.message ?? 'Invalid payload' },
+ { status: 400 }
+ );
+ }
+ if (error instanceof Error) {
+ return NextResponse.json({ message: error.message }, { status: 400 });
+ }
+ return NextResponse.json({ message: 'Unable update approval workflow step' }, { status: 500 });
+ }
+}
+
+export async function DELETE(_request: NextRequest, { params }: Params) {
+ try {
+ const { id, stepId } = await params;
+ const { organization, session } = await requireOrganizationAccess({
+ permission: PERMISSIONS.crmApprovalWorkflowStepManage
+ });
+ const before = await getApprovalWorkflow(id, organization.id);
+ const result = await deleteApprovalWorkflowStep(stepId, organization.id);
+
+ await auditAction({
+ organizationId: organization.id,
+ userId: session.user.id,
+ entityType: 'crm_approval_step',
+ entityId: stepId,
+ action: 'delete_approval_step',
+ beforeData: before.steps,
+ afterData: result.steps
+ });
+
+ return NextResponse.json({
+ success: true,
+ message: 'Approval workflow step deleted successfully'
+ });
+ } catch (error) {
+ if (error instanceof AuthError) {
+ return NextResponse.json({ message: error.message }, { status: error.status });
+ }
+ if (error instanceof Error) {
+ return NextResponse.json({ message: error.message }, { status: 400 });
+ }
+ return NextResponse.json({ message: 'Unable delete approval workflow step' }, { status: 500 });
+ }
+}
diff --git a/src/app/api/crm/settings/approval-workflows/[id]/steps/reorder/route.ts b/src/app/api/crm/settings/approval-workflows/[id]/steps/reorder/route.ts
new file mode 100644
index 0000000..a938b0a
--- /dev/null
+++ b/src/app/api/crm/settings/approval-workflows/[id]/steps/reorder/route.ts
@@ -0,0 +1,55 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { z } from 'zod';
+import { auditAction } from '@/features/foundation/audit-log/service';
+import { reorderApprovalStepsSchema } from '@/features/crm/approval/schemas/approval-step.schema';
+import {
+ getApprovalWorkflow,
+ reorderApprovalWorkflowSteps
+} from '@/features/crm/approval/server/workflow-builder.service';
+import { PERMISSIONS } from '@/lib/auth/rbac';
+import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
+
+type Params = {
+ params: Promise<{ id: string }>;
+};
+
+export async function POST(request: NextRequest, { params }: Params) {
+ try {
+ const { id } = await params;
+ const { organization, session } = await requireOrganizationAccess({
+ permission: PERMISSIONS.crmApprovalWorkflowStepManage
+ });
+ const payload = reorderApprovalStepsSchema.parse(await request.json());
+ const before = await getApprovalWorkflow(id, organization.id);
+ const steps = await reorderApprovalWorkflowSteps(id, organization.id, payload);
+
+ await auditAction({
+ organizationId: organization.id,
+ userId: session.user.id,
+ entityType: 'crm_approval_step',
+ entityId: id,
+ action: 'reorder_approval_steps',
+ beforeData: before.steps,
+ afterData: steps
+ });
+
+ return NextResponse.json({
+ success: true,
+ message: 'Approval workflow steps reordered successfully'
+ });
+ } catch (error) {
+ if (error instanceof AuthError) {
+ return NextResponse.json({ message: error.message }, { status: error.status });
+ }
+ if (error instanceof z.ZodError) {
+ return NextResponse.json(
+ { message: error.issues[0]?.message ?? 'Invalid payload' },
+ { status: 400 }
+ );
+ }
+ if (error instanceof Error) {
+ return NextResponse.json({ message: error.message }, { status: 400 });
+ }
+ return NextResponse.json({ message: 'Unable reorder approval workflow steps' }, { status: 500 });
+ }
+}
diff --git a/src/app/api/crm/settings/approval-workflows/[id]/steps/route.ts b/src/app/api/crm/settings/approval-workflows/[id]/steps/route.ts
index 7ff88bf..115c651 100644
--- a/src/app/api/crm/settings/approval-workflows/[id]/steps/route.ts
+++ b/src/app/api/crm/settings/approval-workflows/[id]/steps/route.ts
@@ -1,50 +1,67 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
-import { auditUpdate } from '@/features/foundation/audit-log/service';
+import { auditAction } from '@/features/foundation/audit-log/service';
+import { createApprovalStepSchema } from '@/features/crm/approval/schemas/approval-step.schema';
import {
+ createApprovalWorkflowStep,
getApprovalWorkflow,
- replaceApprovalWorkflowSteps
-} from '@/features/foundation/approval/server/service';
-import { BUSINESS_ROLES, PERMISSIONS } from '@/lib/auth/rbac';
+ listApprovalWorkflowSteps
+} from '@/features/crm/approval/server/workflow-builder.service';
+import { PERMISSIONS } from '@/lib/auth/rbac';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
type Params = {
params: Promise<{ id: string }>;
};
-const stepSchema = z.object({
- stepNumber: z.number().int().min(1),
- roleCode: z.enum(BUSINESS_ROLES),
- roleName: z.string().min(1),
- isRequired: z.boolean().optional()
-});
+export async function GET(_request: NextRequest, { params }: Params) {
+ try {
+ const { id } = await params;
+ const { organization } = await requireOrganizationAccess({
+ permission: PERMISSIONS.crmApprovalWorkflowRead
+ });
+ const items = await listApprovalWorkflowSteps(id, organization.id);
-const payloadSchema = z.object({
- steps: z.array(stepSchema)
-});
+ return NextResponse.json({
+ success: true,
+ time: new Date().toISOString(),
+ message: 'Approval workflow steps loaded successfully',
+ items
+ });
+ } catch (error) {
+ if (error instanceof AuthError) {
+ return NextResponse.json({ message: error.message }, { status: error.status });
+ }
+ if (error instanceof Error) {
+ return NextResponse.json({ message: error.message }, { status: 400 });
+ }
+ return NextResponse.json({ message: 'Unable load approval workflow steps' }, { status: 500 });
+ }
+}
-export async function PUT(request: NextRequest, { params }: Params) {
+export async function POST(request: NextRequest, { params }: Params) {
try {
const { id } = await params;
const { organization, session } = await requireOrganizationAccess({
- permission: PERMISSIONS.crmApprovalWorkflowUpdate
+ permission: PERMISSIONS.crmApprovalWorkflowStepManage
});
- const payload = payloadSchema.parse(await request.json());
+ const payload = createApprovalStepSchema.parse(await request.json());
const before = await getApprovalWorkflow(id, organization.id);
- const steps = await replaceApprovalWorkflowSteps(id, organization.id, payload.steps);
+ const steps = await createApprovalWorkflowStep(id, organization.id, payload);
- await auditUpdate({
+ await auditAction({
organizationId: organization.id,
userId: session.user.id,
entityType: 'crm_approval_step',
entityId: id,
+ action: 'create_approval_step',
beforeData: before.steps,
afterData: steps
});
return NextResponse.json({
success: true,
- message: 'Approval workflow steps updated successfully'
+ message: 'Approval workflow step created successfully'
});
} catch (error) {
if (error instanceof AuthError) {
@@ -59,9 +76,6 @@ export async function PUT(request: NextRequest, { params }: Params) {
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
- return NextResponse.json(
- { message: 'Unable to update approval workflow steps' },
- { status: 500 }
- );
+ return NextResponse.json({ message: 'Unable create approval workflow step' }, { status: 500 });
}
}
diff --git a/src/app/api/crm/settings/approval-workflows/route.ts b/src/app/api/crm/settings/approval-workflows/route.ts
index 3702036..b5077c3 100644
--- a/src/app/api/crm/settings/approval-workflows/route.ts
+++ b/src/app/api/crm/settings/approval-workflows/route.ts
@@ -1,26 +1,24 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
-import { auditCreate } from '@/features/foundation/audit-log/service';
+import { auditAction } from '@/features/foundation/audit-log/service';
+import { createApprovalWorkflowSchema, approvalWorkflowFilterSchema } from '@/features/crm/approval/schemas/approval-workflow.schema';
import {
createApprovalWorkflow,
listApprovalWorkflows
-} from '@/features/foundation/approval/server/service';
-import { BUSINESS_ROLES, PERMISSIONS } from '@/lib/auth/rbac';
+} from '@/features/crm/approval/server/workflow-builder.service';
+import { PERMISSIONS } from '@/lib/auth/rbac';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
-const workflowSchema = z.object({
- code: z.string().min(1),
- name: z.string().min(1),
- entityType: z.string().min(1),
- isActive: z.boolean().optional()
-});
-
-export async function GET(_request: NextRequest) {
+export async function GET(request: NextRequest) {
try {
const { organization } = await requireOrganizationAccess({
permission: PERMISSIONS.crmApprovalWorkflowRead
});
- const items = await listApprovalWorkflows(organization.id);
+ const filters = approvalWorkflowFilterSchema.parse({
+ entityType: request.nextUrl.searchParams.get('entityType') ?? undefined,
+ activeOnly: request.nextUrl.searchParams.get('activeOnly') ?? undefined
+ });
+ const items = await listApprovalWorkflows(organization.id, filters);
return NextResponse.json({
success: true,
@@ -32,10 +30,16 @@ export async function GET(_request: NextRequest) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
+ if (error instanceof z.ZodError) {
+ return NextResponse.json(
+ { message: error.issues[0]?.message ?? 'Invalid filters' },
+ { status: 400 }
+ );
+ }
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
- return NextResponse.json({ message: 'Unable to load approval workflows' }, { status: 500 });
+ return NextResponse.json({ message: 'Unable load approval workflows' }, { status: 500 });
}
}
@@ -44,21 +48,21 @@ export async function POST(request: NextRequest) {
const { organization, session } = await requireOrganizationAccess({
permission: PERMISSIONS.crmApprovalWorkflowCreate
});
- const payload = workflowSchema.parse(await request.json());
- const created = await createApprovalWorkflow(organization.id, payload);
+ const payload = createApprovalWorkflowSchema.parse(await request.json());
+ const created = await createApprovalWorkflow(organization.id, session.user.id, payload);
- await auditCreate({
+ await auditAction({
organizationId: organization.id,
userId: session.user.id,
entityType: 'crm_approval_workflow',
entityId: created.id,
+ action: 'create_approval_workflow',
afterData: created
});
return NextResponse.json({
success: true,
- message: 'Approval workflow created successfully',
- allowedRoles: BUSINESS_ROLES
+ message: 'Approval workflow created successfully'
});
} catch (error) {
if (error instanceof AuthError) {
@@ -73,6 +77,6 @@ export async function POST(request: NextRequest) {
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
- return NextResponse.json({ message: 'Unable to create approval workflow' }, { status: 500 });
+ return NextResponse.json({ message: 'Unable create approval workflow' }, { status: 500 });
}
}
diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx
index 92cddb5..79795d3 100644
--- a/src/app/not-found.tsx
+++ b/src/app/not-found.tsx
@@ -1,24 +1,33 @@
-'use client';
+"use client";
-import { useRouter } from 'next/navigation';
+import { useRouter } from "next/navigation";
-import { Button } from '@/components/ui/button';
+import { Button } from "@/components/ui/button";
export default function NotFound() {
const router = useRouter();
return (
-
-
+
+
404
-
Something's missing
-
Sorry, the page you are looking for doesn't exist or has been moved.
-
-