mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-04 05:33:39 +00:00
booking flow,summtion, approval, contract, mock payemnt and integration to back office, and also add permissions
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
import {
|
||||
BOOKING_RULE_ENGINE_PERMISSIONS,
|
||||
BOOKING_RULE_ENGINE_PERMISSION_KEYS,
|
||||
ROLE_PERMISSION_PRESETS,
|
||||
} from './freight-permissions.registry';
|
||||
|
||||
export type FreightSeedRole = {
|
||||
key: string;
|
||||
name: { en: string };
|
||||
@@ -183,8 +189,11 @@ export const EDR_FREIGHT_PERMISSIONS = [
|
||||
...HIERARCHY_POSITION_PERMISSIONS,
|
||||
...HIERARCHY_EMPLOYEE_ASSIGNMENT_PERMISSIONS,
|
||||
...POSITION_TYPE_PERMISSIONS,
|
||||
...BOOKING_RULE_ENGINE_PERMISSIONS,
|
||||
];
|
||||
|
||||
export { BOOKING_RULE_ENGINE_PERMISSION_KEYS } from './freight-permissions.registry';
|
||||
|
||||
export const EDR_FREIGHT_ROLES: FreightSeedRole[] = [
|
||||
{
|
||||
key: "edr_employee",
|
||||
@@ -198,11 +207,42 @@ export const EDR_FREIGHT_ROLES: FreightSeedRole[] = [
|
||||
"edr_freight_app:position_types:view",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "edr_line_staff",
|
||||
name: { en: "EDR Line Staff" },
|
||||
permissionKeys: [...ROLE_PERMISSION_PRESETS.lineStaff],
|
||||
},
|
||||
{
|
||||
key: "edr_director",
|
||||
name: { en: "EDR Director" },
|
||||
permissionKeys: [...ROLE_PERMISSION_PRESETS.director],
|
||||
},
|
||||
{
|
||||
key: "edr_ceo",
|
||||
name: { en: "EDR CEO" },
|
||||
permissionKeys: [...ROLE_PERMISSION_PRESETS.ceo],
|
||||
},
|
||||
{
|
||||
key: "edr_finance",
|
||||
name: { en: "EDR Finance" },
|
||||
permissionKeys: [...ROLE_PERMISSION_PRESETS.finance],
|
||||
},
|
||||
{
|
||||
key: "edr_marketing",
|
||||
name: { en: "EDR Marketing" },
|
||||
permissionKeys: [...ROLE_PERMISSION_PRESETS.marketing],
|
||||
},
|
||||
{
|
||||
key: "edr_org_manager",
|
||||
name: { en: "EDR Org Manager" },
|
||||
permissionKeys: [
|
||||
...EDR_FREIGHT_PERMISSIONS.map((permission) => permission.key),
|
||||
...BOOKING_RULE_ENGINE_PERMISSION_KEYS,
|
||||
...EMPLOYEE_REGISTRATION_PERMISSIONS.map((p) => p.key),
|
||||
...ROLE_ASSIGNMENT_PERMISSIONS.map((p) => p.key),
|
||||
...HIERARCHY_UNIT_PERMISSIONS.map((p) => p.key),
|
||||
...HIERARCHY_POSITION_PERMISSIONS.map((p) => p.key),
|
||||
...HIERARCHY_EMPLOYEE_ASSIGNMENT_PERMISSIONS.map((p) => p.key),
|
||||
...POSITION_TYPE_PERMISSIONS.map((p) => p.key),
|
||||
IAM_PERMISSION_KEYS.createEmployee,
|
||||
IAM_PERMISSION_KEYS.deactivateEmployee,
|
||||
IAM_PERMISSION_KEYS.activateEmployee,
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
} from "@tria-plc/iamapi-common";
|
||||
import { DataSource, EntityManager, In } from "typeorm";
|
||||
|
||||
import { ERoleKey } from "@tria-plc/api-common/utils/enums/seed.enum";
|
||||
import { BOOKING_RULE_ENGINE_PERMISSION_KEYS } from "./freight-permissions.registry";
|
||||
import { EDR_FREIGHT_ROLES, type FreightSeedRole } from "./edr-freight.seed";
|
||||
|
||||
const EDR_ORG_KEY = "edr_freight";
|
||||
@@ -37,6 +39,7 @@ export class EdrOrgSeeder {
|
||||
await this.ensureOrganizationConfiguration(manager, organization.id);
|
||||
await this.ensureRoles(manager, EDR_FREIGHT_ROLES);
|
||||
await this.ensureRolePermissions(manager, EDR_FREIGHT_ROLES);
|
||||
await this.ensureSuperAdminPermissions(manager);
|
||||
});
|
||||
|
||||
this.logger.log(`Ensured EDR organization seed for '${EDR_ORG_KEY}'`);
|
||||
@@ -166,4 +169,40 @@ export class EdrOrgSeeder {
|
||||
|
||||
this.logger.log(`Ensured ${rolePermissions.length} EDR role-permission links`);
|
||||
}
|
||||
|
||||
private async ensureSuperAdminPermissions(manager: EntityManager) {
|
||||
const role = await manager.getRepository(Role).findOne({
|
||||
where: { key: ERoleKey.SUPER_ADMIN },
|
||||
select: { id: true, key: true },
|
||||
});
|
||||
|
||||
if (!role) {
|
||||
this.logger.warn(
|
||||
`Role ${ERoleKey.SUPER_ADMIN} not found; skipping booking/rule-engine super_admin links`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const permissions = await manager.getRepository(Permission).find({
|
||||
where: { key: In(BOOKING_RULE_ENGINE_PERMISSION_KEYS) },
|
||||
select: { id: true, key: true },
|
||||
});
|
||||
|
||||
if (!permissions.length) {
|
||||
this.logger.warn('No booking/rule-engine permissions found for super_admin');
|
||||
return;
|
||||
}
|
||||
|
||||
await manager.getRepository(RolePermission).upsert(
|
||||
permissions.map((permission) => ({
|
||||
roleId: role.id,
|
||||
permissionId: permission.id,
|
||||
})),
|
||||
{ conflictPaths: { roleId: true, permissionId: true } },
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Ensured ${permissions.length} booking+rule-engine permissions on super_admin`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
152
apps/edr-freight-api/src/seed/freight-permissions.registry.ts
Normal file
152
apps/edr-freight-api/src/seed/freight-permissions.registry.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
const EDR_FREIGHT_APP_KEY = 'edr_freight_app';
|
||||
|
||||
export type FreightPermissionSeed = {
|
||||
id: string;
|
||||
key: string;
|
||||
name: { am: string; en: string };
|
||||
applicationKey: string;
|
||||
};
|
||||
|
||||
export const RULE_ENGINE_RESOURCE_SLUGS = [
|
||||
'cargo-types',
|
||||
'container-types',
|
||||
'service-types',
|
||||
'yards',
|
||||
'shipping-lines',
|
||||
'weight-limit-rules',
|
||||
'surcharge-types',
|
||||
'priority-rules',
|
||||
'rates',
|
||||
'approval-rules',
|
||||
] as const;
|
||||
|
||||
export type RuleEngineResourceSlug = (typeof RULE_ENGINE_RESOURCE_SLUGS)[number];
|
||||
|
||||
const slugToResourceKey = (slug: RuleEngineResourceSlug): string =>
|
||||
slug.replace(/-/g, '_');
|
||||
|
||||
const perm = (
|
||||
id: string,
|
||||
key: string,
|
||||
en: string,
|
||||
): FreightPermissionSeed => ({
|
||||
id,
|
||||
key,
|
||||
name: { am: en, en },
|
||||
applicationKey: EDR_FREIGHT_APP_KEY,
|
||||
});
|
||||
|
||||
export const BOOKING_PERMISSIONS: FreightPermissionSeed[] = [
|
||||
perm('a1000001-0001-4000-8000-000000000001', 'edr_freight_app:bookings:view', 'View bookings'),
|
||||
perm('a1000001-0001-4000-8000-000000000002', 'edr_freight_app:bookings:staff_accept', 'Accept booking intake'),
|
||||
perm('a1000001-0001-4000-8000-000000000003', 'edr_freight_app:bookings:request_changes', 'Request booking changes'),
|
||||
perm('a1000001-0001-4000-8000-000000000004', 'edr_freight_app:bookings:reject', 'Reject booking submission'),
|
||||
perm('a1000001-0001-4000-8000-000000000005', 'edr_freight_app:bookings:approve_line_staff', 'Approve as line staff'),
|
||||
perm('a1000001-0001-4000-8000-000000000006', 'edr_freight_app:bookings:approve_director', 'Approve as director'),
|
||||
perm('a1000001-0001-4000-8000-000000000007', 'edr_freight_app:bookings:approve_ceo', 'Approve as CEO'),
|
||||
perm('a1000001-0001-4000-8000-000000000008', 'edr_freight_app:bookings:reject_approval', 'Reject at approval step'),
|
||||
perm('a1000001-0001-4000-8000-000000000009', 'edr_freight_app:bookings:generate_contract', 'Generate contract'),
|
||||
perm('a1000001-0001-4000-8000-00000000000a', 'edr_freight_app:bookings:sign_staff', 'Staff contract signature'),
|
||||
perm('a1000001-0001-4000-8000-00000000000b', 'edr_freight_app:bookings:payment_pnr', 'Generate PNR'),
|
||||
perm('a1000001-0001-4000-8000-00000000000c', 'edr_freight_app:bookings:payment_verify', 'Verify payment'),
|
||||
perm('a1000001-0001-4000-8000-00000000000d', 'edr_freight_app:bookings:operations', 'Booking operations'),
|
||||
perm('a1000001-0001-4000-8000-00000000000e', 'edr_freight_app:bookings:cancel', 'Cancel booking'),
|
||||
];
|
||||
|
||||
const RULE_ENGINE_PERMISSION_IDS: Record<RuleEngineResourceSlug, { view: string; manage: string }> = {
|
||||
'cargo-types': { view: 'b2000001-0001-4000-8000-000000000001', manage: 'b2000001-0001-4000-8000-000000000002' },
|
||||
'container-types': { view: 'b2000001-0001-4000-8000-000000000003', manage: 'b2000001-0001-4000-8000-000000000004' },
|
||||
'service-types': { view: 'b2000001-0001-4000-8000-000000000005', manage: 'b2000001-0001-4000-8000-000000000006' },
|
||||
yards: { view: 'b2000001-0001-4000-8000-000000000007', manage: 'b2000001-0001-4000-8000-000000000008' },
|
||||
'shipping-lines': { view: 'b2000001-0001-4000-8000-000000000009', manage: 'b2000001-0001-4000-8000-00000000000a' },
|
||||
'weight-limit-rules': { view: 'b2000001-0001-4000-8000-00000000000b', manage: 'b2000001-0001-4000-8000-00000000000c' },
|
||||
'surcharge-types': { view: 'b2000001-0001-4000-8000-00000000000d', manage: 'b2000001-0001-4000-8000-00000000000e' },
|
||||
'priority-rules': { view: 'b2000001-0001-4000-8000-00000000000f', manage: 'b2000001-0001-4000-8000-000000000010' },
|
||||
rates: { view: 'b2000001-0001-4000-8000-000000000011', manage: 'b2000001-0001-4000-8000-000000000012' },
|
||||
'approval-rules': { view: 'b2000001-0001-4000-8000-000000000013', manage: 'b2000001-0001-4000-8000-000000000014' },
|
||||
};
|
||||
|
||||
export const RULE_ENGINE_PERMISSIONS: FreightPermissionSeed[] = RULE_ENGINE_RESOURCE_SLUGS.flatMap(
|
||||
(slug) => {
|
||||
const resource = slugToResourceKey(slug);
|
||||
const ids = RULE_ENGINE_PERMISSION_IDS[slug];
|
||||
return [
|
||||
perm(ids.view, `edr_freight_app:rule_engine:${resource}:view`, `View ${slug}`),
|
||||
perm(ids.manage, `edr_freight_app:rule_engine:${resource}:manage`, `Manage ${slug}`),
|
||||
];
|
||||
},
|
||||
);
|
||||
|
||||
export const BOOKING_RULE_ENGINE_PERMISSIONS = [
|
||||
...BOOKING_PERMISSIONS,
|
||||
...RULE_ENGINE_PERMISSIONS,
|
||||
];
|
||||
|
||||
export const BOOKING_RULE_ENGINE_PERMISSION_KEYS = BOOKING_RULE_ENGINE_PERMISSIONS.map(
|
||||
(p) => p.key,
|
||||
);
|
||||
|
||||
export const FREIGHT_PERMS = {
|
||||
bookings: {
|
||||
view: 'edr_freight_app:bookings:view',
|
||||
staffAccept: 'edr_freight_app:bookings:staff_accept',
|
||||
requestChanges: 'edr_freight_app:bookings:request_changes',
|
||||
reject: 'edr_freight_app:bookings:reject',
|
||||
approveLineStaff: 'edr_freight_app:bookings:approve_line_staff',
|
||||
approveDirector: 'edr_freight_app:bookings:approve_director',
|
||||
approveCeo: 'edr_freight_app:bookings:approve_ceo',
|
||||
rejectApproval: 'edr_freight_app:bookings:reject_approval',
|
||||
generateContract: 'edr_freight_app:bookings:generate_contract',
|
||||
signStaff: 'edr_freight_app:bookings:sign_staff',
|
||||
operations: 'edr_freight_app:bookings:operations',
|
||||
cancel: 'edr_freight_app:bookings:cancel',
|
||||
},
|
||||
ruleEngine: {
|
||||
view: (slug: RuleEngineResourceSlug) =>
|
||||
`edr_freight_app:rule_engine:${slugToResourceKey(slug)}:view`,
|
||||
manage: (slug: RuleEngineResourceSlug) =>
|
||||
`edr_freight_app:rule_engine:${slugToResourceKey(slug)}:manage`,
|
||||
},
|
||||
} as const;
|
||||
|
||||
const allRuleEngineViewKeys = () =>
|
||||
RULE_ENGINE_RESOURCE_SLUGS.map((s) => FREIGHT_PERMS.ruleEngine.view(s));
|
||||
|
||||
export const ROLE_PERMISSION_PRESETS = {
|
||||
lineStaff: [
|
||||
FREIGHT_PERMS.bookings.view,
|
||||
FREIGHT_PERMS.bookings.staffAccept,
|
||||
FREIGHT_PERMS.bookings.requestChanges,
|
||||
FREIGHT_PERMS.bookings.reject,
|
||||
FREIGHT_PERMS.bookings.approveLineStaff,
|
||||
FREIGHT_PERMS.bookings.rejectApproval,
|
||||
FREIGHT_PERMS.bookings.cancel,
|
||||
...allRuleEngineViewKeys(),
|
||||
],
|
||||
director: [
|
||||
FREIGHT_PERMS.bookings.view,
|
||||
FREIGHT_PERMS.bookings.approveDirector,
|
||||
FREIGHT_PERMS.bookings.rejectApproval,
|
||||
FREIGHT_PERMS.bookings.generateContract,
|
||||
...allRuleEngineViewKeys(),
|
||||
],
|
||||
ceo: [
|
||||
FREIGHT_PERMS.bookings.view,
|
||||
FREIGHT_PERMS.bookings.approveCeo,
|
||||
FREIGHT_PERMS.bookings.rejectApproval,
|
||||
...allRuleEngineViewKeys(),
|
||||
],
|
||||
finance: [FREIGHT_PERMS.bookings.view],
|
||||
marketing: [
|
||||
FREIGHT_PERMS.bookings.view,
|
||||
FREIGHT_PERMS.bookings.generateContract,
|
||||
FREIGHT_PERMS.bookings.signStaff,
|
||||
],
|
||||
orgManager: [...BOOKING_RULE_ENGINE_PERMISSION_KEYS],
|
||||
} as const;
|
||||
|
||||
export const PERMISSIONS_CATALOG = BOOKING_RULE_ENGINE_PERMISSIONS.map((p) => ({
|
||||
key: p.key,
|
||||
label: p.name.en,
|
||||
module: p.key.includes(':bookings:') ? 'bookings' : 'rule_engine',
|
||||
}));
|
||||
127
apps/edr-freight-api/src/seed/freight-staff-users.seeder.ts
Normal file
127
apps/edr-freight-api/src/seed/freight-staff-users.seeder.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { hashPassword } from '@tria-plc/api-common/utils/argon';
|
||||
import { EUserStatus } from '@tria-plc/api-common/utils/enums/user.enum';
|
||||
import {
|
||||
Employee,
|
||||
Organization,
|
||||
Role,
|
||||
User,
|
||||
UserCredential,
|
||||
UserRole,
|
||||
} from '@tria-plc/iamapi-common';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
const SEED_FLAG = 'SEED_FREIGHT_STAFF';
|
||||
const EDR_ORG_KEY = 'edr_freight';
|
||||
|
||||
const STAFF_USERS = [
|
||||
{ email: 'linestaff@edr.local', username: 'linestaff', roleKey: 'edr_line_staff' },
|
||||
{ email: 'director@edr.local', username: 'director', roleKey: 'edr_director' },
|
||||
{ email: 'ceo@edr.local', username: 'ceo', roleKey: 'edr_ceo' },
|
||||
] as const;
|
||||
|
||||
@Injectable()
|
||||
export class FreightStaffUsersSeeder {
|
||||
private readonly logger = new Logger(FreightStaffUsersSeeder.name);
|
||||
|
||||
constructor(private readonly dataSource: DataSource) {}
|
||||
|
||||
async run() {
|
||||
if (process.env[SEED_FLAG]?.trim().toLowerCase() !== 'true') {
|
||||
this.logger.log(`Skipping freight staff seed because ${SEED_FLAG} is not enabled`);
|
||||
return;
|
||||
}
|
||||
|
||||
const password =
|
||||
process.env.DEFAULT_PASSWORD?.trim() || '12345678';
|
||||
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
const organization = await manager.getRepository(Organization).findOne({
|
||||
where: { key: EDR_ORG_KEY },
|
||||
select: { id: true, key: true },
|
||||
});
|
||||
|
||||
if (!organization) {
|
||||
throw new Error(`missing_organization:${EDR_ORG_KEY}`);
|
||||
}
|
||||
|
||||
const roleRepository = manager.getRepository(Role);
|
||||
const userRepository = manager.getRepository(User);
|
||||
const userCredentialRepository = manager.getRepository(UserCredential);
|
||||
const userRoleRepository = manager.getRepository(UserRole);
|
||||
const employeeRepository = manager.getRepository(Employee);
|
||||
|
||||
const hashedPassword = await hashPassword(password);
|
||||
|
||||
for (const staff of STAFF_USERS) {
|
||||
const role = await roleRepository.findOne({
|
||||
where: { key: staff.roleKey },
|
||||
select: { id: true, key: true },
|
||||
});
|
||||
|
||||
if (!role) {
|
||||
throw new Error(`missing_role:${staff.roleKey}`);
|
||||
}
|
||||
|
||||
let user = await userRepository.findOne({
|
||||
where: { email: staff.email },
|
||||
select: { id: true, email: true },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
user = await userRepository.save(
|
||||
userRepository.create({
|
||||
email: staff.email,
|
||||
username: staff.username,
|
||||
name: { en: staff.username },
|
||||
isActive: true,
|
||||
hasSetPassword: true,
|
||||
status: EUserStatus.ACCEPTED,
|
||||
}),
|
||||
);
|
||||
this.logger.log(`Seeded freight staff user ${staff.email}`);
|
||||
}
|
||||
|
||||
const activeCredentialExists = await userCredentialRepository.exists({
|
||||
where: { userId: user.id, isActive: true },
|
||||
});
|
||||
|
||||
if (!activeCredentialExists) {
|
||||
await userCredentialRepository.insert({
|
||||
userId: user.id,
|
||||
password: hashedPassword,
|
||||
isActive: true,
|
||||
});
|
||||
}
|
||||
|
||||
await userRoleRepository.upsert(
|
||||
{
|
||||
userId: user.id,
|
||||
roleId: role.id,
|
||||
organizationId: organization.id,
|
||||
},
|
||||
{ conflictPaths: { userId: true, roleId: true } },
|
||||
);
|
||||
|
||||
const employeeExists = await employeeRepository.exists({
|
||||
where: {
|
||||
userId: user.id,
|
||||
organizationId: organization.id,
|
||||
isCurrent: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!employeeExists) {
|
||||
await employeeRepository.insert({
|
||||
userId: user.id,
|
||||
organizationId: organization.id,
|
||||
isCurrent: true,
|
||||
name: { en: staff.username },
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.logger.log('Ensured freight staff users (linestaff@, director@, ceo@)');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user