mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 05:25:41 +00:00
complete rule engine and booking flow
This commit is contained in:
@@ -0,0 +1,321 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddBookingsRemainingForeignKeys1748800000000 implements MigrationInterface {
|
||||||
|
name = 'AddBookingsRemainingForeignKeys1748800000000';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
// ── freight.bookings: nullable FK cleanup ─────────────────────────────
|
||||||
|
await queryRunner.query(`
|
||||||
|
UPDATE freight.bookings b
|
||||||
|
SET train_id = NULL
|
||||||
|
WHERE b.train_id IS NOT NULL
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM freight.trains t WHERE t.id = b.train_id);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
UPDATE freight.bookings b
|
||||||
|
SET previous_contract_id = NULL
|
||||||
|
WHERE b.previous_contract_id IS NOT NULL
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM freight.bookings pb WHERE pb.id = b.previous_contract_id);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
UPDATE freight.bookings b
|
||||||
|
SET consolidation_partner_id = NULL
|
||||||
|
WHERE b.consolidation_partner_id IS NOT NULL
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM freight.bookings pb WHERE pb.id = b.consolidation_partner_id);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Remove bookings with no matching public.customers row
|
||||||
|
await queryRunner.query(`
|
||||||
|
DELETE FROM freight.booking_cargo_modifier bcm
|
||||||
|
USING freight.bookings b
|
||||||
|
WHERE bcm.booking_id = b.id
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM public.customers c WHERE c.id = b.customer_id);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
DELETE FROM freight.booking_approval_step bas
|
||||||
|
USING freight.bookings b
|
||||||
|
WHERE bas.booking_id = b.id
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM public.customers c WHERE c.id = b.customer_id);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
DELETE FROM freight.booking_rate_snapshot brs
|
||||||
|
USING freight.bookings b
|
||||||
|
WHERE brs.booking_id = b.id
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM public.customers c WHERE c.id = b.customer_id);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
DELETE FROM freight.booking_container bc
|
||||||
|
USING freight.bookings b
|
||||||
|
WHERE bc.booking_id = b.id
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM public.customers c WHERE c.id = b.customer_id);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
DELETE FROM freight.bookings b
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM public.customers c WHERE c.id = b.customer_id);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// ── freight.bookings FKs ────────────────────────────────────────────
|
||||||
|
await queryRunner.query(`
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE freight.bookings
|
||||||
|
ADD CONSTRAINT "FK_bookings_customer_id"
|
||||||
|
FOREIGN KEY (customer_id)
|
||||||
|
REFERENCES public.customers(id)
|
||||||
|
ON DELETE RESTRICT;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN NULL;
|
||||||
|
END $$;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE freight.bookings
|
||||||
|
ADD CONSTRAINT "FK_bookings_train_id"
|
||||||
|
FOREIGN KEY (train_id)
|
||||||
|
REFERENCES freight.trains(id)
|
||||||
|
ON DELETE SET NULL;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN NULL;
|
||||||
|
END $$;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE freight.bookings
|
||||||
|
ADD CONSTRAINT "FK_bookings_previous_contract_id"
|
||||||
|
FOREIGN KEY (previous_contract_id)
|
||||||
|
REFERENCES freight.bookings(id)
|
||||||
|
ON DELETE SET NULL;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN NULL;
|
||||||
|
END $$;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE freight.bookings
|
||||||
|
ADD CONSTRAINT "FK_bookings_consolidation_partner_id"
|
||||||
|
FOREIGN KEY (consolidation_partner_id)
|
||||||
|
REFERENCES freight.bookings(id)
|
||||||
|
ON DELETE SET NULL;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN NULL;
|
||||||
|
END $$;
|
||||||
|
`);
|
||||||
|
|
||||||
|
// ── freight.booking_container ─────────────────────────────────────────
|
||||||
|
await queryRunner.query(`
|
||||||
|
UPDATE freight.booking_container bc
|
||||||
|
SET weight_limit_rule_id = NULL
|
||||||
|
WHERE bc.weight_limit_rule_id IS NOT NULL
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM freight.weight_limit_rules wlr WHERE wlr.id = bc.weight_limit_rule_id
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
DELETE FROM freight.booking_container bc
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM freight.container_types ct WHERE ct.id = bc.container_type_id);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE freight.booking_container
|
||||||
|
ADD CONSTRAINT "FK_booking_container_container_type_id"
|
||||||
|
FOREIGN KEY (container_type_id)
|
||||||
|
REFERENCES freight.container_types(id)
|
||||||
|
ON DELETE RESTRICT;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN NULL;
|
||||||
|
END $$;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE freight.booking_container
|
||||||
|
ADD CONSTRAINT "FK_booking_container_weight_limit_rule_id"
|
||||||
|
FOREIGN KEY (weight_limit_rule_id)
|
||||||
|
REFERENCES freight.weight_limit_rules(id)
|
||||||
|
ON DELETE SET NULL;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN NULL;
|
||||||
|
END $$;
|
||||||
|
`);
|
||||||
|
|
||||||
|
// ── freight.booking_rate_snapshot ─────────────────────────────────────
|
||||||
|
await queryRunner.query(`
|
||||||
|
DELETE FROM freight.booking_cargo_modifier bcm
|
||||||
|
USING freight.booking_rate_snapshot brs
|
||||||
|
WHERE bcm.rate_snapshot_id = brs.id
|
||||||
|
AND (
|
||||||
|
NOT EXISTS (SELECT 1 FROM freight.bookings b WHERE b.id = brs.booking_id)
|
||||||
|
OR NOT EXISTS (SELECT 1 FROM freight.rates r WHERE r.id = brs.rate_id)
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
DELETE FROM freight.booking_rate_snapshot brs
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM freight.bookings b WHERE b.id = brs.booking_id)
|
||||||
|
OR NOT EXISTS (SELECT 1 FROM freight.rates r WHERE r.id = brs.rate_id);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE freight.booking_rate_snapshot
|
||||||
|
ADD CONSTRAINT "FK_booking_rate_snapshot_booking_id"
|
||||||
|
FOREIGN KEY (booking_id)
|
||||||
|
REFERENCES freight.bookings(id)
|
||||||
|
ON DELETE CASCADE;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN NULL;
|
||||||
|
END $$;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE freight.booking_rate_snapshot
|
||||||
|
ADD CONSTRAINT "FK_booking_rate_snapshot_rate_id"
|
||||||
|
FOREIGN KEY (rate_id)
|
||||||
|
REFERENCES freight.rates(id)
|
||||||
|
ON DELETE RESTRICT;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN NULL;
|
||||||
|
END $$;
|
||||||
|
`);
|
||||||
|
|
||||||
|
// ── freight.booking_approval_step ─────────────────────────────────────
|
||||||
|
await queryRunner.query(`
|
||||||
|
DELETE FROM freight.booking_approval_step bas
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM freight.bookings b WHERE b.id = bas.booking_id)
|
||||||
|
OR NOT EXISTS (SELECT 1 FROM freight.approval_rules ar WHERE ar.id = bas.approval_rule_id);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE freight.booking_approval_step
|
||||||
|
ADD CONSTRAINT "FK_booking_approval_step_booking_id"
|
||||||
|
FOREIGN KEY (booking_id)
|
||||||
|
REFERENCES freight.bookings(id)
|
||||||
|
ON DELETE CASCADE;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN NULL;
|
||||||
|
END $$;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE freight.booking_approval_step
|
||||||
|
ADD CONSTRAINT "FK_booking_approval_step_approval_rule_id"
|
||||||
|
FOREIGN KEY (approval_rule_id)
|
||||||
|
REFERENCES freight.approval_rules(id)
|
||||||
|
ON DELETE RESTRICT;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN NULL;
|
||||||
|
END $$;
|
||||||
|
`);
|
||||||
|
|
||||||
|
// ── freight.booking_cargo_modifier ────────────────────────────────────
|
||||||
|
await queryRunner.query(`
|
||||||
|
DELETE FROM freight.booking_cargo_modifier bcm
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM freight.bookings b WHERE b.id = bcm.booking_id)
|
||||||
|
OR NOT EXISTS (SELECT 1 FROM freight.surcharge_types st WHERE st.id = bcm.surcharge_type_id)
|
||||||
|
OR NOT EXISTS (
|
||||||
|
SELECT 1 FROM freight.booking_rate_snapshot brs WHERE brs.id = bcm.rate_snapshot_id
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE freight.booking_cargo_modifier
|
||||||
|
ADD CONSTRAINT "FK_booking_cargo_modifier_booking_id"
|
||||||
|
FOREIGN KEY (booking_id)
|
||||||
|
REFERENCES freight.bookings(id)
|
||||||
|
ON DELETE CASCADE;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN NULL;
|
||||||
|
END $$;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE freight.booking_cargo_modifier
|
||||||
|
ADD CONSTRAINT "FK_booking_cargo_modifier_surcharge_type_id"
|
||||||
|
FOREIGN KEY (surcharge_type_id)
|
||||||
|
REFERENCES freight.surcharge_types(id)
|
||||||
|
ON DELETE RESTRICT;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN NULL;
|
||||||
|
END $$;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE freight.booking_cargo_modifier
|
||||||
|
ADD CONSTRAINT "FK_booking_cargo_modifier_rate_snapshot_id"
|
||||||
|
FOREIGN KEY (rate_snapshot_id)
|
||||||
|
REFERENCES freight.booking_rate_snapshot(id)
|
||||||
|
ON DELETE RESTRICT;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN NULL;
|
||||||
|
END $$;
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.booking_cargo_modifier
|
||||||
|
DROP CONSTRAINT IF EXISTS "FK_booking_cargo_modifier_rate_snapshot_id";
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.booking_cargo_modifier
|
||||||
|
DROP CONSTRAINT IF EXISTS "FK_booking_cargo_modifier_surcharge_type_id";
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.booking_cargo_modifier
|
||||||
|
DROP CONSTRAINT IF EXISTS "FK_booking_cargo_modifier_booking_id";
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.booking_approval_step
|
||||||
|
DROP CONSTRAINT IF EXISTS "FK_booking_approval_step_approval_rule_id";
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.booking_approval_step
|
||||||
|
DROP CONSTRAINT IF EXISTS "FK_booking_approval_step_booking_id";
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.booking_rate_snapshot
|
||||||
|
DROP CONSTRAINT IF EXISTS "FK_booking_rate_snapshot_rate_id";
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.booking_rate_snapshot
|
||||||
|
DROP CONSTRAINT IF EXISTS "FK_booking_rate_snapshot_booking_id";
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.booking_container
|
||||||
|
DROP CONSTRAINT IF EXISTS "FK_booking_container_weight_limit_rule_id";
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.booking_container
|
||||||
|
DROP CONSTRAINT IF EXISTS "FK_booking_container_container_type_id";
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.bookings
|
||||||
|
DROP CONSTRAINT IF EXISTS "FK_bookings_consolidation_partner_id";
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.bookings
|
||||||
|
DROP CONSTRAINT IF EXISTS "FK_bookings_previous_contract_id";
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.bookings
|
||||||
|
DROP CONSTRAINT IF EXISTS "FK_bookings_train_id";
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.bookings
|
||||||
|
DROP CONSTRAINT IF EXISTS "FK_bookings_customer_id";
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class MoveCustomersToFreightSchema1748900000000 implements MigrationInterface {
|
||||||
|
name = 'MoveCustomersToFreightSchema1748900000000';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS freight.customers (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
user_id UUID NOT NULL,
|
||||||
|
first_name VARCHAR(100) NOT NULL,
|
||||||
|
last_name VARCHAR(100) NOT NULL,
|
||||||
|
email VARCHAR(150) NOT NULL UNIQUE,
|
||||||
|
phone VARCHAR(20) NOT NULL,
|
||||||
|
company_name VARCHAR(200) NOT NULL,
|
||||||
|
company_email VARCHAR(150) NOT NULL,
|
||||||
|
company_phone VARCHAR(20) NOT NULL,
|
||||||
|
company_location VARCHAR(100) NOT NULL,
|
||||||
|
company_address TEXT NOT NULL,
|
||||||
|
customer_type VARCHAR(32),
|
||||||
|
status VARCHAR(32),
|
||||||
|
contact_person_name VARCHAR(100) NOT NULL,
|
||||||
|
contact_person_phone VARCHAR(20) NOT NULL,
|
||||||
|
tin_number VARCHAR(10) NOT NULL UNIQUE,
|
||||||
|
vat_number VARCHAR(50),
|
||||||
|
fan_number VARCHAR(16) NOT NULL UNIQUE,
|
||||||
|
general_manager_name VARCHAR(100) NOT NULL,
|
||||||
|
general_manager_email VARCHAR(150) NOT NULL,
|
||||||
|
general_manager_phone VARCHAR(20) NOT NULL,
|
||||||
|
poa_name VARCHAR(100),
|
||||||
|
poa_phone VARCHAR(20),
|
||||||
|
poa_address TEXT,
|
||||||
|
poa_email VARCHAR(150),
|
||||||
|
poa_location VARCHAR(100),
|
||||||
|
notes TEXT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
CREATE INDEX IF NOT EXISTS "IDX_freight_customers_email"
|
||||||
|
ON freight.customers (email);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
CREATE INDEX IF NOT EXISTS "IDX_freight_customers_user_id"
|
||||||
|
ON freight.customers (user_id);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Copy rows from public.customers when that legacy table exists
|
||||||
|
await queryRunner.query(`
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
has_public boolean;
|
||||||
|
has_user_id boolean;
|
||||||
|
has_userid boolean;
|
||||||
|
BEGIN
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public' AND table_name = 'customers'
|
||||||
|
) INTO has_public;
|
||||||
|
|
||||||
|
IF NOT has_public THEN
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public' AND table_name = 'customers' AND column_name = 'user_id'
|
||||||
|
) INTO has_user_id;
|
||||||
|
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public' AND table_name = 'customers' AND column_name = 'userid'
|
||||||
|
) INTO has_userid;
|
||||||
|
|
||||||
|
IF has_user_id THEN
|
||||||
|
INSERT INTO freight.customers (
|
||||||
|
id, user_id, first_name, last_name, email, phone,
|
||||||
|
company_name, company_email, company_phone, company_location, company_address,
|
||||||
|
contact_person_name, contact_person_phone, tin_number, vat_number, fan_number,
|
||||||
|
general_manager_name, general_manager_email, general_manager_phone,
|
||||||
|
poa_name, poa_phone, poa_address, poa_email, poa_location, notes,
|
||||||
|
created_at, updated_at
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
id, user_id, first_name, last_name, email, phone,
|
||||||
|
company_name, company_email, company_phone, company_location, company_address,
|
||||||
|
contact_person_name, contact_person_phone, tin_number, vat_number, fan_number,
|
||||||
|
general_manager_name, general_manager_email, general_manager_phone,
|
||||||
|
poa_name, poa_phone, poa_address, poa_email, poa_location, notes,
|
||||||
|
COALESCE(created_at, now()), COALESCE(updated_at, now())
|
||||||
|
FROM public.customers
|
||||||
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
ELSIF has_userid THEN
|
||||||
|
INSERT INTO freight.customers (
|
||||||
|
id, user_id, first_name, last_name, email, phone,
|
||||||
|
company_name, company_email, company_phone, company_location, company_address,
|
||||||
|
contact_person_name, contact_person_phone, tin_number, vat_number, fan_number,
|
||||||
|
general_manager_name, general_manager_email, general_manager_phone,
|
||||||
|
poa_name, poa_phone, poa_address, poa_email, poa_location, notes,
|
||||||
|
created_at, updated_at
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
id, userid, firstname, lastname, email, phone,
|
||||||
|
companyname, companyemail, companyphone, companylocation, companyaddress,
|
||||||
|
contactpersonname, contactpersonphone, tinnumber, vatnumber, fannumber,
|
||||||
|
generalmanagername, generalmanageremail, generalmanagerphone,
|
||||||
|
poaname, poaphone, poaaddress, poaemail, poalocation, notes,
|
||||||
|
COALESCE("createdAt", now()), COALESCE("updatedAt", now())
|
||||||
|
FROM public.customers
|
||||||
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.bookings
|
||||||
|
DROP CONSTRAINT IF EXISTS "FK_bookings_customer_id";
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
DELETE FROM freight.booking_cargo_modifier bcm
|
||||||
|
USING freight.bookings b
|
||||||
|
WHERE bcm.booking_id = b.id
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM freight.customers c WHERE c.id = b.customer_id);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
DELETE FROM freight.booking_approval_step bas
|
||||||
|
USING freight.bookings b
|
||||||
|
WHERE bas.booking_id = b.id
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM freight.customers c WHERE c.id = b.customer_id);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
DELETE FROM freight.booking_rate_snapshot brs
|
||||||
|
USING freight.bookings b
|
||||||
|
WHERE brs.booking_id = b.id
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM freight.customers c WHERE c.id = b.customer_id);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
DELETE FROM freight.booking_container bc
|
||||||
|
USING freight.bookings b
|
||||||
|
WHERE bc.booking_id = b.id
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM freight.customers c WHERE c.id = b.customer_id);
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
DELETE FROM freight.bookings b
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM freight.customers c WHERE c.id = b.customer_id);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE freight.bookings
|
||||||
|
ADD CONSTRAINT "FK_bookings_customer_id"
|
||||||
|
FOREIGN KEY (customer_id)
|
||||||
|
REFERENCES freight.customers(id)
|
||||||
|
ON DELETE RESTRICT;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN NULL;
|
||||||
|
END $$;
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE freight.bookings
|
||||||
|
DROP CONSTRAINT IF EXISTS "FK_bookings_customer_id";
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE freight.bookings
|
||||||
|
ADD CONSTRAINT "FK_bookings_customer_id"
|
||||||
|
FOREIGN KEY (customer_id)
|
||||||
|
REFERENCES public.customers(id)
|
||||||
|
ON DELETE RESTRICT;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN NULL;
|
||||||
|
END $$;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`DROP TABLE IF EXISTS freight.customers CASCADE;`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,6 +56,8 @@ export class BookingsRepository extends BaseRepository<Booking> {
|
|||||||
.createQueryBuilder('booking')
|
.createQueryBuilder('booking')
|
||||||
.leftJoinAndSelect('booking.bookingContainers', 'bc')
|
.leftJoinAndSelect('booking.bookingContainers', 'bc')
|
||||||
.leftJoinAndSelect('bc.containerType', 'ct')
|
.leftJoinAndSelect('bc.containerType', 'ct')
|
||||||
|
.leftJoinAndSelect('booking.customer', 'customer')
|
||||||
|
.leftJoinAndSelect('booking.train', 'train')
|
||||||
.leftJoinAndSelect('booking.serviceType', 'st')
|
.leftJoinAndSelect('booking.serviceType', 'st')
|
||||||
.leftJoinAndSelect('booking.cargoType', 'cargo')
|
.leftJoinAndSelect('booking.cargoType', 'cargo')
|
||||||
.leftJoinAndSelect('booking.originYard', 'oy')
|
.leftJoinAndSelect('booking.originYard', 'oy')
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { BaseEntity } from '@edr/api-common';
|
import { BaseEntity } from '@edr/api-common';
|
||||||
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
|
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
|
||||||
|
import { Customer } from '../../customers/entities/customer.entity';
|
||||||
import { CargoType } from '../../rule-engine/entities/cargo-type.entity';
|
import { CargoType } from '../../rule-engine/entities/cargo-type.entity';
|
||||||
import { ServiceType } from '../../rule-engine/entities/service-type.entity';
|
import { ServiceType } from '../../rule-engine/entities/service-type.entity';
|
||||||
import { ShippingLine } from '../../rule-engine/entities/shipping-line.entity';
|
import { ShippingLine } from '../../rule-engine/entities/shipping-line.entity';
|
||||||
import { Yard } from '../../rule-engine/entities/yard.entity';
|
import { Yard } from '../../rule-engine/entities/yard.entity';
|
||||||
|
import { Train } from '../../trains/entities/train.entity';
|
||||||
import { FileRecord } from '../../files/entities/file.entity';
|
import { FileRecord } from '../../files/entities/file.entity';
|
||||||
import { BookingApprovalStep } from './booking-approval-step.entity';
|
import { BookingApprovalStep } from './booking-approval-step.entity';
|
||||||
import { BookingCargoModifier } from './booking-cargo-modifier.entity';
|
import { BookingCargoModifier } from './booking-cargo-modifier.entity';
|
||||||
@@ -36,9 +38,17 @@ export class Booking extends BaseEntity {
|
|||||||
@Column({ name: 'customer_id', type: 'uuid' })
|
@Column({ name: 'customer_id', type: 'uuid' })
|
||||||
customerId!: string;
|
customerId!: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Customer)
|
||||||
|
@JoinColumn({ name: 'customer_id' })
|
||||||
|
customer?: Customer;
|
||||||
|
|
||||||
@Column({ name: 'train_id', type: 'uuid', nullable: true })
|
@Column({ name: 'train_id', type: 'uuid', nullable: true })
|
||||||
trainId?: string | null;
|
trainId?: string | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => Train, { nullable: true })
|
||||||
|
@JoinColumn({ name: 'train_id' })
|
||||||
|
train?: Train | null;
|
||||||
|
|
||||||
@Column({ name: 'status', type: 'varchar', length: 40, default: 'DRAFT' })
|
@Column({ name: 'status', type: 'varchar', length: 40, default: 'DRAFT' })
|
||||||
status!: string;
|
status!: string;
|
||||||
|
|
||||||
@@ -57,6 +67,10 @@ export class Booking extends BaseEntity {
|
|||||||
@Column({ name: 'previous_contract_id', type: 'uuid', nullable: true })
|
@Column({ name: 'previous_contract_id', type: 'uuid', nullable: true })
|
||||||
previousContractId?: string | null;
|
previousContractId?: string | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => Booking, { nullable: true })
|
||||||
|
@JoinColumn({ name: 'previous_contract_id' })
|
||||||
|
previousContract?: Booking | null;
|
||||||
|
|
||||||
@Column({ name: 'service_type_id', type: 'uuid' })
|
@Column({ name: 'service_type_id', type: 'uuid' })
|
||||||
serviceTypeId!: string;
|
serviceTypeId!: string;
|
||||||
|
|
||||||
@@ -164,6 +178,10 @@ export class Booking extends BaseEntity {
|
|||||||
@Column({ name: 'consolidation_partner_id', type: 'uuid', nullable: true })
|
@Column({ name: 'consolidation_partner_id', type: 'uuid', nullable: true })
|
||||||
consolidationPartnerId?: string | null;
|
consolidationPartnerId?: string | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => Booking, { nullable: true })
|
||||||
|
@JoinColumn({ name: 'consolidation_partner_id' })
|
||||||
|
consolidationPartner?: Booking | null;
|
||||||
|
|
||||||
@OneToMany(() => BookingContainer, (bc) => bc.booking)
|
@OneToMany(() => BookingContainer, (bc) => bc.booking)
|
||||||
bookingContainers?: BookingContainer[];
|
bookingContainers?: BookingContainer[];
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export class CustomersRepository {
|
|||||||
async findByName(name: string): Promise<Customer[]> {
|
async findByName(name: string): Promise<Customer[]> {
|
||||||
return await this.repository
|
return await this.repository
|
||||||
.createQueryBuilder("customer")
|
.createQueryBuilder("customer")
|
||||||
.where("customer.name ILIKE :name", { name: `%${name}%` })
|
.where("customer.companyName ILIKE :name", { name: `%${name}%` })
|
||||||
.getMany();
|
.getMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export class ResponseCustomerDto {
|
|||||||
this.contactPersonName = customer.contactPersonName;
|
this.contactPersonName = customer.contactPersonName;
|
||||||
this.contactPersonPhone = customer.contactPersonPhone;
|
this.contactPersonPhone = customer.contactPersonPhone;
|
||||||
this.tinNumber = customer.tinNumber;
|
this.tinNumber = customer.tinNumber;
|
||||||
this.vatNumber = customer.vatNumber;
|
this.vatNumber = customer.vatNumber ?? undefined;
|
||||||
this.fanNumber = customer.fanNumber;
|
this.fanNumber = customer.fanNumber;
|
||||||
this.generalManagerName = customer.generalManagerName;
|
this.generalManagerName = customer.generalManagerName;
|
||||||
this.generalManagerEmail = customer.generalManagerEmail;
|
this.generalManagerEmail = customer.generalManagerEmail;
|
||||||
|
|||||||
@@ -1,100 +1,87 @@
|
|||||||
import {
|
import { BaseEntity } from '@edr/api-common';
|
||||||
Column,
|
import { Column, Entity, Index } from 'typeorm';
|
||||||
Entity,
|
|
||||||
CreateDateColumn,
|
|
||||||
UpdateDateColumn,
|
|
||||||
Index,
|
|
||||||
BaseEntity,
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
} from "typeorm";
|
|
||||||
|
|
||||||
@Entity("customers")
|
@Entity({ schema: 'freight', name: 'customers' })
|
||||||
|
@Index(['email'])
|
||||||
|
@Index(['userId'])
|
||||||
|
@Index(['tinNumber'])
|
||||||
|
@Index(['fanNumber'])
|
||||||
export class Customer extends BaseEntity {
|
export class Customer extends BaseEntity {
|
||||||
@PrimaryGeneratedColumn("uuid")
|
@Column({ name: 'user_id', type: 'uuid' })
|
||||||
id!: string;
|
|
||||||
|
|
||||||
@Column({ type: "uuid" })
|
|
||||||
@Index()
|
|
||||||
userId!: string;
|
userId!: string;
|
||||||
|
|
||||||
@Column({ length: 100 })
|
@Column({ name: 'first_name', type: 'varchar', length: 100 })
|
||||||
@Index()
|
|
||||||
firstName!: string;
|
firstName!: string;
|
||||||
|
|
||||||
@Column({ length: 100 })
|
@Column({ name: 'last_name', type: 'varchar', length: 100 })
|
||||||
@Index()
|
|
||||||
lastName!: string;
|
lastName!: string;
|
||||||
|
|
||||||
@Column({ unique: true, length: 150 })
|
@Column({ name: 'email', type: 'varchar', length: 150, unique: true })
|
||||||
@Index()
|
|
||||||
email!: string;
|
email!: string;
|
||||||
|
|
||||||
@Column({ length: 20 })
|
@Column({ name: 'phone', type: 'varchar', length: 20 })
|
||||||
phone!: string;
|
phone!: string;
|
||||||
|
|
||||||
@Column({ length: 200 })
|
@Column({ name: 'company_name', type: 'varchar', length: 200 })
|
||||||
@Index()
|
|
||||||
companyName!: string;
|
companyName!: string;
|
||||||
|
|
||||||
@Column({ length: 150 })
|
@Column({ name: 'company_email', type: 'varchar', length: 150 })
|
||||||
companyEmail!: string;
|
companyEmail!: string;
|
||||||
|
|
||||||
@Column({ length: 20 })
|
@Column({ name: 'company_phone', type: 'varchar', length: 20 })
|
||||||
companyPhone!: string;
|
companyPhone!: string;
|
||||||
|
|
||||||
@Column({ length: 100 })
|
@Column({ name: 'company_location', type: 'varchar', length: 100 })
|
||||||
companyLocation!: string;
|
companyLocation!: string;
|
||||||
|
|
||||||
@Column({ type: "text" })
|
@Column({ name: 'company_address', type: 'text' })
|
||||||
companyAddress!: string;
|
companyAddress!: string;
|
||||||
|
|
||||||
@Column({ length: 100 })
|
@Column({ name: 'customer_type', type: 'varchar', length: 32, nullable: true })
|
||||||
|
customerType?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'status', type: 'varchar', length: 32, nullable: true })
|
||||||
|
status?: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'contact_person_name', type: 'varchar', length: 100 })
|
||||||
contactPersonName!: string;
|
contactPersonName!: string;
|
||||||
|
|
||||||
@Column({ length: 20 })
|
@Column({ name: 'contact_person_phone', type: 'varchar', length: 20 })
|
||||||
contactPersonPhone!: string;
|
contactPersonPhone!: string;
|
||||||
|
|
||||||
@Column({ length: 10, unique: true })
|
@Column({ name: 'tin_number', type: 'varchar', length: 10, unique: true })
|
||||||
@Index()
|
|
||||||
tinNumber!: string;
|
tinNumber!: string;
|
||||||
|
|
||||||
@Column({ length: 50, nullable: true })
|
@Column({ name: 'vat_number', type: 'varchar', length: 50, nullable: true })
|
||||||
vatNumber?: string;
|
vatNumber?: string | null;
|
||||||
|
|
||||||
@Column({ length: 16, unique: true })
|
@Column({ name: 'fan_number', type: 'varchar', length: 16, unique: true })
|
||||||
@Index()
|
|
||||||
fanNumber!: string;
|
fanNumber!: string;
|
||||||
|
|
||||||
@Column({ length: 100 })
|
@Column({ name: 'general_manager_name', type: 'varchar', length: 100 })
|
||||||
generalManagerName!: string;
|
generalManagerName!: string;
|
||||||
|
|
||||||
@Column({ length: 150 })
|
@Column({ name: 'general_manager_email', type: 'varchar', length: 150 })
|
||||||
generalManagerEmail!: string;
|
generalManagerEmail!: string;
|
||||||
|
|
||||||
@Column({ length: 20 })
|
@Column({ name: 'general_manager_phone', type: 'varchar', length: 20 })
|
||||||
generalManagerPhone!: string;
|
generalManagerPhone!: string;
|
||||||
|
|
||||||
@Column({ length: 100, nullable: true })
|
@Column({ name: 'poa_name', type: 'varchar', length: 100, nullable: true })
|
||||||
poaName?: string;
|
poaName?: string | null;
|
||||||
|
|
||||||
@Column({ length: 20, nullable: true })
|
@Column({ name: 'poa_phone', type: 'varchar', length: 20, nullable: true })
|
||||||
poaPhone?: string;
|
poaPhone?: string | null;
|
||||||
|
|
||||||
@Column({ type: "text", nullable: true })
|
@Column({ name: 'poa_address', type: 'text', nullable: true })
|
||||||
poaAddress?: string;
|
poaAddress?: string | null;
|
||||||
|
|
||||||
@Column({ nullable: true, length: 150 })
|
@Column({ name: 'poa_email', type: 'varchar', length: 150, nullable: true })
|
||||||
poaEmail?: string;
|
poaEmail?: string | null;
|
||||||
|
|
||||||
@Column({ length: 100, nullable: true })
|
@Column({ name: 'poa_location', type: 'varchar', length: 100, nullable: true })
|
||||||
poaLocation?: string;
|
poaLocation?: string | null;
|
||||||
|
|
||||||
@Column({ type: "text", nullable: true })
|
@Column({ name: 'notes', type: 'text', nullable: true })
|
||||||
notes?: string;
|
notes?: string | null;
|
||||||
|
}
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt!: Date;
|
|
||||||
|
|
||||||
@UpdateDateColumn()
|
|
||||||
updatedAt!: Date;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
import {
|
|
||||||
Body,
|
|
||||||
Controller,
|
|
||||||
Delete,
|
|
||||||
Get,
|
|
||||||
HttpCode,
|
|
||||||
HttpStatus,
|
|
||||||
Param,
|
|
||||||
ParseUUIDPipe,
|
|
||||||
Patch,
|
|
||||||
Post,
|
|
||||||
} from "@nestjs/common";
|
|
||||||
import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
|
||||||
|
|
||||||
import { CustomersService } from "./customers.service";
|
|
||||||
import { CreateCustomerDto } from "./dto/create-customer.dto";
|
|
||||||
import { UpdateCustomerDto } from "./dto/update-customer.dto";
|
|
||||||
|
|
||||||
@ApiTags("customers")
|
|
||||||
@Controller("customers")
|
|
||||||
export class CustomersController {
|
|
||||||
constructor(private readonly customersService: CustomersService) {}
|
|
||||||
|
|
||||||
@Post()
|
|
||||||
@ApiOperation({ summary: "Create a new customer" })
|
|
||||||
create(@Body() dto: CreateCustomerDto) {
|
|
||||||
return this.customersService.create(dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get()
|
|
||||||
@ApiOperation({ summary: "List all customers" })
|
|
||||||
findAll() {
|
|
||||||
return this.customersService.findAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get(":id")
|
|
||||||
@ApiOperation({ summary: "Get a customer by ID" })
|
|
||||||
findOne(@Param("id", ParseUUIDPipe) id: string) {
|
|
||||||
return this.customersService.findById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Patch(":id")
|
|
||||||
@ApiOperation({ summary: "Update a customer" })
|
|
||||||
update(
|
|
||||||
@Param("id", ParseUUIDPipe) id: string,
|
|
||||||
@Body() dto: UpdateCustomerDto,
|
|
||||||
) {
|
|
||||||
return this.customersService.update(id, dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete(":id")
|
|
||||||
@ApiOperation({ summary: "Soft-delete a customer" })
|
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
|
||||||
remove(@Param("id", ParseUUIDPipe) id: string) {
|
|
||||||
return this.customersService.remove(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { Module } from "@nestjs/common";
|
|
||||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
|
||||||
|
|
||||||
import { CustomersController } from "./customers.controller";
|
|
||||||
import { CustomersRepository } from "./customers.repository";
|
|
||||||
import { CustomersService } from "./customers.service";
|
|
||||||
import { Customer } from "./entities/customer.entity";
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [TypeOrmModule.forFeature([Customer])],
|
|
||||||
controllers: [CustomersController],
|
|
||||||
providers: [CustomersService, CustomersRepository],
|
|
||||||
exports: [CustomersService],
|
|
||||||
})
|
|
||||||
export class CustomersModule {}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { BaseRepository } from "@edr/api-common";
|
|
||||||
import { Injectable } from "@nestjs/common";
|
|
||||||
import { InjectRepository } from "@nestjs/typeorm";
|
|
||||||
import { Repository } from "typeorm";
|
|
||||||
|
|
||||||
import { Customer } from "./entities/customer.entity";
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CustomersRepository extends BaseRepository<Customer> {
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(Customer)
|
|
||||||
repository: Repository<Customer>,
|
|
||||||
) {
|
|
||||||
super(repository);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Find a customer by their unique email. */
|
|
||||||
findByEmail(email: string): Promise<Customer | null> {
|
|
||||||
return this.repository.findOne({ where: { email } });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import {
|
|
||||||
ConflictException,
|
|
||||||
Injectable,
|
|
||||||
NotFoundException,
|
|
||||||
} from "@nestjs/common";
|
|
||||||
|
|
||||||
import { CustomersRepository } from "./customers.repository";
|
|
||||||
import { CreateCustomerDto } from "./dto/create-customer.dto";
|
|
||||||
import { UpdateCustomerDto } from "./dto/update-customer.dto";
|
|
||||||
import { Customer } from "./entities/customer.entity";
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CustomersService {
|
|
||||||
constructor(private readonly customersRepository: CustomersRepository) {}
|
|
||||||
|
|
||||||
async create(dto: CreateCustomerDto): Promise<Customer> {
|
|
||||||
const existing = await this.customersRepository.findByEmail(dto.email);
|
|
||||||
if (existing) {
|
|
||||||
throw new ConflictException(
|
|
||||||
`Customer with email "${dto.email}" already exists`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return this.customersRepository.create(dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
findAll(): Promise<Customer[]> {
|
|
||||||
return this.customersRepository.findAll({ order: { name: "ASC" } });
|
|
||||||
}
|
|
||||||
|
|
||||||
async findById(id: string): Promise<Customer> {
|
|
||||||
const customer = await this.customersRepository.findById(id);
|
|
||||||
if (!customer) {
|
|
||||||
throw new NotFoundException(`Customer ${id} not found`);
|
|
||||||
}
|
|
||||||
return customer;
|
|
||||||
}
|
|
||||||
|
|
||||||
async update(id: string, dto: UpdateCustomerDto): Promise<Customer> {
|
|
||||||
await this.findById(id);
|
|
||||||
|
|
||||||
if (dto.email) {
|
|
||||||
const conflict = await this.customersRepository.findByEmail(dto.email);
|
|
||||||
if (conflict && conflict.id !== id) {
|
|
||||||
throw new ConflictException(
|
|
||||||
`Customer with email "${dto.email}" already exists`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updated = await this.customersRepository.update(id, dto);
|
|
||||||
if (!updated) {
|
|
||||||
throw new NotFoundException(`Customer ${id} not found`);
|
|
||||||
}
|
|
||||||
return updated;
|
|
||||||
}
|
|
||||||
|
|
||||||
async remove(id: string): Promise<void> {
|
|
||||||
await this.findById(id);
|
|
||||||
await this.customersRepository.softDelete(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
import {
|
|
||||||
IsEmail,
|
|
||||||
IsEnum,
|
|
||||||
IsOptional,
|
|
||||||
IsString,
|
|
||||||
MaxLength,
|
|
||||||
} from "class-validator";
|
|
||||||
|
|
||||||
export enum CustomerStatusDto {
|
|
||||||
Active = "Active",
|
|
||||||
Pending = "Pending",
|
|
||||||
Inactive = "Inactive",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum CustomerTypeDto {
|
|
||||||
Importer = "Importer",
|
|
||||||
Exporter = "Exporter",
|
|
||||||
Supplier = "Supplier",
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CreateCustomerDto {
|
|
||||||
@IsString()
|
|
||||||
@MaxLength(256)
|
|
||||||
name!: string;
|
|
||||||
|
|
||||||
@IsEmail()
|
|
||||||
email!: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@MaxLength(32)
|
|
||||||
phone!: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
@MaxLength(256)
|
|
||||||
company?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsEnum(CustomerTypeDto)
|
|
||||||
customerType?: CustomerTypeDto;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsEnum(CustomerStatusDto)
|
|
||||||
status?: CustomerStatusDto;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
@MaxLength(64)
|
|
||||||
tinNumber?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
@MaxLength(128)
|
|
||||||
city?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
@MaxLength(128)
|
|
||||||
country?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
address?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
@MaxLength(64)
|
|
||||||
taxId?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
notes?: string;
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { PartialType } from "@nestjs/mapped-types";
|
|
||||||
|
|
||||||
import { CreateCustomerDto } from "./create-customer.dto";
|
|
||||||
|
|
||||||
export class UpdateCustomerDto extends PartialType(CreateCustomerDto) {}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import { BaseEntity } from "@edr/api-common";
|
|
||||||
import { Column, Entity } from "typeorm";
|
|
||||||
|
|
||||||
export type CustomerStatus = "Active" | "Pending" | "Inactive";
|
|
||||||
export type CustomerType = "Importer" | "Exporter" | "Supplier";
|
|
||||||
|
|
||||||
@Entity({schema:"freight", name: "customers" })
|
|
||||||
export class Customer extends BaseEntity {
|
|
||||||
@Column({ name: "name", type: "varchar", length: 256 })
|
|
||||||
name!: string;
|
|
||||||
|
|
||||||
@Column({ name: "email", type: "varchar", length: 256, unique: true })
|
|
||||||
email!: string;
|
|
||||||
|
|
||||||
@Column({ name: "phone", type: "varchar", length: 32 })
|
|
||||||
phone!: string;
|
|
||||||
|
|
||||||
@Column({ name: "company", type: "varchar", length: 256, nullable: true })
|
|
||||||
company?: string | null;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
name: "customer_type",
|
|
||||||
type: "varchar",
|
|
||||||
length: 32,
|
|
||||||
default: "Importer",
|
|
||||||
})
|
|
||||||
customerType!: CustomerType;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
name: "status",
|
|
||||||
type: "varchar",
|
|
||||||
length: 32,
|
|
||||||
default: "Active",
|
|
||||||
})
|
|
||||||
status!: CustomerStatus;
|
|
||||||
|
|
||||||
@Column({ name: "tin_number", type: "varchar", length: 64, nullable: true })
|
|
||||||
tinNumber?: string | null;
|
|
||||||
|
|
||||||
@Column({ name: "city", type: "varchar", length: 128, nullable: true })
|
|
||||||
city?: string | null;
|
|
||||||
|
|
||||||
@Column({ name: "country", type: "varchar", length: 128, nullable: true })
|
|
||||||
country?: string | null;
|
|
||||||
|
|
||||||
@Column({ name: "address", type: "text", nullable: true })
|
|
||||||
address?: string | null;
|
|
||||||
|
|
||||||
@Column({ name: "tax_id", type: "varchar", length: 64, nullable: true })
|
|
||||||
taxId?: string | null;
|
|
||||||
|
|
||||||
@Column({ name: "notes", type: "text", nullable: true })
|
|
||||||
notes?: string | null;
|
|
||||||
}
|
|
||||||
@@ -2,12 +2,12 @@
|
|||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
PORT=3002
|
PORT=3002
|
||||||
|
|
||||||
# Database
|
# Database (local Docker: run `pnpm dev:db` from edr-platform, then copy to .env)
|
||||||
DB_HOST=localhost
|
DB_HOST=localhost
|
||||||
DB_PORT=5434
|
DB_PORT=5434
|
||||||
DB_NAME=edr_passenger
|
DB_NAME=edr_passenger
|
||||||
DB_USER=postgres
|
DB_USER=postgres
|
||||||
DB_PASSWORD=
|
DB_PASSWORD=postgres
|
||||||
|
|
||||||
# JWT (provided by external auth package — placeholder only)
|
# JWT (provided by external auth package — placeholder only)
|
||||||
JWT_SECRET=
|
JWT_SECRET=
|
||||||
|
|||||||
Reference in New Issue
Block a user