Files
17168ERP/web/admin/bill/verify.vue
T
2026-05-15 17:04:49 +08:00

461 lines
23 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<v-container>
<v-card>
<v-card-title class="bg-success white--text text-center">
<h5 class="mb-0">出納核對匯款人</h5>
<v-spacer></v-spacer>
<v-btn color="primary" @click="submitData">確認送出</v-btn>
<v-btn color="primary" class="ml-2" @click="closeWindow">關閉</v-btn>
</v-card-title>
<v-card-text>
<v-data-table :headers="headers"
:items="items"
:loading="loading"
loading-text="載入中..."
class="elevation-1 mt-3"
item-key="id"
:expanded.sync="expanded"
show-expand>
<template v-slot:item.info="{ item }">
<div>
<div><span class="text-muted">姓名</span>{{ item.name }}</div>
<div><span class="text-muted">電話</span>{{ item.phone }}</div>
<div><span class="text-muted">法會</span>{{ getActivityName(item.activity_num) }}</div>
</div>
</template>
<template v-slot:item.f_num="{ item }">
<span v-if="item.f_num && item.follower">
<a :href="'/admin/follower/reg.aspx?num=' + item.follower.num"
class="text-success"
target="_blank">
{{ item.follower.u_name }}F{{ item.f_num }}
</a>
<div class="small text-muted">
電話{{ item.follower.phone }}<br>
手機{{ item.follower.cellphone }}
</div>
</span>
<span v-else class="text-danger">未自動比對</span>
</template>
<template v-slot:expanded-item="{ headers, item }">
<td :colspan="headers.length">
<div class="pa-4">
<h6 class="mb-3 text-primary">
<v-icon color="primary" small class="mr-1">mdi-receipt</v-icon>
沖帳明細
</h6>
<v-container>
<v-row class="font-weight-bold grey--text text--darken-2">
<v-col>*入帳銀行/帳戶 | 支付資訊/帳號後5碼</v-col>
<v-col>*入帳日期</v-col>
<v-col>*入帳金額</v-col>
<v-col>*收支項目</v-col>
<!--<v-col>備註/狀態 | 核對記錄</v-col>-->
</v-row>
<v-row>
<v-col>
<div class="mb-2">
<span class="badge bg-primary me-1" title="支付方式">{{ payTypeText[item.pay_type] || item.pay_type }}</span>
<span class="badge bg-secondary" title="型態:個人/共同">{{ item.pay_mode }}</span>
<span class="font-weight-bold text-primary" title="帳號後5碼">{{ item.account_last5 }}</span>
</div>
<div>
<v-select :items="bankOptions"
v-model="item.acc_num"
dense
outlined
hide-details
style="max-width: 200px"></v-select>
</div>
</v-col>
<v-col>
<div class="mb-2">
<span class="text-muted small">登記日期</span>
<span class="font-weight-bold error--text"
style="cursor:pointer"
@click="$set(item, 'check_date', item.create_time ? item.create_time.split('T')[0] : '')"
title="點擊帶入日期">
{{ item.create_time | date }}
</span>
</div>
<div class="mb-2">
<v-text-field v-model="item.check_date"
type="date"
dense
outlined
hide-details
style="max-width: 140px"></v-text-field>
</div>
</v-col>
<v-col>
<div class="mb-2">
<span class="text-muted small">金額</span>
<span class="font-weight-bold error--text"
style="cursor:pointer"
@click="$set(item, 'check_amount', item.amount)"
title="點擊帶入金額">
{{ item.amount | currency }}
</span>
</div>
<div>
<v-text-field v-model="item.check_amount"
type="number"
dense
outlined
hide-details
style="max-width: 100px"></v-text-field>
</div>
</v-col>
<v-col>
<div class="mb-2">
<span class="badge bg-primary me-1" title="支付方式">收支項目</span>
</div>
<div>
<v-select :items="accountingKinds"
v-model="item.kind"
dense
outlined
hide-details
style="max-width: 200px"></v-select>
</div>
</v-col>
</v-row>
</v-container>
</div>
</td>
</template>
<template v-slot:item.actions="{ item }">
<v-btn small outlined color="primary" @click="openFollowerDialog(item)">選擇信眾</v-btn>
</template>
<template v-slot:item.check_memo="{ item }">
<div class="d-flex align-center my-2" style="min-width: 300px">
<v-text-field v-model="item.check_memo"
dense
outlined
hide-details
class="mr-2"
style="width: 180px"
placeholder="帳簿備註"></v-text-field>
<v-select :items="checkStatusOptions"
v-model="item.check_status"
item-text="text"
item-value="value"
dense
outlined
hide-details
style="width: 110px"
:menu-props="{ contentClass: 'mini-dropdown', maxHeight: 200 }"></v-select>
</div>
</template>
</v-data-table>
</v-card-text>
</v-card>
<v-dialog v-model="follower_dialog.show" max-width="800px">
<v-card>
<v-card-title class="grey lighten-2">
選擇信眾
<v-spacer></v-spacer>
<v-btn icon @click="follower_dialog.show = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-card-text class="pt-4">
<v-row>
<v-col cols="12">
<v-text-field v-model="follower_dialog.search"
label="搜尋信眾 (按 Enter 搜尋)"
prepend-icon="mdi-magnify"
@keyup.enter="searchFollowers"
clearable></v-text-field>
</v-col>
</v-row>
<v-data-table :headers="follower_dialog.headers"
:items="follower_dialog.items"
:loading="follower_dialog.loading"
item-key="num"
class="elevation-1"
@click:row="selectFollower">
<template v-slot:item.actions="{ item }">
<v-btn small color="primary" @click.stop="selectFollower(item)">
選擇
</v-btn>
</template>
</v-data-table>
</v-card-text>
</v-card>
</v-dialog>
<v-snackbar v-model="snackbar.show" :timeout="3000" top color="error">
{{ snackbar.text }}
<template v-slot:action="{ attrs }">
<v-btn dark text v-bind="attrs" @click="snackbar.show = false">關閉</v-btn>
</template>
</v-snackbar>
</v-container>
</template>
<script>
module.exports = {
data() {
return {
// 【修正點3】補上 snackbar 的資料定義
snackbar: {
show: false,
text: ''
},
headers: [
{ text: '匯款人資訊', value: 'info' },
{ text: '對應信眾', value: 'f_num' },
{ text: '選擇信眾', value: 'actions', sortable: false },
{ text: '備註/狀態 | 核對記錄', value: 'check_memo' }
//{ text: '狀態 | 核對記錄', value: 'status' },
],
// detailHeaders 可保留供其他地方參考,但不應放入 expanded 插槽變數中
detailHeaders: [
{ text: '匯款人資訊', value: 'info' },
{ text: '匯款備註/相片', value: 'note' },
{ text: '*入帳銀行/帳戶 | 支付資訊/帳號後5碼', value: 'acc_num' },
{ text: '*入帳日期', value: 'check_date' },
{ text: '*入帳金額', value: 'check_amount' },
{ text: '*收支項目', value: 'kind' },
{ text: '備註/狀態 | 核對記錄', value: 'check_memo' }
],
items: [],
bankOptions: [],
accountingKinds: [],
checkStatusOptions: [
{ text: '', value: '' },
{ text: '未核對', value: '1' },
{ text: '核對', value: '2' },
{ text: '金額不符', value: '3' },
{ text: '其他問題', value: '4' },
{ text: '作廢', value: '5' }
],
payTypeText: {
1: '現金',
2: '匯款',
3: '支票'
},
activities: [],
expanded: [],
loading: false,
statusOptions: [
{ text: '', value: '' },
{ text: '待確認', value: '1' },
{ text: '確認', value: '2' },
{ text: '作廢', value: '3' }
],
follower_dialog: {
show: false,
loading: false,
search: '',
headers: [
{ text: '編號', value: 'num' },
{ text: '姓名', value: 'u_name' },
{ text: '地址', value: 'address' },
// 【修正點2】補上操作欄位定義,這樣按鈕才出得來
{ text: '操作', value: 'actions', sortable: false }
],
items: [],
selected: null,
current_item: null
}
};
},
filters: {
currency(val) {
if (!val) return '';
return Number(val).toLocaleString();
},
date(val) {
if (!val) return '';
return val.split('T')[0];
}
},
methods: {
getActivityName(num) {
const act = this.activities.find(a => a.num === num);
return act ? act.subject : '';
},
openFollowerDialog(item) {
this.follower_dialog.current_item = item;
this.follower_dialog.show = true;
// 若開啟時已有關鍵字,可以自動帶入搜尋
// this.searchFollowers();
},
async searchFollowers() {
if (!this.follower_dialog.search) return;
this.follower_dialog.loading = true;
try {
const response = await axios.post(HTTP_HOST + 'api/follower/GetList', {
f_number: this.follower_dialog.search,
u_name: this.follower_dialog.search
}, {
params: {
page: 1,
pageSize: 10
}
});
this.follower_dialog.items = response.data.list || [];
} catch (error) {
console.error('Error fetching followers:', error);
this.snackbar.text = "查詢信眾失敗,請確認網路或 API 狀態";
this.snackbar.show = true;
} finally {
this.follower_dialog.loading = false;
}
},
async selectFollower(follower) {
if (this.follower_dialog.current_item) {
try {
this.follower_dialog.show = false;
const selectedFollower = this.follower_dialog.items.find(item => item.num === follower.num);
if (selectedFollower) {
this.follower_dialog.current_item.f_num = follower.num;
this.follower_dialog.current_item.follower = {
num: selectedFollower.num,
u_name: selectedFollower.u_name,
address: selectedFollower.address || '',
phone: selectedFollower.phoneDes || '',
cellphone: selectedFollower.cellphoneDes || ''
};
} else {
throw new Error('找不到信眾詳細資料');
}
} catch (error) {
console.error('取得信眾詳細資料失敗:', error);
this.snackbar.text = '取得信眾詳細資料失敗,請重試';
this.snackbar.show = true;
this.follower_dialog.current_item.f_num = follower.num;
this.follower_dialog.current_item.follower = {
num: follower.num,
u_name: follower.u_name,
address: follower.address || '',
phone: '',
cellphone: ''
};
}
}
},
async loadData() {
this.loading = true;
try {
const actRes = await axios.get('../../api/activity');
this.activities = actRes.data;
const res = await axios.get('../../api/transfer_register/pending');
this.items = res.data.map(item => ({
...item,
status: item.status ? String(item.status) : ''
}));
const bankRes = await axios.post('../../api/accounting/GetAccountKindList', {}, { params: { page: 1, pageSize: 1000 } });
this.bankOptions = bankRes.data.list.map(x => ({
text: x.kind + (x.bank_name ? ' - ' + x.bank_name : '') + (x.bank_id ? ' (' + x.bank_id + ')' : ''),
value: x.num
}));
const kindRes = await axios.post('../../api/accounting/GetTitleKindList', {}, { params: { page: 1, pageSize: 1000 } });
this.accountingKinds = kindRes.data.list.map(x => ({
text: x.kind,
value: x.num
}));
} catch (e) {
console.error('載入資料失敗:', e);
this.snackbar.text = '載入資料失敗';
this.snackbar.show = true;
} finally {
this.loading = false;
}
},
async saveData() {
const updateList = this.items.map(item => ({
id: item.id,
f_num: item.f_num,
status: item.status ? String(item.status) : '',
verify_note: item.verify_note
// 備註:依您的原本邏輯,這裡並未傳送 check_date, check_amount 等欄位,若後端 API 需要這些沖帳資訊,請在這裡一併補上。
}));
try {
const res = await axios.post('../../api/transfer_register/batch_update', updateList);
if (res.data && res.data.success) {
alert('儲存成功!');
await this.loadData();
} else {
alert('儲存失敗,請重試!!');
}
} catch (e) {
alert('儲存失敗,請重試:' + e.message);
}
},
async submitData() {
// 檢查必填欄位 - 入帳銀行/帳戶
const missingAccNum = this.items.filter(item => !item.acc_num);
if (missingAccNum.length > 0) {
alert('請選擇入帳銀行/帳戶!有 ' + missingAccNum.length + ' 筆資料未選擇。');
return;
}
// 檢查必填欄位 - 入帳日期
const missingCheckDate = this.items.filter(item => !item.check_date);
if (missingCheckDate.length > 0) {
alert('請填寫入帳日期!有 ' + missingCheckDate.length + ' 筆資料未填寫。');
return;
}
// 檢查必填欄位 - 入帳金額
const missingCheckAmount = this.items.filter(item => !item.check_amount || item.check_amount <= 0);
if (missingCheckAmount.length > 0) {
alert('請填寫入帳金額!有 ' + missingCheckAmount.length + ' 筆資料未填寫或金額無效。');
return;
}
// 組出要更新的資料
const updateList = this.items.map(item => ({
id: item.id,
f_num: item.f_num,
status: '2',
check_status: item.check_status ? String(item.check_status) : '',
verify_note: item.verify_note,
acc_num: item.acc_num,
check_date: item.check_date,
check_amount: item.check_amount,
check_memo: item.check_memo,
kind: item.kind,
verify_note: item.verify_note
}));
try {
const res = await axios.post('../../api/transfer_register/batch_update', updateList);
if (res.data && res.data.success) {
alert('送出成功!');
// 重新載入資料
await this.loadData();
} else {
alert('送出失敗,請重試 :' + res.data.message);
}
} catch (e) {
console.error('送出失敗:', e);
alert('送出失敗,請再試一次!');
}
},
closeWindow() {
this.$emit('close-dialog');
},
},
created() {
this.loadData();
}
}
</script>