109 lines
3.9 KiB
Vue
109 lines
3.9 KiB
Vue
<script setup lang="ts">
|
|||
|
|
import { onMounted, ref } from 'vue';
|
||
|
|
import { api } from '@/lib/api';
|
||
|
|
import { UiButton, UiPageHeader } from '@/components/ui';
|
||
|
|
import TreasuryNav from '@/components/treasury/TreasuryNav.vue';
|
||
|
|
import { toman } from './treasury-format';
|
||
|
|
|
||
|
|
type Period = {
|
||
|
|
bookedIncome: string;
|
||
|
|
bookedExpense: string;
|
||
|
|
operatingProfit: string;
|
||
|
|
cashIn: string;
|
||
|
|
cashOut: string;
|
||
|
|
cashNet: string;
|
||
|
|
expenseCategories: Array<{ name: string; amount: string; share: string }>;
|
||
|
|
};
|
||
|
|
const current = ref<Period | null>(null);
|
||
|
|
const previous = ref<Period | null>(null);
|
||
|
|
const payablesByMonth = ref<Array<{ month: string; amount: string }>>([]);
|
||
|
|
const receivablesByParty = ref<Array<{ name: string; amount: string }>>([]);
|
||
|
|
const error = ref('');
|
||
|
|
|
||
|
|
const lines = [
|
||
|
|
['درآمد ثبتشده', 'bookedIncome'],
|
||
|
|
['هزینه ثبتشده', 'bookedExpense'],
|
||
|
|
['سود عملیاتی', 'operatingProfit'],
|
||
|
|
['ورود وجه', 'cashIn'],
|
||
|
|
['خروج وجه', 'cashOut'],
|
||
|
|
['خالص جریان نقدی', 'cashNet'],
|
||
|
|
] as const;
|
||
|
|
|
||
|
|
async function load() {
|
||
|
|
const { data } = await api.get<{
|
||
|
|
current: Period;
|
||
|
|
previous: Period;
|
||
|
|
payablesByMonth: Array<{ month: string; amount: string }>;
|
||
|
|
receivablesByParty: Array<{ name: string; amount: string }>;
|
||
|
|
}>('/treasury/reports');
|
||
|
|
current.value = data.current;
|
||
|
|
previous.value = data.previous;
|
||
|
|
payablesByMonth.value = data.payablesByMonth;
|
||
|
|
receivablesByParty.value = data.receivablesByParty;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function download() {
|
||
|
|
const { data } = await api.get<Blob>('/treasury/reports/export', { responseType: 'blob' });
|
||
|
|
const url = URL.createObjectURL(data);
|
||
|
|
const link = document.createElement('a');
|
||
|
|
link.href = url;
|
||
|
|
link.download = 'treasury-report.xlsx';
|
||
|
|
link.click();
|
||
|
|
URL.revokeObjectURL(url);
|
||
|
|
}
|
||
|
|
|
||
|
|
onMounted(() => {
|
||
|
|
void load().catch(() => {
|
||
|
|
error.value = 'گزارش بارگذاری نشد.';
|
||
|
|
});
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<template>
|
||
|
|
<section class="page">
|
||
|
|
<UiPageHeader title="گزارش مالی" description="سود عملیاتی از درآمد و هزینه ثبتشده است، نه از خالص وجه." />
|
||
|
|
<TreasuryNav />
|
||
|
|
<p v-if="error" class="err">{{ error }}</p>
|
||
|
|
<UiButton variant="secondary" @click="download">خروجی اکسل</UiButton>
|
||
|
|
<table v-if="current && previous">
|
||
|
|
<thead>
|
||
|
|
<tr><th>شاخص</th><th>این دوره</th><th>دوره قبل</th></tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr v-for="line in lines" :key="line[1]">
|
||
|
|
<td>{{ line[0] }}</td>
|
||
|
|
<td>{{ toman(current[line[1]]) }}</td>
|
||
|
|
<td>{{ toman(previous[line[1]]) }}</td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
<h2>مطالبات به تفکیک طرف حساب</h2>
|
||
|
|
<ul>
|
||
|
|
<li v-for="row in receivablesByParty" :key="row.name"><span>{{ row.name }}</span><b>{{ toman(row.amount) }}</b></li>
|
||
|
|
<li v-if="!receivablesByParty.length">مورد بازی نیست.</li>
|
||
|
|
</ul>
|
||
|
|
<h2>تعهدات به تفکیک ماه</h2>
|
||
|
|
<ul>
|
||
|
|
<li v-for="row in payablesByMonth" :key="row.month"><span>{{ row.month }}</span><b>{{ toman(row.amount) }}</b></li>
|
||
|
|
<li v-if="!payablesByMonth.length">تعهد بازی نیست.</li>
|
||
|
|
</ul>
|
||
|
|
<h2 v-if="current">سهم هزینهها</h2>
|
||
|
|
<ul v-if="current">
|
||
|
|
<li v-for="row in current.expenseCategories" :key="row.name">
|
||
|
|
<span>{{ row.name }}</span>
|
||
|
|
<b>{{ toman(row.amount) }} · {{ row.share }}٪</b>
|
||
|
|
</li>
|
||
|
|
</ul>
|
||
|
|
</section>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<style scoped>
|
||
|
|
.page { display: grid; gap: 1rem; }
|
||
|
|
table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 16px; overflow: hidden; }
|
||
|
|
th, td { text-align: right; padding: 0.7rem; border-bottom: 1px solid #f1f5f9; }
|
||
|
|
ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.4rem; }
|
||
|
|
li { display: flex; justify-content: space-between; background: #fff; border: 1px solid #e7eef2; border-radius: 12px; padding: 0.7rem 0.9rem; }
|
||
|
|
h2 { margin: 0; font-size: 1rem; }
|
||
|
|
.err { color: #b91c1c; font-weight: 700; }
|
||
|
|
</style>
|