Merge pull request #640 from Tria-plc/freight_feature/usermanagement

Freight feature/usermanagement
This commit is contained in:
marshal
2026-07-13 08:13:01 +03:00
committed by GitHub
120 changed files with 3798 additions and 1409 deletions

View File

@@ -0,0 +1,89 @@
import { Injectable, Logger } from "@nestjs/common";
import { DataSource } from "typeorm";
import {
DropdownSetting,
DropdownSettingMeta,
} from "../modules/dropdown-settings/entities/dropdown-setting.entity";
interface DefaultDropdownSetting {
code: string;
label: string;
description: string;
multiple: boolean;
meta?: DropdownSettingMeta | null;
}
/**
* Known dropdown settings, seeded as empty catalogs (no options). The options
* are managed by the admin from the backoffice Dropdown Settings editor.
*/
const DEFAULT_DROPDOWN_SETTINGS: DefaultDropdownSetting[] = [
{
code: "stations_ter",
label: "Stations TER",
description:
"Temporary freight station list used by booking origin and destination yards.",
multiple: false,
meta: { searchable: true, clearable: true, version: "temporary" },
},
{
code: "general_contract_period",
label: "General Contract Period (months)",
description:
"How many months a general contract stays open for ordering after activation.",
multiple: false,
},
{
code: "contract_validity_periods",
label: "Contract Validity Periods (days)",
description:
"Validity durations (in days) a staff can choose when accepting a submitted contract.",
multiple: false,
},
{
code: "ro_vessel_min_days",
label: "RO vessel minimum lead time (days)",
description:
"Minimum days between today and the vessel departure date on an export Release Order.",
multiple: false,
},
];
@Injectable()
export class DropdownSettingsSeeder {
private readonly logger = new Logger(DropdownSettingsSeeder.name);
constructor(private readonly dataSource: DataSource) {}
async run() {
const settingRepository = this.dataSource.getRepository(DropdownSetting);
// Seed only into an empty table: any existing rows (including
// soft-deleted ones, which would still conflict on the unique `code`)
// mean the data is admin-managed, so leave it untouched.
const existing = await settingRepository.count({ withDeleted: true });
if (existing > 0) {
this.logger.log(
`dropdown_settings already has ${existing} rows — skipping seed`,
);
return;
}
// Insert setting rows only — no DropdownOption rows. Options start empty
// and are configured by the admin from the backoffice editor.
await settingRepository.insert(
DEFAULT_DROPDOWN_SETTINGS.map((setting) => ({
code: setting.code,
label: setting.label,
description: setting.description,
multiple: setting.multiple,
meta: setting.meta ?? null,
})),
);
this.logger.log(
`Seeded ${DEFAULT_DROPDOWN_SETTINGS.length} dropdown settings with empty options`,
);
}
}

View File

@@ -1,7 +1,6 @@
import { Injectable, Logger } from "@nestjs/common";
import { DataSource } from "typeorm";
import { FileUploadField } from "../modules/file-upload-settings/entities/file-upload-field.entity";
import { FileUploadSetting } from "../modules/file-upload-settings/entities/file-upload-setting.entity";
interface OnboardingField {
@@ -576,88 +575,66 @@ export class FileUploadSettingsSeeder {
constructor(private readonly dataSource: DataSource) { }
async run() {
await this.dataSource.transaction(async (manager) => {
const settingRepository = manager.getRepository(FileUploadSetting);
const fieldRepository = manager.getRepository(FileUploadField);
const settingRepository = this.dataSource.getRepository(FileUploadSetting);
const allSettings: Array<
OnboardingDocumentSetting & { description: string }
> = [
...COMPANY_ONBOARDING_DOCUMENTS.map((s) => ({
...s,
description: COMPANY_ONBOARDING_DESCRIPTION,
})),
...CLEARANCE_DOCUMENT_SETTINGS.map((s) => ({
...s,
description: CLEARANCE_DESCRIPTION,
})),
...CONTRACT_CLEARANCE_SETTINGS.map((s) => ({
...s,
description:
"Pre-booking clearance documents collected on the contract (Path B), by operation and freight type.",
})),
...SELF_CLEARANCE_SETTINGS.map((s) => ({
...s,
description:
"Customer self-clearance documents (Path A, no EDR customs service), reviewed by Operations.",
})),
...CONTRACT_INTAKE_SETTINGS.map((s) => ({
...s,
description:
"Commercial/framework documents attached at contract submission.",
})),
...DRIVER_DOCUMENT_SETTINGS.map((s) => ({
...s,
description:
"Documents uploaded against a driver profile (license, ID, contracts, etc.).",
})),
];
// Seed only into an empty table: any existing rows (including
// soft-deleted ones, which would still conflict on the unique `code`)
// mean the data is admin-managed, so leave it untouched.
const existing = await settingRepository.count({ withDeleted: true });
if (existing > 0) {
this.logger.log(
`file_upload_settings already has ${existing} rows — skipping seed`,
);
return;
}
for (const documentSetting of allSettings) {
await settingRepository.upsert(
{
code: documentSetting.code,
label: documentSetting.label,
description: documentSetting.description,
entity: documentSetting.entity,
},
{
conflictPaths: { code: true },
},
);
const allSettings: Array<
OnboardingDocumentSetting & { description: string }
> = [
...COMPANY_ONBOARDING_DOCUMENTS.map((s) => ({
...s,
description: COMPANY_ONBOARDING_DESCRIPTION,
})),
...CLEARANCE_DOCUMENT_SETTINGS.map((s) => ({
...s,
description: CLEARANCE_DESCRIPTION,
})),
...CONTRACT_CLEARANCE_SETTINGS.map((s) => ({
...s,
description:
"Pre-booking clearance documents collected on the contract (Path B), by operation and freight type.",
})),
...SELF_CLEARANCE_SETTINGS.map((s) => ({
...s,
description:
"Customer self-clearance documents (Path A, no EDR customs service), reviewed by Operations.",
})),
...CONTRACT_INTAKE_SETTINGS.map((s) => ({
...s,
description:
"Commercial/framework documents attached at contract submission.",
})),
...DRIVER_DOCUMENT_SETTINGS.map((s) => ({
...s,
description:
"Documents uploaded against a driver profile (license, ID, contracts, etc.).",
})),
];
const setting = await settingRepository.findOne({
where: { code: documentSetting.code },
select: { id: true, code: true },
});
if (!setting) {
throw new Error(
`file_upload_setting_seed_failed:${documentSetting.code}`,
);
}
await fieldRepository.delete({ settingId: setting.id });
await fieldRepository.insert(
documentSetting.fields.map((field, index) => ({
settingId: setting.id,
fileKey: field.fileKey,
fileLabel: field.fileLabel,
helpText: field.helpText,
isRequired: field.isRequired,
isMultiple: field.isMultiple,
maxFiles: field.maxFiles,
allowedExtensions: [...field.allowedExtensions],
maxSizeMb: field.maxSizeMb,
displayOrder: field.displayOrder ?? index + 1,
})),
);
}
});
// Insert setting rows only — no FileUploadField rows. Fields start empty
// and are configured from the backoffice file-settings editor; the field
// definitions above are kept as reference defaults.
await settingRepository.insert(
allSettings.map((documentSetting) => ({
code: documentSetting.code,
label: documentSetting.label,
description: documentSetting.description,
entity: documentSetting.entity,
})),
);
this.logger.log(
"Ensured company onboarding + booking clearance file upload settings",
`Seeded ${allSettings.length} file upload settings with empty fields`,
);
}
}