shipping line

This commit is contained in:
Marshal
2026-08-13 18:56:52 +00:00
parent 0e00a98ef3
commit b9ba830a09
48 changed files with 6766 additions and 639 deletions

View File

@@ -50,6 +50,14 @@ import type {
RegisterShippingLineCompanyResult,
ShippingLineCompany,
} from "@/types/shippingLineCompany";
import type {
CreditInvoicePendingAction,
GeneratedCreditInvoice,
OutstandingTotals,
PaginatedCreditInvoices,
PaginatedShippingLineCredits,
ShippingLineCreditStatus,
} from "@/types/shippingLineCredit";
import {
RuleEngineListResult,
RuleEngineRecord,
@@ -163,6 +171,7 @@ import { containerTypesService } from "./container-types.service";
import { containerService, type Container } from "./containerService";
import { customersService } from "./customers.service";
import { shippingLineCompaniesService } from "./shippingLineCompanies.service";
import { shippingLineCreditsService } from "./shippingLineCredits.service";
import { eimsService } from "./eims.service";
import type { EimsInvoiceStatusView, EimsVerifyResult } from "@/types/eims";
import { invoicesService } from "./invoices.service";
@@ -2836,6 +2845,123 @@ export const api = {
),
},
shippingLineCredits: {
summary: endpoint<{ shippingLineId?: string }, OutstandingTotals>(
"shippingLineCredits",
"summary",
({ shippingLineId }) => shippingLineCreditsService.summary(shippingLineId),
({ shippingLineId }) =>
QUERY_KEYS.SHIPPING_LINE_CREDITS.summary(shippingLineId),
),
list: endpoint<
{
page: number;
pageSize: number;
status?: ShippingLineCreditStatus;
shippingLineId?: string;
},
PaginatedShippingLineCredits
>(
"shippingLineCredits",
"list",
(filter) => shippingLineCreditsService.list(filter),
({ page, pageSize, status, shippingLineId }) =>
QUERY_KEYS.SHIPPING_LINE_CREDITS.list(
page,
pageSize,
status,
shippingLineId,
),
),
generateInvoice: endpoint<
{ creditIds: string[]; dueInDays?: number },
GeneratedCreditInvoice
>(
"shippingLineCredits",
"generateInvoice",
({ creditIds, dueInDays }) =>
shippingLineCreditsService.generateInvoice(creditIds, dueInDays),
undefined,
// Billing a batch changes ledger rows, the summary totals and (via the
// draft invoice) the invoices list.
() => [QUERY_KEYS.SHIPPING_LINE_CREDITS.ROOT, QUERY_KEYS.INVOICES.ROOT],
),
listInvoices: endpoint<
{
page: number;
pageSize: number;
status?: string;
shippingLineId?: string;
},
PaginatedCreditInvoices
>(
"shippingLineCredits",
"listInvoices",
(filter) => shippingLineCreditsService.listInvoices(filter),
({ page, pageSize, status, shippingLineId }) =>
QUERY_KEYS.SHIPPING_LINE_CREDITS.invoices(
page,
pageSize,
status,
shippingLineId,
),
),
pendingInvoiceActions: endpoint<
{ invoiceIds: string[] },
CreditInvoicePendingAction[]
>(
"shippingLineCredits",
"pendingInvoiceActions",
({ invoiceIds }) =>
shippingLineCreditsService.pendingInvoiceActions(invoiceIds),
({ invoiceIds }) =>
[
"shipping-line-credits",
"pending-actions",
[...invoiceIds].sort().join(","),
] as const,
),
requestInvoiceAction: endpoint<
{
invoiceId: string;
action: "MARK_PAID" | "CANCEL";
reason: string;
paymentReference?: string;
},
CreditInvoicePendingAction
>(
"shippingLineCredits",
"requestInvoiceAction",
({ invoiceId, action, reason, paymentReference }) =>
shippingLineCreditsService.requestInvoiceAction(
invoiceId,
action,
reason,
paymentReference,
),
undefined,
() => [QUERY_KEYS.SHIPPING_LINE_CREDITS.ROOT],
),
decideInvoiceAction: endpoint<
{ approvalId: string; approve: boolean; note?: string },
CreditInvoicePendingAction
>(
"shippingLineCredits",
"decideInvoiceAction",
({ approvalId, approve, note }) =>
shippingLineCreditsService.decideInvoiceAction(approvalId, approve, note),
undefined,
// Approving executes a billing action, so both surfaces move.
() => [QUERY_KEYS.SHIPPING_LINE_CREDITS.ROOT, QUERY_KEYS.INVOICES.ROOT],
),
},
customers: {
stats: endpoint<Record<string, never>, CompanyStats>(
"customers",

View File

@@ -0,0 +1,131 @@
import { api as apiClient } from "@/auth/http";
import { URL_CONSTANTS } from "@/constants/URLS";
import type {
CreditInvoicePendingAction,
GeneratedCreditInvoice,
OutstandingTotals,
PaginatedCreditInvoices,
PaginatedShippingLineCredits,
ShippingLineCreditStatus,
} from "@/types/shippingLineCredit";
export interface ShippingLineCreditListFilter {
page?: number;
pageSize?: number;
status?: ShippingLineCreditStatus;
/** Narrow to one line; omit for all lines. */
shippingLineId?: string;
}
export const shippingLineCreditsService = {
/** Outstanding totals — every line, or one line when an id is given. */
summary(shippingLineId?: string): Promise<OutstandingTotals> {
return apiClient
.get<OutstandingTotals>(URL_CONSTANTS.SHIPPING_LINE_CREDITS.SUMMARY, {
params: shippingLineId ? { shippingLineId } : {},
})
.then((r) => r.data);
},
/** The whole credit ledger, newest first, optionally filtered. */
list(
filter: ShippingLineCreditListFilter = {},
): Promise<PaginatedShippingLineCredits> {
const { page = 1, pageSize = 20, status, shippingLineId } = filter;
return apiClient
.get<PaginatedShippingLineCredits>(
URL_CONSTANTS.SHIPPING_LINE_CREDITS.BASE,
{
params: {
page,
pageSize,
...(status ? { status } : {}),
...(shippingLineId ? { shippingLineId } : {}),
},
},
)
.then((r) => r.data);
},
/**
* Bill a batch of unbilled credits as one invoice. The API enforces that all
* credits belong to one shipping line and share one currency.
*/
generateInvoice(
creditIds: string[],
dueInDays?: number,
): Promise<GeneratedCreditInvoice> {
return apiClient
.post<GeneratedCreditInvoice>(URL_CONSTANTS.SHIPPING_LINE_CREDITS.INVOICE, {
creditIds,
...(dueInDays ? { dueInDays } : {}),
})
.then((r) => r.data);
},
/** Credit invoices with any pending manual-action request attached. */
listInvoices(filter: {
page?: number;
pageSize?: number;
status?: string;
shippingLineId?: string;
} = {}): Promise<PaginatedCreditInvoices> {
const { page = 1, pageSize = 20, status, shippingLineId } = filter;
return apiClient
.get<PaginatedCreditInvoices>(URL_CONSTANTS.SHIPPING_LINE_CREDITS.INVOICES, {
params: {
page,
pageSize,
...(status ? { status } : {}),
...(shippingLineId ? { shippingLineId } : {}),
},
})
.then((r) => r.data);
},
/** Undecided manual-action requests for a batch of invoice ids. */
pendingInvoiceActions(
invoiceIds: string[],
): Promise<CreditInvoicePendingAction[]> {
if (!invoiceIds.length) return Promise.resolve([]);
return apiClient
.get<CreditInvoicePendingAction[]>(
`${URL_CONSTANTS.SHIPPING_LINE_CREDITS.BASE}/invoice-actions/pending`,
{ params: { invoiceIds: invoiceIds.join(",") } },
)
.then((r) => r.data);
},
/** Maker step: raise a mark-paid or cancel request on a credit invoice. */
requestInvoiceAction(
invoiceId: string,
action: "MARK_PAID" | "CANCEL",
reason: string,
paymentReference?: string,
): Promise<CreditInvoicePendingAction> {
const url =
action === "MARK_PAID"
? URL_CONSTANTS.SHIPPING_LINE_CREDITS.MARK_PAID_REQUEST(invoiceId)
: URL_CONSTANTS.SHIPPING_LINE_CREDITS.CANCEL_REQUEST(invoiceId);
return apiClient
.post<CreditInvoicePendingAction>(url, {
reason,
...(paymentReference ? { paymentReference } : {}),
})
.then((r) => r.data);
},
/** Decision step: approve (executes) or reject a pending request. */
decideInvoiceAction(
approvalId: string,
approve: boolean,
note?: string,
): Promise<CreditInvoicePendingAction> {
const url = approve
? URL_CONSTANTS.SHIPPING_LINE_CREDITS.APPROVE_ACTION(approvalId)
: URL_CONSTANTS.SHIPPING_LINE_CREDITS.REJECT_ACTION(approvalId);
return apiClient
.post<CreditInvoicePendingAction>(url, note ? { note } : {})
.then((r) => r.data);
},
};