WIP - scaffold approvals

This commit is contained in:
Francisco Gaona
2026-01-16 23:27:09 +01:00
parent 20fc90a3fb
commit cddbd09f0f
24 changed files with 1955 additions and 1 deletions

View File

@@ -0,0 +1,190 @@
exports.up = async function (knex) {
await knex.schema.createTable('approval_definitions', (table) => {
table.string('id', 191).primary().defaultTo(knex.raw('(UUID())'));
table.string('name', 191).notNullable();
table.text('description');
table.string('triggerType', 191).notNullable();
table.string('targetObjectType', 191);
table.json('entryCriteria');
table.json('steps');
table.json('votingPolicy');
table.string('rejectionRule', 191);
table.string('materialChangePolicy', 191);
table.integer('version').notNullable().defaultTo(1);
table.boolean('isActive').notNullable().defaultTo(true);
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now());
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now());
});
await knex.schema.createTable('approval_requests', (table) => {
table.string('id', 191).primary().defaultTo(knex.raw('(UUID())'));
table.string('definitionId', 191).notNullable();
table.string('status', 191).notNullable().defaultTo('pending');
table.string('targetObjectType', 191).notNullable();
table.string('targetObjectId', 191).notNullable();
table.string('action', 191);
table.string('stateFrom', 191);
table.string('stateTo', 191);
table.json('fieldChanges');
table.json('snapshot');
table.string('versionHash', 191);
table.string('submittedById', 191);
table.timestamp('submittedAt');
table.string('currentStepKey', 191);
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now());
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now());
table.index(['definitionId']);
table.index(['targetObjectType', 'targetObjectId']);
table.index(['submittedById']);
table
.foreign('definitionId')
.references('id')
.inTable('approval_definitions')
.onDelete('CASCADE')
.onUpdate('CASCADE');
table
.foreign('submittedById')
.references('id')
.inTable('users')
.onDelete('SET NULL')
.onUpdate('CASCADE');
});
await knex.schema.createTable('approval_steps', (table) => {
table.string('id', 191).primary().defaultTo(knex.raw('(UUID())'));
table.string('requestId', 191).notNullable();
table.string('stepKey', 191).notNullable();
table.string('name', 191).notNullable();
table.integer('stepOrder').notNullable();
table.string('status', 191).notNullable().defaultTo('pending');
table.json('routing');
table.json('voting');
table.timestamp('dueAt');
table.timestamp('completedAt');
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now());
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now());
table.index(['requestId']);
table.index(['status']);
table
.foreign('requestId')
.references('id')
.inTable('approval_requests')
.onDelete('CASCADE')
.onUpdate('CASCADE');
});
await knex.schema.createTable('approval_assignments', (table) => {
table.string('id', 191).primary().defaultTo(knex.raw('(UUID())'));
table.string('stepId', 191).notNullable();
table.string('assigneeId', 191).notNullable();
table.string('status', 191).notNullable().defaultTo('pending');
table.text('response');
table.timestamp('respondedAt');
table.timestamp('dueAt');
table.string('reassignedFromId', 191);
table.string('delegatedById', 191);
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now());
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now());
table.index(['stepId']);
table.index(['assigneeId']);
table.index(['status']);
table
.foreign('stepId')
.references('id')
.inTable('approval_steps')
.onDelete('CASCADE')
.onUpdate('CASCADE');
table
.foreign('assigneeId')
.references('id')
.inTable('users')
.onDelete('RESTRICT')
.onUpdate('CASCADE');
table
.foreign('reassignedFromId')
.references('id')
.inTable('users')
.onDelete('SET NULL')
.onUpdate('CASCADE');
table
.foreign('delegatedById')
.references('id')
.inTable('users')
.onDelete('SET NULL')
.onUpdate('CASCADE');
});
await knex.schema.createTable('approval_effect_logs', (table) => {
table.string('id', 191).primary().defaultTo(knex.raw('(UUID())'));
table.string('requestId', 191).notNullable();
table.string('effectKey', 191).notNullable();
table.string('status', 191).notNullable().defaultTo('success');
table.json('response');
table.timestamp('executedAt').notNullable().defaultTo(knex.fn.now());
table.unique(['requestId', 'effectKey']);
table.index(['requestId']);
table
.foreign('requestId')
.references('id')
.inTable('approval_requests')
.onDelete('CASCADE')
.onUpdate('CASCADE');
});
await knex.schema.createTable('tasks', (table) => {
table.string('id', 191).primary().defaultTo(knex.raw('(UUID())'));
table.string('title', 191).notNullable();
table.text('description');
table.string('status', 191).notNullable().defaultTo('open');
table.string('priority', 191);
table.timestamp('dueAt');
table.string('assignedToId', 191);
table.string('relatedObjectType', 191);
table.string('relatedObjectId', 191);
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now());
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now());
table.index(['assignedToId']);
table.index(['relatedObjectType', 'relatedObjectId']);
table
.foreign('assignedToId')
.references('id')
.inTable('users')
.onDelete('SET NULL')
.onUpdate('CASCADE');
});
await knex.schema.createTable('activity_logs', (table) => {
table.string('id', 191).primary().defaultTo(knex.raw('(UUID())'));
table.string('action', 191).notNullable();
table.string('subjectType', 191).notNullable();
table.string('subjectId', 191).notNullable();
table.text('description');
table.json('properties');
table.string('causerId', 191);
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now());
table.index(['subjectType', 'subjectId']);
table.index(['causerId']);
table
.foreign('causerId')
.references('id')
.inTable('users')
.onDelete('SET NULL')
.onUpdate('CASCADE');
});
};
exports.down = async function (knex) {
await knex.schema.dropTableIfExists('activity_logs');
await knex.schema.dropTableIfExists('tasks');
await knex.schema.dropTableIfExists('approval_effect_logs');
await knex.schema.dropTableIfExists('approval_assignments');
await knex.schema.dropTableIfExists('approval_steps');
await knex.schema.dropTableIfExists('approval_requests');
await knex.schema.dropTableIfExists('approval_definitions');
};