diff --git a/packages/models/src/config/behaviors/api.ts b/packages/models/src/config/behaviors/api.ts index 9fbb4efb..43ff6411 100644 --- a/packages/models/src/config/behaviors/api.ts +++ b/packages/models/src/config/behaviors/api.ts @@ -9,6 +9,7 @@ import { addUrlTagToProvideReactCache } from "../../react/asyncResourceInvalidat import { apiArticleBehaviors } from "../../article/Article/behaviors/index.js"; import { apiContractBehaviors } from "../../contract/Contract/behaviors/index.js"; import { apiContractItemBehaviors } from "../../contract/ContractItem/behaviors/index.js"; +import { apiInvoiceBehaviors } from "../../invoice/Invoice/behaviors/index.js"; class ApiSetupState { private _client: MittwaldAPIV2Client | undefined; @@ -28,6 +29,7 @@ class ApiSetupState { config.behaviors.server = apiServerBehaviors(client); config.behaviors.customer = apiCustomerBehaviors(client); config.behaviors.ingress = apiIngressBehaviors(client); + config.behaviors.invoice = apiInvoiceBehaviors(client); config.behaviors.appInstallation = apiAppInstallationBehaviors(client); config.behaviors.contract = apiContractBehaviors(client); config.behaviors.contractItem = apiContractItemBehaviors(client); diff --git a/packages/models/src/config/config.ts b/packages/models/src/config/config.ts index c20a52c3..39b03a53 100644 --- a/packages/models/src/config/config.ts +++ b/packages/models/src/config/config.ts @@ -6,6 +6,7 @@ import { ContractBehaviors } from "../contract/Contract/behaviors/index.js"; import { AppInstallationBehaviors } from "../app/AppInstallation/behaviors/index.js"; import { ContractItemBehaviors } from "../contract/ContractItem/behaviors/index.js"; import { ArticleBehaviors } from "../article/Article/behaviors/index.js"; +import { InvoiceBehaviors } from "../invoice/Invoice/behaviors/index.js"; interface Config { defaultPaginationLimit: number; @@ -17,6 +18,7 @@ interface Config { server: ServerBehaviors; customer: CustomerBehaviors; ingress: IngressBehaviors; + invoice: InvoiceBehaviors; appInstallation: AppInstallationBehaviors; }; } @@ -31,6 +33,7 @@ export const config: Config = { server: undefined as unknown as ServerBehaviors, customer: undefined as unknown as CustomerBehaviors, ingress: undefined as unknown as IngressBehaviors, + invoice: undefined as unknown as InvoiceBehaviors, appInstallation: undefined as unknown as AppInstallationBehaviors, }, }; diff --git a/packages/models/src/invoice/Invoice/Invoice.ts b/packages/models/src/invoice/Invoice/Invoice.ts new file mode 100644 index 00000000..089eb908 --- /dev/null +++ b/packages/models/src/invoice/Invoice/Invoice.ts @@ -0,0 +1,139 @@ +import { + InvoiceListItemData, + InvoiceData, + InvoiceFileAccessTokenData, + InvoiceListQueryModelData, + InvoiceListQueryData, +} from "./types.js"; +import { config } from "../../config/config.js"; +import { classes } from "polytype"; +import { DataModel } from "../../base/DataModel.js"; +import assertObjectFound from "../../base/assertObjectFound.js"; +import { provideReact } from "../../react/provideReact.js"; +import { ReferenceModel } from "../../base/ReferenceModel.js"; +import { ListDataModel, ListQueryModel } from "../../base/index.js"; + +export class Invoice extends ReferenceModel { + public static ofId(id: string): Invoice { + return new Invoice(id); + } + + public static find = provideReact( + async (id: string): Promise => { + const data = await config.behaviors.invoice.find(id); + + if (data !== undefined) { + return new InvoiceDetailed(data); + } + }, + ); + + public static get = provideReact( + async (id: string): Promise => { + const project = await this.find(id); + assertObjectFound(project, this, id); + return project; + }, + ); + + public static query(query: InvoiceListQueryModelData) { + return new InvoiceListQuery(query); + } + + public static requestFileAccessToken = provideReact( + async ( + invoiceId: string, + customerId: string, + ): Promise => { + return await config.behaviors.invoice.requestFileAccessToken( + invoiceId, + customerId, + ); + }, + ); +} + +class InvoiceCommon extends classes( + DataModel, + Invoice, +) { + public constructor(data: InvoiceListItemData | InvoiceData) { + super([data], [data.customerId]); + } +} + +export class InvoiceDetailed extends classes( + InvoiceCommon, + DataModel, +) { + public constructor(data: InvoiceData) { + super([data]); + } +} + +export class InvoiceListItem extends classes( + InvoiceCommon, + DataModel, +) { + public constructor(data: InvoiceListItemData) { + super([data]); + } +} + +export class InvoiceListQuery extends ListQueryModel { + public constructor(query: InvoiceListQueryModelData) { + super(query); + } + + public refine(query: InvoiceListQueryData) { + return new InvoiceListQuery({ + ...this.query, + ...query, + }); + } + + public execute = provideReact(async () => { + const { customer, ...query } = this.query; + + const customerId = customer.id; + const request = { + customerId: customerId, + queryParameters: { + limit: config.defaultPaginationLimit, + ...query, + }, + }; + const { items, totalCount } = await config.behaviors.invoice.list(request); + + return new InvoiceList( + this.query, + items.map((d) => new InvoiceListItem(d)), + totalCount, + ); + }, [this.queryId]); + + public getTotalCount = provideReact(async () => { + const { totalCount } = await this.refine({ limit: 1 }).execute(); + return totalCount; + }, [this.queryId]); + + public findOneAndOnly = provideReact(async () => { + const { items, totalCount } = await this.refine({ limit: 1 }).execute(); + if (totalCount === 1) { + return items[0]; + } + }, [this.queryId]); +} + +export class InvoiceList extends classes( + InvoiceListQuery, + ListDataModel, +) { + public constructor( + query: InvoiceListQueryModelData, + invoices: InvoiceListItem[], + totalCount: number, + ) { + super([query], [invoices, totalCount]); + } +} diff --git a/packages/models/src/invoice/Invoice/behaviors/api.ts b/packages/models/src/invoice/Invoice/behaviors/api.ts new file mode 100644 index 00000000..1f355b91 --- /dev/null +++ b/packages/models/src/invoice/Invoice/behaviors/api.ts @@ -0,0 +1,40 @@ +import { + assertStatus, + MittwaldAPIV2Client, + extractTotalCountHeader, + assertOneOfStatus, +} from "@mittwald/api-client"; +import { InvoiceBehaviors } from "./types.js"; + +export const apiInvoiceBehaviors = ( + client: MittwaldAPIV2Client, +): InvoiceBehaviors => ({ + find: async (invoiceId) => { + const response = await client.contract.invoiceDetail({ + invoiceId, + }); + + if (response.status === 200) { + return response.data; + } + assertOneOfStatus(response, [404, 429]); + }, + + list: async (request) => { + const response = await client.contract.invoiceListCustomerInvoices(request); + assertStatus(response, 200); + return { + items: response.data, + totalCount: extractTotalCountHeader(response), + }; + }, + + requestFileAccessToken: async (invoiceId, customerId) => { + const response = await client.contract.invoiceGetFileAccessToken({ + invoiceId, + customerId, + }); + assertStatus(response, 200); + return response.data; + }, +}); diff --git a/packages/models/src/invoice/Invoice/behaviors/index.ts b/packages/models/src/invoice/Invoice/behaviors/index.ts new file mode 100644 index 00000000..a7b74f7c --- /dev/null +++ b/packages/models/src/invoice/Invoice/behaviors/index.ts @@ -0,0 +1,2 @@ +export * from "./api.js"; +export * from "./types.js"; diff --git a/packages/models/src/invoice/Invoice/behaviors/types.ts b/packages/models/src/invoice/Invoice/behaviors/types.ts new file mode 100644 index 00000000..3c9a9783 --- /dev/null +++ b/packages/models/src/invoice/Invoice/behaviors/types.ts @@ -0,0 +1,21 @@ +import { + InvoiceData, + InvoiceFileAccessTokenData, + InvoiceListItemData, + InvoiceListQueryData, +} from "../types.js"; +import { QueryResponseData } from "../../../base/index.js"; + +export interface InvoiceBehaviors { + find: (invoiceId: string) => Promise; + + list: (request: { + customerId: string; + queryParameters?: InvoiceListQueryData; + }) => Promise>; + + requestFileAccessToken: ( + invoiceId: string, + customerId: string, + ) => Promise; +} diff --git a/packages/models/src/invoice/Invoice/index.ts b/packages/models/src/invoice/Invoice/index.ts new file mode 100644 index 00000000..9b23d19b --- /dev/null +++ b/packages/models/src/invoice/Invoice/index.ts @@ -0,0 +1,2 @@ +export * from "./Invoice.js"; +export * from "./types.js"; diff --git a/packages/models/src/invoice/Invoice/types.ts b/packages/models/src/invoice/Invoice/types.ts new file mode 100644 index 00000000..c982d64f --- /dev/null +++ b/packages/models/src/invoice/Invoice/types.ts @@ -0,0 +1,20 @@ +import { MittwaldAPIV2 } from "@mittwald/api-client"; +import { Customer } from "../../customer/index.js"; + +export type InvoiceListQueryData = + MittwaldAPIV2.Paths.V2CustomersCustomerIdInvoices.Get.Parameters.Query; + +export type InvoiceData = MittwaldAPIV2.Operations.InvoiceDetail.ResponseData; + +export type InvoiceListItemData = + MittwaldAPIV2.Operations.InvoiceListCustomerInvoices.ResponseData[number]; + +export type InvoiceFileAccessTokenData = + MittwaldAPIV2.Operations.InvoiceGetFileAccessToken.ResponseData; + +export type InvoiceListQueryModelData = Omit< + InvoiceListQueryData, + "customerId" +> & { + customer: Customer; +}; diff --git a/packages/models/src/invoice/index.ts b/packages/models/src/invoice/index.ts new file mode 100644 index 00000000..fe7658c7 --- /dev/null +++ b/packages/models/src/invoice/index.ts @@ -0,0 +1 @@ +export * from "./Invoice/index.js";