# approval

## Client

`import { ... } from "wcz-layout/data/client";`

- Query:
  `approvalsQueryOptions(GetApprovalsSchema)` → `Approval[]` // approvals of the application, optionally filtered; `appName` is sent as `applicationName`
  `approvalQueryOptions(GetApprovalSchema)` → `Approval` // single approval
- Mutation:
  `createApprovalMutationOptions({ queryClient })` → `mutate(CreateApprovalSchema)` → `Approval` // create a new approval request
  `approveApprovalMutationOptions({ queryClient })` → `mutate(ApproveApprovalSchema)` → `Approval` // current approver's decision (`"Approved"` / `"Rejected"`) on the active step
  `resubmitApprovalMutationOptions({ queryClient })` → `mutate(ResubmitApprovalSchema)` → `Approval` // resubmit with new flows (same flow shape as create)
  `withdrawApprovalMutationOptions({ queryClient })` → `mutate(WithdrawApprovalSchema)` → `void` // withdraw the request
  `cancelApprovalMutationOptions({ queryClient })` → `mutate(CancelApprovalSchema)` → `void` // cancel the request

## Server

`import { ... } from "wcz-layout/data/server";`

- Fetch:
  `getApprovals({ data: GetApprovalsSchema })` → `Approval[]` // approvals of the application, optionally filtered
  `getApproval({ data: GetApprovalSchema })` → `Approval` // single approval
- Mutate:
  `createApproval({ data: CreateApprovalSchema })` → `Approval` // create a new approval request
  `approveApproval({ data: ApproveApprovalSchema })` → `Approval` // approve / reject the active step
  `resubmitApproval({ data: ResubmitApprovalSchema })` → `Approval` // resubmit with new flows
  `withdrawApproval({ data: WithdrawApprovalSchema })` → `void` // withdraw the request
  `cancelApproval({ data: CancelApprovalSchema })` → `void` // cancel the request

## Schemas/Models

