From 3419f3d4c9b6999a11273d535238f33a55377b23 Mon Sep 17 00:00:00 2001 From: Sirttas Date: Thu, 30 Jul 2026 22:21:28 +0200 Subject: [PATCH] feat(#47): Cash Flow / Margin toggle on the ledger Statistics profit chart --- docs/mammon-api.yml | 54 ++++++ src/generated/mammon/api.ts | 191 +++++++++++++++++++++- src/pages/ledger/ViewLedgerStatistics.vue | 32 +++- 3 files changed, 269 insertions(+), 8 deletions(-) diff --git a/docs/mammon-api.yml b/docs/mammon-api.yml index 2fd3677..f85d475 100644 --- a/docs/mammon-api.yml +++ b/docs/mammon-api.yml @@ -359,6 +359,23 @@ paths: description: The link is seeded; follow with a full-page nav to /oauth2/authorization/esi "401": description: Missing or expired access token + /balances/rebuild: + post: + tags: + - balance-rebuild-admin-controller + operationId: rebuild + parameters: + - name: ledgerIds + in: query + required: true + schema: + type: array + items: + type: string + format: uuid + responses: + "204": + description: No Content /activity/consume: post: tags: @@ -405,6 +422,15 @@ paths: - activity summary: Process new activities for the authenticated user's characters operationId: processNewActivities + parameters: + - name: from + in: query + description: Reprocess by walking the processed tail back to this UTC datetime + (ISO-8601); omit to only process new activities + required: false + schema: + type: string + format: date-time responses: "202": description: Processing started in the background @@ -932,6 +958,34 @@ paths: description: The ledger is the system ledger or a combined ledger "404": description: No ledger with this id + /ledgers/{ledgerId}/margin: + get: + tags: + - transaction + summary: Get the daily FIFO-realized profit series for a single ledger + operationId: marginPerDayInLedger + parameters: + - name: ledgerId + in: path + description: Id of the ledger + required: true + schema: + type: string + format: uuid + responses: + "200": + description: "Daily realized profit for the ledger, ascending by date, only\ + \ for days with a sale" + content: + '*/*': + schema: + type: array + items: + $ref: "#/components/schemas/DailyProfitResponse" + "400": + description: The ledger is the system ledger or a combined ledger + "404": + description: No ledger with this id /ledgers/{ledgerId}/balance: get: tags: diff --git a/src/generated/mammon/api.ts b/src/generated/mammon/api.ts index de66c1a..18b2d8b 100644 --- a/src/generated/mammon/api.ts +++ b/src/generated/mammon/api.ts @@ -1012,10 +1012,11 @@ export const ActivityApiAxiosParamCreator = function (configuration?: Configurat /** * * @summary Process new activities for the authenticated user\'s characters + * @param {string} [from] Reprocess by walking the processed tail back to this UTC datetime (ISO-8601); omit to only process new activities * @param {*} [options] Override http request option. * @throws {RequiredError} */ - processNewActivities: async (options: RawAxiosRequestConfig = {}): Promise => { + processNewActivities: async (from?: string, options: RawAxiosRequestConfig = {}): Promise => { const localVarPath = `/activities/process`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -1028,6 +1029,12 @@ export const ActivityApiAxiosParamCreator = function (configuration?: Configurat const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; + if (from !== undefined) { + localVarQueryParameter['from'] = (from as any instanceof Date) ? + (from as any).toISOString() : + from; + } + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; @@ -1106,11 +1113,12 @@ export const ActivityApiFp = function(configuration?: Configuration) { /** * * @summary Process new activities for the authenticated user\'s characters + * @param {string} [from] Reprocess by walking the processed tail back to this UTC datetime (ISO-8601); omit to only process new activities * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async processNewActivities(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.processNewActivities(options); + async processNewActivities(from?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.processNewActivities(from, options); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerBasePath = operationServerMap['ActivityApi.processNewActivities']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); @@ -1159,11 +1167,12 @@ export const ActivityApiFactory = function (configuration?: Configuration, baseP /** * * @summary Process new activities for the authenticated user\'s characters + * @param {string} [from] Reprocess by walking the processed tail back to this UTC datetime (ISO-8601); omit to only process new activities * @param {*} [options] Override http request option. * @throws {RequiredError} */ - processNewActivities(options?: RawAxiosRequestConfig): AxiosPromise { - return localVarFp.processNewActivities(options).then((request) => request(axios, basePath)); + processNewActivities(from?: string, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.processNewActivities(from, options).then((request) => request(axios, basePath)); }, /** * @@ -1206,11 +1215,12 @@ export class ActivityApi extends BaseAPI { /** * * @summary Process new activities for the authenticated user\'s characters + * @param {string} [from] Reprocess by walking the processed tail back to this UTC datetime (ISO-8601); omit to only process new activities * @param {*} [options] Override http request option. * @throws {RequiredError} */ - public processNewActivities(options?: RawAxiosRequestConfig) { - return ActivityApiFp(this.configuration).processNewActivities(options).then((request) => request(this.axios, this.basePath)); + public processNewActivities(from?: string, options?: RawAxiosRequestConfig) { + return ActivityApiFp(this.configuration).processNewActivities(from, options).then((request) => request(this.axios, this.basePath)); } /** @@ -1501,6 +1511,105 @@ export class AuthApi extends BaseAPI { +/** + * BalanceRebuildAdminControllerApi - axios parameter creator + */ +export const BalanceRebuildAdminControllerApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * + * @param {Array} ledgerIds + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + rebuild: async (ledgerIds: Array, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'ledgerIds' is not null or undefined + assertParamExists('rebuild', 'ledgerIds', ledgerIds) + const localVarPath = `/balances/rebuild`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + if (ledgerIds) { + localVarQueryParameter['ledgerIds'] = ledgerIds; + } + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * BalanceRebuildAdminControllerApi - functional programming interface + */ +export const BalanceRebuildAdminControllerApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = BalanceRebuildAdminControllerApiAxiosParamCreator(configuration) + return { + /** + * + * @param {Array} ledgerIds + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async rebuild(ledgerIds: Array, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.rebuild(ledgerIds, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['BalanceRebuildAdminControllerApi.rebuild']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + } +}; + +/** + * BalanceRebuildAdminControllerApi - factory interface + */ +export const BalanceRebuildAdminControllerApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = BalanceRebuildAdminControllerApiFp(configuration) + return { + /** + * + * @param {Array} ledgerIds + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + rebuild(ledgerIds: Array, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.rebuild(ledgerIds, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * BalanceRebuildAdminControllerApi - object-oriented interface + */ +export class BalanceRebuildAdminControllerApi extends BaseAPI { + /** + * + * @param {Array} ledgerIds + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public rebuild(ledgerIds: Array, options?: RawAxiosRequestConfig) { + return BalanceRebuildAdminControllerApiFp(this.configuration).rebuild(ledgerIds, options).then((request) => request(this.axios, this.basePath)); + } +} + + + /** * CharacterApi - axios parameter creator */ @@ -3926,6 +4035,40 @@ export const TransactionApiAxiosParamCreator = function (configuration?: Configu options: localVarRequestOptions, }; }, + /** + * + * @summary Get the daily FIFO-realized profit series for a single ledger + * @param {string} ledgerId Id of the ledger + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + marginPerDayInLedger: async (ledgerId: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'ledgerId' is not null or undefined + assertParamExists('marginPerDayInLedger', 'ledgerId', ledgerId) + const localVarPath = `/ledgers/{ledgerId}/margin` + .replace('{ledgerId}', encodeURIComponent(String(ledgerId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + localVarHeaderParameter['Accept'] = '*/*'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @summary Get the daily profit series for a single ledger @@ -3982,6 +4125,19 @@ export const TransactionApiFp = function(configuration?: Configuration) { const localVarOperationServerBasePath = operationServerMap['TransactionApi.finAllTransactionsInLedger']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, + /** + * + * @summary Get the daily FIFO-realized profit series for a single ledger + * @param {string} ledgerId Id of the ledger + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async marginPerDayInLedger(ledgerId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.marginPerDayInLedger(ledgerId, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['TransactionApi.marginPerDayInLedger']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, /** * * @summary Get the daily profit series for a single ledger @@ -4014,6 +4170,16 @@ export const TransactionApiFactory = function (configuration?: Configuration, ba finAllTransactionsInLedger(ledgerId: string, options?: RawAxiosRequestConfig): AxiosPromise> { return localVarFp.finAllTransactionsInLedger(ledgerId, options).then((request) => request(axios, basePath)); }, + /** + * + * @summary Get the daily FIFO-realized profit series for a single ledger + * @param {string} ledgerId Id of the ledger + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + marginPerDayInLedger(ledgerId: string, options?: RawAxiosRequestConfig): AxiosPromise> { + return localVarFp.marginPerDayInLedger(ledgerId, options).then((request) => request(axios, basePath)); + }, /** * * @summary Get the daily profit series for a single ledger @@ -4042,6 +4208,17 @@ export class TransactionApi extends BaseAPI { return TransactionApiFp(this.configuration).finAllTransactionsInLedger(ledgerId, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @summary Get the daily FIFO-realized profit series for a single ledger + * @param {string} ledgerId Id of the ledger + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public marginPerDayInLedger(ledgerId: string, options?: RawAxiosRequestConfig) { + return TransactionApiFp(this.configuration).marginPerDayInLedger(ledgerId, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @summary Get the daily profit series for a single ledger diff --git a/src/pages/ledger/ViewLedgerStatistics.vue b/src/pages/ledger/ViewLedgerStatistics.vue index 4fb1a0c..3a3040b 100644 --- a/src/pages/ledger/ViewLedgerStatistics.vue +++ b/src/pages/ledger/ViewLedgerStatistics.vue @@ -34,12 +34,22 @@ ChartJS.register( const {ledgerId} = useLedgerParam(); +const ProfitModes = { + CashFlow: 'CASH_FLOW', + Margin: 'MARGIN', +} as const; +type ProfitMode = typeof ProfitModes[keyof typeof ProfitModes]; + +const mode = ref(ProfitModes.CashFlow); + const profits = useProcessedResource(async () => { if (!ledgerId.value) { return []; } - const {data} = await transactionApi.profitPerDayInLedger(ledgerId.value); + const {data} = mode.value === ProfitModes.Margin + ? await transactionApi.marginPerDayInLedger(ledgerId.value) + : await transactionApi.profitPerDayInLedger(ledgerId.value); return data; }, []); @@ -131,6 +141,15 @@ const chartOptions = computed>(() => ({ + +