mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 21:50:57 +00:00
feat: implement shipping line bookings management
- Add ShippingLineBookingsPage for listing and managing shipping line bookings. - Create ShippingLineDocumentsModal for document uploads related to bookings. - Introduce ShippingLineInitiateModal for initiating new shipping line bookings. - Implement booking document state management with booking-doc-state utility. - Add shipping line bookings service for API interactions. - Update index to export new components and services. - Enhance types for freight to include shipping line credits.
This commit is contained in:
@@ -19,7 +19,8 @@ import { FilesModule } from "../files/files.module";
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Invoice, InvoiceLine]),
|
||||
forwardRef(() => PaymentModule),
|
||||
CompaniesModule,
|
||||
// Cycles back via ShippingLineCompaniesModule, which imports this module.
|
||||
forwardRef(() => CompaniesModule),
|
||||
DocumentsModule,
|
||||
UserTradeAccessModule,
|
||||
FilesModule,
|
||||
@@ -29,3 +30,4 @@ import { FilesModule } from "../files/files.module";
|
||||
exports: [BillingService],
|
||||
})
|
||||
export class BillingModule {}
|
||||
|
||||
@@ -112,8 +112,16 @@ export interface GenerateInvoiceInput {
|
||||
sourceId: string;
|
||||
/** What the invoice is for (e.g. "prepaid", "credit"). */
|
||||
type: string;
|
||||
companyId: string;
|
||||
companyProfileId: string;
|
||||
/** The customer billed. Omit only when billing a shipping line instead. */
|
||||
companyId?: string | null;
|
||||
companyProfileId?: string | null;
|
||||
/**
|
||||
* The shipping line billed, for an invoice covering batched shipping-line
|
||||
* credits. Mutually exclusive with `companyId` — the DB enforces this via
|
||||
* `chk_invoices_single_payer`, and {@link createInvoice} rejects a payload
|
||||
* setting both or neither before it ever reaches the constraint.
|
||||
*/
|
||||
shippingLineCompanyId?: string | null;
|
||||
lines: InvoiceLineInput[];
|
||||
currency?: string;
|
||||
/** Explicit pre-tax subtotal; defaults to the sum of line amounts. */
|
||||
@@ -139,8 +147,11 @@ export interface InvoiceEventPayload {
|
||||
source: Freight.InvoiceSource;
|
||||
sourceId: string;
|
||||
type: string;
|
||||
companyId: string;
|
||||
companyProfileId: string;
|
||||
/** Null when the payer is a shipping line rather than a customer company. */
|
||||
companyId: string | null;
|
||||
companyProfileId: string | null;
|
||||
/** Set only on shipping-line invoices; mutually exclusive with `companyId`. */
|
||||
shippingLineCompanyId?: string | null;
|
||||
totalAmount: number;
|
||||
currency: string;
|
||||
status: Freight.InvoiceStatus;
|
||||
@@ -609,7 +620,6 @@ export class BillingService {
|
||||
input: GenerateInvoiceInput,
|
||||
manager?: EntityManager,
|
||||
): Promise<Invoice & { lines: InvoiceLine[] }> {
|
||||
console.log("oooooooooo", input);
|
||||
const run = (mg: EntityManager) => this.createInvoice(input, mg);
|
||||
return manager ? run(manager) : this.dataSource.transaction(run);
|
||||
}
|
||||
@@ -622,6 +632,21 @@ export class BillingService {
|
||||
const status = input.status ?? Freight.InvoiceStatus.Pending;
|
||||
const issued = status !== Freight.InvoiceStatus.Draft;
|
||||
|
||||
// Exactly one payer, checked here so a bad payload fails with a clear
|
||||
// message instead of a raw `chk_invoices_single_payer` violation.
|
||||
const billsCompany = Boolean(input.companyId);
|
||||
const billsShippingLine = Boolean(input.shippingLineCompanyId);
|
||||
if (billsCompany === billsShippingLine) {
|
||||
throw new BadRequestException(
|
||||
"An invoice must be billed to exactly one payer: either companyId or shippingLineCompanyId.",
|
||||
);
|
||||
}
|
||||
if (billsCompany && !input.companyProfileId) {
|
||||
throw new BadRequestException(
|
||||
"companyProfileId is required when billing a company.",
|
||||
);
|
||||
}
|
||||
|
||||
const lines = input.lines.map((l) => {
|
||||
const quantity = l.quantity ?? 1;
|
||||
const unitRate = l.unitRate ?? 0;
|
||||
@@ -657,8 +682,9 @@ export class BillingService {
|
||||
source: input.source,
|
||||
sourceId: input.sourceId,
|
||||
type: input.type,
|
||||
companyId: input.companyId,
|
||||
companyProfileId: input.companyProfileId,
|
||||
companyId: input.companyId ?? null,
|
||||
companyProfileId: input.companyProfileId ?? null,
|
||||
shippingLineCompanyId: input.shippingLineCompanyId ?? null,
|
||||
subtotalAmount: round2(subtotalAmount),
|
||||
taxAmount: round2(taxAmount),
|
||||
totalAmount: round2(totalAmount),
|
||||
@@ -988,6 +1014,7 @@ export class BillingService {
|
||||
type: invoice.type,
|
||||
companyId: invoice.companyId,
|
||||
companyProfileId: invoice.companyProfileId,
|
||||
shippingLineCompanyId: invoice.shippingLineCompanyId ?? null,
|
||||
totalAmount: invoice.totalAmount,
|
||||
currency: invoice.currency,
|
||||
status: invoice.status,
|
||||
|
||||
@@ -23,22 +23,37 @@ export class Invoice extends BaseEntity {
|
||||
@Column({ name: "invoice_number", type: "varchar", length: 64, unique: true })
|
||||
invoiceNumber!: string;
|
||||
|
||||
/** The customer (company) this invoice is billed to. */
|
||||
@Column({ name: "company_id", type: "uuid" })
|
||||
companyId!: string;
|
||||
/**
|
||||
* The customer (company) this invoice is billed to. Null on a shipping-line
|
||||
* invoice, which is billed to `shippingLineCompanyId` instead — a shipping
|
||||
* line is deliberately not a `companies` row. A DB CHECK
|
||||
* (`chk_invoices_single_payer`) guarantees exactly one of the two is set.
|
||||
*/
|
||||
@Column({ name: "company_id", type: "uuid", nullable: true })
|
||||
companyId!: string | null;
|
||||
|
||||
@ManyToOne(() => Company)
|
||||
@JoinColumn({ name: "company_id" })
|
||||
company?: Company;
|
||||
|
||||
/** The specific company profile (importer/exporter/forwarder/...) billed. */
|
||||
@Column({ name: "company_profile_id", type: "uuid" })
|
||||
companyProfileId!: string;
|
||||
@Column({ name: "company_profile_id", type: "uuid", nullable: true })
|
||||
companyProfileId!: string | null;
|
||||
|
||||
@ManyToOne(() => CompanyProfile)
|
||||
@JoinColumn({ name: "company_profile_id" })
|
||||
companyProfile?: CompanyProfile;
|
||||
|
||||
/**
|
||||
* The shipping line billed, when this invoice bills batched shipping-line
|
||||
* credits rather than a customer booking. Mutually exclusive with
|
||||
* `companyId`. No relation is declared: `ShippingLineCredit` already owns
|
||||
* that edge, and importing the shipping-lines module here would close an
|
||||
* import cycle (shipping-lines already depends on billing).
|
||||
*/
|
||||
@Column({ name: "shipping_line_company_id", type: "uuid", nullable: true })
|
||||
shippingLineCompanyId?: string | null;
|
||||
|
||||
/** Sum of line amounts before tax; defaults to `totalAmount` for tax-free invoices. */
|
||||
@Column({ name: "subtotal_amount", type: "numeric", precision: 14, scale: 2, default: 0 })
|
||||
subtotalAmount!: number;
|
||||
|
||||
Reference in New Issue
Block a user