| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="客æ·:"> |
| | | <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="对账æé´:"> |
| | | <el-date-picker v-model="filters.startMonth" type="month" placeholder="å¼å§æä»½" value-format="YYYY-MM" style="width: 140px;" /> |
| | | <span style="margin: 0 10px;">è³</span> |
| | | <el-date-picker v-model="filters.endMonth" type="month" placeholder="ç»ææä»½" value-format="YYYY-MM" style="width: 140px;" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div> |
| | | <el-button type="primary" @click="generateStatement" icon="Document">çæå¯¹è´¦å</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button @click="handleOut" 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 #openingBalance="{ row }"> |
| | | <span :class="row.openingBalance >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(row.openingBalance) }}</span> |
| | | </template> |
| | | <template #currentPlan="{ row }"> |
| | | <span class="text-primary">Â¥{{ formatMoney(row.currentPlan) }}</span> |
| | | </template> |
| | | <template #currentActually="{ row }"> |
| | | <span class="text-success">Â¥{{ formatMoney(row.currentActually) }}</span> |
| | | </template> |
| | | <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="danger" link @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <FormDialog title="对账æç»" v-model="detailDialogVisible" width="900px" @confirm="printDetail" @cancel="detailDialogVisible = false" operationType="detail"> |
| | | <div class="statement-header"> |
| | | <h3>{{ currentCustomer }} åºæ¶å¯¹è´¦å</h3> |
| | | <p>对账æé´: {{ currentPeriod }}</p> |
| | | </div> |
| | | <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-tag :type="row.type === 'åºåº' ? 'success' : row.type === 'éè´§' ? 'danger' : 'primary'">{{ row.type }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="code" label="åæ®ç¼å·" width="150" /> |
| | | <el-table-column prop="debit" label="åæ¹(åºæ¶)" width="120"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.debit > 0" class="text-danger">Â¥{{ formatMoney(row.debit) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="credit" label="è´·æ¹(æ¶æ¬¾)" width="120"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.credit > 0" class="text-success">Â¥{{ formatMoney(row.credit) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="balance" label="ä½é¢" width="120"> |
| | | <template #default="{ row }"> |
| | | <span :class="row.balance >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(row.balance) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="remark" label="夿³¨" show-overflow-tooltip /> |
| | | </el-table> |
| | | <template #footer> |
| | | <el-button type="primary" @click="printDetail">æå°</el-button> |
| | | <el-button @click="detailDialogVisible = false">å
³é</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | |
| | | <FormDialog title="çæå¯¹è´¦å" v-model="generateDialogVisible" width="1000px" @confirm="confirmGenerate" @cancel="generateDialogVisible = false"> |
| | | <el-form :model="generateForm" label-width="100px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="鿩客æ·" prop="customerId"> |
| | | <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="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="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="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="getDetailTypeTagType(row.type)">{{ row.typeLabel }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="amount" label="éé¢" width="120"> |
| | | <template #default="{ row }"> |
| | | <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.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 && generateForm.statementMonth && !salesLoading" class="empty-tip"> |
| | | <el-empty description="è¯¥å®¢æ·æ¬æææ é宿°æ®" /> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <el-button type="primary" @click="confirmGenerate" :disabled="!canGenerate" :loading="submitLoading">确认çæ</el-button> |
| | | <el-button @click="generateDialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | 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 filters = reactive({ |
| | | customerId: "", |
| | | startMonth: "", |
| | | endMonth: "", |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "对账åå·", prop: "statementNumber", width: "150" }, |
| | | { label: "客æ·åç§°", prop: "customerName", width: "180" }, |
| | | { 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: "", |
| | | 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.statementMonth && selectedSales.value.length > 0; |
| | | }); |
| | | |
| | | 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"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | 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 = () => { |
| | | filters.customerId = ""; |
| | | filters.startMonth = ""; |
| | | filters.endMonth = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ current, size }) => { |
| | | pagination.currentPage = current; |
| | | pagination.pageSize = size; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const generateStatement = () => { |
| | | generateForm.customerId = ""; |
| | | generateForm.customerName = ""; |
| | | 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.value.find((item) => item.id === customerId); |
| | | generateForm.customerName = customer?.customerName ?? ""; |
| | | loadSalesData(); |
| | | }; |
| | | |
| | | const onStatementMonthChange = () => { |
| | | loadSalesData(); |
| | | }; |
| | | |
| | | const loadSalesData = () => { |
| | | 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; |
| | | |
| | | 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; |
| | | |
| | | 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 === 1) { |
| | | receivable += item.amount; |
| | | } else if (item.type === 5) { |
| | | receivable -= item.amount; |
| | | } else if (item.type === 3) { |
| | | receipt += item.amount; |
| | | } |
| | | }); |
| | | |
| | | generateForm.currentPlan = receivable; |
| | | generateForm.currentActually = receipt; |
| | | generateForm.closingBalance = calculateEndBalance( |
| | | generateForm.openingBalance, |
| | | generateForm.currentPlan, |
| | | generateForm.currentActually |
| | | ); |
| | | }; |
| | | |
| | | const handleSalesSelectionChange = (selection) => { |
| | | selectedSales.value = selection; |
| | | calculateSummary(); |
| | | }; |
| | | |
| | | const confirmGenerate = () => { |
| | | 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; |
| | | }); |
| | | }; |
| | | |
| | | 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 ?? "", |
| | | }); |
| | | }); |
| | | |
| | | return rows; |
| | | }; |
| | | |
| | | const viewDetail = (row) => { |
| | | if (!row.customerId || !row.statementMonth) { |
| | | ElMessage.warning("缺å°å®¢æ·æå¯¹è´¦æä»½ï¼æ æ³æ¥è¯¢æç»"); |
| | | return; |
| | | } |
| | | |
| | | 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.statementNumber}`); |
| | | }; |
| | | |
| | | const printDetail = () => { |
| | | ElMessage.info("æå°æç»"); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | const params = buildExportParams(); |
| | | proxy.download("/accountStatement/exportAccountStatement", params, `åºæ¶å¯¹è´¦å_${Date.now()}.xlsx`); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getCustomerList(); |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .text-success { |
| | | color: #67c23a; |
| | | } |
| | | |
| | | .text-danger { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .text-primary { |
| | | color: #409eff; |
| | | } |
| | | |
| | | .statement-header { |
| | | text-align: center; |
| | | margin-bottom: 20px; |
| | | h3 { |
| | | margin: 0 0 10px 0; |
| | | } |
| | | p { |
| | | color: #909399; |
| | | margin: 0; |
| | | } |
| | | } |
| | | |
| | | .sales-section { |
| | | margin-top: 20px; |
| | | |
| | | .section-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | margin-bottom: 15px; |
| | | padding-left: 10px; |
| | | border-left: 4px solid #409eff; |
| | | } |
| | | } |
| | | |
| | | .summary-row { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | padding: 15px; |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | margin-top: 15px; |
| | | |
| | | span { |
| | | font-size: 14px; |
| | | |
| | | strong { |
| | | font-size: 16px; |
| | | margin-left: 5px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .empty-tip { |
| | | margin-top: 30px; |
| | | } |
| | | </style> |