<template>
|
<div class="app-container">
|
<el-form :model="filters" :inline="true">
|
<el-form-item label="发票号码:">
|
<el-input v-model="filters.invoiceNumber" placeholder="请输入发票号码" clearable style="width: 200px;" />
|
</el-form-item>
|
<el-form-item label="供应商:">
|
<el-select v-model="filters.supplierId" placeholder="请选择供应商" clearable filterable style="width: 200px;">
|
<el-option
|
v-for="item in supplierList"
|
:key="item.id"
|
:label="item.supplierName"
|
:value="item.id"
|
/>
|
</el-select>
|
</el-form-item>
|
<el-form-item label="开票日期:">
|
<el-date-picker
|
v-model="filters.dateRange"
|
type="daterange"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
range-separator="至"
|
start-placeholder="开始日期"
|
end-placeholder="结束日期"
|
clearable
|
style="width: 240px;"
|
/>
|
</el-form-item>
|
<el-form-item label="状态:">
|
<el-select v-model="filters.status" placeholder="请选择状态" clearable style="width: 150px;">
|
<el-option label="正常" :value="0" />
|
<el-option label="作废" :value="1" />
|
</el-select>
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary" @click="onSearch">搜索</el-button>
|
<el-button @click="resetFilters">重置</el-button>
|
</el-form-item>
|
</el-form>
|
<div class="table_list">
|
<div class="actions">
|
<div></div>
|
<div>
|
<el-button type="primary" @click="add" icon="Plus">录入发票</el-button>
|
<el-button @click="handleExport" icon="Download">导出</el-button>
|
</div>
|
</div>
|
<PIMTable
|
rowKey="id"
|
:column="columns"
|
:tableData="dataList"
|
:tableLoading="tableLoading"
|
:page="{
|
current: pagination.currentPage,
|
size: pagination.pageSize,
|
total: pagination.total,
|
}"
|
@pagination="changePage"
|
>
|
<template #amount="{ row }">
|
<span class="text-primary">¥{{ formatMoney(row.amount) }}</span>
|
</template>
|
<template #taxAmount="{ row }">
|
<span class="text-danger">¥{{ formatMoney(row.taxAmount) }}</span>
|
</template>
|
<template #totalAmount="{ row }">
|
<span class="text-success">¥{{ formatMoney(row.totalAmount) }}</span>
|
</template>
|
<template #status="{ row }">
|
<el-tag :type="getStatusType(row.status)" effect="light" round>
|
{{ getStatusLabel(row.status) }}
|
</el-tag>
|
</template>
|
<template #operation="{ row }">
|
<el-button type="primary" link @click="view(row)">查看</el-button>
|
<el-button type="warning" link @click="handleCancel(row)" v-if="isNormalStatus(row.status)">作废</el-button>
|
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
</template>
|
</PIMTable>
|
</div>
|
|
<FormDialog
|
:title="dialogTitle"
|
v-model="dialogVisible"
|
width="800px"
|
:operation-type="isView ? 'detail' : ''"
|
@confirm="submitForm"
|
@cancel="closeDialog"
|
>
|
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
|
<el-row v-if="isView" :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="状态">
|
<el-tag :type="getStatusType(form.status)" effect="light" round>
|
{{ getStatusLabel(form.status) }}
|
</el-tag>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="发票号码" prop="invoiceNo">
|
<el-input v-model="form.invoiceNo" placeholder="请输入发票号码" :disabled="isView" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="供应商" prop="supplierId">
|
<el-select
|
v-model="form.supplierId"
|
placeholder="请选择供应商"
|
style="width: 100%;"
|
filterable
|
:disabled="isView"
|
@change="handleSupplierChange"
|
>
|
<el-option
|
v-for="item in supplierList"
|
:key="item.id"
|
:label="item.supplierName"
|
:value="item.id"
|
/>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="关联入库单" prop="stockInRecordIds">
|
<el-input
|
:model-value="inboundBatchDisplayText"
|
placeholder="请先选择供应商"
|
readonly
|
:disabled="!form.supplierId || isView"
|
class="inbound-batch-input"
|
@click="handleInboundInputClick"
|
>
|
<template v-if="!isView" #append>
|
<el-button
|
:disabled="!form.supplierId"
|
:loading="inboundBatchLoading"
|
@click.stop="openInboundSelectDialog"
|
>
|
选择
|
</el-button>
|
</template>
|
</el-input>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="开票日期" prop="invoiceDate">
|
<el-date-picker
|
v-model="form.invoiceDate"
|
type="date"
|
placeholder="选择日期"
|
value-format="YYYY-MM-DD"
|
style="width: 100%;"
|
:disabled="isView"
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="发票类型" prop="invoiceType">
|
<el-select
|
v-model="form.invoiceType"
|
placeholder="请选择发票类型"
|
style="width: 100%;"
|
:disabled="isView"
|
>
|
<el-option label="增值税专用发票" value="增值税专用发票" />
|
<el-option label="增值税普通发票" value="增值税普通发票" />
|
<el-option label="电子发票" value="电子发票" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="税率" prop="taxRate">
|
<el-select
|
v-model="form.taxRate"
|
placeholder="请选择税率"
|
style="width: 100%;"
|
:disabled="isView"
|
@change="handleTaxRateChange"
|
>
|
<el-option
|
v-for="dict in tax_rate"
|
:key="dict.value"
|
:label="dict.label"
|
:value="Number(dict.value)"
|
/>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<el-form-item label="金额(不含税)" prop="amount">
|
<el-input-number
|
v-model="form.amount"
|
:min="0"
|
:precision="2"
|
style="width: 100%;"
|
:disabled="isView"
|
placeholder="根据入库单含税金额自动换算,可修改"
|
@change="calculateTaxFromExclusive"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="税额">
|
<el-input-number
|
v-model="form.taxAmount"
|
:min="0"
|
:precision="2"
|
:controls="false"
|
style="width: 100%;"
|
disabled
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="价税合计">
|
<el-input-number
|
v-model="form.totalAmount"
|
:min="0"
|
:precision="2"
|
:controls="false"
|
style="width: 100%;"
|
disabled
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-form-item label="发票内容" prop="content">
|
<el-input v-model="form.content" type="textarea" :rows="3" placeholder="请输入发票内容" :disabled="isView" />
|
</el-form-item>
|
<el-form-item label="备注" prop="remark">
|
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" :disabled="isView" />
|
</el-form-item>
|
</el-form>
|
<template v-if="!isView" #footer>
|
<el-button type="primary" :loading="submitLoading" @click="submitForm">确定</el-button>
|
<el-button @click="closeDialog">取消</el-button>
|
</template>
|
</FormDialog>
|
|
<el-dialog
|
v-model="inboundSelectVisible"
|
title="选择入库单号"
|
width="1100px"
|
append-to-body
|
destroy-on-close
|
:close-on-click-modal="false"
|
@closed="handleInboundDialogClosed"
|
>
|
<el-table
|
ref="inboundTableRef"
|
v-loading="inboundBatchLoading"
|
:data="inboundBatchList"
|
row-key="id"
|
border
|
stripe
|
max-height="480"
|
@selection-change="handleInboundDialogSelectionChange"
|
>
|
<el-table-column type="selection" width="55" align="center" />
|
<el-table-column prop="inboundBatches" label="入库单号" min-width="140" show-overflow-tooltip />
|
<el-table-column prop="supplierName" label="供应商" min-width="120" show-overflow-tooltip />
|
<el-table-column prop="productName" label="产品名称" min-width="120" show-overflow-tooltip />
|
<el-table-column prop="specificationModel" label="规格型号" min-width="140" show-overflow-tooltip />
|
<el-table-column prop="purchaseContractNumber" label="采购订单号" min-width="140" show-overflow-tooltip />
|
<el-table-column prop="inboundDate" label="入库日期" width="110" align="center" />
|
<el-table-column prop="inboundAmount" label="入库金额(含税)" width="120" align="right">
|
<template #default="{ row }">¥{{ formatMoney(getInboundRowTaxInclusiveAmount(row)) }}</template>
|
</el-table-column>
|
</el-table>
|
<template #footer>
|
<el-button type="primary" @click="confirmInboundSelection">确定</el-button>
|
<el-button @click="inboundSelectVisible = false">取消</el-button>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, computed, onMounted, nextTick, getCurrentInstance } from "vue";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
import FormDialog from "@/components/Dialog/FormDialog.vue";
|
import { getOptions } from "@/api/procurementManagement/procurementLedger.js";
|
import {
|
getInboundBatchesBySupplier,
|
addAccountPurchaseInvoice,
|
listPageAccountPurchaseInvoice,
|
cancelAccountPurchaseInvoice,
|
deleteAccountPurchaseInvoice,
|
} from "@/api/financialManagement/accountPurchaseInvoice.js";
|
|
defineOptions({
|
name: "进项发票",
|
});
|
|
const { proxy } = getCurrentInstance();
|
const { tax_rate } = proxy.useDict("tax_rate");
|
|
const filters = reactive({
|
invoiceNumber: "",
|
supplierId: "",
|
dateRange: [],
|
status: "",
|
});
|
|
const pagination = reactive({
|
currentPage: 1,
|
pageSize: 10,
|
total: 0,
|
});
|
|
const columns = [
|
{ label: "发票号码", prop: "invoiceNo", width: "140" },
|
{ label: "供应商", prop: "supplierName", width: "180" },
|
{ label: "开票日期", prop: "invoiceDate", width: "120" },
|
{ label: "金额", prop: "amount", dataType: "slot", slot: "amount" },
|
{ label: "税额", prop: "taxAmount", dataType: "slot", slot: "taxAmount" },
|
{ label: "价税合计", prop: "totalAmount", dataType: "slot", slot: "totalAmount" },
|
{ label: "发票类型", prop: "invoiceType", width: "130" },
|
{ label: "状态", prop: "status", dataType: "slot", slot: "status", width: "90", align: "center" },
|
{ label: "操作", prop: "operation", dataType: "slot", slot: "operation", width: "200", fixed: "right" },
|
];
|
|
const dataList = ref([]);
|
const tableLoading = ref(false);
|
const dialogVisible = ref(false);
|
const dialogTitle = ref("");
|
const formRef = ref(null);
|
const isView = ref(false);
|
const submitLoading = ref(false);
|
const supplierList = ref([]);
|
|
const inboundBatchList = ref([]);
|
const inboundBatchOptions = ref([]);
|
const inboundBatchLoading = ref(false);
|
const inboundSelectVisible = ref(false);
|
const inboundTableRef = ref(null);
|
const dialogInboundSelection = ref([]);
|
|
const STATUS_LABEL_MAP = { 0: "正常", 1: "作废" };
|
const STATUS_TYPE_MAP = { 0: "success", 1: "info" };
|
|
const form = reactive({
|
invoiceNo: "",
|
supplierId: "",
|
invoiceDate: "",
|
invoiceType: "增值税专用发票",
|
taxRate: 13,
|
amount: 0,
|
taxAmount: 0,
|
totalAmount: 0,
|
content: "",
|
remark: "",
|
stockInRecordIds: [],
|
inboundBatches: "",
|
storageAttachmentId: undefined,
|
status: 0,
|
});
|
|
const rules = {
|
invoiceNo: [{ required: true, message: "请输入发票号码", trigger: "blur" }],
|
supplierId: [{ required: true, message: "请选择供应商", trigger: "change" }],
|
stockInRecordIds: [{ required: true, type: "array", min: 1, message: "请选择关联入库单", trigger: "change" }],
|
invoiceDate: [{ required: true, message: "请选择开票日期", trigger: "change" }],
|
invoiceType: [{ required: true, message: "请选择发票类型", trigger: "change" }],
|
taxRate: [{ required: true, message: "请选择税率", trigger: "change" }],
|
amount: [{ required: true, message: "请输入金额", trigger: "blur" }],
|
};
|
|
const formatMoney = (value) => {
|
if (value === undefined || value === null) return "0.00";
|
return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
};
|
|
const normalizeStatus = (status) => {
|
if (status === undefined || status === null || status === "") return 0;
|
const num = Number(status);
|
return Number.isNaN(num) ? 0 : num;
|
};
|
|
const isNormalStatus = (status) => normalizeStatus(status) === 0;
|
|
const getStatusLabel = (status) => STATUS_LABEL_MAP[normalizeStatus(status)] ?? "正常";
|
|
const getStatusType = (status) => STATUS_TYPE_MAP[normalizeStatus(status)] ?? "success";
|
|
const parseStockInRecordIds = (value) => {
|
if (!value) return [];
|
if (Array.isArray(value)) return value;
|
return String(value)
|
.split(/[,,]/)
|
.map((s) => s.trim())
|
.filter(Boolean)
|
.map((s) => (/^\d+$/.test(s) ? Number(s) : s));
|
};
|
|
const formatInboundBatches = (value) => {
|
if (value === undefined || value === null || value === "") return "";
|
if (Array.isArray(value)) return value.filter(Boolean).join("、");
|
return String(value)
|
.split(/[,,]/)
|
.map((s) => s.trim())
|
.filter(Boolean)
|
.join("、");
|
};
|
|
const isSameInboundId = (a, b) => String(a) === String(b);
|
|
const getInboundRowId = (row) => row?.id ?? row?.stockInRecordId;
|
|
/** 入库单金额为含税价 */
|
const getInboundRowTaxInclusiveAmount = (row) =>
|
Number(row?.inboundAmount ?? row?.taxInclusivePrice ?? row?.totalAmount ?? 0);
|
|
const normalizeInboundBatchOptions = (data) => {
|
const list = Array.isArray(data) ? data : [];
|
return list.map((item, index) => {
|
if (typeof item === "string" || typeof item === "number") {
|
const text = String(item);
|
return { label: text, value: text, inboundAmount: 0 };
|
}
|
const label =
|
item.inboundBatches ?? item.batchNo ?? item.inboundNo ?? item.label ?? `入库单${index + 1}`;
|
const value = item.id ?? item.stockInRecordId ?? label;
|
return {
|
label: String(label),
|
value,
|
inboundAmount: getInboundRowTaxInclusiveAmount(item),
|
};
|
});
|
};
|
|
/** 不含税金额变更:税额、价税合计正向计算 */
|
const calculateTaxFromExclusive = () => {
|
form.taxAmount = Number((form.amount * form.taxRate / 100).toFixed(2));
|
form.totalAmount = Number((form.amount + form.taxAmount).toFixed(2));
|
};
|
|
/** 价税合计变更:按税率反算不含税金额、税额 */
|
const calculateTaxFromInclusive = (inclusiveTotal) => {
|
const total = Number(inclusiveTotal ?? form.totalAmount ?? 0);
|
if (total <= 0) {
|
form.amount = 0;
|
form.taxAmount = 0;
|
form.totalAmount = 0;
|
return;
|
}
|
const rate = Number(form.taxRate) / 100;
|
form.totalAmount = Number(total.toFixed(2));
|
form.amount = Number((form.totalAmount / (1 + rate)).toFixed(2));
|
form.taxAmount = Number((form.totalAmount - form.amount).toFixed(2));
|
};
|
|
const handleTaxRateChange = () => {
|
if (form.totalAmount > 0) {
|
calculateTaxFromInclusive(form.totalAmount);
|
} else {
|
calculateTaxFromExclusive();
|
}
|
};
|
|
/** 根据已选入库单汇总含税金额,反算不含税金额与税额 */
|
const syncInvoiceAmount = () => {
|
const selected = form.stockInRecordIds || [];
|
const sumFromOptions = inboundBatchOptions.value
|
.filter((opt) => selected.some((id) => isSameInboundId(id, opt.value)))
|
.reduce((acc, opt) => acc + (Number(opt.inboundAmount) || 0), 0);
|
|
let taxInclusiveSum = sumFromOptions;
|
if (taxInclusiveSum <= 0 && selected.length) {
|
taxInclusiveSum = inboundBatchList.value
|
.filter((row) => selected.some((id) => isSameInboundId(id, getInboundRowId(row))))
|
.reduce((acc, row) => acc + getInboundRowTaxInclusiveAmount(row), 0);
|
}
|
|
calculateTaxFromInclusive(taxInclusiveSum > 0 ? Number(taxInclusiveSum.toFixed(2)) : 0);
|
};
|
|
const inboundBatchDisplayText = computed(() => {
|
if (form.inboundBatches) return form.inboundBatches;
|
const ids = form.stockInRecordIds || [];
|
if (!ids.length) return "";
|
const labels = inboundBatchOptions.value
|
.filter((opt) => ids.some((id) => isSameInboundId(id, opt.value)))
|
.map((opt) => opt.label);
|
if (labels.length) return labels.join("、");
|
return ids.join("、");
|
});
|
|
const normalizeTableRow = (row) => ({
|
...row,
|
invoiceNo: row.invoiceNumber ?? row.invoiceNo,
|
invoiceDate: row.issueDate ?? row.invoiceDate,
|
amount: row.taxExclusivelPrice ?? row.amount,
|
taxAmount: row.taxPrice ?? row.taxAmount,
|
totalAmount: row.taxInclusivePrice ?? row.totalAmount,
|
content: row.invoiceContent ?? row.content,
|
status: normalizeStatus(row.status),
|
stockInRecordIds: row.stockInRecordIds ?? "",
|
inboundBatches: formatInboundBatches(row.inboundBatches),
|
});
|
|
const toFormNumber = (val) => {
|
const n = Number(val);
|
return Number.isFinite(n) ? n : 0;
|
};
|
|
const resolveFormAmounts = (row) => {
|
let amount = toFormNumber(row.taxExclusivelPrice ?? row.amount);
|
let taxAmount = toFormNumber(row.taxPrice ?? row.taxAmount);
|
let totalAmount = toFormNumber(row.taxInclusivePrice ?? row.totalAmount);
|
const taxRate = toFormNumber(row.taxRate) || 13;
|
|
if (totalAmount > 0 && amount === 0 && taxAmount === 0) {
|
amount = Number((totalAmount / (1 + taxRate / 100)).toFixed(2));
|
taxAmount = Number((totalAmount - amount).toFixed(2));
|
} else if (totalAmount > 0 && amount > 0 && taxAmount === 0) {
|
taxAmount = Number((totalAmount - amount).toFixed(2));
|
} else if (amount > 0 && taxAmount === 0 && totalAmount === 0) {
|
taxAmount = Number((amount * taxRate / 100).toFixed(2));
|
totalAmount = Number((amount + taxAmount).toFixed(2));
|
} else if (amount > 0 && taxAmount > 0 && totalAmount === 0) {
|
totalAmount = Number((amount + taxAmount).toFixed(2));
|
}
|
|
return { amount, taxAmount, totalAmount };
|
};
|
|
const fillFormFromRow = (row) => {
|
const stockInRecordIds = parseStockInRecordIds(row.stockInRecordIds);
|
const { amount, taxAmount, totalAmount } = resolveFormAmounts(row);
|
Object.assign(form, {
|
invoiceNo: row.invoiceNo ?? row.invoiceNumber ?? "",
|
supplierId: row.supplierId,
|
invoiceDate: row.invoiceDate ?? row.issueDate ?? "",
|
invoiceType: row.invoiceType ?? "增值税专用发票",
|
taxRate: row.taxRate ?? 13,
|
amount,
|
taxAmount,
|
totalAmount,
|
content: row.content ?? row.invoiceContent ?? "",
|
remark: row.remark ?? "",
|
stockInRecordIds,
|
inboundBatches: formatInboundBatches(row.inboundBatches),
|
storageAttachmentId: row.storageAttachmentId,
|
status: normalizeStatus(row.status),
|
});
|
};
|
|
const buildCancelPayload = (row) => ({
|
id: row.id,
|
invoiceNumber: row.invoiceNumber ?? row.invoiceNo,
|
taxRate: row.taxRate,
|
invoiceType: row.invoiceType,
|
issueDate: row.issueDate ?? row.invoiceDate,
|
taxExclusivelPrice: row.taxExclusivelPrice ?? row.amount,
|
taxPrice: row.taxPrice ?? row.taxAmount,
|
taxInclusivePrice: row.taxInclusivePrice ?? row.totalAmount,
|
remark: row.remark ?? "",
|
invoiceContent: row.invoiceContent ?? row.content,
|
supplierId: row.supplierId,
|
storageAttachmentId: row.storageAttachmentId,
|
stockInRecordIds: row.stockInRecordIds ?? "",
|
status: 1,
|
});
|
|
const buildSubmitPayload = () => ({
|
invoiceNumber: form.invoiceNo,
|
supplierId: form.supplierId,
|
issueDate: form.invoiceDate,
|
invoiceType: form.invoiceType,
|
taxRate: form.taxRate,
|
taxExclusivelPrice: form.amount,
|
taxPrice: form.taxAmount,
|
taxInclusivePrice: form.totalAmount,
|
invoiceContent: form.content,
|
remark: form.remark || "",
|
stockInRecordIds: (form.stockInRecordIds || []).join(","),
|
status: 0,
|
storageAttachmentId: form.storageAttachmentId,
|
});
|
|
const getSupplierList = () => {
|
getOptions().then((res) => {
|
if (res.code === 200) {
|
supplierList.value = res.data ?? [];
|
}
|
});
|
};
|
|
const appendFilterParams = (params) => {
|
if (filters.invoiceNumber) {
|
params.invoiceNumber = filters.invoiceNumber;
|
}
|
if (filters.supplierId) {
|
params.supplierId = filters.supplierId;
|
}
|
if (filters.dateRange?.length === 2) {
|
params.startDate = filters.dateRange[0];
|
params.endDate = filters.dateRange[1];
|
}
|
if (filters.status !== "" && filters.status != null) {
|
params.status = filters.status;
|
}
|
return params;
|
};
|
|
const buildListParams = () =>
|
appendFilterParams({
|
current: pagination.currentPage,
|
size: pagination.pageSize,
|
});
|
|
const buildExportParams = () => appendFilterParams({});
|
|
const handleExport = () => {
|
proxy.download(
|
"/accountPurchaseInvoice/exportAccountPurchaseInvoice",
|
buildExportParams(),
|
`进项发票_${Date.now()}.xlsx`
|
);
|
};
|
|
const getTableData = () => {
|
tableLoading.value = true;
|
listPageAccountPurchaseInvoice(buildListParams())
|
.then((res) => {
|
if (res.code === 200) {
|
const records = res.data?.records ?? [];
|
dataList.value = records.map(normalizeTableRow);
|
pagination.total = res.data?.total ?? 0;
|
} else {
|
dataList.value = [];
|
pagination.total = 0;
|
ElMessage.error(res.msg || "查询失败");
|
}
|
})
|
.catch(() => {
|
dataList.value = [];
|
pagination.total = 0;
|
ElMessage.error("查询失败");
|
})
|
.finally(() => {
|
tableLoading.value = false;
|
});
|
};
|
|
const onSearch = () => {
|
pagination.currentPage = 1;
|
getTableData();
|
};
|
|
const resetFilters = () => {
|
filters.invoiceNumber = "";
|
filters.supplierId = "";
|
filters.dateRange = [];
|
filters.status = "";
|
pagination.currentPage = 1;
|
getTableData();
|
};
|
|
const changePage = ({ page, limit }) => {
|
pagination.currentPage = page;
|
pagination.pageSize = limit;
|
getTableData();
|
};
|
|
const closeDialog = () => {
|
dialogVisible.value = false;
|
isView.value = false;
|
inboundSelectVisible.value = false;
|
};
|
|
const resetForm = () => {
|
Object.assign(form, {
|
invoiceNo: "",
|
supplierId: "",
|
invoiceDate: new Date().toISOString().split("T")[0],
|
invoiceType: "增值税专用发票",
|
taxRate: 13,
|
amount: 0,
|
taxAmount: 0,
|
totalAmount: 0,
|
content: "",
|
remark: "",
|
stockInRecordIds: [],
|
inboundBatches: "",
|
storageAttachmentId: undefined,
|
status: 0,
|
});
|
inboundBatchList.value = [];
|
inboundBatchOptions.value = [];
|
};
|
|
const add = () => {
|
isView.value = false;
|
dialogTitle.value = "录入发票";
|
resetForm();
|
dialogVisible.value = true;
|
};
|
|
const view = (row) => {
|
isView.value = true;
|
dialogTitle.value = "查看发票";
|
fillFormFromRow(row);
|
if (row.supplierId) {
|
loadInboundBatches(row.supplierId, true, false);
|
}
|
dialogVisible.value = true;
|
};
|
|
const handleCancel = (row) => {
|
ElMessageBox.confirm(`确认作废发票「${row.invoiceNo ?? row.invoiceNumber}」吗?`, "作废确认", {
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning",
|
}).then(() => {
|
cancelAccountPurchaseInvoice(buildCancelPayload(row))
|
.then((res) => {
|
if (res.code === 200) {
|
ElMessage.success("作废成功");
|
getTableData();
|
} else {
|
ElMessage.error(res.msg || "作废失败");
|
}
|
})
|
.catch(() => {
|
ElMessage.error("作废失败");
|
});
|
});
|
};
|
|
const handleDelete = (row) => {
|
ElMessageBox.confirm(`确认删除发票「${row.invoiceNo ?? row.invoiceNumber}」吗?`, "删除确认", {
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning",
|
}).then(() => {
|
deleteAccountPurchaseInvoice([row.id])
|
.then((res) => {
|
if (res.code === 200) {
|
ElMessage.success("删除成功");
|
getTableData();
|
} else {
|
ElMessage.error(res.msg || "删除失败");
|
}
|
})
|
.catch(() => {
|
ElMessage.error("删除失败");
|
});
|
});
|
};
|
|
const submitForm = () => {
|
formRef.value?.validate((valid) => {
|
if (!valid) return;
|
submitLoading.value = true;
|
addAccountPurchaseInvoice(buildSubmitPayload())
|
.then((res) => {
|
if (res.code === 200) {
|
ElMessage.success("录入成功");
|
closeDialog();
|
pagination.currentPage = 1;
|
getTableData();
|
} else {
|
ElMessage.error(res.msg || "录入失败");
|
}
|
})
|
.catch(() => {
|
ElMessage.error("录入失败");
|
})
|
.finally(() => {
|
submitLoading.value = false;
|
});
|
});
|
};
|
|
const ensureInboundOptionsForSelected = () => {
|
const ids = form.stockInRecordIds || [];
|
ids.forEach((id) => {
|
const exists = inboundBatchOptions.value.some((opt) => isSameInboundId(opt.value, id));
|
if (exists) return;
|
const fromList = inboundBatchList.value.find((row) => isSameInboundId(getInboundRowId(row), id));
|
if (fromList) {
|
const [option] = normalizeInboundBatchOptions([fromList]);
|
if (option) inboundBatchOptions.value.push(option);
|
return;
|
}
|
inboundBatchOptions.value.push({
|
label: String(id),
|
value: id,
|
inboundAmount: 0,
|
});
|
});
|
};
|
|
const restoreInboundTableSelection = () => {
|
nextTick(() => {
|
const table = inboundTableRef.value;
|
if (!table) return;
|
table.clearSelection();
|
const selectedIds = new Set((form.stockInRecordIds || []).map((id) => String(id)));
|
inboundBatchList.value.forEach((row) => {
|
const rowId = getInboundRowId(row);
|
if (rowId !== undefined && rowId !== null && selectedIds.has(String(rowId))) {
|
table.toggleRowSelection(row, true);
|
}
|
});
|
});
|
};
|
|
const loadInboundBatches = (supplierId, keepSelected = false, syncAmount = true) => {
|
if (!supplierId) {
|
inboundBatchList.value = [];
|
inboundBatchOptions.value = [];
|
if (!keepSelected) {
|
form.stockInRecordIds = [];
|
form.inboundBatches = "";
|
form.amount = 0;
|
form.taxAmount = 0;
|
form.totalAmount = 0;
|
}
|
return Promise.resolve();
|
}
|
inboundBatchLoading.value = true;
|
return getInboundBatchesBySupplier({ supplierId })
|
.then((res) => {
|
if (res.code === 200) {
|
const list = res.data?.records ?? res.data ?? [];
|
inboundBatchList.value = Array.isArray(list) ? list : [];
|
inboundBatchOptions.value = normalizeInboundBatchOptions(list);
|
} else {
|
inboundBatchList.value = [];
|
inboundBatchOptions.value = [];
|
}
|
})
|
.catch(() => {
|
inboundBatchList.value = [];
|
inboundBatchOptions.value = [];
|
})
|
.finally(() => {
|
inboundBatchLoading.value = false;
|
if (keepSelected) {
|
ensureInboundOptionsForSelected();
|
restoreInboundTableSelection();
|
if (syncAmount && !isView.value) {
|
syncInvoiceAmount();
|
}
|
}
|
});
|
};
|
|
const handleSupplierChange = (supplierId) => {
|
form.stockInRecordIds = [];
|
form.inboundBatches = "";
|
form.amount = 0;
|
form.taxAmount = 0;
|
form.totalAmount = 0;
|
loadInboundBatches(supplierId);
|
};
|
|
const handleInboundInputClick = () => {
|
if (isView.value) return;
|
openInboundSelectDialog();
|
};
|
|
const openInboundSelectDialog = () => {
|
if (!form.supplierId || isView.value) return;
|
inboundSelectVisible.value = true;
|
loadInboundBatches(form.supplierId, true).then(() => {
|
restoreInboundTableSelection();
|
});
|
};
|
|
const handleInboundDialogSelectionChange = (selection) => {
|
dialogInboundSelection.value = selection;
|
};
|
|
const confirmInboundSelection = () => {
|
if (dialogInboundSelection.value.length === 0) {
|
ElMessage.warning("请至少选择一条入库单");
|
return;
|
}
|
form.stockInRecordIds = dialogInboundSelection.value
|
.map((row) => getInboundRowId(row))
|
.filter((id) => id !== undefined && id !== null);
|
form.inboundBatches = dialogInboundSelection.value
|
.map((row) => row.inboundBatches ?? row.batchNo ?? "")
|
.filter(Boolean)
|
.join("、");
|
dialogInboundSelection.value.forEach((row) => {
|
const [option] = normalizeInboundBatchOptions([row]);
|
if (option && !inboundBatchOptions.value.some((opt) => isSameInboundId(opt.value, option.value))) {
|
inboundBatchOptions.value.push(option);
|
}
|
});
|
inboundSelectVisible.value = false;
|
syncInvoiceAmount();
|
formRef.value?.validateField("stockInRecordIds");
|
};
|
|
const handleInboundDialogClosed = () => {
|
dialogInboundSelection.value = [];
|
};
|
|
onMounted(() => {
|
getSupplierList();
|
getTableData();
|
});
|
</script>
|
|
<style lang="scss" scoped>
|
.actions {
|
display: flex;
|
justify-content: space-between;
|
margin-bottom: 15px;
|
}
|
|
.text-primary {
|
color: #409eff;
|
font-weight: bold;
|
}
|
|
.text-danger {
|
color: #f56c6c;
|
font-weight: bold;
|
}
|
|
.text-success {
|
color: #67c23a;
|
font-weight: bold;
|
}
|
|
.inbound-batch-input :deep(.el-input__wrapper) {
|
cursor: pointer;
|
}
|
</style>
|