```ts
const ApprovalRequestType = z.enum(["Single", "Batch"]);

const ApprovalStatus = z.enum([
  "WaitingForApproval",
  "Approved",
  "PartiallyApproved",
  "Rejected",
  "Withdrawn",
  "Cancelled",
]);

const ApprovalStepResult = z.enum([
  "NotAvailable",
  "FutureApproval",
  "WaitingForApproval",
  "Approved",
  "Rejected",
  "Skipped",
  "Withdrawn",
  "Cancelled",
]);

const StepApprovalOrder = z.enum(["Serial", "Parallel", "OneOfThem"]);

const ApprovalEmployeeSchema = z.object({
  employeeId: z.string().trim().min(1).max(20),
  name: z.string().trim().min(1).max(50),
  email: z.email().min(1).max(100),
});

const ApprovalProxySchema = z.object({
  proxy: ApprovalEmployeeSchema,
});

const ApprovalFlowStepSchema = z.object({
  id: z.uuid(),
  order: z.number(),
  approverRole: z.string().trim().min(1).max(100).default("Approver").optional(),
  approver: ApprovalEmployeeSchema,
  actualApprover: ApprovalEmployeeSchema.optional(),
  proxies: z.array(ApprovalProxySchema),
  result: ApprovalStepResult.default("NotAvailable").optional(),
  resultDate: z.date().optional(),
  resultComment: z.string().trim().max(2000).optional(),
});

const ApprovalFlowSchema = z.object({
  id: z.uuid(),
  approvalStage: z.number(),
  batchSequence: z.number().optional(),
  approvalSequence: z.number(),
  stepApprovalOrder: StepApprovalOrder,
  steps: z.array(ApprovalFlowStepSchema),
  isActive: z.boolean().default(false).optional(),
});

const ApprovalCoRequestorGroupSchema = z.object({
  role: z.string().trim().min(1),
  email: z.email(),
});

const ApprovalCoRequestorEmployeeSchema = z.object({
  employeeId: z.string().trim().min(1),
  name: z.string().trim().min(1),
  email: z.email(),
});

const ApprovalCoRequestorPrincipalSchema = z.union([
  ApprovalCoRequestorGroupSchema,
  ApprovalCoRequestorEmployeeSchema,
]);

const ApprovalCoRequestorPermissionsSchema = z.object({
  withdraw: z.boolean().default(false).optional(),
  cancel: z.boolean().default(false).optional(),
});

const ApprovalCoRequestorSchema = z.object({
  id: z.uuid(),
  principal: ApprovalCoRequestorPrincipalSchema,
  permissions: ApprovalCoRequestorPermissionsSchema,
});

const HqApprovalColSchema = z.object({
  title: z.string().optional(),
  text: z.string().optional(),
  textColor: z.string().optional(),
  textSize: z.string().optional(),
});

const HqApprovalRowSchema = z.object({ col: z.array(HqApprovalColSchema) });

const HqApprovalSectionSchema = z.object({
  row: z.array(HqApprovalRowSchema),
  title: z.string().optional(),
  type: z.string().optional(),
  text: z.string().optional(),
});

const HqApprovalPageSchema = z.object({ section: z.array(HqApprovalSectionSchema) });

const HqApprovalSummarySchema = z.object({ page: z.array(HqApprovalPageSchema) });

const HqApprovalFormDataSchema = z.object({
  systemId: z.string().optional(),
  formType: z.string().optional(),
  formId: z.string().optional(),
  stepName: z.string().optional(),
  userId: z.string().optional(),
  title: z.string().optional(),
  subject: z.string().optional(),
  summary: HqApprovalSummarySchema.optional(),
  messageId: z.string().optional(),
  attachList: z.string().optional(),
  chkResult: z.string().optional(),
  apprSummary: z.string().optional(),
  approvalComment: z.string().optional(),
});

const HqApprovalHeaderSchema = z.object({
  serviceId: z.string().optional(),
  authorizationCode: z.string().optional(),
  dataCreatedTime: z.string().optional(),
  characterEnable: z.boolean().default(false).optional(),
});

const HqApprovalRequestSchema = z.object({
  header: HqApprovalHeaderSchema.optional(),
  formData: HqApprovalFormDataSchema.optional(),
});

const ApprovalSchema = z.object({
  id: z.uuid(),
  number: z.string().trim().min(1).max(30),
  applicationName: z.string().trim().min(1).max(50),
  type: ApprovalRequestType.default("Single").optional(),
  status: ApprovalStatus,
  created: z.date(),
  createdBy: ApprovalEmployeeSchema,
  updated: z.date().optional(),
  updatedBy: ApprovalEmployeeSchema.optional(),
  approvalFlows: z.array(ApprovalFlowSchema).min(1),
  currentApprovers: z.array(
    ApprovalEmployeeSchema.extend({ proxies: z.array(ApprovalProxySchema) }),
  ),
  coRequestors: z.array(ApprovalCoRequestorSchema).optional(),
});

const GetApprovalsSchema = z.object({
  appName: z.string().min(1).max(255),
  status: ApprovalStatus.optional(),
  approverEmployeeId: z.string().min(1).max(20).optional(),
  stepResult: ApprovalStepResult.optional(),
});

const GetApprovalSchema = z.object({
  id: z.uuid(),
});

const CreateApprovalSchema = ApprovalSchema.pick({
  id: true,
  number: true,
  applicationName: true,
  type: true,
  status: true,
  approvalFlows: true,
}).extend({
  emailBody: z.string().trim().min(1),
  hqApprovalRequest: HqApprovalRequestSchema.optional(),
  approvalFlows: z
    .array(
      ApprovalFlowSchema.pick({
        approvalStage: true,
        batchSequence: true,
        stepApprovalOrder: true,
        steps: true,
      }).extend({
        steps: z.array(ApprovalFlowStepSchema.pick({ approverRole: true, approver: true })).min(1),
      }),
    )
    .min(1),
  coRequestors: z.array(ApprovalCoRequestorSchema).optional(),
});

const ApproveApprovalSchema = z.object({
  id: z.uuid(),
  result: ApprovalStepResult,
  resultComment: z.string().trim().max(2000).optional(),
  emailBody: z.string().trim().min(1),
});

const ResubmitApprovalSchema = ApprovalSchema.pick({
  id: true,
  approvalFlows: true,
}).extend({
  emailBody: z.string().trim().min(1),
  approvalFlows: z
    .array(
      ApprovalFlowSchema.pick({
        approvalStage: true,
        batchSequence: true,
        stepApprovalOrder: true,
        steps: true,
      }).extend({
        steps: z.array(ApprovalFlowStepSchema.pick({ approverRole: true, approver: true })).min(1),
      }),
    )
    .min(1),
});

const CancelApprovalSchema = z.object({
  id: z.uuid(),
  comment: z.string().trim().min(1).max(2000),
  emailBody: z.string().trim().min(1),
});

const WithdrawApprovalSchema = z.object({
  id: z.uuid(),
  comment: z.string().trim().min(1).max(2000),
  emailBody: z.string().trim().min(1),
});
```

## Examples

```ts
import {
  approvalQueryOptions,
  approvalsQueryOptions,
  approveApprovalMutationOptions,
  createApprovalMutationOptions,
} from "wcz-layout/data/client";
import { createApproval } from "wcz-layout/data/server";
import { uuidv7 } from "wcz-layout/utils";

const { queryClient } = Route.useRouteContext();

// fetch
const { data: approvals = [] } = useQuery(
  approvalsQueryOptions({ appName: packageJson.name, status: "WaitingForApproval" }),
);
const { data: approval } = useQuery(approvalQueryOptions({ id }));

// create
const { mutate: create } = useMutation(createApprovalMutationOptions({ queryClient }));

create({
  id: uuidv7(),
  number: "REQ-0001",
  applicationName: packageJson.name,
  status: "WaitingForApproval",
  emailBody: "<p>Please approve REQ-0001.</p>",
  approvalFlows: [
    {
      approvalStage: 1,
      stepApprovalOrder: "Serial",
      steps: [{ approver: { employeeId, name, email } }],
    },
  ],
});

// approve / reject
const { mutate: approve } = useMutation(approveApprovalMutationOptions({ queryClient }));

approve({ id, result: "Approved", emailBody: "<p>REQ-0001 was approved.</p>" });

// server (inside another server function)
const created = await createApproval({
  data: { id, number, applicationName, status, emailBody, approvalFlows },
});
```
