| | |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="客户:"> |
| | | <el-select v-model="filters.customerId" placeholder="请选择客户" clearable style="width: 200px;"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | <el-select v-model="filters.customerId" placeholder="请选择客户" clearable filterable style="width: 200px;"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="对账期间:"> |
| | |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | |
| | | }" |
| | | @pagination="changePage" |
| | | > |
| | | <template #beginBalance="{ row }"> |
| | | <span :class="row.beginBalance >= 0 ? 'text-success' : 'text-danger'">¥{{ formatMoney(row.beginBalance) }}</span> |
| | | <template #openingBalance="{ row }"> |
| | | <span :class="row.openingBalance >= 0 ? 'text-success' : 'text-danger'">¥{{ formatMoney(row.openingBalance) }}</span> |
| | | </template> |
| | | <template #currentReceivable="{ row }"> |
| | | <span class="text-primary">¥{{ formatMoney(row.currentReceivable) }}</span> |
| | | <template #currentPlan="{ row }"> |
| | | <span class="text-primary">¥{{ formatMoney(row.currentPlan) }}</span> |
| | | </template> |
| | | <template #currentReceipt="{ row }"> |
| | | <span class="text-success">¥{{ formatMoney(row.currentReceipt) }}</span> |
| | | <template #currentActually="{ row }"> |
| | | <span class="text-success">¥{{ formatMoney(row.currentActually) }}</span> |
| | | </template> |
| | | <template #endBalance="{ row }"> |
| | | <span :class="row.endBalance >= 0 ? 'text-success' : 'text-danger'">¥{{ formatMoney(row.endBalance) }}</span> |
| | | <template #closingBalance="{ row }"> |
| | | <span :class="row.closingBalance >= 0 ? 'text-success' : 'text-danger'">¥{{ formatMoney(row.closingBalance) }}</span> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="viewDetail(row)">查看明细</el-button> |
| | | <el-button type="primary" link @click="printStatement(row)">打印</el-button> |
| | | <!-- <el-button type="primary" link @click="printStatement(row)">打印</el-button> --> |
| | | <el-button type="danger" link @click="handleDelete(row)">删除</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | |
| | | <h3>{{ currentCustomer }} 应收对账单</h3> |
| | | <p>对账期间: {{ currentPeriod }}</p> |
| | | </div> |
| | | <el-table :data="detailData" border style="width: 100%"> |
| | | <el-table :data="detailData" border style="width: 100%" v-loading="detailLoading"> |
| | | <el-table-column prop="date" label="日期" width="120" /> |
| | | <el-table-column prop="type" label="类型" width="100"> |
| | | <template #default="{ row }"> |
| | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="选择客户" prop="customerId"> |
| | | <el-select v-model="generateForm.customerId" placeholder="请选择客户" style="width: 100%;" @change="onCustomerChange"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | <el-select |
| | | v-model="generateForm.customerId" |
| | | placeholder="请选择客户" |
| | | style="width: 100%;" |
| | | filterable |
| | | @change="onCustomerChange" |
| | | > |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="对账月份" prop="period"> |
| | | <el-date-picker v-model="generateForm.period" type="month" placeholder="选择月份" value-format="YYYY-MM" style="width: 100%;" @change="onPeriodChange" /> |
| | | <el-form-item label="对账月份" prop="statementMonth"> |
| | | <el-date-picker v-model="generateForm.statementMonth" type="month" placeholder="选择月份" value-format="YYYY-MM" style="width: 100%;" @change="onStatementMonthChange" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <div v-if="salesData.length > 0" class="sales-section"> |
| | | <div class="section-title">本月销售数据</div> |
| | | <el-table :data="salesData" border style="width: 100%; margin-bottom: 15px;" v-loading="salesLoading" @selection-change="handleSalesSelectionChange"> |
| | | <div v-if="statementDetailLoaded" class="sales-section"> |
| | | <div v-if="salesData.length > 0" class="section-title">本月销售数据</div> |
| | | <el-table |
| | | v-if="salesData.length > 0" |
| | | ref="salesTableRef" |
| | | :data="salesData" |
| | | border |
| | | row-key="id" |
| | | style="width: 100%; margin-bottom: 15px;" |
| | | v-loading="salesLoading" |
| | | @selection-change="handleSalesSelectionChange" |
| | | > |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column prop="date" label="日期" width="120" /> |
| | | <el-table-column prop="code" label="单据编号" width="150" /> |
| | | <el-table-column prop="occurrenceDate" label="日期" width="120" /> |
| | | <el-table-column prop="receiptNumber" label="单据编号" width="150" /> |
| | | <el-table-column prop="type" label="类型" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.type === '出库' ? 'success' : row.type === '收款' ? 'primary' : 'danger'">{{ row.type }}</el-tag> |
| | | <el-tag :type="getDetailTypeTagType(row.type)">{{ row.typeLabel }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="amount" label="金额" width="120"> |
| | | <template #default="{ row }"> |
| | | <span :class="row.type === '出库' ? 'text-primary' : row.type === '收款' ? 'text-success' : 'text-danger'">¥{{ formatMoney(row.amount) }}</span> |
| | | <span :class="getDetailAmountClass(row.type)">¥{{ formatMoney(row.amount) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="remark" label="备注" /> |
| | | </el-table> |
| | | <el-empty v-else description="该客户本月暂无明细数据" :image-size="80" /> |
| | | |
| | | <div class="summary-row"> |
| | | <span>期初余额: <strong class="text-primary">¥{{ formatMoney(generateForm.beginBalance) }}</strong></span> |
| | | <span>本期应收: <strong class="text-primary">¥{{ formatMoney(generateForm.currentReceivable) }}</strong></span> |
| | | <span>本期收款: <strong class="text-success">¥{{ formatMoney(generateForm.currentReceipt) }}</strong></span> |
| | | <span>期末余额: <strong :class="calculateEndBalance(generateForm.beginBalance, generateForm.currentReceivable, generateForm.currentReceipt) >= 0 ? 'text-success' : 'text-danger'">¥{{ formatMoney(calculateEndBalance(generateForm.beginBalance, generateForm.currentReceivable, generateForm.currentReceipt)) }}</strong></span> |
| | | <span>期初余额: <strong class="text-primary">¥{{ formatMoney(generateForm.openingBalance) }}</strong></span> |
| | | <span>本期应收: <strong class="text-primary">¥{{ formatMoney(generateForm.currentPlan) }}</strong></span> |
| | | <span>本期收款: <strong class="text-success">¥{{ formatMoney(generateForm.currentActually) }}</strong></span> |
| | | <span>期末余额: <strong :class="displayClosingBalance >= 0 ? 'text-success' : 'text-danger'">¥{{ formatMoney(displayClosingBalance) }}</strong></span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-else-if="generateForm.customerId && !salesLoading" class="empty-tip"> |
| | | <div v-else-if="generateForm.customerId && generateForm.statementMonth && !salesLoading" class="empty-tip"> |
| | | <el-empty description="该客户本月暂无销售数据" /> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <el-button type="primary" @click="confirmGenerate" :disabled="!canGenerate">确认生成</el-button> |
| | | <el-button type="primary" @click="confirmGenerate" :disabled="!canGenerate" :loading="submitLoading">确认生成</el-button> |
| | | <el-button @click="generateDialogVisible = false">取消</el-button> |
| | | </template> |
| | | </FormDialog> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { ref, reactive, onMounted, computed, nextTick, getCurrentInstance } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { listCustomer } from "@/api/basicData/customer.js"; |
| | | import { |
| | | getAccountStatementDetailsByMonth, |
| | | addAccountStatement, |
| | | listPageAccountStatement, |
| | | deleteAccountStatement, |
| | | } from "@/api/financialManagement/accountStatement.js"; |
| | | |
| | | const ACCOUNT_TYPE_RECEIVABLE = 1; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | defineOptions({ |
| | | name: "应收对账", |
| | |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "对账单号", prop: "statementCode", width: "150" }, |
| | | { label: "对账单号", prop: "statementNumber", width: "150" }, |
| | | { label: "客户名称", prop: "customerName", width: "180" }, |
| | | { label: "对账期间", prop: "period", width: "150" }, |
| | | { label: "期初余额", prop: "beginBalance", slot: "beginBalance" }, |
| | | { label: "本期应收", prop: "currentReceivable", slot: "currentReceivable" }, |
| | | { label: "本期收款", prop: "currentReceipt", slot: "currentReceipt" }, |
| | | { label: "期末余额", prop: "endBalance", slot: "endBalance" }, |
| | | { label: "操作", prop: "operation", slot: "operation", width: "150", fixed: "right" }, |
| | | { label: "对账期间", prop: "statementMonth", width: "150" }, |
| | | { label: "期初余额", prop: "openingBalance", dataType: "slot", slot: "openingBalance" }, |
| | | { label: "本期应收", prop: "currentPlan", dataType: "slot", slot: "currentPlan" }, |
| | | { label: "本期收款", prop: "currentActually", dataType: "slot", slot: "currentActually" }, |
| | | { label: "期末余额", prop: "closingBalance", dataType: "slot", slot: "closingBalance" }, |
| | | { label: "操作", prop: "operation", dataType: "slot", slot: "operation", width: "200", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const submitLoading = ref(false); |
| | | const detailDialogVisible = ref(false); |
| | | const currentCustomer = ref(""); |
| | | const currentPeriod = ref(""); |
| | | const detailData = ref([]); |
| | | const detailLoading = ref(false); |
| | | |
| | | const generateDialogVisible = ref(false); |
| | | const salesLoading = ref(false); |
| | | const statementDetailLoaded = ref(false); |
| | | const salesData = ref([]); |
| | | const selectedSales = ref([]); |
| | | const salesTableRef = ref(null); |
| | | const customerList = ref([]); |
| | | |
| | | /** 明细 type:1出库 2入库 3收款 4付款 5退货 */ |
| | | const STATEMENT_DETAIL_TYPE_MAP = { |
| | | 1: "出库", |
| | | 2: "入库", |
| | | 3: "收款", |
| | | 4: "付款", |
| | | 5: "退货", |
| | | }; |
| | | |
| | | const calculateEndBalance = (openingBalance, currentPlan, currentActually) => { |
| | | return openingBalance + currentPlan - currentActually; |
| | | }; |
| | | |
| | | const getDetailTypeLabel = (type) => STATEMENT_DETAIL_TYPE_MAP[Number(type)] ?? ""; |
| | | |
| | | const getDetailTypeTagType = (type) => { |
| | | const t = Number(type); |
| | | if (t === 1) return "success"; |
| | | if (t === 3) return "primary"; |
| | | if (t === 5) return "danger"; |
| | | return "info"; |
| | | }; |
| | | |
| | | const getDetailAmountClass = (type) => { |
| | | const t = Number(type); |
| | | if (t === 1) return "text-primary"; |
| | | if (t === 3) return "text-success"; |
| | | return "text-danger"; |
| | | }; |
| | | |
| | | const generateForm = reactive({ |
| | | customerId: "", |
| | | customerName: "", |
| | | period: "", |
| | | beginBalance: 0, |
| | | currentReceivable: 0, |
| | | currentReceipt: 0, |
| | | statementMonth: "", |
| | | openingBalance: 0, |
| | | currentPlan: 0, |
| | | currentActually: 0, |
| | | closingBalance: 0, |
| | | }); |
| | | |
| | | const displayClosingBalance = computed(() => { |
| | | return calculateEndBalance( |
| | | generateForm.openingBalance, |
| | | generateForm.currentPlan, |
| | | generateForm.currentActually |
| | | ); |
| | | }); |
| | | |
| | | const canGenerate = computed(() => { |
| | | return generateForm.customerId && generateForm.period && selectedSales.value.length > 0; |
| | | return generateForm.customerId && generateForm.statementMonth && selectedSales.value.length > 0; |
| | | }); |
| | | |
| | | const customerList = [ |
| | | { id: 1, name: "北京科技有限公司" }, |
| | | { id: 2, name: "上海贸易公司" }, |
| | | { id: 3, name: "广州实业有限公司" }, |
| | | { id: 4, name: "深圳电子公司" }, |
| | | ]; |
| | | |
| | | const mockData = [ |
| | | { id: 1, statementCode: "DZ202401001", customerId: 1, customerName: "北京科技有限公司", period: "2024-01", beginBalance: 10000, currentReceivable: 15000, currentReceipt: 8000, endBalance: 17000 }, |
| | | { id: 2, statementCode: "DZ202401002", customerId: 2, customerName: "上海贸易公司", period: "2024-01", beginBalance: 5000, currentReceivable: 12000, currentReceipt: 10000, endBalance: 7000 }, |
| | | { id: 3, statementCode: "DZ202402001", customerId: 1, customerName: "北京科技有限公司", period: "2024-02", beginBalance: 17000, currentReceivable: 20000, currentReceipt: 15000, endBalance: 22000 }, |
| | | ]; |
| | | |
| | | const calculateEndBalance = (beginBalance, currentReceivable, currentReceipt) => { |
| | | return beginBalance + currentReceivable - currentReceipt; |
| | | const applyStatementSummary = (data) => { |
| | | generateForm.openingBalance = Number(data.openingBalance ?? 0); |
| | | generateForm.currentPlan = Number(data.currentPlan ?? 0); |
| | | generateForm.currentActually = Number(data.currentActually ?? 0); |
| | | generateForm.closingBalance = Number( |
| | | data.closingBalance ?? |
| | | calculateEndBalance( |
| | | generateForm.openingBalance, |
| | | generateForm.currentPlan, |
| | | generateForm.currentActually |
| | | ) |
| | | ); |
| | | }; |
| | | |
| | | const getCustomerList = () => { |
| | | listCustomer({ current: -1, size: -1, type: 0 }).then((res) => { |
| | | if (res.code === 200) { |
| | | customerList.value = res.data?.records || []; |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const normalizeSalesRows = (list) => { |
| | | const rows = Array.isArray(list) ? list : []; |
| | | return rows.map((item, index) => { |
| | | const type = Number(item.type); |
| | | return { |
| | | id: item.id ?? `detail-${index}`, |
| | | accountStatementId: item.accountStatementId, |
| | | occurrenceDate: item.occurrenceDate ?? "", |
| | | receiptNumber: item.receiptNumber ?? "", |
| | | type, |
| | | typeLabel: getDetailTypeLabel(type), |
| | | amount: Math.abs(Number(item.amount ?? 0)), |
| | | remark: item.remark ?? "", |
| | | }; |
| | | }); |
| | | }; |
| | | |
| | | const selectAllSalesRows = (keepApiSummary = false) => { |
| | | nextTick(() => { |
| | | const table = salesTableRef.value; |
| | | if (!table) return; |
| | | table.clearSelection(); |
| | | salesData.value.forEach((row) => table.toggleRowSelection(row, true)); |
| | | selectedSales.value = [...salesData.value]; |
| | | if (!keepApiSummary) { |
| | | calculateSummary(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const isNumericId = (id) => id !== undefined && id !== null && id !== "" && /^\d+$/.test(String(id)); |
| | | |
| | | const buildFilterParams = (params = {}) => { |
| | | const result = { ...params, accountType: ACCOUNT_TYPE_RECEIVABLE }; |
| | | if (filters.customerId) { |
| | | result.customerId = filters.customerId; |
| | | } |
| | | if (filters.startMonth && filters.endMonth && filters.startMonth === filters.endMonth) { |
| | | result.statementMonth = filters.startMonth; |
| | | } else if (filters.startMonth) { |
| | | result.startMonth = filters.startMonth; |
| | | } |
| | | if (filters.endMonth && filters.startMonth !== filters.endMonth) { |
| | | result.endMonth = filters.endMonth; |
| | | } |
| | | return result; |
| | | }; |
| | | |
| | | const buildListParams = () => |
| | | buildFilterParams({ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | }); |
| | | |
| | | const buildExportParams = () => buildFilterParams({}); |
| | | |
| | | const buildDetailSubmitItem = (row) => { |
| | | const item = { |
| | | occurrenceDate: row.occurrenceDate, |
| | | receiptNumber: row.receiptNumber, |
| | | type: row.type, |
| | | amount: row.amount, |
| | | remark: row.remark ?? "", |
| | | }; |
| | | if (isNumericId(row.id)) { |
| | | item.id = Number(row.id); |
| | | } |
| | | if (row.accountStatementId) { |
| | | item.accountStatementId = row.accountStatementId; |
| | | } |
| | | return item; |
| | | }; |
| | | |
| | | const buildAddPayload = () => ({ |
| | | customerId: generateForm.customerId, |
| | | customerName: generateForm.customerName, |
| | | statementMonth: generateForm.statementMonth, |
| | | accountType: ACCOUNT_TYPE_RECEIVABLE, |
| | | statementNumber: "", |
| | | openingBalance: generateForm.openingBalance, |
| | | currentPlan: generateForm.currentPlan, |
| | | currentActually: generateForm.currentActually, |
| | | closingBalance: generateForm.closingBalance, |
| | | accountStatementDetails: selectedSales.value.map(buildDetailSubmitItem), |
| | | }); |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.customerId) { |
| | | result = result.filter(item => item.customerId === filters.customerId); |
| | | } |
| | | if (filters.startMonth && filters.endMonth) { |
| | | result = result.filter(item => item.period >= filters.startMonth && item.period <= filters.endMonth); |
| | | } |
| | | pagination.total = result.length; |
| | | dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize); |
| | | tableLoading.value = true; |
| | | listPageAccountStatement(buildListParams()) |
| | | .then((res) => { |
| | | const ok = res.code === 200 || res.code === 0; |
| | | if (ok && res.data) { |
| | | pagination.total = res.data.total ?? 0; |
| | | dataList.value = res.data.records ?? []; |
| | | } else { |
| | | ElMessage.error(res.msg || "查询失败"); |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | ElMessage.error("查询失败"); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | |
| | | const generateStatement = () => { |
| | | generateForm.customerId = ""; |
| | | generateForm.customerName = ""; |
| | | generateForm.period = ""; |
| | | generateForm.beginBalance = 0; |
| | | generateForm.currentReceivable = 0; |
| | | generateForm.currentReceipt = 0; |
| | | generateForm.statementMonth = ""; |
| | | generateForm.openingBalance = 0; |
| | | generateForm.currentPlan = 0; |
| | | generateForm.currentActually = 0; |
| | | generateForm.closingBalance = 0; |
| | | statementDetailLoaded.value = false; |
| | | salesData.value = []; |
| | | selectedSales.value = []; |
| | | generateDialogVisible.value = true; |
| | | }; |
| | | |
| | | const onCustomerChange = (customerId) => { |
| | | const customer = customerList.find(item => item.id === customerId); |
| | | if (customer) { |
| | | generateForm.customerName = customer.name; |
| | | } |
| | | const customer = customerList.value.find((item) => item.id === customerId); |
| | | generateForm.customerName = customer?.customerName ?? ""; |
| | | loadSalesData(); |
| | | }; |
| | | |
| | | const onPeriodChange = () => { |
| | | const onStatementMonthChange = () => { |
| | | loadSalesData(); |
| | | }; |
| | | |
| | | const loadSalesData = () => { |
| | | if (!generateForm.customerId || !generateForm.period) { |
| | | if (!generateForm.customerId || !generateForm.statementMonth) { |
| | | salesData.value = []; |
| | | selectedSales.value = []; |
| | | statementDetailLoaded.value = false; |
| | | generateForm.openingBalance = 0; |
| | | generateForm.currentPlan = 0; |
| | | generateForm.currentActually = 0; |
| | | generateForm.closingBalance = 0; |
| | | return; |
| | | } |
| | | |
| | | salesLoading.value = true; |
| | | selectedSales.value = []; |
| | | statementDetailLoaded.value = false; |
| | | |
| | | setTimeout(() => { |
| | | const mockSalesData = [ |
| | | { id: 1, date: generateForm.period + "-03", code: "CK2024001", type: "出库", amount: 8000, remark: "产品A销售" }, |
| | | { id: 2, date: generateForm.period + "-08", code: "SK2024001", type: "收款", amount: 5000, remark: "客户回款" }, |
| | | { id: 3, date: generateForm.period + "-12", code: "CK2024002", type: "出库", amount: 12000, remark: "产品B销售" }, |
| | | { id: 4, date: generateForm.period + "-15", code: "TH2024001", type: "退货", amount: 2000, remark: "质量问题退货" }, |
| | | { id: 5, date: generateForm.period + "-20", code: "CK2024003", type: "出库", amount: 5000, remark: "产品C销售" }, |
| | | { id: 6, date: generateForm.period + "-25", code: "SK2024002", type: "收款", amount: 8000, remark: "客户回款" }, |
| | | ]; |
| | | getAccountStatementDetailsByMonth({ |
| | | accountType: ACCOUNT_TYPE_RECEIVABLE, |
| | | customerId: generateForm.customerId, |
| | | statementMonth: generateForm.statementMonth, |
| | | }) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | const data = res.data ?? {}; |
| | | const details = data.accountStatementDetails; |
| | | const list = Array.isArray(details) ? details : []; |
| | | salesData.value = normalizeSalesRows(list); |
| | | applyStatementSummary(data); |
| | | statementDetailLoaded.value = true; |
| | | |
| | | salesData.value = mockSalesData; |
| | | |
| | | const lastPeriod = getLastPeriod(generateForm.period); |
| | | const lastStatement = mockData.find(item => |
| | | item.customerId === generateForm.customerId && item.period === lastPeriod |
| | | ); |
| | | generateForm.beginBalance = lastStatement ? lastStatement.endBalance : 0; |
| | | |
| | | calculateSummary(); |
| | | |
| | | salesLoading.value = false; |
| | | }, 500); |
| | | }; |
| | | |
| | | const getLastPeriod = (period) => { |
| | | const [year, month] = period.split("-").map(Number); |
| | | if (month === 1) { |
| | | return `${year - 1}-12`; |
| | | } |
| | | return `${year}-${String(month - 1).padStart(2, "0")}`; |
| | | if (salesData.value.length > 0) { |
| | | selectAllSalesRows(true); |
| | | } |
| | | } else { |
| | | salesData.value = []; |
| | | statementDetailLoaded.value = false; |
| | | ElMessage.error(res.msg || "查询对账明细失败"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | salesData.value = []; |
| | | statementDetailLoaded.value = false; |
| | | ElMessage.error("查询对账明细失败"); |
| | | }) |
| | | .finally(() => { |
| | | salesLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const calculateSummary = () => { |
| | | let receivable = 0; |
| | | let receipt = 0; |
| | | |
| | | selectedSales.value.forEach(item => { |
| | | if (item.type === "出库") { |
| | | selectedSales.value.forEach((item) => { |
| | | if (item.type === 1) { |
| | | receivable += item.amount; |
| | | } else if (item.type === "退货") { |
| | | } else if (item.type === 5) { |
| | | receivable -= item.amount; |
| | | } else if (item.type === "收款") { |
| | | } else if (item.type === 3) { |
| | | receipt += item.amount; |
| | | } |
| | | }); |
| | | |
| | | generateForm.currentReceivable = receivable; |
| | | generateForm.currentReceipt = receipt; |
| | | generateForm.currentPlan = receivable; |
| | | generateForm.currentActually = receipt; |
| | | generateForm.closingBalance = calculateEndBalance( |
| | | generateForm.openingBalance, |
| | | generateForm.currentPlan, |
| | | generateForm.currentActually |
| | | ); |
| | | }; |
| | | |
| | | const handleSalesSelectionChange = (selection) => { |
| | |
| | | }; |
| | | |
| | | const confirmGenerate = () => { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | const endBalance = calculateEndBalance(generateForm.beginBalance, generateForm.currentReceivable, generateForm.currentReceipt); |
| | | if (!canGenerate.value) return; |
| | | submitLoading.value = true; |
| | | addAccountStatement(buildAddPayload()) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | generateDialogVisible.value = false; |
| | | ElMessage.success("对账单生成成功"); |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "生成失败"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("生成失败"); |
| | | }) |
| | | .finally(() => { |
| | | submitLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | mockData.unshift({ |
| | | id: newId, |
| | | statementCode: "DZ" + Date.now(), |
| | | customerId: generateForm.customerId, |
| | | customerName: generateForm.customerName, |
| | | period: generateForm.period, |
| | | beginBalance: generateForm.beginBalance, |
| | | currentReceivable: generateForm.currentReceivable, |
| | | currentReceipt: generateForm.currentReceipt, |
| | | endBalance, |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm(`确认删除对账单「${row.statementNumber || row.id}」吗?`, "提示", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }).then(() => { |
| | | deleteAccountStatement([row.id]) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("删除成功"); |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "删除失败"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("删除失败"); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const buildDetailTableFromApi = (data, statementMonth) => { |
| | | const details = Array.isArray(data.accountStatementDetails) ? data.accountStatementDetails : []; |
| | | let runningBalance = Number(data.openingBalance ?? 0); |
| | | const rows = [ |
| | | { |
| | | date: statementMonth ?? "", |
| | | type: "期初", |
| | | code: "-", |
| | | debit: 0, |
| | | credit: 0, |
| | | balance: runningBalance, |
| | | remark: "期初余额", |
| | | }, |
| | | ]; |
| | | |
| | | details.forEach((item) => { |
| | | const amount = Math.abs(Number(item.amount ?? 0)); |
| | | const type = Number(item.type); |
| | | let debit = 0; |
| | | let credit = 0; |
| | | |
| | | if (type === 1) { |
| | | debit = amount; |
| | | runningBalance += amount; |
| | | } else if (type === 3 || type === 5) { |
| | | credit = amount; |
| | | runningBalance -= amount; |
| | | } |
| | | |
| | | rows.push({ |
| | | date: item.occurrenceDate ?? "", |
| | | type: getDetailTypeLabel(type), |
| | | code: item.receiptNumber ?? "", |
| | | debit, |
| | | credit, |
| | | balance: runningBalance, |
| | | remark: item.remark ?? "", |
| | | }); |
| | | }); |
| | | |
| | | generateDialogVisible.value = false; |
| | | ElMessage.success("对账单生成成功"); |
| | | getTableData(); |
| | | return rows; |
| | | }; |
| | | |
| | | const viewDetail = (row) => { |
| | | currentCustomer.value = row.customerName; |
| | | currentPeriod.value = row.period; |
| | | if (!row.customerId || !row.statementMonth) { |
| | | ElMessage.warning("缺少客户或对账月份,无法查询明细"); |
| | | return; |
| | | } |
| | | |
| | | const saleOutAmount = Math.floor(row.currentReceivable * 0.6); |
| | | const returnAmount = Math.floor(row.currentReceivable * 0.1); |
| | | const firstReceipt = Math.floor(row.currentReceipt * 0.4); |
| | | const secondReceipt = row.currentReceipt - firstReceipt; |
| | | |
| | | let runningBalance = row.beginBalance; |
| | | |
| | | detailData.value = [ |
| | | { date: row.period + "-01", type: "期初", code: "-", debit: 0, credit: 0, balance: runningBalance, remark: "期初余额" }, |
| | | { date: row.period + "-05", type: "出库", code: "CK2024001", debit: saleOutAmount, credit: 0, balance: runningBalance += saleOutAmount, remark: "销售出库" }, |
| | | { date: row.period + "-10", type: "收款", code: "SK2024001", debit: 0, credit: firstReceipt, balance: runningBalance -= firstReceipt, remark: "客户回款" }, |
| | | { date: row.period + "-15", type: "出库", code: "CK2024002", debit: row.currentReceivable - saleOutAmount - returnAmount, credit: 0, balance: runningBalance += (row.currentReceivable - saleOutAmount - returnAmount), remark: "销售出库" }, |
| | | { date: row.period + "-20", type: "退货", code: "TH2024001", debit: 0, credit: returnAmount, balance: runningBalance -= returnAmount, remark: "销售退货" }, |
| | | { date: row.period + "-25", type: "收款", code: "SK2024002", debit: 0, credit: secondReceipt, balance: runningBalance -= secondReceipt, remark: "客户回款" }, |
| | | ]; |
| | | |
| | | currentCustomer.value = row.customerName ?? ""; |
| | | currentPeriod.value = row.statementMonth ?? ""; |
| | | detailData.value = []; |
| | | detailDialogVisible.value = true; |
| | | detailLoading.value = true; |
| | | |
| | | getAccountStatementDetailsByMonth({ |
| | | accountType: ACCOUNT_TYPE_RECEIVABLE, |
| | | customerId: row.customerId, |
| | | statementMonth: row.statementMonth, |
| | | }) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | detailData.value = buildDetailTableFromApi(res.data ?? {}, row.statementMonth); |
| | | } else { |
| | | ElMessage.error(res.msg || "查询明细失败"); |
| | | detailDialogVisible.value = false; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("查询明细失败"); |
| | | detailDialogVisible.value = false; |
| | | }) |
| | | .finally(() => { |
| | | detailLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const printStatement = (row) => { |
| | | ElMessage.info(`打印对账单: ${row.statementCode}`); |
| | | ElMessage.info(`打印对账单: ${row.statementNumber}`); |
| | | }; |
| | | |
| | | const printDetail = () => { |
| | |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessage.success("导出成功"); |
| | | const params = buildExportParams(); |
| | | proxy.download("/accountStatement/exportAccountStatement", params, `应收对账单_${Date.now()}.xlsx`); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getCustomerList(); |
| | | getTableData(); |
| | | }); |
| | | </script> |