feat(collections): Add AssignmentTransfers collection and migrations
Add new AssignmentTransfers collection with fields for managing assignment transfer requests: - requestor, requestedAssignment, currentAssignment (relationships to users/assignments) - status (pending/approved/rejected/appealed/escalated/completed/cancelled) - rejectionReason, appealReason (for rejections and appeals) - transferAt, approvedAt, appealedAt, completedAt (audit timestamps) - transferLead (relationship to users, current and requested assignment leaders) - requestMessage, appealMessage (optional context messages) Add database migrations: - 20260831_180233_assignment_transfers: Create the main collection table - 20260901_014006_add_assignment_transfer_event_target: Add assignment-transfers to GameEventLogs target collections The collection enables the assignment transfer workflow where users can request transfers, leaders can approve/reject with reasons, and users can appeal rejections.
This commit is contained in:
parent
7a4db71c25
commit
be459cde0f
5 changed files with 40817 additions and 0 deletions
316
src/collections/users/AssignmentTransfers.ts
Normal file
316
src/collections/users/AssignmentTransfers.ts
Normal file
|
|
@ -0,0 +1,316 @@
|
||||||
|
import { CollectionConfig, Where } from "payload";
|
||||||
|
import { isDeveloper } from "@/utils/access-control/isRole";
|
||||||
|
import hasRoles from "@/utils/access-control/hasRoles";
|
||||||
|
import type { User } from "@/payload-types";
|
||||||
|
|
||||||
|
export const TRANSFER_STATUSES = [
|
||||||
|
"pending",
|
||||||
|
"rejected",
|
||||||
|
"awaiting_superuser",
|
||||||
|
"completed",
|
||||||
|
"cancelled",
|
||||||
|
"denied",
|
||||||
|
] as const;
|
||||||
|
export type TransferStatus = (typeof TRANSFER_STATUSES)[number];
|
||||||
|
|
||||||
|
export const TRANSFER_DECISIONS = ["pending", "approved", "rejected", "not_required"] as const;
|
||||||
|
export type TransferDecision = (typeof TRANSFER_DECISIONS)[number];
|
||||||
|
|
||||||
|
export const TRANSFER_ESCALATION_REASONS = ["no_leaders", "appeal", "leader_changed"] as const;
|
||||||
|
export type TransferEscalationReason = (typeof TRANSFER_ESCALATION_REASONS)[number];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single source of truth for the assignment-transfer state machine. Both the
|
||||||
|
* beforeChange hook below and the lib core in src/lib/transfers/index.ts must
|
||||||
|
* agree on which transitions are legal — edit only this table, never both.
|
||||||
|
*/
|
||||||
|
export const TRANSFER_STATUS_TRANSITIONS: Record<TransferStatus, readonly TransferStatus[]> = {
|
||||||
|
pending: ["completed", "rejected", "cancelled", "awaiting_superuser"],
|
||||||
|
rejected: ["awaiting_superuser"],
|
||||||
|
awaiting_superuser: ["completed", "denied"],
|
||||||
|
completed: [],
|
||||||
|
cancelled: [],
|
||||||
|
denied: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TRANSFER_DECISION_TRANSITIONS: Record<TransferDecision, readonly TransferDecision[]> = {
|
||||||
|
pending: ["approved", "rejected"],
|
||||||
|
approved: [],
|
||||||
|
rejected: [],
|
||||||
|
not_required: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const DECISION_FIELDS = ["fromLeaderDecision", "toLeaderDecision"] as const;
|
||||||
|
|
||||||
|
export const AssignmentTransfers: CollectionConfig = {
|
||||||
|
slug: "assignment-transfers",
|
||||||
|
admin: {
|
||||||
|
group: "Users",
|
||||||
|
useAsTitle: "id",
|
||||||
|
defaultColumns: ["id", "requester", "fromAssignment", "toAssignment", "status", "createdAt"],
|
||||||
|
listSearchableFields: ["id"],
|
||||||
|
pagination: { defaultLimit: 50 },
|
||||||
|
},
|
||||||
|
access: {
|
||||||
|
read: ({ req }) => {
|
||||||
|
if (!req.user) return false;
|
||||||
|
const user = req.user as User;
|
||||||
|
if (hasRoles(["admin", "developer"], user)) return true;
|
||||||
|
// Requester, or the current leader of either side. The dotted conditions
|
||||||
|
// follow each assignment relationship to its live leader field, so a newly
|
||||||
|
// appointed leader gains access to older pending requests automatically.
|
||||||
|
return {
|
||||||
|
or: [
|
||||||
|
{ requester: { equals: user.id } },
|
||||||
|
{ "fromAssignment.leader": { equals: user.id } },
|
||||||
|
{ "toAssignment.leader": { equals: user.id } },
|
||||||
|
],
|
||||||
|
} as Where;
|
||||||
|
},
|
||||||
|
create: ({ req }) => !!req.user,
|
||||||
|
update: isDeveloper,
|
||||||
|
delete: isDeveloper,
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
beforeChange: [
|
||||||
|
({ data, operation, context }) => {
|
||||||
|
if (operation !== "update") return data;
|
||||||
|
const original = context.originalDoc as Record<string, unknown> | undefined;
|
||||||
|
if (!original) return data;
|
||||||
|
|
||||||
|
const nextStatus = data?.status as TransferStatus | undefined;
|
||||||
|
const prevStatus = original.status as TransferStatus | undefined;
|
||||||
|
if (nextStatus !== undefined && prevStatus !== undefined && nextStatus !== prevStatus) {
|
||||||
|
const allowed = TRANSFER_STATUS_TRANSITIONS[prevStatus] ?? [];
|
||||||
|
if (!allowed.includes(nextStatus)) {
|
||||||
|
throw new Error(
|
||||||
|
`Illegal assignment-transfer status transition: ${prevStatus} -> ${nextStatus}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const field of DECISION_FIELDS) {
|
||||||
|
const next = data?.[field] as TransferDecision | undefined;
|
||||||
|
const prev = original[field] as TransferDecision | undefined;
|
||||||
|
if (next !== undefined && next !== prev) {
|
||||||
|
const allowed = TRANSFER_DECISION_TRANSITIONS[prev ?? "pending"] ?? [];
|
||||||
|
if (!allowed.includes(next)) {
|
||||||
|
throw new Error(
|
||||||
|
`Illegal assignment-transfer decision transition on ${field}: ${prev} -> ${next}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "requester",
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: "users",
|
||||||
|
required: true,
|
||||||
|
admin: {
|
||||||
|
description: "The user requesting the transfer.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fromAssignment",
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: "assignments",
|
||||||
|
required: true,
|
||||||
|
admin: {
|
||||||
|
description: "The assignment the requester currently belongs to.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "toAssignment",
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: "assignments",
|
||||||
|
required: true,
|
||||||
|
admin: {
|
||||||
|
description: "The assignment the requester wants to join.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "status",
|
||||||
|
type: "select",
|
||||||
|
required: true,
|
||||||
|
defaultValue: "pending",
|
||||||
|
index: true,
|
||||||
|
options: TRANSFER_STATUSES.map((value) => ({ label: value, value })),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "escalationReason",
|
||||||
|
type: "select",
|
||||||
|
options: TRANSFER_ESCALATION_REASONS.map((value) => ({ label: value, value })),
|
||||||
|
admin: {
|
||||||
|
description:
|
||||||
|
"Only set when status is awaiting_superuser. no_leaders: neither side had a leader at creation; appeal: requester appealed a rejection; leader_changed: leadership changed mid-pending.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fromLeaderDecision",
|
||||||
|
type: "select",
|
||||||
|
required: true,
|
||||||
|
defaultValue: "pending",
|
||||||
|
options: TRANSFER_DECISIONS.map((value) => ({ label: value, value })),
|
||||||
|
admin: {
|
||||||
|
description:
|
||||||
|
"The source assignment leader's decision. not_required is stamped at creation when the source has no leader.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "toLeaderDecision",
|
||||||
|
type: "select",
|
||||||
|
required: true,
|
||||||
|
defaultValue: "pending",
|
||||||
|
options: TRANSFER_DECISIONS.map((value) => ({ label: value, value })),
|
||||||
|
admin: {
|
||||||
|
description:
|
||||||
|
"The destination assignment leader's decision. not_required is stamped at creation when the destination has no leader.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fromDecisionBy",
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: "users",
|
||||||
|
admin: {
|
||||||
|
description: "The user who recorded the source-side decision (audit).",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "toDecisionBy",
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: "users",
|
||||||
|
admin: {
|
||||||
|
description: "The user who recorded the destination-side decision (audit).",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fromDecidedAt",
|
||||||
|
type: "date",
|
||||||
|
admin: {
|
||||||
|
description: "When the source-side decision was recorded.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "toDecidedAt",
|
||||||
|
type: "date",
|
||||||
|
admin: {
|
||||||
|
description: "When the destination-side decision was recorded.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fromLeaderSnapshot",
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: "users",
|
||||||
|
admin: {
|
||||||
|
description:
|
||||||
|
"The source assignment's leader at creation time (audit snapshot; may be null).",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "toLeaderSnapshot",
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: "users",
|
||||||
|
admin: {
|
||||||
|
description:
|
||||||
|
"The destination assignment's leader at creation time (audit snapshot; may be null).",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rejectionReason",
|
||||||
|
type: "textarea",
|
||||||
|
admin: {
|
||||||
|
description: "Required when a side rejects the request.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "requestNote",
|
||||||
|
type: "textarea",
|
||||||
|
admin: {
|
||||||
|
description: "Optional note from the requester explaining the transfer.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "appealNote",
|
||||||
|
type: "textarea",
|
||||||
|
admin: {
|
||||||
|
description: "Optional note from the requester when appealing a rejection.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "resolvedBy",
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: "users",
|
||||||
|
admin: {
|
||||||
|
description: "The admin/developer who resolved an escalated request (audit).",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "resolutionNote",
|
||||||
|
type: "textarea",
|
||||||
|
admin: {
|
||||||
|
description: "Optional note from the resolver.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "resolvedAt",
|
||||||
|
type: "date",
|
||||||
|
admin: {
|
||||||
|
description: "When the escalated request was resolved.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "discordFromLeaderMessageId",
|
||||||
|
type: "text",
|
||||||
|
admin: {
|
||||||
|
description: "Discord message id of the notification sent to the source leader (Phase 2).",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "discordToLeaderMessageId",
|
||||||
|
type: "text",
|
||||||
|
admin: {
|
||||||
|
description: "Discord message id of the notification sent to the destination leader (Phase 2).",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "discordRequesterMessageId",
|
||||||
|
type: "text",
|
||||||
|
admin: {
|
||||||
|
description: "Discord message id of the notification sent to the requester (Phase 2).",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sourceNotifiedToUserId",
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: "users",
|
||||||
|
admin: {
|
||||||
|
description:
|
||||||
|
"The user actually notified on the source side (the leader at notification time; may differ from the snapshot).",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "destinationNotifiedToUserId",
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: "users",
|
||||||
|
admin: {
|
||||||
|
description:
|
||||||
|
"The user actually notified on the destination side (the leader at notification time; may differ from the snapshot).",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "discordDeliveredKeys",
|
||||||
|
type: "json",
|
||||||
|
defaultValue: [],
|
||||||
|
admin: {
|
||||||
|
description:
|
||||||
|
"Delivery keys already sent to Discord, used by the Phase 2 bot service for idempotent delivery.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
20204
src/migrations/20260831_180233_assignment_transfers.json
Normal file
20204
src/migrations/20260831_180233_assignment_transfers.json
Normal file
File diff suppressed because it is too large
Load diff
80
src/migrations/20260831_180233_assignment_transfers.ts
Normal file
80
src/migrations/20260831_180233_assignment_transfers.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||||
|
|
||||||
|
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
|
||||||
|
await db.execute(sql`
|
||||||
|
CREATE TYPE "public"."enum_assignment_transfers_status" AS ENUM('pending', 'rejected', 'awaiting_superuser', 'completed', 'cancelled', 'denied');
|
||||||
|
CREATE TYPE "public"."enum_assignment_transfers_escalation_reason" AS ENUM('no_leaders', 'appeal', 'leader_changed');
|
||||||
|
CREATE TYPE "public"."enum_assignment_transfers_from_leader_decision" AS ENUM('pending', 'approved', 'rejected', 'not_required');
|
||||||
|
CREATE TYPE "public"."enum_assignment_transfers_to_leader_decision" AS ENUM('pending', 'approved', 'rejected', 'not_required');
|
||||||
|
CREATE TABLE "assignment_transfers" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"requester_id" integer NOT NULL,
|
||||||
|
"from_assignment_id" integer NOT NULL,
|
||||||
|
"to_assignment_id" integer NOT NULL,
|
||||||
|
"status" "enum_assignment_transfers_status" DEFAULT 'pending' NOT NULL,
|
||||||
|
"escalation_reason" "enum_assignment_transfers_escalation_reason",
|
||||||
|
"from_leader_decision" "enum_assignment_transfers_from_leader_decision" DEFAULT 'pending' NOT NULL,
|
||||||
|
"to_leader_decision" "enum_assignment_transfers_to_leader_decision" DEFAULT 'pending' NOT NULL,
|
||||||
|
"from_decision_by_id" integer,
|
||||||
|
"to_decision_by_id" integer,
|
||||||
|
"from_decided_at" timestamp(3) with time zone,
|
||||||
|
"to_decided_at" timestamp(3) with time zone,
|
||||||
|
"from_leader_snapshot_id" integer,
|
||||||
|
"to_leader_snapshot_id" integer,
|
||||||
|
"rejection_reason" varchar,
|
||||||
|
"request_note" varchar,
|
||||||
|
"appeal_note" varchar,
|
||||||
|
"resolved_by_id" integer,
|
||||||
|
"resolution_note" varchar,
|
||||||
|
"resolved_at" timestamp(3) with time zone,
|
||||||
|
"discord_from_leader_message_id" varchar,
|
||||||
|
"discord_to_leader_message_id" varchar,
|
||||||
|
"discord_requester_message_id" varchar,
|
||||||
|
"source_notified_to_user_id_id" integer,
|
||||||
|
"destination_notified_to_user_id_id" integer,
|
||||||
|
"discord_delivered_keys" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||||
|
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE "payload_locked_documents_rels" ADD COLUMN "assignment_transfers_id" integer;
|
||||||
|
ALTER TABLE "assignment_transfers" ADD CONSTRAINT "assignment_transfers_requester_id_users_id_fk" FOREIGN KEY ("requester_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;
|
||||||
|
ALTER TABLE "assignment_transfers" ADD CONSTRAINT "assignment_transfers_from_assignment_id_assignments_id_fk" FOREIGN KEY ("from_assignment_id") REFERENCES "public"."assignments"("id") ON DELETE set null ON UPDATE no action;
|
||||||
|
ALTER TABLE "assignment_transfers" ADD CONSTRAINT "assignment_transfers_to_assignment_id_assignments_id_fk" FOREIGN KEY ("to_assignment_id") REFERENCES "public"."assignments"("id") ON DELETE set null ON UPDATE no action;
|
||||||
|
ALTER TABLE "assignment_transfers" ADD CONSTRAINT "assignment_transfers_from_decision_by_id_users_id_fk" FOREIGN KEY ("from_decision_by_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;
|
||||||
|
ALTER TABLE "assignment_transfers" ADD CONSTRAINT "assignment_transfers_to_decision_by_id_users_id_fk" FOREIGN KEY ("to_decision_by_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;
|
||||||
|
ALTER TABLE "assignment_transfers" ADD CONSTRAINT "assignment_transfers_from_leader_snapshot_id_users_id_fk" FOREIGN KEY ("from_leader_snapshot_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;
|
||||||
|
ALTER TABLE "assignment_transfers" ADD CONSTRAINT "assignment_transfers_to_leader_snapshot_id_users_id_fk" FOREIGN KEY ("to_leader_snapshot_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;
|
||||||
|
ALTER TABLE "assignment_transfers" ADD CONSTRAINT "assignment_transfers_resolved_by_id_users_id_fk" FOREIGN KEY ("resolved_by_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;
|
||||||
|
ALTER TABLE "assignment_transfers" ADD CONSTRAINT "assignment_transfers_source_notified_to_user_id_id_users_id_fk" FOREIGN KEY ("source_notified_to_user_id_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;
|
||||||
|
ALTER TABLE "assignment_transfers" ADD CONSTRAINT "assignment_transfers_destination_notified_to_user_id_id_users_id_fk" FOREIGN KEY ("destination_notified_to_user_id_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;
|
||||||
|
CREATE INDEX "assignment_transfers_requester_idx" ON "assignment_transfers" USING btree ("requester_id");
|
||||||
|
CREATE INDEX "assignment_transfers_from_assignment_idx" ON "assignment_transfers" USING btree ("from_assignment_id");
|
||||||
|
CREATE INDEX "assignment_transfers_to_assignment_idx" ON "assignment_transfers" USING btree ("to_assignment_id");
|
||||||
|
CREATE INDEX "assignment_transfers_status_idx" ON "assignment_transfers" USING btree ("status");
|
||||||
|
CREATE INDEX "assignment_transfers_from_decision_by_idx" ON "assignment_transfers" USING btree ("from_decision_by_id");
|
||||||
|
CREATE INDEX "assignment_transfers_to_decision_by_idx" ON "assignment_transfers" USING btree ("to_decision_by_id");
|
||||||
|
CREATE INDEX "assignment_transfers_from_leader_snapshot_idx" ON "assignment_transfers" USING btree ("from_leader_snapshot_id");
|
||||||
|
CREATE INDEX "assignment_transfers_to_leader_snapshot_idx" ON "assignment_transfers" USING btree ("to_leader_snapshot_id");
|
||||||
|
CREATE INDEX "assignment_transfers_resolved_by_idx" ON "assignment_transfers" USING btree ("resolved_by_id");
|
||||||
|
CREATE INDEX "assignment_transfers_source_notified_to_user_id_idx" ON "assignment_transfers" USING btree ("source_notified_to_user_id_id");
|
||||||
|
CREATE INDEX "assignment_transfers_destination_notified_to_user_id_idx" ON "assignment_transfers" USING btree ("destination_notified_to_user_id_id");
|
||||||
|
CREATE INDEX "assignment_transfers_updated_at_idx" ON "assignment_transfers" USING btree ("updated_at");
|
||||||
|
CREATE INDEX "assignment_transfers_created_at_idx" ON "assignment_transfers" USING btree ("created_at");
|
||||||
|
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_assignment_transfers_fk" FOREIGN KEY ("assignment_transfers_id") REFERENCES "public"."assignment_transfers"("id") ON DELETE cascade ON UPDATE no action;
|
||||||
|
CREATE INDEX "payload_locked_documents_rels_assignment_transfers_id_idx" ON "payload_locked_documents_rels" USING btree ("assignment_transfers_id");`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
|
||||||
|
await db.execute(sql`
|
||||||
|
ALTER TABLE "assignment_transfers" DISABLE ROW LEVEL SECURITY;
|
||||||
|
DROP TABLE "assignment_transfers" CASCADE;
|
||||||
|
ALTER TABLE "payload_locked_documents_rels" DROP CONSTRAINT "payload_locked_documents_rels_assignment_transfers_fk";
|
||||||
|
|
||||||
|
DROP INDEX "payload_locked_documents_rels_assignment_transfers_id_idx";
|
||||||
|
ALTER TABLE "payload_locked_documents_rels" DROP COLUMN "assignment_transfers_id";
|
||||||
|
DROP TYPE "public"."enum_assignment_transfers_status";
|
||||||
|
DROP TYPE "public"."enum_assignment_transfers_escalation_reason";
|
||||||
|
DROP TYPE "public"."enum_assignment_transfers_from_leader_decision";
|
||||||
|
DROP TYPE "public"."enum_assignment_transfers_to_leader_decision";`)
|
||||||
|
}
|
||||||
20205
src/migrations/20260901_014006_add_assignment_transfer_event_target.json
Normal file
20205
src/migrations/20260901_014006_add_assignment_transfer_event_target.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { MigrateDownArgs, MigrateUpArgs, sql } from "@payloadcms/db-postgres";
|
||||||
|
|
||||||
|
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||||
|
await db.execute(sql`
|
||||||
|
ALTER TYPE "public"."enum_game_event_logs_target_collection"
|
||||||
|
ADD VALUE IF NOT EXISTS 'assignment-transfers';
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down({ db }: MigrateDownArgs): Promise<void> {
|
||||||
|
void db;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue