feat(#40): Show an appraised estimate of held items on the ledger
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
import {describe, expect, test} from 'vitest';
|
||||||
|
import {appraiseItemBalances} from './balanceAppraisal';
|
||||||
|
import type {MarketTypePrice} from '@/market';
|
||||||
|
|
||||||
|
const price = (id: number, buy: number, sell: number): MarketTypePrice => ({
|
||||||
|
type: {id, name: `type-${id}`} as MarketTypePrice['type'],
|
||||||
|
buy,
|
||||||
|
sell,
|
||||||
|
orderCount: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('appraiseItemBalances', () => {
|
||||||
|
test('sums quantity times buy and sell across stacks', () => {
|
||||||
|
const result = appraiseItemBalances(
|
||||||
|
[{typeId: 1, quantity: 2}, {typeId: 2, quantity: 3}],
|
||||||
|
[price(1, 10, 12), price(2, 100, 110)],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual({buy: 2 * 10 + 3 * 100, sell: 2 * 12 + 3 * 110});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ignores stacks with no resolved price', () => {
|
||||||
|
const result = appraiseItemBalances(
|
||||||
|
[{typeId: 1, quantity: 2}, {typeId: 99, quantity: 5}],
|
||||||
|
[price(1, 10, 12)],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual({buy: 20, sell: 24});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns zero totals for no items', () => {
|
||||||
|
expect(appraiseItemBalances([], [])).toEqual({buy: 0, sell: 0});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import {ItemBalanceResponse} from "@/generated/mammon";
|
||||||
|
import {MarketTypePrice} from "@/market";
|
||||||
|
|
||||||
|
export interface ItemBalanceAppraisal {
|
||||||
|
buy: number;
|
||||||
|
sell: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const appraiseItemBalances = (
|
||||||
|
itemBalances: ItemBalanceResponse[],
|
||||||
|
prices: MarketTypePrice[],
|
||||||
|
): ItemBalanceAppraisal => {
|
||||||
|
const priceByTypeId = new Map(prices.map(p => [p.type.id, p]));
|
||||||
|
|
||||||
|
return itemBalances.reduce<ItemBalanceAppraisal>((totals, {typeId, quantity}) => {
|
||||||
|
const price = priceByTypeId.get(typeId);
|
||||||
|
|
||||||
|
if (price) {
|
||||||
|
totals.buy += quantity * price.buy;
|
||||||
|
totals.sell += quantity * price.sell;
|
||||||
|
}
|
||||||
|
|
||||||
|
return totals;
|
||||||
|
}, {buy: 0, sell: 0});
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from './ledger';
|
export * from './ledger';
|
||||||
|
export * from './balanceAppraisal';
|
||||||
|
|
||||||
export {default as LedgerLabel} from './LedgerLabel.vue';
|
export {default as LedgerLabel} from './LedgerLabel.vue';
|
||||||
export {default as LedgerSelect} from './LedgerSelect.vue';
|
export {default as LedgerSelect} from './LedgerSelect.vue';
|
||||||
|
|||||||
@@ -1,10 +1,29 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {RouterLink, RouterView} from 'vue-router';
|
import {RouterLink, RouterView} from 'vue-router';
|
||||||
import {isMain, LedgerLabel, useLedgerParam} from "@/ledger";
|
import {appraiseItemBalances, getLedgerBalance, isMain, LedgerLabel, useLedgerParam} from "@/ledger";
|
||||||
import {routeNames} from "@/routes.ts";
|
import {routeNames} from "@/routes.ts";
|
||||||
import {IskLabel} from "@/market";
|
import {getMarketTypes, getPrices, IskLabel} from "@/market";
|
||||||
|
import {computedAsync} from "@vueuse/core";
|
||||||
|
|
||||||
const {ledger} = useLedgerParam();
|
const {ledgerId, ledger} = useLedgerParam();
|
||||||
|
|
||||||
|
const itemAppraisal = computedAsync(async () => {
|
||||||
|
if (!ledgerId.value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {itemBalances} = await getLedgerBalance(ledgerId.value);
|
||||||
|
|
||||||
|
if (itemBalances.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const types = await getMarketTypes([...new Set(itemBalances.map(i => i.typeId))]);
|
||||||
|
const prices = await getPrices(types);
|
||||||
|
const appraisal = appraiseItemBalances(itemBalances, prices);
|
||||||
|
|
||||||
|
return appraisal.buy === 0 && appraisal.sell === 0 ? undefined : appraisal;
|
||||||
|
}, undefined);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -14,8 +33,12 @@ const {ledger} = useLedgerParam();
|
|||||||
<div class="flex w-full">
|
<div class="flex w-full">
|
||||||
<LedgerLabel :ledger="ledger" :link="false" />
|
<LedgerLabel :ledger="ledger" :link="false" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end w-full">
|
<div class="flex justify-end items-baseline gap-3 w-full mb-2">
|
||||||
<IskLabel class="mb-2" :amount="ledger.balance" />
|
<span v-if="itemAppraisal" class="text-sm text-gray-400">
|
||||||
|
~<IskLabel :amount="itemAppraisal.buy" :colored="false" /> buy /
|
||||||
|
~<IskLabel :amount="itemAppraisal.sell" :colored="false" /> sell
|
||||||
|
</span>
|
||||||
|
<IskLabel :amount="ledger.balance" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex border-b-2 border-emerald-500 mt-4">
|
<div class="flex border-b-2 border-emerald-500 mt-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user