This commit is contained in:
parent
2a2613d72c
commit
c750ed5fee
4 changed files with 276 additions and 1 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
// NOTE: Library
|
// NOTE: Library
|
||||||
import { computed, onMounted, reactive, watch } from 'vue';
|
import { ref, computed, onMounted, reactive, watch } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { getRole } from 'src/services/keycloak';
|
import { getRole } from 'src/services/keycloak';
|
||||||
|
|
||||||
|
|
@ -19,28 +19,58 @@ import {
|
||||||
colReport,
|
colReport,
|
||||||
colReportProduct,
|
colReportProduct,
|
||||||
colReportSale,
|
colReportSale,
|
||||||
|
colProfit,
|
||||||
|
colProfitByMoth,
|
||||||
|
colProfitByYear,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import useFlowStore from 'src/stores/flow';
|
import useFlowStore from 'src/stores/flow';
|
||||||
import { useReportStore } from 'src/stores/report';
|
import { useReportStore } from 'src/stores/report';
|
||||||
import BadgeComponent from 'src/components/BadgeComponent.vue';
|
import BadgeComponent from 'src/components/BadgeComponent.vue';
|
||||||
import Expansion from 'src/components/14_report/Expansion.vue';
|
import Expansion from 'src/components/14_report/Expansion.vue';
|
||||||
import { SaveButton } from 'src/components/button';
|
import { SaveButton } from 'src/components/button';
|
||||||
|
import { ReportProfit } from 'src/stores/report/types';
|
||||||
|
|
||||||
// NOTE: Variable
|
// NOTE: Variable
|
||||||
const navigatorStore = useNavigator();
|
const navigatorStore = useNavigator();
|
||||||
const reportStore = useReportStore();
|
const reportStore = useReportStore();
|
||||||
const flow = useFlowStore();
|
const flow = useFlowStore();
|
||||||
|
|
||||||
|
const dataReportProfitByYears = ref<ReportProfit['dataset']>([]);
|
||||||
|
const pastYears = ref<number>(5);
|
||||||
|
const optionsPastYears = ref<number[]>([5, 10, 20, 30, 50, 80]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
dataReportQuotation,
|
dataReportQuotation,
|
||||||
dataReportInvoice,
|
dataReportInvoice,
|
||||||
dataReportReceipt,
|
dataReportReceipt,
|
||||||
dataReportSale,
|
dataReportSale,
|
||||||
dataReportProduct,
|
dataReportProduct,
|
||||||
|
dataReportProfit,
|
||||||
} = storeToRefs(reportStore);
|
} = storeToRefs(reportStore);
|
||||||
|
|
||||||
const userRoles = computed(() => getRole() || []);
|
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() {
|
async function fetchReportQuotation() {
|
||||||
dataReportQuotation.value = (await reportStore.getReportQuotation()) || [];
|
dataReportQuotation.value = (await reportStore.getReportQuotation()) || [];
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +88,16 @@ async function fetchReportProduct() {
|
||||||
dataReportProduct.value = (await reportStore.getReportProduct()) || [];
|
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 () => {
|
onMounted(async () => {
|
||||||
navigatorStore.current.title = 'report.title';
|
navigatorStore.current.title = 'report.title';
|
||||||
navigatorStore.current.path = [{ text: '' }];
|
navigatorStore.current.path = [{ text: '' }];
|
||||||
|
|
@ -102,6 +142,10 @@ async function fetchReportTab() {
|
||||||
await fetchReportSale();
|
await fetchReportSale();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case ViewMode.Profit: {
|
||||||
|
await fetchReportProfit();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,6 +156,10 @@ onMounted(async () => {
|
||||||
watch([() => pageState.currentTab], async () => {
|
watch([() => pageState.currentTab], async () => {
|
||||||
await fetchReportTab();
|
await fetchReportTab();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch([() => pastYears.value], async () => {
|
||||||
|
await fetchReportProfitByYears(pastYears.value);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -495,6 +543,99 @@ watch([() => pageState.currentTab], async () => {
|
||||||
</Expansion>
|
</Expansion>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export enum ViewMode {
|
||||||
Receipt = 'receipt',
|
Receipt = 'receipt',
|
||||||
Product = 'product',
|
Product = 'product',
|
||||||
Sale = 'sale',
|
Sale = 'sale',
|
||||||
|
Profit = 'profit',
|
||||||
}
|
}
|
||||||
|
|
||||||
type ColumnsSale = {
|
type ColumnsSale = {
|
||||||
|
|
@ -28,6 +29,25 @@ type ColumnsBySale = ColumnsSale & {
|
||||||
gender?: string;
|
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 = [
|
export const colReportQuotation = [
|
||||||
{
|
{
|
||||||
name: 'code',
|
name: 'code',
|
||||||
|
|
@ -169,9 +189,81 @@ export const colReportBySale = [
|
||||||
},
|
},
|
||||||
] as const satisfies QTableProps['columns'];
|
] 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 = [
|
export const pageTabs = [
|
||||||
{ label: 'Document', value: ViewMode.Document, by: ['user'] },
|
{ label: 'Document', value: ViewMode.Document, by: ['user'] },
|
||||||
{ label: 'Invoice', value: ViewMode.Invoice, by: ['user'] },
|
{ label: 'Invoice', value: ViewMode.Invoice, by: ['user'] },
|
||||||
{ label: 'Product', value: ViewMode.Product, by: ['user'] },
|
{ label: 'Product', value: ViewMode.Product, by: ['user'] },
|
||||||
{ label: 'Sale', value: ViewMode.Sale, by: ['admin'] },
|
{ label: 'Sale', value: ViewMode.Sale, by: ['admin'] },
|
||||||
|
{ label: 'Profit', value: ViewMode.Profit, by: ['admin'] },
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
ReportProduct,
|
ReportProduct,
|
||||||
ReportQuotation,
|
ReportQuotation,
|
||||||
ReportSale,
|
ReportSale,
|
||||||
|
ReportProfit,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { baseUrl } from '../utils';
|
import { baseUrl } from '../utils';
|
||||||
import { getToken } from 'src/services/keycloak';
|
import { getToken } from 'src/services/keycloak';
|
||||||
|
|
@ -120,6 +121,31 @@ export async function getReportPayment(params?: {
|
||||||
return null;
|
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', () => {
|
export const useReportStore = defineStore('report-store', () => {
|
||||||
const dataReportQuotation = ref<ReportQuotation[]>([]);
|
const dataReportQuotation = ref<ReportQuotation[]>([]);
|
||||||
const dataReportInvoice = ref<Report[]>([]);
|
const dataReportInvoice = ref<Report[]>([]);
|
||||||
|
|
@ -127,6 +153,7 @@ export const useReportStore = defineStore('report-store', () => {
|
||||||
const dataReportSale = ref<ReportSale>();
|
const dataReportSale = ref<ReportSale>();
|
||||||
const dataReportProduct = ref<ReportProduct[]>([]);
|
const dataReportProduct = ref<ReportProduct[]>([]);
|
||||||
const dataReportPayment = ref<ReportPayment[]>([]);
|
const dataReportPayment = ref<ReportPayment[]>([]);
|
||||||
|
const dataReportProfit = ref<ReportProfit>();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dataReportQuotation,
|
dataReportQuotation,
|
||||||
|
|
@ -135,6 +162,7 @@ export const useReportStore = defineStore('report-store', () => {
|
||||||
dataReportSale,
|
dataReportSale,
|
||||||
dataReportProduct,
|
dataReportProduct,
|
||||||
dataReportPayment,
|
dataReportPayment,
|
||||||
|
dataReportProfit,
|
||||||
|
|
||||||
downloadReportQuotation,
|
downloadReportQuotation,
|
||||||
getReportQuotation,
|
getReportQuotation,
|
||||||
|
|
@ -147,5 +175,6 @@ export const useReportStore = defineStore('report-store', () => {
|
||||||
downloadReportProduct,
|
downloadReportProduct,
|
||||||
getReportProduct,
|
getReportProduct,
|
||||||
getReportPayment,
|
getReportPayment,
|
||||||
|
getReportProfit,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -50,3 +50,16 @@ export type ReportSale = {
|
||||||
bySale: (User & { _count: number })[];
|
bySale: (User & { _count: number })[];
|
||||||
byProductGroup: (Omit<ProductGroup, '_count'> & { _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;
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue