Compute cost basis in SQL where possible
This commit is contained in:
parent
2404def901
commit
c932ee21de
@ -51,6 +51,7 @@ export class Balance {
|
|||||||
|
|
||||||
export function asCost(quantity: number, commodity: string): number {
|
export function asCost(quantity: number, commodity: string): number {
|
||||||
// Convert the amount to cost price in the reporting commodity
|
// Convert the amount to cost price in the reporting commodity
|
||||||
|
// NB: This function is rarely used - most conversions are performed in SQL via the transactions_with_quantity_ascost view
|
||||||
|
|
||||||
if (commodity === db.metadata.reporting_commodity) {
|
if (commodity === db.metadata.reporting_commodity) {
|
||||||
return quantity;
|
return quantity;
|
||||||
|
@ -135,6 +135,7 @@ export function joinedToTransactions(joinedTransactionPostings: JoinedTransactio
|
|||||||
account: joinedTransactionPosting.account,
|
account: joinedTransactionPosting.account,
|
||||||
quantity: joinedTransactionPosting.quantity,
|
quantity: joinedTransactionPosting.quantity,
|
||||||
commodity: joinedTransactionPosting.commodity,
|
commodity: joinedTransactionPosting.commodity,
|
||||||
|
quantity_ascost: joinedTransactionPosting.quantity_ascost,
|
||||||
running_balance: joinedTransactionPosting.running_balance
|
running_balance: joinedTransactionPosting.running_balance
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -233,6 +234,7 @@ export interface Posting {
|
|||||||
account: string,
|
account: string,
|
||||||
quantity: number,
|
quantity: number,
|
||||||
commodity: string,
|
commodity: string,
|
||||||
|
quantity_ascost?: number,
|
||||||
running_balance?: number
|
running_balance?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,6 +247,7 @@ export interface JoinedTransactionPosting {
|
|||||||
account: string,
|
account: string,
|
||||||
quantity: number,
|
quantity: number,
|
||||||
commodity: string,
|
commodity: string,
|
||||||
|
quantity_ascost?: number,
|
||||||
running_balance?: number
|
running_balance?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +66,6 @@
|
|||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { asCost } from '../amounts.ts';
|
|
||||||
import { db } from '../db.ts';
|
import { db } from '../db.ts';
|
||||||
import { pp } from '../display.ts';
|
import { pp } from '../display.ts';
|
||||||
import { ReportingStage, ReportingWorkflow } from '../reporting.ts';
|
import { ReportingStage, ReportingWorkflow } from '../reporting.ts';
|
||||||
@ -108,7 +107,7 @@
|
|||||||
if (dayjs(transaction.dt) <= balanceAssertionDt) {
|
if (dayjs(transaction.dt) <= balanceAssertionDt) {
|
||||||
for (const posting of transaction.postings) {
|
for (const posting of transaction.postings) {
|
||||||
if (posting.account === balanceAssertion.account) {
|
if (posting.account === balanceAssertion.account) {
|
||||||
accountBalance += asCost(posting.quantity, posting.commodity);
|
accountBalance += posting.quantity_ascost!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,11 +46,10 @@
|
|||||||
const session = await db.load();
|
const session = await db.load();
|
||||||
|
|
||||||
const joinedTransactionPostings: JoinedTransactionPosting[] = await session.select(
|
const joinedTransactionPostings: JoinedTransactionPosting[] = await session.select(
|
||||||
`SELECT transaction_id, dt, transactions.description AS transaction_description, postings.id, postings.description, account, quantity, commodity
|
`SELECT transaction_id, dt, transaction_description, id, description, account, quantity, commodity
|
||||||
FROM transactions
|
FROM joined_transactions
|
||||||
JOIN postings ON transactions.id = postings.transaction_id
|
WHERE transaction_id = $1
|
||||||
WHERE transactions.id = $1
|
ORDER BY id`,
|
||||||
ORDER BY postings.id`,
|
|
||||||
[route.params.id]
|
[route.params.id]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<!--
|
<!--
|
||||||
DrCr: Web-based double-entry bookkeeping framework
|
DrCr: Web-based double-entry bookkeeping framework
|
||||||
Copyright (C) 2022–2024 Lee Yingtong Li (RunasSudo)
|
Copyright (C) 2022–2025 Lee Yingtong Li (RunasSudo)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -67,7 +67,6 @@
|
|||||||
|
|
||||||
import { onUnmounted, ref, watch } from 'vue';
|
import { onUnmounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { asCost } from '../amounts.ts';
|
|
||||||
import { Transaction, db } from '../db.ts';
|
import { Transaction, db } from '../db.ts';
|
||||||
import { pp, ppWithCommodity } from '../display.ts';
|
import { pp, ppWithCommodity } from '../display.ts';
|
||||||
import { ReportingStage, ReportingWorkflow } from '../reporting.ts';
|
import { ReportingStage, ReportingWorkflow } from '../reporting.ts';
|
||||||
@ -132,10 +131,10 @@
|
|||||||
<td class="py-0.5 px-1 text-gray-900 text-end"><i>${ posting.quantity >= 0 ? 'Dr' : 'Cr' }</i></td>
|
<td class="py-0.5 px-1 text-gray-900 text-end"><i>${ posting.quantity >= 0 ? 'Dr' : 'Cr' }</i></td>
|
||||||
<td class="py-0.5 px-1 text-gray-900 lg:w-[30%]"><a href="/transactions/${ encodeURIComponent(posting.account) }" class="text-gray-900 hover:text-blue-700 hover:underline">${ posting.account }</a></td>
|
<td class="py-0.5 px-1 text-gray-900 lg:w-[30%]"><a href="/transactions/${ encodeURIComponent(posting.account) }" class="text-gray-900 hover:text-blue-700 hover:underline">${ posting.account }</a></td>
|
||||||
<td class="py-0.5 px-1 text-gray-900 lg:w-[12ex] text-end">
|
<td class="py-0.5 px-1 text-gray-900 lg:w-[12ex] text-end">
|
||||||
${ posting.quantity >= 0 ? pp(asCost(posting.quantity, posting.commodity)) : '' }
|
${ posting.quantity >= 0 ? pp(posting.quantity_ascost!) : '' }
|
||||||
</td>
|
</td>
|
||||||
<td class="py-0.5 pl-1 text-gray-900 lg:w-[12ex] text-end">
|
<td class="py-0.5 pl-1 text-gray-900 lg:w-[12ex] text-end">
|
||||||
${ posting.quantity < 0 ? pp(asCost(-posting.quantity, posting.commodity)) : '' }
|
${ posting.quantity < 0 ? pp(-posting.quantity_ascost!) : '' }
|
||||||
</td>
|
</td>
|
||||||
</tr>`
|
</tr>`
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<!--
|
<!--
|
||||||
DrCr: Web-based double-entry bookkeeping framework
|
DrCr: Web-based double-entry bookkeeping framework
|
||||||
Copyright (C) 2022–2024 Lee Yingtong Li (RunasSudo)
|
Copyright (C) 2022–2025 Lee Yingtong Li (RunasSudo)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -68,7 +68,6 @@
|
|||||||
|
|
||||||
import { onUnmounted, ref, watch } from 'vue';
|
import { onUnmounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { asCost } from '../amounts.ts';
|
|
||||||
import { JoinedTransactionPosting, Transaction, db, joinedToTransactions } from '../db.ts';
|
import { JoinedTransactionPosting, Transaction, db, joinedToTransactions } from '../db.ts';
|
||||||
import { pp, ppWithCommodity } from '../display.ts';
|
import { pp, ppWithCommodity } from '../display.ts';
|
||||||
import { renderComponent } from '../webutil.ts';
|
import { renderComponent } from '../webutil.ts';
|
||||||
@ -82,10 +81,9 @@
|
|||||||
const session = await db.load();
|
const session = await db.load();
|
||||||
|
|
||||||
const joinedTransactionPostings: JoinedTransactionPosting[] = await session.select(
|
const joinedTransactionPostings: JoinedTransactionPosting[] = await session.select(
|
||||||
`SELECT transaction_id, dt, transactions.description AS transaction_description, postings.id, postings.description, account, quantity, commodity
|
`SELECT transaction_id, dt, transaction_description, id, description, account, quantity, commodity, quantity_ascost
|
||||||
FROM transactions
|
FROM transactions_with_quantity_ascost
|
||||||
JOIN postings ON transactions.id = postings.transaction_id
|
ORDER BY dt DESC, transaction_id DESC, id`
|
||||||
ORDER BY dt DESC, transaction_id DESC, postings.id`
|
|
||||||
);
|
);
|
||||||
|
|
||||||
transactions.value = joinedToTransactions(joinedTransactionPostings);
|
transactions.value = joinedToTransactions(joinedTransactionPostings);
|
||||||
@ -131,10 +129,10 @@
|
|||||||
<td class="py-0.5 px-1 text-gray-900 text-end"><i>${ posting.quantity >= 0 ? 'Dr' : 'Cr' }</i></td>
|
<td class="py-0.5 px-1 text-gray-900 text-end"><i>${ posting.quantity >= 0 ? 'Dr' : 'Cr' }</i></td>
|
||||||
<td class="py-0.5 px-1 text-gray-900 lg:w-[30%]"><a href="/transactions/${ encodeURIComponent(posting.account) }" class="text-gray-900 hover:text-blue-700 hover:underline">${ posting.account }</a></td>
|
<td class="py-0.5 px-1 text-gray-900 lg:w-[30%]"><a href="/transactions/${ encodeURIComponent(posting.account) }" class="text-gray-900 hover:text-blue-700 hover:underline">${ posting.account }</a></td>
|
||||||
<td class="py-0.5 px-1 text-gray-900 lg:w-[12ex] text-end">
|
<td class="py-0.5 px-1 text-gray-900 lg:w-[12ex] text-end">
|
||||||
${ posting.quantity >= 0 ? pp(asCost(posting.quantity, posting.commodity)) : '' }
|
${ posting.quantity >= 0 ? pp(posting.quantity_ascost!) : '' }
|
||||||
</td>
|
</td>
|
||||||
<td class="py-0.5 pl-1 text-gray-900 lg:w-[12ex] text-end">
|
<td class="py-0.5 pl-1 text-gray-900 lg:w-[12ex] text-end">
|
||||||
${ posting.quantity < 0 ? pp(asCost(-posting.quantity, posting.commodity)) : '' }
|
${ posting.quantity < 0 ? pp(-posting.quantity_ascost!) : '' }
|
||||||
</td>
|
</td>
|
||||||
</tr>`
|
</tr>`
|
||||||
);
|
);
|
||||||
|
@ -49,7 +49,6 @@
|
|||||||
import { onMounted, onUnmounted, watch } from 'vue';
|
import { onMounted, onUnmounted, watch } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
import { asCost } from '../amounts.ts';
|
|
||||||
import { Transaction } from '../db.ts';
|
import { Transaction } from '../db.ts';
|
||||||
import { pp } from '../display.ts';
|
import { pp } from '../display.ts';
|
||||||
import { renderComponent } from '../webutil.ts';
|
import { renderComponent } from '../webutil.ts';
|
||||||
@ -67,7 +66,7 @@
|
|||||||
const transaction = transactions[i];
|
const transaction = transactions[i];
|
||||||
for (const posting of transaction.postings) {
|
for (const posting of transaction.postings) {
|
||||||
if (posting.account === route.params.account) {
|
if (posting.account === route.params.account) {
|
||||||
balance += asCost(posting.quantity, posting.commodity);
|
balance += posting.quantity_ascost!;
|
||||||
posting.running_balance = balance;
|
posting.running_balance = balance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,8 +99,8 @@
|
|||||||
<td class="py-0.5 pr-1 text-gray-900 lg:w-[12ex]">${ dayjs(transaction.dt).format('YYYY-MM-DD') }</td>
|
<td class="py-0.5 pr-1 text-gray-900 lg:w-[12ex]">${ dayjs(transaction.dt).format('YYYY-MM-DD') }</td>
|
||||||
<td class="py-0.5 px-1 text-gray-900">${ transaction.description } ${ editLink }</td>
|
<td class="py-0.5 px-1 text-gray-900">${ transaction.description } ${ editLink }</td>
|
||||||
<td class="py-0.5 px-1 text-gray-900"><a href="/transactions/${ encodeURIComponent(otherAccountPosting!.account) }" class="text-gray-900 hover:text-blue-700 hover:underline">${ otherAccountPosting!.account }</a></td>
|
<td class="py-0.5 px-1 text-gray-900"><a href="/transactions/${ encodeURIComponent(otherAccountPosting!.account) }" class="text-gray-900 hover:text-blue-700 hover:underline">${ otherAccountPosting!.account }</a></td>
|
||||||
<td class="py-0.5 px-1 text-gray-900 lg:w-[12ex] text-end">${ thisAccountPosting!.quantity >= 0 ? pp(asCost(thisAccountPosting!.quantity, thisAccountPosting!.commodity)) : '' }</td>
|
<td class="py-0.5 px-1 text-gray-900 lg:w-[12ex] text-end">${ thisAccountPosting!.quantity >= 0 ? pp(thisAccountPosting!.quantity_ascost!) : '' }</td>
|
||||||
<td class="py-0.5 px-1 text-gray-900 lg:w-[12ex] text-end">${ thisAccountPosting!.quantity < 0 ? pp(asCost(-thisAccountPosting!.quantity, thisAccountPosting!.commodity)) : '' }</td>
|
<td class="py-0.5 px-1 text-gray-900 lg:w-[12ex] text-end">${ thisAccountPosting!.quantity < 0 ? pp(-thisAccountPosting!.quantity_ascost!) : '' }</td>
|
||||||
<td class="py-0.5 px-1 text-gray-900 lg:w-[12ex] text-end">${ pp(Math.abs(thisAccountPosting!.running_balance!)) }</td>
|
<td class="py-0.5 px-1 text-gray-900 lg:w-[12ex] text-end">${ pp(Math.abs(thisAccountPosting!.running_balance!)) }</td>
|
||||||
<td class="py-0.5 text-gray-900">${ thisAccountPosting!.running_balance! >= 0 ? 'Dr' : 'Cr' }</td>
|
<td class="py-0.5 text-gray-900">${ thisAccountPosting!.running_balance! >= 0 ? 'Dr' : 'Cr' }</td>
|
||||||
</tr>`
|
</tr>`
|
||||||
@ -125,8 +124,8 @@
|
|||||||
<td></td>
|
<td></td>
|
||||||
<td class="py-0.5 px-1 text-gray-900 text-end"><i>${ posting.quantity >= 0 ? 'Dr' : 'Cr' }</i></td>
|
<td class="py-0.5 px-1 text-gray-900 text-end"><i>${ posting.quantity >= 0 ? 'Dr' : 'Cr' }</i></td>
|
||||||
<td class="py-0.5 px-1 text-gray-900"><a href="/transactions/${ encodeURIComponent(posting.account) }" class="text-gray-900 hover:text-blue-700 hover:underline">${ posting.account }</a></td>
|
<td class="py-0.5 px-1 text-gray-900"><a href="/transactions/${ encodeURIComponent(posting.account) }" class="text-gray-900 hover:text-blue-700 hover:underline">${ posting.account }</a></td>
|
||||||
<td class="py-0.5 px-1 text-gray-900 lg:w-[12ex] text-end">${ posting.quantity >= 0 ? pp(asCost(posting.quantity, posting.commodity)) : '' }</td>
|
<td class="py-0.5 px-1 text-gray-900 lg:w-[12ex] text-end">${ posting.quantity >= 0 ? pp(posting.quantity_ascost!) : '' }</td>
|
||||||
<td class="py-0.5 px-1 text-gray-900 lg:w-[12ex] text-end">${ posting.quantity < 0 ? pp(asCost(-posting.quantity, posting.commodity)) : '' }</td>
|
<td class="py-0.5 px-1 text-gray-900 lg:w-[12ex] text-end">${ posting.quantity < 0 ? pp(-posting.quantity_ascost!) : '' }</td>
|
||||||
<td class="py-0.5 px-1 text-gray-900 lg:w-[12ex] text-end">${ posting.account === route.params.account ? pp(Math.abs(posting.running_balance!)) : '' }</td>
|
<td class="py-0.5 px-1 text-gray-900 lg:w-[12ex] text-end">${ posting.account === route.params.account ? pp(Math.abs(posting.running_balance!)) : '' }</td>
|
||||||
<td class="py-0.5 text-gray-900">${ posting.account === route.params.account ? (posting.running_balance! >= 0 ? 'Dr' : 'Cr') : '' }</td>
|
<td class="py-0.5 text-gray-900">${ posting.account === route.params.account ? (posting.running_balance! >= 0 ? 'Dr' : 'Cr') : '' }</td>
|
||||||
</tr>`
|
</tr>`
|
||||||
|
@ -75,7 +75,7 @@ export class ReportingWorkflow {
|
|||||||
let joinedTransactionPostings: JoinedTransactionPosting[];
|
let joinedTransactionPostings: JoinedTransactionPosting[];
|
||||||
if (dt) {
|
if (dt) {
|
||||||
joinedTransactionPostings = await session.select(
|
joinedTransactionPostings = await session.select(
|
||||||
`SELECT transaction_id, dt, transaction_description, id, description, account, quantity, commodity, running_balance
|
`SELECT transaction_id, dt, transaction_description, id, description, account, quantity, commodity, quantity_ascost, running_balance
|
||||||
FROM transactions_with_running_balances
|
FROM transactions_with_running_balances
|
||||||
WHERE DATE(dt) <= DATE($1)
|
WHERE DATE(dt) <= DATE($1)
|
||||||
ORDER BY dt, transaction_id, id`,
|
ORDER BY dt, transaction_id, id`,
|
||||||
@ -83,7 +83,7 @@ export class ReportingWorkflow {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
joinedTransactionPostings = await session.select(
|
joinedTransactionPostings = await session.select(
|
||||||
`SELECT transaction_id, dt, transaction_description, id, description, account, quantity, commodity, running_balance
|
`SELECT transaction_id, dt, transaction_description, id, description, account, quantity, commodity, quantity_ascost, running_balance
|
||||||
FROM transactions_with_running_balances
|
FROM transactions_with_running_balances
|
||||||
ORDER BY dt, transaction_id, id`
|
ORDER BY dt, transaction_id, id`
|
||||||
);
|
);
|
||||||
@ -127,14 +127,16 @@ export class ReportingWorkflow {
|
|||||||
description: null,
|
description: null,
|
||||||
account: line.source_account,
|
account: line.source_account,
|
||||||
quantity: line.quantity,
|
quantity: line.quantity,
|
||||||
commodity: line.commodity
|
commodity: line.commodity,
|
||||||
|
quantity_ascost: asCost(line.quantity, line.commodity),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: null,
|
id: null,
|
||||||
description: null,
|
description: null,
|
||||||
account: unclassifiedAccount,
|
account: unclassifiedAccount,
|
||||||
quantity: -line.quantity,
|
quantity: -line.quantity,
|
||||||
commodity: line.commodity
|
commodity: line.commodity,
|
||||||
|
quantity_ascost: asCost(-line.quantity, line.commodity),
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
));
|
));
|
||||||
@ -163,7 +165,7 @@ export class ReportingWorkflow {
|
|||||||
for (const transaction of this.transactionsForStage.get(ReportingStage.OrdinaryAPITransactions)!) {
|
for (const transaction of this.transactionsForStage.get(ReportingStage.OrdinaryAPITransactions)!) {
|
||||||
if (!dayjs(transaction.dt).isAfter(dayBeforePeriodStart)) {
|
if (!dayjs(transaction.dt).isAfter(dayBeforePeriodStart)) {
|
||||||
for (const posting of transaction.postings) {
|
for (const posting of transaction.postings) {
|
||||||
balancesAtPeriodStart.set(posting.account, (balancesAtPeriodStart.get(posting.account) ?? 0) + asCost(posting.quantity, posting.commodity));
|
balancesAtPeriodStart.set(posting.account, (balancesAtPeriodStart.get(posting.account) ?? 0) + posting.quantity_ascost!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,14 +195,16 @@ export class ReportingWorkflow {
|
|||||||
description: null,
|
description: null,
|
||||||
account: account,
|
account: account,
|
||||||
quantity: -balanceAtPeriodStart,
|
quantity: -balanceAtPeriodStart,
|
||||||
commodity: db.metadata.reporting_commodity
|
commodity: db.metadata.reporting_commodity,
|
||||||
|
quantity_ascost: asCost(-balanceAtPeriodStart, db.metadata.reporting_commodity),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: null,
|
id: null,
|
||||||
description: null,
|
description: null,
|
||||||
account: 'Accumulated surplus (deficit)',
|
account: 'Accumulated surplus (deficit)',
|
||||||
quantity: balanceAtPeriodStart,
|
quantity: balanceAtPeriodStart,
|
||||||
commodity: db.metadata.reporting_commodity
|
commodity: db.metadata.reporting_commodity,
|
||||||
|
quantity_ascost: asCost(balanceAtPeriodStart, db.metadata.reporting_commodity),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
));
|
));
|
||||||
@ -280,9 +284,7 @@ function applyTransactionsToBalances(balances: Map<string, number>, transactions
|
|||||||
for (const transaction of transactions) {
|
for (const transaction of transactions) {
|
||||||
for (const posting of transaction.postings) {
|
for (const posting of transaction.postings) {
|
||||||
const openingBalance = newBalances.get(posting.account) ?? 0;
|
const openingBalance = newBalances.get(posting.account) ?? 0;
|
||||||
const quantityCost = asCost(posting.quantity, posting.commodity);
|
const runningBalance = openingBalance + posting.quantity_ascost!;
|
||||||
const runningBalance = openingBalance + quantityCost;
|
|
||||||
|
|
||||||
newBalances.set(posting.account, runningBalance);
|
newBalances.set(posting.account, runningBalance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user