From bdab9c9995b2048ab8af0bb09b8e5d99a48c0166 Mon Sep 17 00:00:00 2001 From: Mathis Fullriede Date: Mon, 29 Apr 2024 08:05:23 +0200 Subject: [PATCH 1/5] feat(Invoice): add invoice model --- packages/models/src/config/behaviors/api.ts | 2 + packages/models/src/config/config.ts | 3 + .../models/src/invoice/Invoice/Invoice.ts | 89 +++++++++++++++++++ .../src/invoice/Invoice/behaviors/api.ts | 35 ++++++++ .../src/invoice/Invoice/behaviors/index.ts | 2 + .../src/invoice/Invoice/behaviors/types.ts | 16 ++++ packages/models/src/invoice/Invoice/index.ts | 2 + packages/models/src/invoice/Invoice/types.ts | 12 +++ packages/models/src/invoice/index.ts | 1 + 9 files changed, 162 insertions(+) create mode 100644 packages/models/src/invoice/Invoice/Invoice.ts create mode 100644 packages/models/src/invoice/Invoice/behaviors/api.ts create mode 100644 packages/models/src/invoice/Invoice/behaviors/index.ts create mode 100644 packages/models/src/invoice/Invoice/behaviors/types.ts create mode 100644 packages/models/src/invoice/Invoice/index.ts create mode 100644 packages/models/src/invoice/Invoice/types.ts create mode 100644 packages/models/src/invoice/index.ts diff --git a/packages/models/src/config/behaviors/api.ts b/packages/models/src/config/behaviors/api.ts index 23613d01..4835d054 100644 --- a/packages/models/src/config/behaviors/api.ts +++ b/packages/models/src/config/behaviors/api.ts @@ -5,6 +5,7 @@ import { apiServerBehaviors } from "../../server/Server/behaviors/index.js"; import { apiCustomerBehaviors } from "../../customer/Customer/behaviors/index.js"; import { apiIngressBehaviors } from "../../domain/Ingress/behaviors/index.js"; import { apiAppInstallationBehaviors } from "../../app/AppInstallation/behaviors/index.js"; +import { apiInvoiceBehaviors } from "../../invoice/Invoice/behaviors/index.js"; class ApiSetupState { private _client: MittwaldAPIV2Client | undefined; @@ -21,6 +22,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); } diff --git a/packages/models/src/config/config.ts b/packages/models/src/config/config.ts index fbee52b8..5bcd14c7 100644 --- a/packages/models/src/config/config.ts +++ b/packages/models/src/config/config.ts @@ -3,6 +3,7 @@ import { ServerBehaviors } from "../server/Server/behaviors/index.js"; import { CustomerBehaviors } from "../customer/Customer/behaviors/index.js"; import { IngressBehaviors } from "../domain/Ingress/behaviors/index.js"; import { AppInstallationBehaviors } from "../app/AppInstallation/behaviors/index.js"; +import { InvoiceBehaviors } from "../invoice/Invoice/behaviors/index.js"; interface Config { behaviors: { @@ -10,6 +11,7 @@ interface Config { server: ServerBehaviors; customer: CustomerBehaviors; ingress: IngressBehaviors; + invoice: InvoiceBehaviors; appInstallation: AppInstallationBehaviors; }; } @@ -20,6 +22,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..fe88c66b --- /dev/null +++ b/packages/models/src/invoice/Invoice/Invoice.ts @@ -0,0 +1,89 @@ +import { InvoiceListItemData, InvoiceData, InvoiceListQuery } 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 { + type AsyncResourceVariant, + provideReact, +} from "../../lib/provideReact.js"; +import { Customer } from "../../customer/index.js"; +import { ReferenceModel } from "../../base/ReferenceModel.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 getDetailed = provideReact(() => + Invoice.get(this.id), + ) as AsyncResourceVariant; + + public static list = provideReact( + async ( + query: InvoiceListQuery = {}, + ): Promise>> => { + const data = await config.behaviors.project.list(query); + return Object.freeze(data.map((d) => new InvoiceListItem(d))); + }, + ); + + public static async requestFileAccessToken( + invoiceId: string, + customerId: string, + ): Promise { + const { token } = await config.behaviors.invoice.requestFileAccessToken( + invoiceId, + customerId, + ); + return token; + } +} + +class InvoiceCommon extends classes( + DataModel, + Invoice, +) { + public readonly customer: Customer; + + public constructor(data: InvoiceListItemData | InvoiceData) { + super([data.id]); + this.customer = Customer.ofId(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]); + } +} 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..70880b57 --- /dev/null +++ b/packages/models/src/invoice/Invoice/behaviors/api.ts @@ -0,0 +1,35 @@ +import { assertStatus, MittwaldAPIV2Client } from "@mittwald/api-client"; +import { assertOneOfStatus } from "@mittwald/api-client"; +import { InvoiceBehaviors } from "./types.js"; + +export const apiInvoiceBehaviors = ( + client: MittwaldAPIV2Client, +): InvoiceBehaviors => ({ + find: async (invoiceId) => { + const response = await client.invoice.invoiceDetail({ + invoiceId, + }); + + if (response.status === 200) { + return response.data; + } + assertOneOfStatus(response, [403]); + }, + + list: async (query) => { + const response = await client.invoice.invoiceListCustomerInvoices({ + queryParameters: query, + }); + assertStatus(response, 200); + return response.data; + }, + + requestFileAccessToken: async (invoiceId, customerId) => { + const response = await client.invoice.requestFileAccessToken({ + 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..236ae947 --- /dev/null +++ b/packages/models/src/invoice/Invoice/behaviors/types.ts @@ -0,0 +1,16 @@ +import { + InvoiceData, + InvoiceFileAccessTokenData, + InvoiceListItemData, + InvoiceListQuery, +} from "../types.js"; + +export interface InvoiceBehaviors { + find: (invoiceId: string) => Promise; + list: (query?: InvoiceListQuery) => 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..85423bdb --- /dev/null +++ b/packages/models/src/invoice/Invoice/types.ts @@ -0,0 +1,12 @@ +import { MittwaldAPIV2 } from "@mittwald/api-client"; + +export type InvoiceListQuery = + MittwaldAPIV2.Paths.InvoiceListCustomerInvoices.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; 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"; From 0a6bc929b228d641d4411a4be36eed89ca5ef5cd Mon Sep 17 00:00:00 2001 From: Mathis Fullriede Date: Mon, 29 Apr 2024 08:49:04 +0200 Subject: [PATCH 2/5] feat(Invoice): add invoice model --- .../models/src/invoice/Invoice/Invoice.ts | 31 ++++++++++++------- .../src/invoice/Invoice/behaviors/api.ts | 3 +- .../src/invoice/Invoice/behaviors/types.ts | 5 ++- packages/models/src/invoice/Invoice/types.ts | 2 +- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/packages/models/src/invoice/Invoice/Invoice.ts b/packages/models/src/invoice/Invoice/Invoice.ts index fe88c66b..5c3f5ae9 100644 --- a/packages/models/src/invoice/Invoice/Invoice.ts +++ b/packages/models/src/invoice/Invoice/Invoice.ts @@ -1,4 +1,9 @@ -import { InvoiceListItemData, InvoiceData, InvoiceListQuery } from "./types.js"; +import { + InvoiceListItemData, + InvoiceData, + InvoiceListQuery, + InvoiceFileAccessTokenData, +} from "./types.js"; import { config } from "../../config/config.js"; import { classes } from "polytype"; import { DataModel } from "../../base/DataModel.js"; @@ -39,23 +44,25 @@ export class Invoice extends ReferenceModel { public static list = provideReact( async ( + customerId: string, query: InvoiceListQuery = {}, ): Promise>> => { - const data = await config.behaviors.project.list(query); + const data = await config.behaviors.invoice.list(customerId, query); return Object.freeze(data.map((d) => new InvoiceListItem(d))); }, ); - public static async requestFileAccessToken( - invoiceId: string, - customerId: string, - ): Promise { - const { token } = await config.behaviors.invoice.requestFileAccessToken( - invoiceId, - customerId, - ); - return token; - } + public static requestFileAccessToken = provideReact( + async ( + invoiceId: string, + customerId: string, + ): Promise => { + return await config.behaviors.invoice.requestFileAccessToken( + invoiceId, + customerId, + ); + }, + ); } class InvoiceCommon extends classes( diff --git a/packages/models/src/invoice/Invoice/behaviors/api.ts b/packages/models/src/invoice/Invoice/behaviors/api.ts index 70880b57..15a422e4 100644 --- a/packages/models/src/invoice/Invoice/behaviors/api.ts +++ b/packages/models/src/invoice/Invoice/behaviors/api.ts @@ -16,8 +16,9 @@ export const apiInvoiceBehaviors = ( assertOneOfStatus(response, [403]); }, - list: async (query) => { + list: async (customerId: string, query) => { const response = await client.invoice.invoiceListCustomerInvoices({ + customerId, queryParameters: query, }); assertStatus(response, 200); diff --git a/packages/models/src/invoice/Invoice/behaviors/types.ts b/packages/models/src/invoice/Invoice/behaviors/types.ts index 236ae947..ef9da7be 100644 --- a/packages/models/src/invoice/Invoice/behaviors/types.ts +++ b/packages/models/src/invoice/Invoice/behaviors/types.ts @@ -7,7 +7,10 @@ import { export interface InvoiceBehaviors { find: (invoiceId: string) => Promise; - list: (query?: InvoiceListQuery) => Promise; + list: ( + customerId: string, + query?: InvoiceListQuery, + ) => Promise; requestFileAccessToken: ( invoiceId: string, diff --git a/packages/models/src/invoice/Invoice/types.ts b/packages/models/src/invoice/Invoice/types.ts index 85423bdb..ee912f22 100644 --- a/packages/models/src/invoice/Invoice/types.ts +++ b/packages/models/src/invoice/Invoice/types.ts @@ -3,7 +3,7 @@ import { MittwaldAPIV2 } from "@mittwald/api-client"; export type InvoiceListQuery = MittwaldAPIV2.Paths.InvoiceListCustomerInvoices.Get.Parameters.Query; -export type InvoiceData = MittwaldAPIV2.Operations.invoiceDetail.ResponseData; +export type InvoiceData = MittwaldAPIV2.Operations.InvoiceDetail.ResponseData; export type InvoiceListItemData = MittwaldAPIV2.Operations.InvoiceListCustomerInvoices.ResponseData[number]; From ce3b3a73a1200027bb0417c421c56c1055d95f45 Mon Sep 17 00:00:00 2001 From: Mathis Fullriede Date: Mon, 23 Sep 2024 11:33:33 +0200 Subject: [PATCH 3/5] add missing import --- packages/models/src/config/behaviors/api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/models/src/config/behaviors/api.ts b/packages/models/src/config/behaviors/api.ts index d769863a..c1b59357 100644 --- a/packages/models/src/config/behaviors/api.ts +++ b/packages/models/src/config/behaviors/api.ts @@ -6,6 +6,7 @@ import { apiCustomerBehaviors } from "../../customer/Customer/behaviors/index.js import { apiIngressBehaviors } from "../../domain/Ingress/behaviors/index.js"; import { apiAppInstallationBehaviors } from "../../app/AppInstallation/behaviors/index.js"; import { apiInvoiceBehaviors } from "../../invoice/Invoice/behaviors/index.js"; +import { updateCacheTagsBeforeRequest } from "../../react/asyncResourceInvalidation.js"; class ApiSetupState { private _client: MittwaldAPIV2Client | undefined; From fea0ebaab962bcd9203e1f4727ac1b659a38efcc Mon Sep 17 00:00:00 2001 From: Mathis Fullriede Date: Mon, 30 Sep 2024 10:13:16 +0200 Subject: [PATCH 4/5] recreate invoice model with new structure --- .../models/src/invoice/Invoice/Invoice.ts | 89 ++++++++++++++----- .../src/invoice/Invoice/behaviors/api.ts | 25 +++--- .../src/invoice/Invoice/behaviors/types.ts | 12 +-- packages/models/src/invoice/Invoice/types.ts | 12 ++- 4 files changed, 98 insertions(+), 40 deletions(-) diff --git a/packages/models/src/invoice/Invoice/Invoice.ts b/packages/models/src/invoice/Invoice/Invoice.ts index 5c3f5ae9..089eb908 100644 --- a/packages/models/src/invoice/Invoice/Invoice.ts +++ b/packages/models/src/invoice/Invoice/Invoice.ts @@ -1,19 +1,17 @@ import { InvoiceListItemData, InvoiceData, - InvoiceListQuery, 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 { - type AsyncResourceVariant, - provideReact, -} from "../../lib/provideReact.js"; -import { Customer } from "../../customer/index.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 { @@ -38,19 +36,9 @@ export class Invoice extends ReferenceModel { }, ); - public getDetailed = provideReact(() => - Invoice.get(this.id), - ) as AsyncResourceVariant; - - public static list = provideReact( - async ( - customerId: string, - query: InvoiceListQuery = {}, - ): Promise>> => { - const data = await config.behaviors.invoice.list(customerId, query); - return Object.freeze(data.map((d) => new InvoiceListItem(d))); - }, - ); + public static query(query: InvoiceListQueryModelData) { + return new InvoiceListQuery(query); + } public static requestFileAccessToken = provideReact( async ( @@ -69,11 +57,8 @@ class InvoiceCommon extends classes( DataModel, Invoice, ) { - public readonly customer: Customer; - public constructor(data: InvoiceListItemData | InvoiceData) { - super([data.id]); - this.customer = Customer.ofId(data.customerId); + super([data], [data.customerId]); } } @@ -94,3 +79,61 @@ export class InvoiceListItem extends classes( 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 index 47ddf8bf..14d2353f 100644 --- a/packages/models/src/invoice/Invoice/behaviors/api.ts +++ b/packages/models/src/invoice/Invoice/behaviors/api.ts @@ -1,31 +1,36 @@ -import { assertStatus, MittwaldAPIV2Client } from "@mittwald/api-client"; +import { + assertStatus, + MittwaldAPIV2Client, + extractTotalCountHeader, +} from "@mittwald/api-client"; import { InvoiceBehaviors } from "./types.js"; +import { assertOneOfStatus } from "../../../../../../.nx/cache/4583642927684206384/outputs/packages/commons/dist/types/index.js"; export const apiInvoiceBehaviors = ( client: MittwaldAPIV2Client, ): InvoiceBehaviors => ({ find: async (invoiceId) => { - const response = await client.invoice.invoiceDetail({ + const response = await client.contract.invoiceDetail({ invoiceId, }); if (response.status === 200) { return response.data; } - assertStatus(response, 403); + assertOneOfStatus(response, [404, 429]); }, - list: async (customerId: string, query) => { - const response = await client.invoice.invoiceListCustomerInvoices({ - customerId, - queryParameters: query, - }); + list: async (request) => { + const response = await client.contract.invoiceListCustomerInvoices(request); assertStatus(response, 200); - return response.data; + return { + items: response.data, + totalCount: extractTotalCountHeader(response), + }; }, requestFileAccessToken: async (invoiceId, customerId) => { - const response = await client.invoice.requestFileAccessToken({ + const response = await client.contract.invoiceGetFileAccessToken({ invoiceId, customerId, }); diff --git a/packages/models/src/invoice/Invoice/behaviors/types.ts b/packages/models/src/invoice/Invoice/behaviors/types.ts index ef9da7be..3c9a9783 100644 --- a/packages/models/src/invoice/Invoice/behaviors/types.ts +++ b/packages/models/src/invoice/Invoice/behaviors/types.ts @@ -2,15 +2,17 @@ import { InvoiceData, InvoiceFileAccessTokenData, InvoiceListItemData, - InvoiceListQuery, + InvoiceListQueryData, } from "../types.js"; +import { QueryResponseData } from "../../../base/index.js"; export interface InvoiceBehaviors { find: (invoiceId: string) => Promise; - list: ( - customerId: string, - query?: InvoiceListQuery, - ) => Promise; + + list: (request: { + customerId: string; + queryParameters?: InvoiceListQueryData; + }) => Promise>; requestFileAccessToken: ( invoiceId: string, diff --git a/packages/models/src/invoice/Invoice/types.ts b/packages/models/src/invoice/Invoice/types.ts index ee912f22..c982d64f 100644 --- a/packages/models/src/invoice/Invoice/types.ts +++ b/packages/models/src/invoice/Invoice/types.ts @@ -1,7 +1,8 @@ import { MittwaldAPIV2 } from "@mittwald/api-client"; +import { Customer } from "../../customer/index.js"; -export type InvoiceListQuery = - MittwaldAPIV2.Paths.InvoiceListCustomerInvoices.Get.Parameters.Query; +export type InvoiceListQueryData = + MittwaldAPIV2.Paths.V2CustomersCustomerIdInvoices.Get.Parameters.Query; export type InvoiceData = MittwaldAPIV2.Operations.InvoiceDetail.ResponseData; @@ -10,3 +11,10 @@ export type InvoiceListItemData = export type InvoiceFileAccessTokenData = MittwaldAPIV2.Operations.InvoiceGetFileAccessToken.ResponseData; + +export type InvoiceListQueryModelData = Omit< + InvoiceListQueryData, + "customerId" +> & { + customer: Customer; +}; From a73e70f434017de5f84ee41f27789b9e7cc228c3 Mon Sep 17 00:00:00 2001 From: Mathis Fullriede Date: Mon, 30 Sep 2024 10:16:01 +0200 Subject: [PATCH 5/5] correct assertOneOfStatus import location --- packages/models/src/invoice/Invoice/behaviors/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/models/src/invoice/Invoice/behaviors/api.ts b/packages/models/src/invoice/Invoice/behaviors/api.ts index 14d2353f..1f355b91 100644 --- a/packages/models/src/invoice/Invoice/behaviors/api.ts +++ b/packages/models/src/invoice/Invoice/behaviors/api.ts @@ -2,9 +2,9 @@ import { assertStatus, MittwaldAPIV2Client, extractTotalCountHeader, + assertOneOfStatus, } from "@mittwald/api-client"; import { InvoiceBehaviors } from "./types.js"; -import { assertOneOfStatus } from "../../../../../../.nx/cache/4583642927684206384/outputs/packages/commons/dist/types/index.js"; export const apiInvoiceBehaviors = ( client: MittwaldAPIV2Client,