feat: show profit
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 10s

This commit is contained in:
Thanaphon Frappet 2025-03-07 15:54:25 +07:00
parent 2a2613d72c
commit c750ed5fee
4 changed files with 276 additions and 1 deletions

View file

@ -1,6 +1,6 @@
<script lang="ts" setup>
// NOTE: Library
import { computed, onMounted, reactive, watch } from 'vue';
import { ref, computed, onMounted, reactive, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { getRole } from 'src/services/keycloak';
@ -19,28 +19,58 @@ import {
colReport,
colReportProduct,
colReportSale,
colProfit,
colProfitByMoth,
colProfitByYear,
} from './constants';
import useFlowStore from 'src/stores/flow';
import { useReportStore } from 'src/stores/report';
import BadgeComponent from 'src/components/BadgeComponent.vue';
import Expansion from 'src/components/14_report/Expansion.vue';
import { SaveButton } from 'src/components/button';
import { ReportProfit } from 'src/stores/report/types';
// NOTE: Variable
const navigatorStore = useNavigator();
const reportStore = useReportStore();
const flow = useFlowStore();
const dataReportProfitByYears = ref<ReportProfit['dataset']>([]);
const pastYears = ref<number>(5);
const optionsPastYears = ref<number[]>([5, 10, 20, 30, 50, 80]);
const {
dataReportQuotation,
dataReportInvoice,
dataReportReceipt,
dataReportSale,
dataReportProduct,
dataReportProfit,
} = storeToRefs(reportStore);
const userRoles = computed(() => getRole() || []);
const combinedProfitYear = computed(() => {
return (
dataReportProfitByYears.value?.reduce<ReportProfit['dataset']>(
(acc, val) => {
let find = acc.find((item) => item.year === val.year);
if (find) {
find.expenses += val.expenses;
find.netProfit += val.netProfit;
find.income += val.income;
} else {
acc.push({ ...val });
}
return acc;
},
[],
) ?? []
);
});
async function fetchReportQuotation() {
dataReportQuotation.value = (await reportStore.getReportQuotation()) || [];
}
@ -58,6 +88,16 @@ async function fetchReportProduct() {
dataReportProduct.value = (await reportStore.getReportProduct()) || [];
}
async function fetchReportProfit() {
dataReportProfit.value = (await reportStore.getReportProfit()) || undefined;
dataReportProfitByYears.value = dataReportProfit.value?.dataset || [];
}
async function fetchReportProfitByYears(years: number) {
const res = (await reportStore.getReportProfit({ years })) || undefined;
dataReportProfitByYears.value = res?.dataset || [];
}
onMounted(async () => {
navigatorStore.current.title = 'report.title';
navigatorStore.current.path = [{ text: '' }];
@ -102,6 +142,10 @@ async function fetchReportTab() {
await fetchReportSale();
break;
}
case ViewMode.Profit: {
await fetchReportProfit();
break;
}
}
}
@ -112,6 +156,10 @@ onMounted(async () => {
watch([() => pageState.currentTab], async () => {
await fetchReportTab();
});
watch([() => pastYears.value], async () => {
await fetchReportProfitByYears(pastYears.value);
});
</script>
<template>
@ -495,6 +543,99 @@ watch([() => pageState.currentTab], async () => {
</Expansion>
</div>
</template>
<template v-if="pageState.currentTab === ViewMode.Profit">
<div class="q-gutter-y-md">
<Expansion default-opened>
<template #header>
<div class="flex full-width items-center">
{{ $t('report.profit.byMonth') }}
</div>
</template>
<template #main>
<TableReport
:row="dataReportProfit?.dataset"
:columns="colProfitByMoth"
>
<template #title="{ item }">
{{ $t(`month.${item.row.month}`) }}
</template>
</TableReport>
</template>
</Expansion>
<Expansion default-opened>
<template #header>
<div class="flex full-width items-center">
{{ $t('report.profit.byYear') }}
</div>
</template>
<template #main>
<div class="q-gutter-y-md">
<q-select
style="max-width: 150px"
outlined
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
input-debounce="0"
option-value="value"
option-label="label"
dense
v-model="pastYears"
:options="
optionsPastYears.map((v) => ({
value: v,
label: `${v} ${$t('general.year')}`,
}))
"
label="เลือกปีย้อนหลัง"
></q-select>
<TableReport
:row="combinedProfitYear"
:columns="colProfitByYear"
></TableReport>
</div>
</template>
</Expansion>
<Expansion default-opened>
<template #header>
<div class="flex full-width items-center">
{{ $t('report.view.Profit') }}
</div>
</template>
<template #main>
<TableReport
:row="[
{
title: $t('report.profit.netProfit'),
amount: dataReportProfit?.netProfit || 0,
},
{
title: $t('report.profit.expenses'),
amount: dataReportProfit?.expenses || 0,
},
{
title: $t('report.profit.income'),
amount: dataReportProfit?.income || 0,
},
]"
:columns="colProfit"
hide-header
hide-bottom
></TableReport>
</template>
</Expansion>
</div>
</template>
</article>
</div>
</section>

View file

@ -14,6 +14,7 @@ export enum ViewMode {
Receipt = 'receipt',
Product = 'product',
Sale = 'sale',
Profit = 'profit',
}
type ColumnsSale = {
@ -28,6 +29,25 @@ type ColumnsBySale = ColumnsSale & {
gender?: string;
};
type ColumnsProfix = {
title: string;
amount: number;
};
type ColumnsProfixByMonth = {
month: string;
netProfit: number;
expenses: number;
income: number;
};
type ColumnsProfixByYear = Omit<ColumnsProfixByMonth, 'moth'> & {
year: string;
netProfit: number;
expenses: number;
income: number;
};
export const colReportQuotation = [
{
name: 'code',
@ -169,9 +189,81 @@ export const colReportBySale = [
},
] as const satisfies QTableProps['columns'];
export const colProfit = [
{
name: 'title',
align: 'left',
label: '',
field: (data: ColumnsProfix) => data.title,
},
{
name: 'amount',
align: 'left',
label: '',
field: (data: ColumnsProfix) => formatNumberDecimal(data.amount, 2),
},
] as const satisfies QTableProps['columns'];
export const colProfitByMoth = [
{
name: '#title',
align: 'left',
label: 'report.profit.month',
field: '',
},
{
name: 'netProfit',
align: 'left',
label: 'report.profit.netProfit',
field: (data: ColumnsProfixByMonth) =>
formatNumberDecimal(data.netProfit, 2),
},
{
name: 'expenses',
align: 'left',
label: 'report.profit.expenses',
field: (data: ColumnsProfixByMonth) =>
formatNumberDecimal(data.expenses, 2),
},
{
name: 'income',
align: 'left',
label: 'report.profit.income',
field: (data: ColumnsProfixByMonth) => formatNumberDecimal(data.income, 2),
},
] as const satisfies QTableProps['columns'];
export const colProfitByYear = [
{
name: 'title',
align: 'left',
label: 'report.profit.year',
field: (data: ColumnsProfixByYear) => data.year,
},
{
name: 'netProfit',
align: 'left',
label: 'report.profit.netProfit',
field: (data: ColumnsProfixByYear) =>
formatNumberDecimal(data.netProfit, 2),
},
{
name: 'expenses',
align: 'left',
label: 'report.profit.expenses',
field: (data: ColumnsProfixByYear) => formatNumberDecimal(data.expenses, 2),
},
{
name: 'income',
align: 'left',
label: 'report.profit.income',
field: (data: ColumnsProfixByYear) => formatNumberDecimal(data.income, 2),
},
] as const satisfies QTableProps['columns'];
export const pageTabs = [
{ label: 'Document', value: ViewMode.Document, by: ['user'] },
{ label: 'Invoice', value: ViewMode.Invoice, by: ['user'] },
{ label: 'Product', value: ViewMode.Product, by: ['user'] },
{ label: 'Sale', value: ViewMode.Sale, by: ['admin'] },
{ label: 'Profit', value: ViewMode.Profit, by: ['admin'] },
];

View file

@ -7,6 +7,7 @@ import {
ReportProduct,
ReportQuotation,
ReportSale,
ReportProfit,
} from './types';
import { baseUrl } from '../utils';
import { getToken } from 'src/services/keycloak';
@ -120,6 +121,31 @@ export async function getReportPayment(params?: {
return null;
}
export async function getReportProfit(params?: {
years?: number;
startDate?: string | Date;
endDate?: string | Date;
}) {
let opts = params || {};
if (params?.years) {
const currentYear = new Date().getFullYear();
opts.startDate = new Date(currentYear - params.years, 0, 1); // ✅ 1 ม.ค. ของปีที่ต้องการ
opts.endDate = new Date(currentYear, 11, 31); // ✅ 31 ธ.ค. ของปีปัจจุบัน
}
const res = await api.get<ReportProfit>(`/${ENDPOINT}/profit`, {
params: {
startDate: opts.startDate,
endDate: opts.endDate,
},
});
if (res.status < 400) {
return res.data;
}
return null;
}
export const useReportStore = defineStore('report-store', () => {
const dataReportQuotation = ref<ReportQuotation[]>([]);
const dataReportInvoice = ref<Report[]>([]);
@ -127,6 +153,7 @@ export const useReportStore = defineStore('report-store', () => {
const dataReportSale = ref<ReportSale>();
const dataReportProduct = ref<ReportProduct[]>([]);
const dataReportPayment = ref<ReportPayment[]>([]);
const dataReportProfit = ref<ReportProfit>();
return {
dataReportQuotation,
@ -135,6 +162,7 @@ export const useReportStore = defineStore('report-store', () => {
dataReportSale,
dataReportProduct,
dataReportPayment,
dataReportProfit,
downloadReportQuotation,
getReportQuotation,
@ -147,5 +175,6 @@ export const useReportStore = defineStore('report-store', () => {
downloadReportProduct,
getReportProduct,
getReportPayment,
getReportProfit,
};
});

View file

@ -50,3 +50,16 @@ export type ReportSale = {
bySale: (User & { _count: number })[];
byProductGroup: (Omit<ProductGroup, '_count'> & { _count: number })[];
};
export type ReportProfit = {
dataset: {
netProfit: number;
expenses: number;
income: number;
year: number;
month: number;
}[];
netProfit: number;
expenses: number;
income: number;
};