Files
17168ERP/web/admin/transfer/verify.aspx
2025-08-29 01:27:25 +08:00

575 lines
21 KiB
Plaintext
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.
<%@ Page Title="出納核對匯款人" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" EnableEventValidation="false" CodeFile="verify.aspx.cs" Inherits="admin_transfer_verify" %>
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" runat="Server">
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="page_nav" runat="Server">
<h5 class="mb-0">出納 - 核對匯款人</h5>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
<div id="verify-app">
<v-app>
<v-container v-if="step === 1">
<v-card>
<v-card-title class="bg-success white--text text-center">
<h5 class="mb-0">出納核對匯款人程序1</h5>
<v-spacer></v-spacer>
<v-btn color="primary" @click="saveAndGoStep2">確認</v-btn>
</v-card-title>
<v-card-text>
<v-data-table
:headers="headers"
:items="items"
:loading="loading"
loading-text="載入中..."
class="elevation-1"
item-key="id"
>
<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:item.actions="{ item }">
<v-btn small outlined color="primary" @click="openFollowerDialog(item)">選擇信眾</v-btn>
</template>
<template v-slot:item.status="{ item }">
<div class="d-flex align-center">
<v-select
:items="statusOptions"
v-model="item.status"
item-text="text"
item-value="value"
dense
outlined
hide-details
class="mr-2"
style="width: 110px;max-width: 110px;"
:menu-props="{ contentClass: 'mini-dropdown', maxHeight: 200 }"
></v-select>
<v-text-field
v-model="item.verify_note"
dense
outlined
hide-details
style="width: 200px"
placeholder="核對記錄"
></v-text-field>
</div>
</template>
</v-data-table>
</v-card-text>
</v-card>
</v-container>
<!-- 階段2區塊預設隱藏 -->
<v-container v-if="step === 2">
<v-card>
<v-card-title class="bg-info white--text text-center">
<h5 class="mb-0">出納核對金額程序2</h5>
<v-spacer></v-spacer>
<v-btn color="primary" @click="submitStep2">送出</v-btn>
</v-card-title>
<v-card-text>
<v-data-table
:headers="step2Headers"
:items="items"
class="elevation-1 step2-table"
item-key="id"
>
<template v-slot:item.info="{ item }">
<div>
<div><span class="text-muted">法會:</span>{{ getActivityName(item.activity_num) }}</div>
<div><span class="text-muted">姓名:</span>{{ item.follower ? item.follower.u_name : '' }}</div>
<div><span class="text-muted">電話:</span>{{ item.follower ? (item.follower.phone || item.follower.cellphone) : '' }}</div>
</div>
</template>
<template v-slot:item.note="{ item }">
<div>
{{ item.note }}
<div v-if="item.proof_img">
<a :href="'../../upload/transfer_proof/' + item.proof_img" target="_blank">查看相片</a>
</div>
</div>
</template>
<template v-slot:item.acc_num="{ item }">
<div>
<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="fw-bold text-primary" title="帳號後5碼">{{ item.account_last5 }}</span>
</div>
</div>
<div>
<v-select
:items="bankOptions"
v-model="item.acc_num"
dense
outlined
hide-details
style="max-width: 200px"
></v-select>
</div>
</template>
<template v-slot:item.check_date="{ item }">
<div class="mb-2">
<span class="text-muted small">登記日期:</span>
<span
class="fw-bold text-danger"
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>
</template>
<template v-slot:item.check_amount="{ item }">
<div class="mb-2">
<span
class="text-muted small"
>金額:</span>
<span
class="fw-bold text-danger"
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>
</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>
<div class="my-2">
<v-text-field
v-model="item.verify_note"
dense
outlined
hide-details
style="max-width: 310px"
placeholder="核對記錄"
></v-text-field>
</div>
</template>
</v-data-table>
</v-card-text>
</v-card>
</v-container>
<!-- 信眾選擇對話框 -->
<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>
<v-row>
<v-col cols="12">
<v-text-field
v-model="follower_dialog.search"
label="搜尋信眾"
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"
:search="follower_dialog.search"
item-key="num"
class="elevation-1"
@click:row="selectFollower"
>
<template v-slot:item.actions="{ item }">
<v-btn small color="primary" @click="selectFollower(item)">
選擇
</v-btn>
</template>
</v-data-table>
</v-card-text>
</v-card>
</v-dialog>
</v-app>
</div>
</asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" runat="Server">
<script>
function openFollowerModal(rowIdx) {
// 這裡可記錄目前要選擇哪一列
window.currentRowIdx = rowIdx;
var modal = new bootstrap.Modal(document.getElementById('followerModal'));
modal.show();
}
function selectFollower(fNum, name, rowIdx) {
// 這裡可將選擇結果寫回對應列(需配合實際資料結構)
var table = document.querySelector('table.table-bordered tbody');
var row = table.children[rowIdx-1];
var cell = row.children[1];
cell.innerHTML = '<a href="#" class="link-success">' + name + '' + fNum + '</a>' +
'<button type="button" class="btn btn-sm btn-outline-secondary ms-2" onclick="openFollowerModal(' + rowIdx + ')">選擇信眾</button>';
var modal = bootstrap.Modal.getInstance(document.getElementById('followerModal'));
modal.hide();
}
function showStep2() {
document.getElementById('verify-step1').style.display = 'none';
document.getElementById('verify-step2').style.display = '';
}
</script>
<script>
new Vue({
el: '#verify-app',
vuetify: new Vuetify(),
data() {
return {
step: 1,
headers: [
{ text: '匯款人資訊', value: 'info' },
{ text: '對應信眾', value: 'f_num' },
{ text: '選擇信眾', value: 'actions', sortable: false },
{ text: '狀態 | 核對記錄', value: 'status' },0
],
items: [],
activities: [],
loading: false,
statusOptions: [
{ text: '', value: '' },
{ text: '待確認', value: '1' },
{ text: '確認', value: '2' },
{ text: '作廢', value: '3' }
],
checkStatusOptions: [
{ text: '', value: '' },
{ text: '未核對', value: '1' },
{ text: '核對', value: '2' },
{ text: '金額不符', value: '3' },
{ text: '其他問題', value: '4' },
{ text: '作廢', value: '5' }
],
follower_dialog: {
show: false,
loading: false,
search: '',
headers: [
{ text: '編號', value: 'num' },
{ text: '姓名', value: 'u_name' },
{ text: '地址', value: 'address' }
],
items: [],
selected: null,
current_item: null
},
step2Headers: [
{ text: '匯款人資訊', value: 'info' },
{ text: '匯款備註/相片', value: 'note' },
{ text: '入帳銀行/帳戶 | 支付資訊/帳號後5碼', value: 'acc_num' },
{ text: '入帳日期', value: 'check_date' },
{ text: '入帳金額', value: 'check_amount' },
{ text: '備註/狀態 | 核對記錄', value: 'check_memo' }
],
bankOptions: [],
payTypeText: {
1: '現金',
2: '匯款',
3: '支票'
},
};
},
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() {
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 = "查詢信眾失敗";
this.snackbar.show = true;
} finally {
this.follower_dialog.loading = false;
}
},
selectFollower(follower) {
if (this.follower_dialog.current_item) {
// 更新當前項目的 f_num
this.follower_dialog.current_item.f_num = follower.num;
// 更新 follower 物件
this.follower_dialog.current_item.follower = {
num: follower.num,
u_name: follower.u_name,
address: follower.address
};
}
this.follower_dialog.show = false;
},
async loadData() {
this.loading = true;
// 取得活動清單
const actRes = await axios.get('../../api/activity');
this.activities = actRes.data;
// 依 step 取得不同資料
let res;
if (this.step === 1) {
res = await axios.get('../../api/transfer_register/pending');
} else if (this.step === 2) {
res = await axios.get('../../api/transfer_register/step2_list');
}
this.items = res.data.map(item => ({
...item,
status: item.status ? String(item.status) : '',
check_status: item.check_status ? String(item.check_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
}));
this.loading = false;
},
async saveAndGoStep2() {
// 組出要更新的資料
const updateList = this.items.map(item => ({
id: item.id,
f_num: item.f_num,
status: item.status ? String(item.status) : '',
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
}));
try {
const res = await axios.post('../../api/transfer_register/batch_update', updateList);
// 檢查回傳格式
if (res.data && res.data.success) {
this.step = 2;
await this.loadData();
} else {
alert('儲存失敗,請重試!!');
}
} catch (e) {
alert('儲存失敗,請重試:' + e.message);
}
},
async submitStep2() {
// 組出要更新的資料
const updateList = this.items.map(item => ({
id: item.id,
f_num: item.f_num,
status: item.status ? String(item.status) : '',
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
}));
try {
const res = await axios.post('../../api/transfer_register/batch_update', updateList);
if (res.data && res.data.success) {
alert('送出成功!');
} else {
alert('送出失敗,請重試 :' + res.data.message);
}
} catch (e) {
alert('送出失敗,請再試一次!');
}
}
},
created() {
this.loadData();
}
});
/*
fix:
step 2 submit: f_num don't clear
*/
</script>
<style>
.mini-dropdown {
min-width: 110px !important;
font-size: 0.95rem !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
.mini-dropdown .v-list-item {
min-height: 32px !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
/* 統一所有 dense 輸入元件高度 */
.v-input.v-input--dense {
min-height: 32px !important;
height: 32px !important;
font-size: 0.95rem !important;
}
.v-input.v-input--dense .v-input__slot,
.v-input.v-input--dense .v-select__slot,
.v-input.v-input--dense .v-select__selections,
.v-input.v-input--dense .v-text-field__slot {
min-height: 32px !important;
height: 32px !important;
line-height: 32px !important;
font-size: 0.95rem !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
/* 強制覆蓋所有 Vuetify 權重,讓下拉箭頭垂直置中 */
.v-input.v-input--dense .v-input__append-inner,
.v-text-field.v-input--dense .v-input__append-inner,
.v-input__append-inner[style] {
margin-top: 0 !important;
margin-bottom: 0 !important;
align-items: center !important;
display: flex !important;
height: 32px !important;
}
.v-input.v-input--dense .v-icon {
line-height: 32px !important;
font-size: 20px !important;
}
/* 讓 v-text-field 內的 input 也置中 */
.v-input.v-input--dense input {
height: 32px !important;
line-height: 32px !important;
font-size: 0.95rem !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
/* ===== 新增step2第一欄寬度加大 ===== */
.v-data-table td:first-child, .v-data-table th:first-child {
min-width: 220px !important;
width: 220px !important;
max-width: 300px !important;
white-space: normal !important;
}
/* ===== 新增:隱藏 number 欄位上下箭頭 ===== */
.v-input input[type=number]::-webkit-inner-spin-button,
.v-input input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
.v-input input[type=number] {
-moz-appearance: textfield;
}
.step2-table tbody td{
height:90px !important;
min-height:90px !important;
}
/* 針對所有 v-select 內容垂直置中 */
.v-select__selection {
align-items: center !important;
display: flex !important;
height: 32px !important; /* 跟 input 高度一致 */
line-height: 32px !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
margin-top: 0 !important;
}
</style>
</asp:Content>