1. 计算部门开票价格
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
const prefix = 'department-finance-report/';
|
||||
|
||||
/**
|
||||
* 报表批次列表
|
||||
*/
|
||||
export async function getDepartmentFinanceReportListApi(params: Record<string, unknown>) {
|
||||
return requestClient.get<any>(`${prefix}list`, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 报表详情
|
||||
*/
|
||||
export async function getDepartmentFinanceReportDetailApi(id: number) {
|
||||
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
|
||||
}
|
||||
|
||||
/**
|
||||
* 诊所药品供货明细
|
||||
*/
|
||||
export async function getDepartmentFinanceStoreDrugDetailApi(params: {
|
||||
store_id: number;
|
||||
report_id?: number;
|
||||
search_time?: [string, string];
|
||||
}) {
|
||||
return requestClient.get<any>(`${prefix}store-drug-detail`, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动生成报表(须传统计区间)
|
||||
*/
|
||||
export async function generateDepartmentFinanceReportApi(data: {
|
||||
search_time: [string, string];
|
||||
}) {
|
||||
return requestClient.post<any>(`${prefix}generate`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取生成配置
|
||||
*/
|
||||
export async function getDepartmentFinanceConfigApi() {
|
||||
return requestClient.get<any>(`${prefix}config`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存生成配置
|
||||
*/
|
||||
export async function saveDepartmentFinanceConfigApi(data: {
|
||||
generate_day: number;
|
||||
generate_hour: number;
|
||||
}) {
|
||||
return requestClient.post<any>(`${prefix}save-config`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出 Excel
|
||||
*/
|
||||
export async function exportDepartmentFinanceReportApi(params: {
|
||||
id: number;
|
||||
include_drugs?: 0 | 1;
|
||||
}) {
|
||||
return requestClient.download(`${prefix}export-excel`, { params });
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Form, InputNumber, message } from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
getDepartmentFinanceConfigApi,
|
||||
saveDepartmentFinanceConfigApi,
|
||||
} from '#/views/finance/department-finance/api';
|
||||
|
||||
const emit = defineEmits<{ saved: [] }>();
|
||||
|
||||
const generateDay = ref(14);
|
||||
const generateHour = ref(2);
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
title: '部门财务报表生成配置',
|
||||
draggable: true,
|
||||
onConfirm: async () => {
|
||||
modalApi.setState({ confirmLoading: true });
|
||||
try {
|
||||
await saveDepartmentFinanceConfigApi({
|
||||
generate_day: generateDay.value,
|
||||
generate_hour: generateHour.value,
|
||||
});
|
||||
message.success('配置已保存');
|
||||
emit('saved');
|
||||
modalApi.close();
|
||||
} finally {
|
||||
modalApi.setState({ confirmLoading: false });
|
||||
}
|
||||
},
|
||||
onOpenChange(isOpen) {
|
||||
if (isOpen) {
|
||||
loadConfig();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/** 读取当前生成配置 */
|
||||
async function loadConfig() {
|
||||
const res = await getDepartmentFinanceConfigApi();
|
||||
generateDay.value = Number(res.generate_day ?? 14);
|
||||
generateHour.value = Number(res.generate_hour ?? 2);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open() {
|
||||
modalApi.open();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal>
|
||||
<Form layout="vertical">
|
||||
<Form.Item label="每月生成日(1-28)">
|
||||
<InputNumber v-model:value="generateDay" :min="1" :max="28" class="w-full" />
|
||||
</Form.Item>
|
||||
<Form.Item label="生成小时(0-23)">
|
||||
<InputNumber v-model:value="generateHour" :min="0" :max="23" class="w-full" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -0,0 +1,79 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { DatePicker, Form, message } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { generateDepartmentFinanceReportApi } from '#/views/finance/department-finance/api';
|
||||
|
||||
const emit = defineEmits<{ success: [] }>();
|
||||
|
||||
/** 默认统计区间:当月1日 ~ 今天(与对账单一致) */
|
||||
const searchTime = ref<[Dayjs, Dayjs]>([dayjs().startOf('month'), dayjs()]);
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
title: '手动生成部门财务报表',
|
||||
draggable: true,
|
||||
onConfirm: async () => {
|
||||
if (!searchTime.value?.[0] || !searchTime.value?.[1]) {
|
||||
message.warning('请选择统计开始和结束时间');
|
||||
return;
|
||||
}
|
||||
if (searchTime.value[0].isAfter(searchTime.value[1], 'day')) {
|
||||
message.warning('开始时间不能晚于结束时间');
|
||||
return;
|
||||
}
|
||||
modalApi.setState({ confirmLoading: true });
|
||||
try {
|
||||
const res = await generateDepartmentFinanceReportApi({
|
||||
search_time: [
|
||||
searchTime.value[0].format('YYYY-MM-DD'),
|
||||
searchTime.value[1].format('YYYY-MM-DD'),
|
||||
],
|
||||
});
|
||||
message.success(`生成成功:${res.report_no ?? ''}`);
|
||||
emit('success');
|
||||
modalApi.close();
|
||||
} finally {
|
||||
modalApi.setState({ confirmLoading: false });
|
||||
}
|
||||
},
|
||||
onOpenChange(isOpen) {
|
||||
if (isOpen) {
|
||||
searchTime.value = [dayjs().startOf('month'), dayjs()];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function popupContainerBody() {
|
||||
return document.body;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open() {
|
||||
modalApi.open();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal>
|
||||
<Form layout="vertical">
|
||||
<Form.Item label="统计时间范围" required>
|
||||
<DatePicker.RangePicker
|
||||
v-model:value="searchTime"
|
||||
format="YYYY-MM-DD"
|
||||
class="w-full"
|
||||
:get-popup-container="popupContainerBody"
|
||||
/>
|
||||
</Form.Item>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
将按所选区间汇总各诊所开票相关金额(供货价、代煎费、运费等),结束日期不能晚于今天。
|
||||
</p>
|
||||
</Form>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -0,0 +1,160 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenDrawer } from '@vben/common-ui';
|
||||
|
||||
import { Button, Table } from 'ant-design-vue';
|
||||
|
||||
import StatisticsReconciliation from '#/views/finance/reconciliation/components/statistics.vue';
|
||||
import {
|
||||
exportDepartmentFinanceReportApi,
|
||||
getDepartmentFinanceReportDetailApi,
|
||||
getDepartmentFinanceStoreDrugDetailApi,
|
||||
} from '#/views/finance/department-finance/api';
|
||||
|
||||
const loading = ref(false);
|
||||
const reportId = ref(0);
|
||||
const detail = ref<any>(null);
|
||||
const drugLoading = ref(false);
|
||||
const drugItems = ref<any[]>([]);
|
||||
const drugStoreName = ref('');
|
||||
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
onOpenChange(isOpen) {
|
||||
if (isOpen) {
|
||||
loadDetail();
|
||||
} else {
|
||||
detail.value = null;
|
||||
drugItems.value = [];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const statistics = computed(() => {
|
||||
const s = detail.value?.summary;
|
||||
if (!s) return null;
|
||||
return [
|
||||
{ title: '开票合计', value: s.invoice_total, icon: 'mdi:cash-multiple', color: 'text-blue-600', unit: '¥' },
|
||||
{ title: '中药供货价', value: s.herbal_supply_price, icon: 'mdi:leaf', color: 'text-green-600', unit: '¥' },
|
||||
{ title: '颗粒药供货价', value: s.granule_supply_price, icon: 'mdi:pill', color: 'text-teal-600', unit: '¥' },
|
||||
{ title: '西药供货价', value: s.medicine_supply_price, icon: 'mdi:pharmacy', color: 'text-purple-600', unit: '¥' },
|
||||
{ title: '中药代煎费', value: s.herbal_decoct_fee, icon: 'mdi:fire', color: 'text-orange-600', unit: '¥' },
|
||||
{ title: '中药运费', value: s.herbal_express_fee, icon: 'skill-icons:expressjs-dark', color: 'text-orange-500', unit: '¥' },
|
||||
{ title: '西药运费', value: s.medicine_express_fee, icon: 'mdi:truck', color: 'text-indigo-600', unit: '¥' },
|
||||
{ title: '颗粒药运费', value: s.granule_express_fee, icon: 'mdi:truck-outline', color: 'text-cyan-600', unit: '¥' },
|
||||
];
|
||||
});
|
||||
|
||||
const storeColumns = [
|
||||
{ title: '诊所名称', dataIndex: 'store_name', key: 'store_name', width: 160, ellipsis: true },
|
||||
{ title: '中药供货价', dataIndex: 'herbal_supply_price', key: 'herbal_supply_price', width: 100 },
|
||||
{ title: '颗粒药供货价', dataIndex: 'granule_supply_price', key: 'granule_supply_price', width: 110 },
|
||||
{ title: '西药供货价', dataIndex: 'medicine_supply_price', key: 'medicine_supply_price', width: 100 },
|
||||
{ title: '中药代煎费', dataIndex: 'herbal_decoct_fee', key: 'herbal_decoct_fee', width: 100 },
|
||||
{ title: '中药运费', dataIndex: 'herbal_express_fee', key: 'herbal_express_fee', width: 90 },
|
||||
{ title: '西药运费', dataIndex: 'medicine_express_fee', key: 'medicine_express_fee', width: 90 },
|
||||
{ title: '颗粒药运费', dataIndex: 'granule_express_fee', key: 'granule_express_fee', width: 100 },
|
||||
{ title: '开票合计', dataIndex: 'invoice_total', key: 'invoice_total', width: 100 },
|
||||
{ title: '操作', key: 'action', width: 100, fixed: 'right' as const },
|
||||
];
|
||||
|
||||
const drugColumns = [
|
||||
{ title: '药名', dataIndex: 'drug_name', key: 'drug_name', width: 160, ellipsis: true },
|
||||
{ title: '药品编号', dataIndex: 'drug_number', key: 'drug_number', width: 120 },
|
||||
{ title: '类型', dataIndex: 'type_txt', key: 'type_txt', width: 100 },
|
||||
{ title: '数量', dataIndex: 'number', key: 'number', width: 80 },
|
||||
{ title: '供货单价', dataIndex: 'market_price', key: 'market_price', width: 100 },
|
||||
{ title: '供货总额', dataIndex: 'total_supply_price', key: 'total_supply_price', width: 110 },
|
||||
];
|
||||
|
||||
/** 加载报表详情 */
|
||||
async function loadDetail() {
|
||||
const data = drawerApi.getData<{ id: number }>();
|
||||
if (!data?.id) return;
|
||||
reportId.value = data.id;
|
||||
loading.value = true;
|
||||
try {
|
||||
detail.value = await getDepartmentFinanceReportDetailApi(data.id);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 查看诊所药品供货明细 */
|
||||
async function showStoreDrugs(storeId: number, name: string) {
|
||||
drugStoreName.value = name;
|
||||
drugLoading.value = true;
|
||||
try {
|
||||
const res = await getDepartmentFinanceStoreDrugDetailApi({
|
||||
store_id: storeId,
|
||||
report_id: reportId.value,
|
||||
});
|
||||
drugItems.value = res.items ?? [];
|
||||
} finally {
|
||||
drugLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 导出当前报表 */
|
||||
async function handleExport() {
|
||||
if (!reportId.value) return;
|
||||
await exportDepartmentFinanceReportApi({ id: reportId.value, include_drugs: 1 });
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open(id: number) {
|
||||
drawerApi.setData({ id });
|
||||
drawerApi.open();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Drawer
|
||||
:title="detail ? `报表详情 - ${detail.report_no}` : '报表详情'"
|
||||
class="w-[1100px]"
|
||||
>
|
||||
<div v-if="detail" class="mb-3 text-sm text-muted-foreground">
|
||||
统计区间:{{ detail.period_start }} ~ {{ detail.period_end }}
|
||||
<span class="ml-3">生成时间:{{ detail.generated_at }}</span>
|
||||
</div>
|
||||
<StatisticsReconciliation
|
||||
v-if="statistics"
|
||||
:statistics="statistics"
|
||||
/>
|
||||
<div class="mb-3 mt-4 flex justify-end">
|
||||
<Button type="primary" @click="handleExport">导出 Excel</Button>
|
||||
</div>
|
||||
<Table
|
||||
:columns="storeColumns"
|
||||
:data-source="detail?.stores ?? []"
|
||||
:loading="loading"
|
||||
:pagination="{ pageSize: 10 }"
|
||||
row-key="id"
|
||||
size="small"
|
||||
bordered
|
||||
:scroll="{ x: 1200 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<Button type="link" size="small" @click="showStoreDrugs(record.store_id, record.store_name)">
|
||||
药品明细
|
||||
</Button>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
<div v-if="drugStoreName" class="mt-4">
|
||||
<div class="mb-2 font-medium">{{ drugStoreName }} - 药品供货明细</div>
|
||||
<Table
|
||||
:columns="drugColumns"
|
||||
:data-source="drugItems"
|
||||
:loading="drugLoading"
|
||||
:pagination="{ pageSize: 8 }"
|
||||
row-key="drug_id"
|
||||
size="small"
|
||||
bordered
|
||||
:scroll="{ x: 700 }"
|
||||
/>
|
||||
</div>
|
||||
</Drawer>
|
||||
</template>
|
||||
138
apps/web-antd/src/views/finance/department-finance/index.vue
Normal file
138
apps/web-antd/src/views/finance/department-finance/index.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { Button, Table } from 'ant-design-vue';
|
||||
|
||||
import { TableAction } from '#/components/table-action';
|
||||
import {
|
||||
exportDepartmentFinanceReportApi,
|
||||
getDepartmentFinanceReportListApi,
|
||||
} from '#/views/finance/department-finance/api';
|
||||
|
||||
import ConfigModal from './components/ConfigModal.vue';
|
||||
import GenerateReportModal from './components/GenerateReportModal.vue';
|
||||
import ReportDetailDrawer from './components/ReportDetailDrawer.vue';
|
||||
|
||||
const loading = ref(false);
|
||||
const list = ref<any[]>([]);
|
||||
const pagination = ref({ current: 1, pageSize: 20, total: 0 });
|
||||
|
||||
const reportDetailDrawerRef = ref<InstanceType<typeof ReportDetailDrawer>>();
|
||||
const configModalRef = ref<InstanceType<typeof ConfigModal>>();
|
||||
const generateReportModalRef = ref<InstanceType<typeof GenerateReportModal>>();
|
||||
|
||||
const columns = [
|
||||
{ title: '报表编号', dataIndex: 'report_no', key: 'report_no', width: 160 },
|
||||
{ title: '统计开始', dataIndex: 'period_start', key: 'period_start', width: 170 },
|
||||
{ title: '统计结束', dataIndex: 'period_end', key: 'period_end', width: 170 },
|
||||
{ title: '生成时间', dataIndex: 'generated_at', key: 'generated_at', width: 170 },
|
||||
{ title: '开票合计', dataIndex: 'invoice_total', key: 'invoice_total', width: 120 },
|
||||
{ title: '操作', key: 'action', width: 180, fixed: 'right' as const },
|
||||
];
|
||||
|
||||
/** 拉取报表列表 */
|
||||
async function fetchList(page = pagination.value.current) {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getDepartmentFinanceReportListApi({
|
||||
page,
|
||||
pageSize: pagination.value.pageSize,
|
||||
});
|
||||
const data = res.list ?? res;
|
||||
list.value = data?.items ?? [];
|
||||
pagination.value = {
|
||||
current: data?.page ?? page,
|
||||
pageSize: data?.size ?? pagination.value.pageSize,
|
||||
total: data?.total ?? 0,
|
||||
};
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开手动生成弹窗 */
|
||||
function handleGenerate() {
|
||||
generateReportModalRef.value?.open();
|
||||
}
|
||||
|
||||
/** 生成成功后刷新列表 */
|
||||
function onGenerateSuccess() {
|
||||
fetchList(1);
|
||||
}
|
||||
|
||||
/** 查看报表详情 */
|
||||
function openDetail(id: number) {
|
||||
reportDetailDrawerRef.value?.open(id);
|
||||
}
|
||||
|
||||
/** 导出报表 */
|
||||
async function handleExport(id: number) {
|
||||
await exportDepartmentFinanceReportApi({ id, include_drugs: 1 });
|
||||
}
|
||||
|
||||
function openConfig() {
|
||||
configModalRef.value?.open();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height title="部门财务">
|
||||
<ReportDetailDrawer ref="reportDetailDrawerRef" />
|
||||
<ConfigModal ref="configModalRef" />
|
||||
<GenerateReportModal ref="generateReportModalRef" @success="onGenerateSuccess" />
|
||||
<div class="mb-3 flex flex-wrap gap-2">
|
||||
<Button type="primary" @click="handleGenerate">
|
||||
手动生成报表
|
||||
</Button>
|
||||
<Button @click="openConfig">生成配置</Button>
|
||||
<Button @click="fetchList()">刷新</Button>
|
||||
</div>
|
||||
<Table
|
||||
:columns="columns"
|
||||
:data-source="list"
|
||||
:loading="loading"
|
||||
:pagination="{
|
||||
current: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
total: pagination.total,
|
||||
showSizeChanger: true,
|
||||
onChange: (p: number, ps: number) => {
|
||||
pagination.pageSize = ps;
|
||||
fetchList(p);
|
||||
},
|
||||
}"
|
||||
row-key="id"
|
||||
bordered
|
||||
size="small"
|
||||
:scroll="{ x: 1100 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '详情',
|
||||
type: 'link',
|
||||
size: 'small',
|
||||
onClick: () => openDetail(record.id),
|
||||
},
|
||||
{
|
||||
label: '导出',
|
||||
type: 'link',
|
||||
size: 'small',
|
||||
onClick: () => handleExport(record.id),
|
||||
},
|
||||
]"
|
||||
:drop-down-actions="[]"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
</Page>
|
||||
</template>
|
||||
@@ -0,0 +1,39 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const prefix = 'reconciliation/';
|
||||
|
||||
function formatSearchTimeParam(st: unknown): [string, string] | undefined {
|
||||
if (!Array.isArray(st) || st.length < 2) return undefined;
|
||||
const fmt = (v: unknown) => {
|
||||
if (v && typeof v === 'object' && 'format' in v && typeof (v as { format: (s: string) => string }).format === 'function') {
|
||||
return (v as dayjs.Dayjs).format('YYYY-MM-DD');
|
||||
}
|
||||
const d = dayjs(v as string | number);
|
||||
return d.isValid() ? d.format('YYYY-MM-DD') : '';
|
||||
};
|
||||
const a = fmt(st[0]);
|
||||
const b = fmt(st[1]);
|
||||
if (!a || !b) return undefined;
|
||||
return [a, b];
|
||||
}
|
||||
|
||||
/**
|
||||
* 诊所开票价格(部门财务口径)
|
||||
*/
|
||||
export async function getInvoicePriceApi(params: {
|
||||
store_id: number;
|
||||
search_time: unknown;
|
||||
}) {
|
||||
const search_time = formatSearchTimeParam(params.search_time);
|
||||
if (!search_time) {
|
||||
return Promise.reject(new Error('时间范围无效'));
|
||||
}
|
||||
return requestClient.get<any>(`${prefix}invoice-price`, {
|
||||
params: {
|
||||
store_id: params.store_id,
|
||||
search_time,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenDrawer } from '@vben/common-ui';
|
||||
|
||||
import { Table, Tag } from 'ant-design-vue';
|
||||
|
||||
import StatisticsReconciliation from '#/views/finance/reconciliation/components/statistics.vue';
|
||||
import { getInvoicePriceApi } from '#/views/finance/reconciliation/api/invoice-price';
|
||||
|
||||
const loading = ref(false);
|
||||
const storeId = ref(0);
|
||||
const storeName = ref('');
|
||||
const searchTime = ref<[string, string]>(['', '']);
|
||||
const metrics = ref<Record<string, number> | null>(null);
|
||||
const drugItems = ref<any[]>([]);
|
||||
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
onOpenChange(isOpen) {
|
||||
if (isOpen) {
|
||||
fetchData();
|
||||
} else {
|
||||
metrics.value = null;
|
||||
drugItems.value = [];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const statistics = computed(() => {
|
||||
if (!metrics.value) return null;
|
||||
const m = metrics.value;
|
||||
return [
|
||||
{ title: '开票合计', value: m.invoice_total, icon: 'mdi:cash-multiple', color: 'text-blue-600', unit: '¥' },
|
||||
{ title: '中药供货价', value: m.herbal_supply_price, icon: 'mdi:leaf', color: 'text-green-600', unit: '¥' },
|
||||
{ title: '颗粒药供货价', value: m.granule_supply_price, icon: 'mdi:pill', color: 'text-teal-600', unit: '¥' },
|
||||
{ title: '西药供货价', value: m.medicine_supply_price, icon: 'mdi:pharmacy', color: 'text-purple-600', unit: '¥' },
|
||||
{ title: '中药代煎费', value: m.herbal_decoct_fee, icon: 'mdi:fire', color: 'text-orange-600', unit: '¥' },
|
||||
{ title: '中药运费', value: m.herbal_express_fee, icon: 'skill-icons:expressjs-dark', color: 'text-orange-500', unit: '¥' },
|
||||
{ title: '西药运费', value: m.medicine_express_fee, icon: 'mdi:truck', color: 'text-indigo-600', unit: '¥' },
|
||||
{ title: '颗粒药运费', value: m.granule_express_fee, icon: 'mdi:truck-outline', color: 'text-cyan-600', unit: '¥' },
|
||||
];
|
||||
});
|
||||
|
||||
const drugColumns = [
|
||||
{ title: '药名', dataIndex: 'drug_name', key: 'drug_name', width: 160, ellipsis: true },
|
||||
{ title: '药品编号', dataIndex: 'drug_number', key: 'drug_number', width: 120 },
|
||||
{ title: '类型', dataIndex: 'type_txt', key: 'type_txt', width: 100 },
|
||||
{ title: '数量', dataIndex: 'number', key: 'number', width: 80 },
|
||||
{ title: '供货单价', dataIndex: 'market_price', key: 'market_price', width: 100 },
|
||||
{ title: '供货总额', dataIndex: 'total_supply_price', key: 'total_supply_price', width: 110 },
|
||||
];
|
||||
|
||||
/** 拉取开票价格与药品供货明细 */
|
||||
async function fetchData() {
|
||||
const data = drawerApi.getData<{
|
||||
store_id: number;
|
||||
store_name: string;
|
||||
search_time: [string, string];
|
||||
}>();
|
||||
if (!data?.store_id || !data.search_time?.[0]) {
|
||||
return;
|
||||
}
|
||||
storeId.value = data.store_id;
|
||||
storeName.value = data.store_name;
|
||||
searchTime.value = data.search_time;
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getInvoicePriceApi({
|
||||
store_id: data.store_id,
|
||||
search_time: data.search_time,
|
||||
});
|
||||
metrics.value = res.metrics ?? null;
|
||||
drugItems.value = res.drug_items ?? [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open(payload: { store_id: number; store_name: string; search_time: [string, string] }) {
|
||||
drawerApi.setData(payload);
|
||||
drawerApi.open();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Drawer
|
||||
:title="`开票价格 - ${storeName}`"
|
||||
class="w-[900px]"
|
||||
>
|
||||
<div class="mb-2 text-sm text-muted-foreground">
|
||||
统计区间:{{ searchTime[0] }} ~ {{ searchTime[1] }}
|
||||
<Tag color="blue" class="ml-2">诊所 ID: {{ storeId }}</Tag>
|
||||
</div>
|
||||
<StatisticsReconciliation
|
||||
v-if="statistics"
|
||||
:statistics="statistics"
|
||||
/>
|
||||
<div class="mt-4 mb-2 font-medium">药品供货明细</div>
|
||||
<Table
|
||||
:columns="drugColumns"
|
||||
:data-source="drugItems"
|
||||
:loading="loading"
|
||||
:pagination="{ pageSize: 10 }"
|
||||
row-key="drug_id"
|
||||
size="small"
|
||||
bordered
|
||||
:scroll="{ x: 700 }"
|
||||
/>
|
||||
</Drawer>
|
||||
</template>
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
} from '#/views/finance/reconciliation/api';
|
||||
|
||||
import ReconciliationExportModal from './components/ReconciliationExportModal.vue';
|
||||
import InvoicePriceDrawer from './components/InvoicePriceDrawer.vue';
|
||||
import StatisticsReconciliation from './components/statistics.vue';
|
||||
import {
|
||||
loadExcludeZeroSales,
|
||||
@@ -77,6 +78,13 @@ const [ExportModal, exportModalApi] = useVbenModal({
|
||||
connectedComponent: ReconciliationExportModal,
|
||||
});
|
||||
|
||||
const invoicePriceDrawerRef = ref<InstanceType<typeof InvoicePriceDrawer>>();
|
||||
|
||||
/** 平台财务等可查看开票价格 */
|
||||
const canViewInvoicePrice = computed(
|
||||
() => Number(userInfo.value?.roles?.user_type) === 2,
|
||||
);
|
||||
|
||||
const activeTab = ref('store');
|
||||
|
||||
/** 整页共用时间 */
|
||||
@@ -268,6 +276,15 @@ function goDrug(storeId: number, name: string) {
|
||||
activeTab.value = 'drug';
|
||||
}
|
||||
|
||||
/** 打开诊所开票价格抽屉 */
|
||||
function openInvoicePrice(storeId: number, name: string) {
|
||||
invoicePriceDrawerRef.value?.open({
|
||||
store_id: storeId,
|
||||
store_name: name,
|
||||
search_time: searchTimeParam(),
|
||||
});
|
||||
}
|
||||
|
||||
function clearDrugStoreFilter() {
|
||||
currentStoreId.value = null;
|
||||
currentStoreName.value = '';
|
||||
@@ -306,7 +323,7 @@ const storeColumns = [
|
||||
{ title: '总销售总价', key: 'total_sales_price', width: 120 },
|
||||
{ title: '总供货总价', key: 'total_supply_price', width: 120 },
|
||||
{ title: '挂号费用', key: 'registration_price', width: 100 },
|
||||
{ title: '操作', key: 'action', width: 100, fixed: 'right' as const },
|
||||
{ title: '操作', key: 'action', width: 160, fixed: 'right' as const },
|
||||
];
|
||||
|
||||
const drugColumnsAll = [
|
||||
@@ -355,6 +372,7 @@ watch(byOrder, () => {
|
||||
title="对账单"
|
||||
>
|
||||
<ExportModal />
|
||||
<InvoicePriceDrawer ref="invoicePriceDrawerRef" />
|
||||
<StatisticsReconciliation
|
||||
v-if="statistics"
|
||||
:statistics="statistics"
|
||||
@@ -460,6 +478,15 @@ watch(byOrder, () => {
|
||||
size: 'small',
|
||||
onClick: () => goDrug(record.id, record.name),
|
||||
},
|
||||
...(canViewInvoicePrice
|
||||
? [{
|
||||
label: '开票价格',
|
||||
type: 'link',
|
||||
icon: 'mdi:receipt-text',
|
||||
size: 'small',
|
||||
onClick: () => openInvoicePrice(record.id, record.name),
|
||||
}]
|
||||
: []),
|
||||
]"
|
||||
:drop-down-actions="[]"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user