Compare commits
No commits in common. "914cb384b1fb35229fd850578c9b235ac0c83e85" and "b445bfbca79a7249692684e2f2fb202464f63d6b" have entirely different histories.
914cb384b1
...
b445bfbca7
@ -15,7 +15,6 @@
|
|||||||
"fs:default",
|
"fs:default",
|
||||||
"fs:allow-read-text-file",
|
"fs:allow-read-text-file",
|
||||||
"fs:allow-resource-read-recursive",
|
"fs:allow-resource-read-recursive",
|
||||||
"fs:allow-write-text-file",
|
|
||||||
"shell:allow-open",
|
"shell:allow-open",
|
||||||
"sql:default",
|
"sql:default",
|
||||||
"sql:allow-execute",
|
"sql:allow-execute",
|
||||||
|
@ -19,48 +19,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="relative print:hidden">
|
<div class="relative print:hidden">
|
||||||
<button class="text-gray-400 align-middle hover:text-gray-500" @click="isMenuOpen = !isMenuOpen"><EllipsisHorizontalCircleIcon class="size-6" /></button>
|
<button class="text-gray-400 align-middle hover:text-gray-500" @click="isMenuOpen = !isMenuOpen"><EllipsisHorizontalCircleIcon class="size-6" /></button>
|
||||||
<ul class="absolute top-8 right-0 py-1 bg-white w-[11rem] shadow-lg ring-1 ring-black/5 focus:outline-hidden" :class="isMenuOpen ? 'block' : 'hidden'">
|
<ul class="absolute top-8 right-0 bg-white w-[11rem] shadow-lg ring-1 ring-black/5 focus:outline-hidden" :class="isMenuOpen ? 'block' : 'hidden'">
|
||||||
<li class="group cursor-pointer select-none py-1 px-3 text-gray-900 hover:text-white hover:bg-emerald-600" @click="menuPrint">
|
<li class="group cursor-pointer select-none py-1 px-3 text-gray-900 hover:text-white hover:bg-emerald-600" @click="menuPrint">
|
||||||
<PrinterIcon class="inline size-5 text-gray-500 group-hover:text-white" />
|
<PrinterIcon class="inline size-5 text-gray-500 group-hover:text-white" />
|
||||||
Print/Save as PDF
|
Print/Save as PDF
|
||||||
</li>
|
</li>
|
||||||
<li class="group cursor-pointer select-none py-1 px-3 text-gray-900 hover:text-white hover:bg-emerald-600" @click="menuCsv">
|
|
||||||
<DocumentTextIcon class="inline size-5 text-gray-500 group-hover:text-white" />
|
|
||||||
Save as CSV
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup type="ts">
|
||||||
import { DocumentTextIcon, EllipsisHorizontalCircleIcon, PrinterIcon } from '@heroicons/vue/24/outline';
|
import { EllipsisHorizontalCircleIcon, PrinterIcon } from '@heroicons/vue/24/outline';
|
||||||
import { save } from '@tauri-apps/plugin-dialog';
|
|
||||||
import { writeTextFile } from '@tauri-apps/plugin-fs';
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { DynamicReport } from '../reports/base.ts';
|
|
||||||
|
|
||||||
const { report, columns, subtitle } = defineProps<{ report: DynamicReport | null, columns?: string[], subtitle?: string }>();
|
|
||||||
|
|
||||||
const isMenuOpen = ref(false);
|
const isMenuOpen = ref(false);
|
||||||
|
|
||||||
async function menuCsv() {
|
|
||||||
// Export report to CSV
|
|
||||||
const csv = report!.toCSV(columns, subtitle);
|
|
||||||
|
|
||||||
// Save to file
|
|
||||||
const csvFilename = await save({
|
|
||||||
filters: [
|
|
||||||
{ name: 'Comma separated values (CSV)', extensions: ['csv'] }
|
|
||||||
],
|
|
||||||
});
|
|
||||||
if (csvFilename !== null) {
|
|
||||||
await writeTextFile(csvFilename, csv);
|
|
||||||
}
|
|
||||||
|
|
||||||
isMenuOpen.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function menuPrint() {
|
function menuPrint() {
|
||||||
window.print();
|
window.print();
|
||||||
isMenuOpen.value = false;
|
isMenuOpen.value = false;
|
||||||
|
@ -63,7 +63,7 @@
|
|||||||
import { drcrAccountKinds, getAccountKinds } from '../registry.ts';
|
import { drcrAccountKinds, getAccountKinds } from '../registry.ts';
|
||||||
import { db } from '../db.ts';
|
import { db } from '../db.ts';
|
||||||
import DropdownBox from '../components/DropdownBox.vue';
|
import DropdownBox from '../components/DropdownBox.vue';
|
||||||
import { DynamicReport, Row, Section } from '../reports/base.ts';
|
import { DynamicReport, reportEntryById, Row, Section } from '../reports/base.ts';
|
||||||
|
|
||||||
const accountKinds = ref([...drcrAccountKinds]);
|
const accountKinds = ref([...drcrAccountKinds]);
|
||||||
const accountKindsMap = computed(() => new Map(accountKinds.value));
|
const accountKindsMap = computed(() => new Map(accountKinds.value));
|
||||||
@ -77,8 +77,8 @@
|
|||||||
const session = await db.load();
|
const session = await db.load();
|
||||||
|
|
||||||
// Get all accounts on the trial balance
|
// Get all accounts on the trial balance
|
||||||
const trialBalance = DynamicReport.fromJSON(await invoke('get_trial_balance', { date: '9999-12-31' })) as DynamicReport;
|
const trialBalance = JSON.parse(await invoke('get_trial_balance', { date: '9999-12-31' })) as DynamicReport;
|
||||||
const trialBalanceAccounts = (trialBalance.byId('accounts') as { Section: Section }).Section.entries.map((e) => (e as { Row: Row }).Row.text);
|
const trialBalanceAccounts = (reportEntryById(trialBalance, 'accounts') as { Section: Section }).Section.entries.map((e) => (e as { Row: Row }).Row.text);
|
||||||
|
|
||||||
// Get all configured account kinds
|
// Get all configured account kinds
|
||||||
const accountKindsRaw: {account: string, kind: string}[] = await session.select(
|
const accountKindsRaw: {account: string, kind: string}[] = await session.select(
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
<DynamicReportComponent :report="report">
|
<DynamicReportComponent :report="report">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="absolute -top-10 right-0">
|
<div class="absolute -top-10 right-0">
|
||||||
<DynamicReportMenu :report="report" />
|
<DynamicReportMenu />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DynamicReportComponent>
|
</DynamicReportComponent>
|
||||||
@ -37,7 +37,7 @@
|
|||||||
const report = ref(null as DynamicReport | null);
|
const report = ref(null as DynamicReport | null);
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
report.value = DynamicReport.fromJSON(await invoke('get_tax_summary'));
|
report.value = JSON.parse(await invoke('get_tax_summary'));
|
||||||
}
|
}
|
||||||
load();
|
load();
|
||||||
</script>
|
</script>
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DynamicReportMenu :report="report" :columns="reportColumns" :subtitle="'As at ' + dt" />
|
<DynamicReportMenu />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-md bg-red-50 mt-4 p-4 col-span-2" v-if="!doesBalance">
|
<div class="rounded-md bg-red-50 mt-4 p-4 col-span-2" v-if="!doesBalance">
|
||||||
@ -55,7 +55,7 @@
|
|||||||
|
|
||||||
import { ExclamationCircleIcon } from '@heroicons/vue/20/solid';
|
import { ExclamationCircleIcon } from '@heroicons/vue/20/solid';
|
||||||
|
|
||||||
import { DynamicReport, Row } from './base.ts';
|
import { DynamicReport, Row, reportEntryById } from './base.ts';
|
||||||
import { db } from '../db.ts';
|
import { db } from '../db.ts';
|
||||||
import DynamicReportComponent from '../components/DynamicReportComponent.vue';
|
import DynamicReportComponent from '../components/DynamicReportComponent.vue';
|
||||||
import DynamicReportMenu from '../components/DynamicReportMenu.vue';
|
import DynamicReportMenu from '../components/DynamicReportMenu.vue';
|
||||||
@ -111,7 +111,7 @@
|
|||||||
newReportColumns = ['$'];
|
newReportColumns = ['$'];
|
||||||
}
|
}
|
||||||
|
|
||||||
report.value = DynamicReport.fromJSON(await invoke('get_balance_sheet', { dates: reportDates }));
|
report.value = JSON.parse(await invoke('get_balance_sheet', { dates: reportDates }));
|
||||||
reportColumns.value = newReportColumns; // Wait until report available to update this
|
reportColumns.value = newReportColumns; // Wait until report available to update this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,9 +120,9 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalAssets = (report.value.byId('total_assets') as { Row: Row }).Row.quantity;
|
const totalAssets = (reportEntryById(report.value, 'total_assets') as { Row: Row }).Row.quantity;
|
||||||
const totalLiabilities = (report.value.byId('total_liabilities') as { Row: Row }).Row.quantity;
|
const totalLiabilities = (reportEntryById(report.value, 'total_liabilities') as { Row: Row }).Row.quantity;
|
||||||
const totalEquity = (report.value.byId('total_equity') as { Row: Row }).Row.quantity;
|
const totalEquity = (reportEntryById(report.value, 'total_equity') as { Row: Row }).Row.quantity;
|
||||||
|
|
||||||
let doesBalance = true;
|
let doesBalance = true;
|
||||||
for (let column = 0; column < report.value.columns.length; column++) {
|
for (let column = 0; column < report.value.columns.length; column++) {
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DynamicReportMenu :report="report" :columns="reportColumns" :subtitle="dtStart + ' to ' + dt" />
|
<DynamicReportMenu />
|
||||||
</div>
|
</div>
|
||||||
</DynamicReportComponent>
|
</DynamicReportComponent>
|
||||||
</template>
|
</template>
|
||||||
@ -106,7 +106,7 @@
|
|||||||
newReportColumns = ['$'];
|
newReportColumns = ['$'];
|
||||||
}
|
}
|
||||||
|
|
||||||
report.value = DynamicReport.fromJSON(await invoke('get_income_statement', { dates: reportDates }));
|
report.value = JSON.parse(await invoke('get_income_statement', { dates: reportDates }));
|
||||||
reportColumns.value = newReportColumns; // Wait until report available to update this
|
reportColumns.value = newReportColumns; // Wait until report available to update this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<div class="my-2 py-2 flex gap-x-2 items-baseline">
|
<div class="my-2 py-2 flex gap-x-2 items-baseline">
|
||||||
<span class="whitespace-nowrap">As at</span>
|
<span class="whitespace-nowrap">As at</span>
|
||||||
<input type="date" class="bordered-field" v-model.lazy="dt">
|
<input type="date" class="bordered-field" v-model.lazy="dt">
|
||||||
<DynamicReportMenu :report="report" :subtitle="'As at ' + dt" />
|
<DynamicReportMenu />
|
||||||
</div>
|
</div>
|
||||||
</DynamicReportComponent>
|
</DynamicReportComponent>
|
||||||
</template>
|
</template>
|
||||||
@ -55,6 +55,6 @@
|
|||||||
|
|
||||||
async function updateReport() {
|
async function updateReport() {
|
||||||
const reportDate = dayjs(dt.value!).format('YYYY-MM-DD');
|
const reportDate = dayjs(dt.value!).format('YYYY-MM-DD');
|
||||||
report.value = DynamicReport.fromJSON(await invoke('get_trial_balance', { date: reportDate }));
|
report.value = JSON.parse(await invoke('get_trial_balance', { date: reportDate }));
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -16,45 +16,11 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { db, serialiseAmount } from '../db.ts';
|
// Cannot be a class as these are directly deserialised from JSON
|
||||||
import { CriticalError } from '../error.ts';
|
export interface DynamicReport {
|
||||||
|
title: string;
|
||||||
export class DynamicReport {
|
columns: string[];
|
||||||
title!: string;
|
entries: DynamicReportEntry[];
|
||||||
columns!: string[];
|
|
||||||
entries!: DynamicReportEntry[];
|
|
||||||
|
|
||||||
static fromJSON(json: string): DynamicReport {
|
|
||||||
return Object.assign(new DynamicReport(), JSON.parse(json));
|
|
||||||
}
|
|
||||||
|
|
||||||
byId(id: string): DynamicReportEntry | null {
|
|
||||||
return reportEntryById(this, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to report to CSV
|
|
||||||
toCSV(columns?: string[], subtitle?: string): string {
|
|
||||||
let csv = '';
|
|
||||||
|
|
||||||
// Title and subtitle
|
|
||||||
csv += escapeCSV(this.title) + '\n';
|
|
||||||
if (subtitle) {
|
|
||||||
csv += escapeCSV(subtitle) + '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Columns
|
|
||||||
for (const column of columns || this.columns) {
|
|
||||||
csv += ',' + escapeCSV(column);
|
|
||||||
}
|
|
||||||
csv += '\n';
|
|
||||||
|
|
||||||
// Entries
|
|
||||||
for (const entry of this.entries) {
|
|
||||||
csv += entryToCSV(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
return csv;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// serde_json serialises an enum like this
|
// serde_json serialises an enum like this
|
||||||
@ -100,37 +66,3 @@ export function reportEntryById(report: DynamicReport | Section, id: string): Dy
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escape the given text as contents of a single CSV field
|
|
||||||
function escapeCSV(cell: string): string {
|
|
||||||
if (cell.indexOf('"') >= 0) {
|
|
||||||
return '"' + cell.replaceAll('"', '""') + '"';
|
|
||||||
}
|
|
||||||
if (cell.indexOf(',') >= 0) {
|
|
||||||
return '"' + cell + '"';
|
|
||||||
}
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
function entryToCSV(entry: DynamicReportEntry): string {
|
|
||||||
if (entry === 'Spacer') {
|
|
||||||
return '\n';
|
|
||||||
} else if ((entry as { Section: Section }).Section) {
|
|
||||||
const section = (entry as { Section: Section }).Section;
|
|
||||||
let csv = '';
|
|
||||||
for (const sectionEntry of section.entries) {
|
|
||||||
csv += entryToCSV(sectionEntry);
|
|
||||||
}
|
|
||||||
return csv;
|
|
||||||
} else if ((entry as { Row: Row }).Row) {
|
|
||||||
const row = (entry as { Row: Row}).Row;
|
|
||||||
let csv = escapeCSV(row.text);
|
|
||||||
for (const quantity of row.quantity) {
|
|
||||||
csv += ',' + escapeCSV(serialiseAmount(quantity, db.metadata.reporting_commodity));
|
|
||||||
}
|
|
||||||
csv += '\n';
|
|
||||||
return csv;
|
|
||||||
} else {
|
|
||||||
throw new CriticalError('Unexpected DynamicReportEntry');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user