feat(#10): Add per-ledger acquisitions view + ledger column

This commit is contained in:
Sirttas
2026-07-05 00:55:13 +02:00
parent 8d73fe3ed1
commit 698156cf00
13 changed files with 156 additions and 104 deletions
+15 -19
View File
@@ -814,10 +814,17 @@ paths:
get: get:
tags: tags:
- acquisition - acquisition
summary: "Find acquisitions, optionally filtered by market type and consumption\ summary: "Find the caller's acquisitions, optionally filtered by ledger, market\
\ state" \ type and consumption state"
operationId: findAllAcquisitions operationId: findAllAcquisitions
parameters: parameters:
- name: ledgerId
in: query
description: Only return acquisitions held in this ledger
required: false
schema:
type: string
format: uuid
- name: marketTypeId - name: marketTypeId
in: query in: query
description: Only return acquisitions of this market type description: Only return acquisitions of this market type
@@ -848,10 +855,6 @@ components:
description: Request to update the caller's rule book; replaces its mutable description: Request to update the caller's rule book; replaces its mutable
fields. fields.
properties: properties:
usedForAcquisitions:
type: boolean
description: Whether this rule book is used to derive acquisitions.
example: true
bindings: bindings:
type: object type: object
additionalProperties: additionalProperties:
@@ -866,7 +869,6 @@ components:
required: required:
- bindings - bindings
- script - script
- usedForAcquisitions
RuleBookResponse: RuleBookResponse:
type: object type: object
description: "The caller's rule book: a script that classifies transactions,\ description: "The caller's rule book: a script that classifies transactions,\
@@ -877,10 +879,6 @@ components:
format: uuid format: uuid
description: Owning user identifier. description: Owning user identifier.
example: 3fa85f64-5717-4562-b3fc-2c963f66afa6 example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
usedForAcquisitions:
type: boolean
description: Whether this rule book is used to derive acquisitions.
example: true
bindings: bindings:
type: object type: object
additionalProperties: additionalProperties:
@@ -895,7 +893,6 @@ components:
required: required:
- bindings - bindings
- script - script
- usedForAcquisitions
- userId - userId
UpdateMainLedgerRequest: UpdateMainLedgerRequest:
type: object type: object
@@ -1686,6 +1683,11 @@ components:
format: uuid format: uuid
description: Unique acquisition identifier. description: Unique acquisition identifier.
example: 3fa85f64-5717-4562-b3fc-2c963f66afa6 example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
ledgerId:
type: string
format: uuid
description: Ledger the acquisition is held in.
example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
source: source:
$ref: "#/components/schemas/ActivitySourceResponse" $ref: "#/components/schemas/ActivitySourceResponse"
description: "Where the acquisition came from: a character, a corporation,\ description: "Where the acquisition came from: a character, a corporation,\
@@ -1695,12 +1697,6 @@ components:
format: int64 format: int64
description: EVE market type (item) id that was acquired. description: EVE market type (item) id that was acquired.
example: 34 example: 34
origin:
type: string
description: How the item entered the inventory.
enum:
- BOUGHT
- MANUAL
datetime: datetime:
type: string type: string
format: date-time format: date-time
@@ -1723,8 +1719,8 @@ components:
required: required:
- acquisitionId - acquisitionId
- datetime - datetime
- ledgerId
- marketTypeId - marketTypeId
- origin
- quantity - quantity
- remaining - remaining
- source - source
+23 -31
View File
@@ -31,6 +31,10 @@ export interface AcquisitionResponse {
* Unique acquisition identifier. * Unique acquisition identifier.
*/ */
'acquisitionId': string; 'acquisitionId': string;
/**
* Ledger the acquisition is held in.
*/
'ledgerId': string;
/** /**
* Where the acquisition came from: a character, a corporation, or nothing. * Where the acquisition came from: a character, a corporation, or nothing.
*/ */
@@ -39,10 +43,6 @@ export interface AcquisitionResponse {
* EVE market type (item) id that was acquired. * EVE market type (item) id that was acquired.
*/ */
'marketTypeId': number; 'marketTypeId': number;
/**
* How the item entered the inventory.
*/
'origin': AcquisitionResponseOriginEnum;
/** /**
* When the acquisition occurred. * When the acquisition occurred.
*/ */
@@ -60,14 +60,6 @@ export interface AcquisitionResponse {
*/ */
'unitCost': number; 'unitCost': number;
} }
export const AcquisitionResponseOriginEnum = {
Bought: 'BOUGHT',
Manual: 'MANUAL',
} as const;
export type AcquisitionResponseOriginEnum = typeof AcquisitionResponseOriginEnum[keyof typeof AcquisitionResponseOriginEnum];
/** /**
* Where an activity or transaction came from: a character, a corporation wallet, or nothing (a manual entry). * Where an activity or transaction came from: a character, a corporation wallet, or nothing (a manual entry).
*/ */
@@ -612,10 +604,6 @@ export interface RuleBookResponse {
* Owning user identifier. * Owning user identifier.
*/ */
'userId': string; 'userId': string;
/**
* Whether this rule book is used to derive acquisitions.
*/
'usedForAcquisitions': boolean;
/** /**
* Ledger references the script writes to, each bound to one of the user\'s ledgers. * Ledger references the script writes to, each bound to one of the user\'s ledgers.
*/ */
@@ -682,10 +670,6 @@ export interface UpdateMainLedgerRequest {
* Request to update the caller\'s rule book; replaces its mutable fields. * Request to update the caller\'s rule book; replaces its mutable fields.
*/ */
export interface UpdateRuleBookRequest { export interface UpdateRuleBookRequest {
/**
* Whether this rule book is used to derive acquisitions.
*/
'usedForAcquisitions': boolean;
/** /**
* Ledger references the script writes to, each bound to one of the user\'s ledgers. * Ledger references the script writes to, each bound to one of the user\'s ledgers.
*/ */
@@ -703,13 +687,14 @@ export const AcquisitionApiAxiosParamCreator = function (configuration?: Configu
return { return {
/** /**
* *
* @summary Find acquisitions, optionally filtered by market type and consumption state * @summary Find the caller\'s acquisitions, optionally filtered by ledger, market type and consumption state
* @param {string} [ledgerId] Only return acquisitions held in this ledger
* @param {number} [marketTypeId] Only return acquisitions of this market type * @param {number} [marketTypeId] Only return acquisitions of this market type
* @param {boolean} [includeConsumed] Include fully consumed acquisitions (no remaining stock) * @param {boolean} [includeConsumed] Include fully consumed acquisitions (no remaining stock)
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
findAllAcquisitions: async (marketTypeId?: number, includeConsumed?: boolean, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => { findAllAcquisitions: async (ledgerId?: string, marketTypeId?: number, includeConsumed?: boolean, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/acquisitions`; const localVarPath = `/acquisitions`;
// use dummy base URL string because the URL constructor only accepts absolute URLs. // use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
@@ -722,6 +707,10 @@ export const AcquisitionApiAxiosParamCreator = function (configuration?: Configu
const localVarHeaderParameter = {} as any; const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any; const localVarQueryParameter = {} as any;
if (ledgerId !== undefined) {
localVarQueryParameter['ledgerId'] = ledgerId;
}
if (marketTypeId !== undefined) { if (marketTypeId !== undefined) {
localVarQueryParameter['marketTypeId'] = marketTypeId; localVarQueryParameter['marketTypeId'] = marketTypeId;
} }
@@ -752,14 +741,15 @@ export const AcquisitionApiFp = function(configuration?: Configuration) {
return { return {
/** /**
* *
* @summary Find acquisitions, optionally filtered by market type and consumption state * @summary Find the caller\'s acquisitions, optionally filtered by ledger, market type and consumption state
* @param {string} [ledgerId] Only return acquisitions held in this ledger
* @param {number} [marketTypeId] Only return acquisitions of this market type * @param {number} [marketTypeId] Only return acquisitions of this market type
* @param {boolean} [includeConsumed] Include fully consumed acquisitions (no remaining stock) * @param {boolean} [includeConsumed] Include fully consumed acquisitions (no remaining stock)
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
async findAllAcquisitions(marketTypeId?: number, includeConsumed?: boolean, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AcquisitionResponse>>> { async findAllAcquisitions(ledgerId?: string, marketTypeId?: number, includeConsumed?: boolean, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AcquisitionResponse>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.findAllAcquisitions(marketTypeId, includeConsumed, options); const localVarAxiosArgs = await localVarAxiosParamCreator.findAllAcquisitions(ledgerId, marketTypeId, includeConsumed, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['AcquisitionApi.findAllAcquisitions']?.[localVarOperationServerIndex]?.url; const localVarOperationServerBasePath = operationServerMap['AcquisitionApi.findAllAcquisitions']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
@@ -775,14 +765,15 @@ export const AcquisitionApiFactory = function (configuration?: Configuration, ba
return { return {
/** /**
* *
* @summary Find acquisitions, optionally filtered by market type and consumption state * @summary Find the caller\'s acquisitions, optionally filtered by ledger, market type and consumption state
* @param {string} [ledgerId] Only return acquisitions held in this ledger
* @param {number} [marketTypeId] Only return acquisitions of this market type * @param {number} [marketTypeId] Only return acquisitions of this market type
* @param {boolean} [includeConsumed] Include fully consumed acquisitions (no remaining stock) * @param {boolean} [includeConsumed] Include fully consumed acquisitions (no remaining stock)
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
findAllAcquisitions(marketTypeId?: number, includeConsumed?: boolean, options?: RawAxiosRequestConfig): AxiosPromise<Array<AcquisitionResponse>> { findAllAcquisitions(ledgerId?: string, marketTypeId?: number, includeConsumed?: boolean, options?: RawAxiosRequestConfig): AxiosPromise<Array<AcquisitionResponse>> {
return localVarFp.findAllAcquisitions(marketTypeId, includeConsumed, options).then((request) => request(axios, basePath)); return localVarFp.findAllAcquisitions(ledgerId, marketTypeId, includeConsumed, options).then((request) => request(axios, basePath));
}, },
}; };
}; };
@@ -793,14 +784,15 @@ export const AcquisitionApiFactory = function (configuration?: Configuration, ba
export class AcquisitionApi extends BaseAPI { export class AcquisitionApi extends BaseAPI {
/** /**
* *
* @summary Find acquisitions, optionally filtered by market type and consumption state * @summary Find the caller\'s acquisitions, optionally filtered by ledger, market type and consumption state
* @param {string} [ledgerId] Only return acquisitions held in this ledger
* @param {number} [marketTypeId] Only return acquisitions of this market type * @param {number} [marketTypeId] Only return acquisitions of this market type
* @param {boolean} [includeConsumed] Include fully consumed acquisitions (no remaining stock) * @param {boolean} [includeConsumed] Include fully consumed acquisitions (no remaining stock)
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
public findAllAcquisitions(marketTypeId?: number, includeConsumed?: boolean, options?: RawAxiosRequestConfig) { public findAllAcquisitions(ledgerId?: string, marketTypeId?: number, includeConsumed?: boolean, options?: RawAxiosRequestConfig) {
return AcquisitionApiFp(this.configuration).findAllAcquisitions(marketTypeId, includeConsumed, options).then((request) => request(this.axios, this.basePath)); return AcquisitionApiFp(this.configuration).findAllAcquisitions(ledgerId, marketTypeId, includeConsumed, options).then((request) => request(this.axios, this.basePath));
} }
} }
+4 -3
View File
@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import {isCombined, Ledger, systemLedger} from "@/ledger/ledger.ts"; import {isCombined, isMain, Ledger, systemLedger} from "@/ledger/ledger.ts";
import {FolderOpenIcon} from '@heroicons/vue/24/outline'; import {DocumentIcon, FolderOpenIcon} from '@heroicons/vue/24/outline';
import {RouterLink} from "vue-router"; import {RouterLink} from "vue-router";
import {routeNames} from "@/routes"; import {routeNames} from "@/routes";
@@ -15,7 +15,8 @@ const props = defineProps<Props>();
<template> <template>
<div class="flex"> <div class="flex">
<FolderOpenIcon v-if="isCombined(ledger)" class="w-4 me-1" /> <DocumentIcon v-if="isMain(ledger)" class="w-4 me-1" />
<FolderOpenIcon v-else-if="isCombined(ledger)" class="w-4 me-1" />
<div v-else class="w-4 me-1"/> <div v-else class="w-4 me-1"/>
<RouterLink v-if="link" :to="{name: routeNames.viewLedger, params: {ledgerId: ledger.ledgerId}}">{{ ledger.name }}</RouterLink> <RouterLink v-if="link" :to="{name: routeNames.viewLedger, params: {ledgerId: ledger.ledgerId}}">{{ ledger.name }}</RouterLink>
<span v-else :class="{'system-ledger': ledger === systemLedger}">{{ ledger.name }}</span> <span v-else :class="{'system-ledger': ledger === systemLedger}">{{ ledger.name }}</span>
@@ -7,6 +7,7 @@ import {computed, ref} from 'vue';
import {AcquiredType} from './AcquiredType'; import {AcquiredType} from './AcquiredType';
import AcquisitionQuartilesTooltip from './AcquisitionQuartilesTooltip.vue'; import AcquisitionQuartilesTooltip from './AcquisitionQuartilesTooltip.vue';
import {formatEveDate, formatIsk, percentFormater} from "@/formaters.ts"; import {formatEveDate, formatIsk, percentFormater} from "@/formaters.ts";
import {Ledger, LedgerLabel, useLedgersStore} from "@/ledger";
type Result = { type Result = {
id: string; id: string;
@@ -20,6 +21,8 @@ type Result = {
precentProfit: number; precentProfit: number;
iskProfit: number; iskProfit: number;
date: Date; date: Date;
ledger?: Ledger;
ledgerName: string;
acquisitions: AcquiredType[]; acquisitions: AcquiredType[];
} }
@@ -46,15 +49,22 @@ const props = withDefaults(defineProps<Props>(), {
defineEmits<Emits>(); defineEmits<Emits>();
const columnsToIgnore = computed(() => { const columnsToIgnore = computed(() => {
const ic = typeof props.ignoredColums === 'string' ? [props.ignoredColums] : props.ignoredColums; const ignoredColums = typeof props.ignoredColums === 'string' ? [props.ignoredColums] : [...props.ignoredColums];
if (props.infoOnly && !ic.includes('buttons')) { if (props.infoOnly && !ignoredColums.includes('buttons')) {
return [...ic, 'buttons']; ignoredColums.push('buttons');
} }
return ic; if (!props.showAll && !ignoredColums.includes('ledger')) {
ignoredColums.push('ledger');
}
if (ignoredColums.includes('ledger')) {
ignoredColums.push('ledgerName');
}
return ignoredColums;
}); });
const marketTaxStore = useMarketTaxStore(); const marketTaxStore = useMarketTaxStore();
const ledgersStore = useLedgersStore();
const threshold = useStorage('market-acquisition-threshold', 10); const threshold = useStorage('market-acquisition-threshold', 10);
const filter = ref(""); const filter = ref("");
@@ -77,6 +87,8 @@ const { sortedArray, headerProps, showColumn } = useSort<Result>(computed(() =>
precentProfit, precentProfit,
iskProfit: r.price * precentProfit * r.remaining, iskProfit: r.price * precentProfit * r.remaining,
date: r.date, date: r.date,
ledger: ledgersStore.findById(r.ledgerId),
ledgerName: ledgersStore.findById(r.ledgerId)?.name ?? '',
acquisitions: [r] acquisitions: [r]
}; };
}); });
@@ -109,6 +121,7 @@ const { sortedArray, headerProps, showColumn } = useSort<Result>(computed(() =>
precentProfit, precentProfit,
iskProfit: price * precentProfit * totalRemaining, iskProfit: price * precentProfit * totalRemaining,
date: first.date, date: first.date,
ledgerName: '',
acquisitions: group acquisitions: group
}); });
}); });
@@ -176,6 +189,7 @@ const total = computed(() => {
<SortableHeader v-bind="headerProps" sortKey="name">Item</SortableHeader> <SortableHeader v-bind="headerProps" sortKey="name">Item</SortableHeader>
<SortableHeader v-bind="headerProps" sortKey="buy">Buy</SortableHeader> <SortableHeader v-bind="headerProps" sortKey="buy">Buy</SortableHeader>
<SortableHeader v-bind="headerProps" sortKey="sell">Sell</SortableHeader> <SortableHeader v-bind="headerProps" sortKey="sell">Sell</SortableHeader>
<SortableHeader v-bind="headerProps" sortKey="ledgerName">Ledger</SortableHeader>
<SortableHeader v-bind="headerProps" sortKey="date">Bought at</SortableHeader> <SortableHeader v-bind="headerProps" sortKey="date">Bought at</SortableHeader>
<SortableHeader v-bind="headerProps" sortKey="price">Bought Price</SortableHeader> <SortableHeader v-bind="headerProps" sortKey="price">Bought Price</SortableHeader>
<SortableHeader v-bind="headerProps" sortKey="remaining">Remaining Amount</SortableHeader> <SortableHeader v-bind="headerProps" sortKey="remaining">Remaining Amount</SortableHeader>
@@ -194,6 +208,9 @@ const total = computed(() => {
</td> </td>
<td v-if="showColumn('buy')" class="text-right">{{ formatIsk(r.data.buy) }}</td> <td v-if="showColumn('buy')" class="text-right">{{ formatIsk(r.data.buy) }}</td>
<td v-if="showColumn('sell')" class="text-right">{{ formatIsk(r.data.sell) }}</td> <td v-if="showColumn('sell')" class="text-right">{{ formatIsk(r.data.sell) }}</td>
<td v-if="showColumn('ledger')">
<LedgerLabel v-if="r.data.ledger" :ledger="r.data.ledger" link />
</td>
<td v-if="showColumn('date')" class="text-right">{{ formatEveDate(r.data.date) }}</td> <td v-if="showColumn('date')" class="text-right">{{ formatEveDate(r.data.date) }}</td>
<td v-if="showColumn('price')" class="text-right">{{ formatIsk(r.data.price) }}</td> <td v-if="showColumn('price')" class="text-right">{{ formatIsk(r.data.price) }}</td>
<td v-if="showColumn('remaining')" class="text-right">{{ r.data.remaining }}/{{ r.data.quantity }}</td> <td v-if="showColumn('remaining')" class="text-right">{{ r.data.remaining }}/{{ r.data.quantity }}</td>
@@ -214,9 +231,12 @@ const total = computed(() => {
<td v-if="showColumn('sell')"> <td v-if="showColumn('sell')">
<template v-if="!showColumn('name') && !showColumn('buy')">Total</template> <template v-if="!showColumn('name') && !showColumn('buy')">Total</template>
</td> </td>
<td v-if="showColumn('date')"> <td v-if="showColumn('ledger')">
<template v-if="!showColumn('name') && !showColumn('buy') && !showColumn('sell')">Total</template> <template v-if="!showColumn('name') && !showColumn('buy') && !showColumn('sell')">Total</template>
</td> </td>
<td v-if="showColumn('date')">
<template v-if="!showColumn('name') && !showColumn('buy') && !showColumn('sell') && !showColumn('ledger')">Total</template>
</td>
<td v-if="showColumn('price')" class="text-right"> <td v-if="showColumn('price')" class="text-right">
<template v-if="total.sameItem"> <template v-if="total.sameItem">
{{ formatIsk(total.price) }} {{ formatIsk(total.price) }}
@@ -0,0 +1,51 @@
<script setup lang="ts">
import {getMarketTypes, MarketTypePrice, useApraisalStore} from "@/market";
import {ref, watch} from 'vue';
import {AcquiredType} from './AcquiredType';
import {RawAcquiredType} from './acquisition';
import AcquisitionResultTable from './AcquisitionResultTable.vue';
import BuyModal from './BuyModal.vue';
import SellModal from './SellModal.vue';
interface Props {
items: RawAcquiredType[];
ignoredColums?: string[] | string;
}
const props = defineProps<Props>();
const buyModal = ref<typeof BuyModal>();
const sellModal = ref<typeof SellModal>();
const apraisalStore = useApraisalStore();
const enriched = ref<AcquiredType[]>([]);
watch(() => props.items, async itms => {
if (itms.length === 0) {
enriched.value = [];
return;
}
const types = await getMarketTypes([...new Set(itms.map(i => i.type))]);
const prices = await apraisalStore.getPrices(types);
enriched.value = itms.map(i => {
const price = prices.find(p => p.type.id === i.type) as MarketTypePrice;
return {
...i,
type: price.type,
buy: price.buy,
sell: price.sell
};
});
}, {immediate: true});
</script>
<template>
<template v-if="enriched.length > 0">
<AcquisitionResultTable :items="enriched" :ignoredColums="ignoredColums" @buy="(types, price, buy, sell) => buyModal?.open(types[0].type, { 'Price': price, 'Buy': buy, 'Sell': sell })" @sell="types => sellModal?.open(types)" />
<BuyModal ref="buyModal" />
<SellModal ref="sellModal" />
</template>
</template>
+3 -1
View File
@@ -7,6 +7,7 @@ export type AcquiredTypeSource = 'bo' | 'so' | 'prod' | 'misc';
export type RawAcquiredType = { export type RawAcquiredType = {
id: string; id: string;
ledgerId: string;
type: number; type: number;
quantity: number; quantity: number;
remaining: number; remaining: number;
@@ -15,8 +16,9 @@ export type RawAcquiredType = {
source: ActivitySourceResponse; source: ActivitySourceResponse;
} }
const toAcquiredType = (a: AcquisitionResponse): RawAcquiredType => ({ export const toAcquiredType = (a: AcquisitionResponse): RawAcquiredType => ({
id: a.acquisitionId, id: a.acquisitionId,
ledgerId: a.ledgerId,
type: a.marketTypeId, type: a.marketTypeId,
quantity: a.quantity, quantity: a.quantity,
remaining: a.remaining, remaining: a.remaining,
+1
View File
@@ -2,6 +2,7 @@ export * from './AcquiredType';
export * from './acquisition'; export * from './acquisition';
export { default as AcquisitionResultTable } from './AcquisitionResultTable.vue'; export { default as AcquisitionResultTable } from './AcquisitionResultTable.vue';
export { default as AcquisitionsPanel } from './AcquisitionsPanel.vue';
export { default as BuyModal } from './BuyModal.vue'; export { default as BuyModal } from './BuyModal.vue';
export { default as SellModal } from './SellModal.vue'; export { default as SellModal } from './SellModal.vue';
+4 -1
View File
@@ -3,7 +3,7 @@ import {RouterLink, RouterView} from 'vue-router';
import {useLedgerParam} from "@/ledger"; import {useLedgerParam} from "@/ledger";
import {routeNames} from "@/routes.ts"; import {routeNames} from "@/routes.ts";
const {ledgerId, ledger} = useLedgerParam(); const {ledger} = useLedgerParam();
</script> </script>
@@ -16,6 +16,9 @@ const {ledgerId, ledger} = useLedgerParam();
<RouterLink :to="{name: routeNames.listLedgerTransactions}" class="tab"> <RouterLink :to="{name: routeNames.listLedgerTransactions}" class="tab">
<span>Transactions</span> <span>Transactions</span>
</RouterLink> </RouterLink>
<RouterLink :to="{name: routeNames.listLedgerAcquisitions}" class="tab">
<span>Acquisitions</span>
</RouterLink>
</div> </div>
<RouterView /> <RouterView />
</div> </div>
@@ -0,0 +1,24 @@
<script setup lang="ts">
import {AcquisitionsPanel, toAcquiredType} from '@/market/acquisition';
import {useLedgerParam} from "@/ledger";
import {acquisitionApi} from "@/mammon";
import {computedAsync} from "@vueuse/core";
const {ledgerId} = useLedgerParam();
const items = computedAsync(async () => {
if (!ledgerId.value) {
return [];
}
const {data} = await acquisitionApi.findAllAcquisitions(ledgerId.value);
return data.map(toAcquiredType).filter(a => a.remaining > 0);
}, []);
</script>
<template>
<div class="mt-4">
<AcquisitionsPanel :items="items" :ignoredColums="['date', 'ledger']" />
</div>
</template>
+2 -33
View File
@@ -1,42 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import {getMarketTypes, MarketTypePrice, useApraisalStore} from "@/market"; import {AcquisitionsPanel, useAcquiredTypesStore} from '@/market/acquisition';
import {AcquiredType, AcquisitionResultTable, BuyModal, SellModal, useAcquiredTypesStore} from '@/market/acquisition';
import {ref, watch} from 'vue';
import {ArrowPathIcon} from '@heroicons/vue/24/outline'; import {ArrowPathIcon} from '@heroicons/vue/24/outline';
import {useAutoRefresh} from '@/composables'; import {useAutoRefresh} from '@/composables';
const buyModal = ref<typeof BuyModal>();
const sellModal = ref<typeof SellModal>();
const apraisalStore = useApraisalStore();
const acquiredTypesStore = useAcquiredTypesStore(); const acquiredTypesStore = useAcquiredTypesStore();
const items = ref<AcquiredType[]>([]);
const refresh = async () => { const refresh = async () => {
await acquiredTypesStore.refresh(); await acquiredTypesStore.refresh();
} }
const {active: autoRefresh, toggle: toggleAutoRefresh} = useAutoRefresh(refresh, 5 * 60 * 1000); const {active: autoRefresh, toggle: toggleAutoRefresh} = useAutoRefresh(refresh, 5 * 60 * 1000);
watch(() => acquiredTypesStore.acquiredTypes, async itms => {
if (itms.length === 0) {
return;
}
const prices = await apraisalStore.getPrices(await getMarketTypes([...new Set(itms.map(i => i.type))]));
items.value = itms.map(i => {
const price = prices.find(p => p.type.id === i.type) as MarketTypePrice;
return {
...i,
type: price.type,
buy: price.buy,
sell: price.sell
};
});
}, { immediate: true })
</script> </script>
<template> <template>
@@ -49,10 +22,6 @@ watch(() => acquiredTypesStore.acquiredTypes, async itms => {
</button> </button>
</div> </div>
</div> </div>
<template v-if="items.length > 0"> <AcquisitionsPanel :items="acquiredTypesStore.acquiredTypes" ignoredColums="date" />
<AcquisitionResultTable :items="items" @buy="(types, price, buy, sell) => buyModal?.open(types[0].type, { 'Price': price, 'Buy': buy, 'Sell': sell })" @sell="types => sellModal?.open(types)" ignoredColums="date" />
<BuyModal ref="buyModal" />
<SellModal ref="sellModal" />
</template>
</div> </div>
</template> </template>
+2 -1
View File
@@ -40,9 +40,10 @@ const result = computedAsync(async () => {
const acquisitions = computedAsync(async () => { const acquisitions = computedAsync(async () => {
const p = price.value; const p = price.value;
return !p ? [] : (await acquisitionApi.findAllAcquisitions(item.value?.id, true)).data return !p ? [] : (await acquisitionApi.findAllAcquisitions(undefined, item.value?.id, true)).data
.map(a => ({ .map(a => ({
id: a.acquisitionId, id: a.acquisitionId,
ledgerId: a.ledgerId,
quantity: a.quantity, quantity: a.quantity,
remaining: a.remaining, remaining: a.remaining,
price: a.unitCost, price: a.unitCost,
-10
View File
@@ -4,7 +4,6 @@ import {useEventListener} from "@vueuse/core";
import log from "loglevel"; import log from "loglevel";
import {ScriptEditor, useRuleBookStore} from "@/rules"; import {ScriptEditor, useRuleBookStore} from "@/rules";
import {PlusIcon, TrashIcon} from "@heroicons/vue/24/outline"; import {PlusIcon, TrashIcon} from "@heroicons/vue/24/outline";
import {SliderCheckbox} from "@/components";
import {isMain, Ledger, LedgerSelect, systemLedger, useLedgersStore} from "@/ledger"; import {isMain, Ledger, LedgerSelect, systemLedger, useLedgersStore} from "@/ledger";
type Binding = { ref: string; ledger: Ledger }; type Binding = { ref: string; ledger: Ledger };
@@ -14,7 +13,6 @@ const ledgersStore = useLedgersStore();
const ledgersToUse = computed(() => [systemLedger, ...ledgersStore.ledgers.filter(isMain)]); const ledgersToUse = computed(() => [systemLedger, ...ledgersStore.ledgers.filter(isMain)]);
const usedForAcquisitions = ref<boolean>(false);
const bindings = ref<Binding[]>([]); const bindings = ref<Binding[]>([]);
const script = ref<string>(''); const script = ref<string>('');
@@ -23,7 +21,6 @@ const ledgerRefs = computed<string[]>(() => bindings.value.map(b => b.ref));
watchEffect(() => { watchEffect(() => {
const ruleBook = ruleBookStore.ruleBook; const ruleBook = ruleBookStore.ruleBook;
usedForAcquisitions.value = ruleBook?.usedForAcquisitions ?? false;
script.value = ruleBook?.script ?? ''; script.value = ruleBook?.script ?? '';
bindings.value = Object.entries(ruleBook?.bindings ?? {}) bindings.value = Object.entries(ruleBook?.bindings ?? {})
.map(([ref, id]) => ({ref, ledger: ledgersToUse.value.find(l => l.ledgerId === id) ?? systemLedger})); .map(([ref, id]) => ({ref, ledger: ledgersToUse.value.find(l => l.ledgerId === id) ?? systemLedger}));
@@ -39,7 +36,6 @@ const removeBinding = (index: number) => {
}; };
const save = () => ruleBookStore.update({ const save = () => ruleBookStore.update({
usedForAcquisitions: usedForAcquisitions.value,
bindings: Object.fromEntries( bindings: Object.fromEntries(
bindings.value bindings.value
.filter(b => b.ref) .filter(b => b.ref)
@@ -59,12 +55,6 @@ useEventListener(window, 'keydown', (event: KeyboardEvent) => {
<template> <template>
<div class="flex flex-col mb-2 mt-4 h-[calc(100vh-4.5rem)]"> <div class="flex flex-col mb-2 mt-4 h-[calc(100vh-4.5rem)]">
<div class="flex flex-col grow min-h-0"> <div class="flex flex-col grow min-h-0">
<div class="flex grow border-b-1">
<label class="flex items-center mb-2">
<SliderCheckbox class="me-2" v-model="usedForAcquisitions" />
Used for acquisitions
</label>
</div>
<div class="border-b-1"> <div class="border-b-1">
Ledger Bindings: Ledger Bindings:
<div class="flex flex-wrap items-center mt-2"> <div class="flex flex-wrap items-center mt-2">
+2
View File
@@ -12,6 +12,7 @@ export const routeNames = {
viewLedger: 'view-ledger', viewLedger: 'view-ledger',
viewLedgerBalance: 'view-ledger-balance', viewLedgerBalance: 'view-ledger-balance',
listLedgerTransactions: 'list-ledger-transactions', listLedgerTransactions: 'list-ledger-transactions',
listLedgerAcquisitions: 'list-ledger-acquisitions',
editRuleBook: 'edit-rule-book', editRuleBook: 'edit-rule-book',
marketTypes: 'market-types', marketTypes: 'market-types',
about: 'about', about: 'about',
@@ -27,6 +28,7 @@ export const routes: RouteRecordRaw[] = [
{path: '', name: routeNames.viewLedger, redirect: {name: routeNames.viewLedgerBalance}}, {path: '', name: routeNames.viewLedger, redirect: {name: routeNames.viewLedgerBalance}},
{path: 'balance', name: routeNames.viewLedgerBalance, component: () => import('@/pages/ledger/ViewLedgerBalance.vue')}, {path: 'balance', name: routeNames.viewLedgerBalance, component: () => import('@/pages/ledger/ViewLedgerBalance.vue')},
{path: 'transactions', name: routeNames.listLedgerTransactions, component: () => import('@/pages/ledger/ListLedgerTransactions.vue')}, {path: 'transactions', name: routeNames.listLedgerTransactions, component: () => import('@/pages/ledger/ListLedgerTransactions.vue')},
{path: 'acquisitions', name: routeNames.listLedgerAcquisitions, component: () => import('@/pages/ledger/ViewLedgerAcquisitions.vue')},
]}, ]},
]}, ]},