feat(#47): Cash Flow / Margin toggle on the ledger Statistics profit chart

This commit is contained in:
Sirttas
2026-07-30 22:21:28 +02:00
parent d1237bf1d2
commit 3419f3d4c9
3 changed files with 269 additions and 8 deletions
+54
View File
@@ -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:
+184 -7
View File
@@ -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<RequestArgs> => {
processNewActivities: async (from?: string, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
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<void>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.processNewActivities(options);
async processNewActivities(from?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
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<void> {
return localVarFp.processNewActivities(options).then((request) => request(axios, basePath));
processNewActivities(from?: string, options?: RawAxiosRequestConfig): AxiosPromise<void> {
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<string>} ledgerIds
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
rebuild: async (ledgerIds: Array<string>, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// 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<string>} ledgerIds
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async rebuild(ledgerIds: Array<string>, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
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<string>} ledgerIds
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
rebuild(ledgerIds: Array<string>, options?: RawAxiosRequestConfig): AxiosPromise<void> {
return localVarFp.rebuild(ledgerIds, options).then((request) => request(axios, basePath));
},
};
};
/**
* BalanceRebuildAdminControllerApi - object-oriented interface
*/
export class BalanceRebuildAdminControllerApi extends BaseAPI {
/**
*
* @param {Array<string>} ledgerIds
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public rebuild(ledgerIds: Array<string>, 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<RequestArgs> => {
// 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<Array<DailyProfitResponse>>> {
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<Array<TransactionResponse>> {
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<Array<DailyProfitResponse>> {
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
+31 -1
View File
@@ -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<ProfitMode>(ProfitModes.CashFlow);
const profits = useProcessedResource<DailyProfitResponse[]>(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<ChartOptions<'bar' | 'line'>>(() => ({
<template>
<div class="mt-4">
<div class="flex justify-center mb-4">
<div class="flex bg-slate-600 rounded-s-md p-1">
<button class="switch" :class="{active: mode === ProfitModes.CashFlow}" @click="mode = ProfitModes.CashFlow">Cash Flow</button>
</div>
<div class="switch flex bg-slate-600 rounded-e-md p-1">
<button class="switch" :class="{active: mode === ProfitModes.Margin}" @click="mode = ProfitModes.Margin">Margin</button>
</div>
</div>
<div class="flex justify-end mb-4">
<span class="mr-2">Total profit:</span>
<IskLabel :amount="total" />
@@ -153,3 +172,14 @@ const chartOptions = computed<ChartOptions<'bar' | 'line'>>(() => ({
</div>
</template>
<style scoped>
@reference "@/style.css";
button.switch {
@apply flex items-center px-4 rounded-md bg-slate-600;
&.active {
@apply bg-emerald-500;
}
}
</style>