migrate to new git
This commit is contained in:
917
web/admin/transfer/balance_reconcile.aspx
Normal file
917
web/admin/transfer/balance_reconcile.aspx
Normal file
@@ -0,0 +1,917 @@
|
||||
<%@ Page Title="餘額核銷" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" EnableEventValidation="false" CodeFile="balance_reconcile.aspx.cs" Inherits="admin_transfer_balance_reconcile" %>
|
||||
<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="balance-app">
|
||||
<v-app>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<v-card-title class="bg-warning white--text text-center">
|
||||
<h5 class="mb-0">餘額核銷 - 處理剩餘金額</h5>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="items"
|
||||
:loading="loading"
|
||||
loading-text="載入中..."
|
||||
class="elevation-1 balance-reconcile-table"
|
||||
item-key="id"
|
||||
>
|
||||
<template v-slot:item.follower="{ item }">
|
||||
<div>
|
||||
<div class="font-weight-bold">
|
||||
<a
|
||||
:href="`../follower/reg.aspx?num=${item.f_num}`"
|
||||
target="_blank"
|
||||
class="text-decoration-none"
|
||||
:title="`查看 ${item.follower} 的詳細資料`"
|
||||
>
|
||||
{{ item.follower }}
|
||||
<v-icon small color="primary" class="ml-1">mdi-open-in-new</v-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="caption text--secondary" v-if="item.phone">
|
||||
{{ item.phone }}
|
||||
</div>
|
||||
<div class="caption text--secondary" v-if="item.phone2">
|
||||
{{ item.phone2 }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.bank_info="{ item }">
|
||||
<div>
|
||||
<div class="font-weight-bold">{{ item.acc_name }}</div>
|
||||
<div class="caption text--secondary">{{ item.check_date | date }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.amount_info="{ item }">
|
||||
<div>
|
||||
<div class="font-weight-bold text-primary">
|
||||
已入帳:{{ item.check_amount }}
|
||||
</div>
|
||||
<div class="text-success">
|
||||
已沖帳:{{ item.check_amount - item.remain_amount }}
|
||||
</div>
|
||||
<div class="text-warning font-weight-bold">
|
||||
待核銷:{{ item.remain_amount }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.detail_info="{ item }">
|
||||
<v-btn
|
||||
color="info"
|
||||
outlined
|
||||
@click="showDetailDialog(item)"
|
||||
>
|
||||
<v-icon small class="mr-1">mdi-information-outline</v-icon>
|
||||
詳細
|
||||
</v-btn>
|
||||
</template>
|
||||
<!-- 選取項目欄位 -->
|
||||
<template v-slot:item.select_items="{ item }">
|
||||
<div style="min-width: 200px;">
|
||||
<v-text-field
|
||||
v-model="item.selected_items_text"
|
||||
placeholder="項目名稱"
|
||||
dense
|
||||
outlined
|
||||
readonly
|
||||
hide-details
|
||||
style="font-size: 12px;"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<v-btn
|
||||
small
|
||||
color="primary"
|
||||
icon
|
||||
@click="showItemSelectDialog(item)"
|
||||
style="margin-right: -8px;"
|
||||
>
|
||||
<v-icon small>mdi-table</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 狀態與操作欄位 -->
|
||||
<template v-slot:item.status_action="{ item }">
|
||||
<div style="min-width: 280px;">
|
||||
<v-row no-gutters>
|
||||
<v-col cols="6" class="pr-1">
|
||||
<v-select
|
||||
v-model="item.new_status"
|
||||
:items="statusOptions"
|
||||
dense
|
||||
outlined
|
||||
hide-details
|
||||
placeholder="狀態"
|
||||
style="font-size: 12px;"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-col cols="6" class="pl-1">
|
||||
<v-btn
|
||||
color="primary"
|
||||
block
|
||||
@click="processBalance(item)"
|
||||
:disabled="!canUpdate(item)"
|
||||
>
|
||||
更新狀態
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row no-gutters class="mt-2">
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="item.verify_note"
|
||||
dense
|
||||
outlined
|
||||
hide-details
|
||||
placeholder="核對記錄"
|
||||
style="font-size: 12px;"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-container>
|
||||
|
||||
<!-- 餘額處理對話框 -->
|
||||
<v-dialog v-model="dialog.show" max-width="800px">
|
||||
<v-card>
|
||||
<v-card-title class="grey lighten-2">
|
||||
<v-icon color="warning" class="mr-2">mdi-cash-multiple</v-icon>
|
||||
餘額處理
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="dialog.show = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="pb-0">
|
||||
<div v-if="dialog.selected">
|
||||
<v-row class="my-3">
|
||||
<v-col cols="12" md="6">
|
||||
<div class="mb-2">
|
||||
<span class="font-weight-bold">信眾:</span>
|
||||
{{ dialog.selected.follower }}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-weight-bold">入帳銀行:</span>
|
||||
{{ dialog.selected.acc_name }}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-weight-bold">入帳日期:</span>
|
||||
{{ dialog.selected.check_date | date }}
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<div class="mb-2">
|
||||
<span class="font-weight-bold">入帳金額:</span>
|
||||
<span class="text-primary">{{ dialog.selected.check_amount | currency }}</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-weight-bold">剩餘金額:</span>
|
||||
<span class="text-warning font-weight-bold">{{ dialog.selected.remain_amount | currency }}</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-weight-bold">狀態:</span>
|
||||
<v-chip small color="warning" text-color="white">
|
||||
{{ getStatusText(dialog.selected.check_status) }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- 歷史沖帳記錄 -->
|
||||
<v-divider class="my-3"></v-divider>
|
||||
<h6 class="mb-3">
|
||||
<v-icon color="info" class="mr-1">mdi-history</v-icon>
|
||||
歷史沖帳記錄
|
||||
</h6>
|
||||
<v-data-table
|
||||
:headers="dialog.historyHeaders"
|
||||
:items="dialog.historyItems"
|
||||
class="elevation-1 mb-4"
|
||||
hide-default-footer
|
||||
:disable-pagination="true"
|
||||
dense
|
||||
>
|
||||
<template v-slot:item.reconcile_amount="{ item }">
|
||||
<span>{{ item.reconcile_amount | currency }}</span>
|
||||
</template>
|
||||
<template v-slot:item.reconcile_date="{ item }">
|
||||
<span>{{ item.reconcile_date | date }}</span>
|
||||
</template>
|
||||
</v-data-table>
|
||||
|
||||
<!-- 餘額處理選項 -->
|
||||
<v-divider class="my-3"></v-divider>
|
||||
<h6 class="mb-3">
|
||||
<v-icon color="warning" class="mr-1">mdi-cash-multiple</v-icon>
|
||||
餘額處理方式
|
||||
</h6>
|
||||
<v-radio-group v-model="dialog.balanceAction" class="mt-0">
|
||||
<v-radio
|
||||
label="轉入下次活動"
|
||||
value="transfer"
|
||||
color="primary"
|
||||
></v-radio>
|
||||
<v-radio
|
||||
label="現金退費"
|
||||
value="refund_cash"
|
||||
color="success"
|
||||
></v-radio>
|
||||
<v-radio
|
||||
label="銀行轉帳退費"
|
||||
value="refund_bank"
|
||||
color="success"
|
||||
></v-radio>
|
||||
<v-radio
|
||||
label="結餘處理"
|
||||
value="balance_off"
|
||||
color="grey"
|
||||
></v-radio>
|
||||
</v-radio-group>
|
||||
|
||||
<!-- 處理說明 -->
|
||||
<v-textarea
|
||||
v-model="dialog.memo"
|
||||
label="處理說明"
|
||||
outlined
|
||||
rows="3"
|
||||
placeholder="請輸入處理說明..."
|
||||
class="mt-3"
|
||||
></v-textarea>
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions class="pa-4">
|
||||
<div v-if="dialog.errorMessage" class="text-danger">
|
||||
❌ {{ dialog.errorMessage }}
|
||||
</div>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="grey" text @click="dialog.show = false">取消</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
@click="confirmBalance"
|
||||
:disabled="!dialog.balanceAction || dialog.loading"
|
||||
:loading="dialog.loading"
|
||||
>
|
||||
確認處理
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
|
||||
|
||||
<!-- 詳細資訊對話框 -->
|
||||
<v-dialog v-model="detailDialog.show" max-width="900px">
|
||||
<v-card>
|
||||
<v-card-title class="grey lighten-2">
|
||||
<v-icon color="info" class="mr-2">mdi-information-outline</v-icon>
|
||||
詳細資訊
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="detailDialog.show = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-tabs v-model="detailDialog.tab" background-color="grey lighten-4">
|
||||
<v-tab>
|
||||
<v-icon small class="mr-2">mdi-information</v-icon>
|
||||
基本資訊
|
||||
</v-tab>
|
||||
<v-tab>
|
||||
<v-icon small class="mr-2">mdi-table</v-icon>
|
||||
沖帳明細
|
||||
</v-tab>
|
||||
<v-tab>
|
||||
<v-icon small class="mr-2">mdi-image</v-icon>
|
||||
證明圖片
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
|
||||
<v-tabs-items v-model="detailDialog.tab">
|
||||
<!-- Tab 1: 基本資訊 -->
|
||||
<v-tab-item>
|
||||
<v-card-text>
|
||||
<div v-if="detailDialog.selected">
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<h6 class="mb-3 text-primary">
|
||||
<v-icon color="primary" small class="mr-1">mdi-account</v-icon>
|
||||
匯款人資訊
|
||||
</h6>
|
||||
<div class="mb-2">
|
||||
<span class="font-weight-bold">姓名:</span>
|
||||
{{ detailDialog.selected.name }}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-weight-bold">支付方式:</span>
|
||||
{{ payTypeText[detailDialog.selected.pay_type] || detailDialog.selected.pay_type }}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-weight-bold">帳號後5碼:</span>
|
||||
{{ detailDialog.selected.account_last5 || '-' }}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-weight-bold">支付型態:</span>
|
||||
{{ detailDialog.selected.pay_mode }}
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<h6 class="mb-3 text-success">
|
||||
<v-icon color="success" small class="mr-1">mdi-cash</v-icon>
|
||||
入帳資訊
|
||||
</h6>
|
||||
<div class="mb-2">
|
||||
<div class="font-weight-bold">{{ detailDialog.selected.acc_name }}</div>
|
||||
<div class="caption text--secondary">{{ detailDialog.selected.check_date | date }}</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-weight-bold text-primary">已入帳:{{ detailDialog.selected.check_amount }}</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="text-success">已沖帳:{{ detailDialog.selected.check_amount - detailDialog.selected.remain_amount }}</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="text-warning font-weight-bold">待核銷:{{ detailDialog.selected.remain_amount }}</span>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-divider class="my-4"></v-divider>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<h6 class="mb-3 text-purple">
|
||||
<v-icon color="purple" small class="mr-1">mdi-calendar-check</v-icon>
|
||||
活動資訊
|
||||
</h6>
|
||||
<div class="mb-2">
|
||||
<span class="font-weight-bold">活動名稱:</span>
|
||||
{{ detailDialog.selected.activity_name || '-' }}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-weight-bold">開始日期:</span>
|
||||
{{ detailDialog.selected.activity_start_date | date }}
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<h6 class="mb-3 text-info">
|
||||
<v-icon color="info" small class="mr-1">mdi-clipboard-check</v-icon>
|
||||
核對記錄
|
||||
</h6>
|
||||
<div class="mb-2">
|
||||
<span class="font-weight-bold">核對記錄:</span>
|
||||
{{ detailDialog.selected.verify_note || '-' }}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-weight-bold">帳簿備註:</span>
|
||||
{{ detailDialog.selected.check_memo || '-' }}
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-tab-item>
|
||||
|
||||
<!-- Tab 2: 沖帳明細表格 -->
|
||||
<v-tab-item>
|
||||
<v-card-text>
|
||||
<div v-if="detailDialog.selected">
|
||||
<v-data-table
|
||||
:headers="detailDialog.reconcileHeaders"
|
||||
:items="detailDialog.reconcileItems"
|
||||
class="elevation-1"
|
||||
hide-default-footer
|
||||
:disable-pagination="true"
|
||||
>
|
||||
<template v-slot:item.reconcile="{ item }">
|
||||
<span class="font-weight-bold text-success">{{ item.reconcile | currency }}</span>
|
||||
</template>
|
||||
<template v-slot:item.register_date="{ item }">
|
||||
<span>{{ item.register_date | date }}</span>
|
||||
</template>
|
||||
<template v-slot:item.price="{ item }">
|
||||
<span>{{ item.price | currency }}</span>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-tab-item>
|
||||
|
||||
<!-- Tab 3: 證明圖片 -->
|
||||
<v-tab-item>
|
||||
<v-card-text>
|
||||
<div v-if="detailDialog.selected">
|
||||
<div v-if="detailDialog.selected.proof_img">
|
||||
<h6 class="mb-3 text-orange">
|
||||
<v-icon color="orange" small class="mr-1">mdi-image</v-icon>
|
||||
證明圖片
|
||||
</h6>
|
||||
<div class="text-center">
|
||||
<img
|
||||
:src="detailDialog.selected.proof_img"
|
||||
alt="證明圖片"
|
||||
style="max-width: 100%; max-height: 500px;"
|
||||
class="elevation-3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-center py-5">
|
||||
<v-icon size="64" color="grey lighten-2">mdi-image-off</v-icon>
|
||||
<div class="mt-3 text--secondary">無證明圖片</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-tab-item>
|
||||
</v-tabs-items>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 活動品項選擇對話框 -->
|
||||
<v-dialog v-model="search_dialog.show" max-width="700px">
|
||||
<v-card>
|
||||
<v-card-title class="justify-space-between grey lighten-2">
|
||||
查詢:{{search_dialog.current.title}}
|
||||
<v-btn icon @click="search_dialog.show=false"><v-icon>mdi-close</v-icon></v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text >
|
||||
<v-row>
|
||||
|
||||
<v-col v-for="item in search_dialog.current.keys"
|
||||
:cols="search_dialog.current.keys.length>1?6:12" >
|
||||
<v-text-field v-model="item.value" :label="item.title" v-if="item.visible===undefined || item.visible==true "></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="4" md="3">
|
||||
<v-checkbox
|
||||
v-model="search_is_reconcile"
|
||||
label="核銷項目"
|
||||
:true-value="'Y'"
|
||||
:false-value="''"
|
||||
hide-details
|
||||
@change="search_get"
|
||||
></v-checkbox>
|
||||
</v-col>
|
||||
<v-col cols="12" class="text-end">
|
||||
<v-btn color="primary" elevation="0" @click="search_get()">查詢</v-btn>
|
||||
<v-btn elevation="0" @click="search_clear()">清除條件</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-data-table
|
||||
:headers="search_headers()"
|
||||
:items="search_dialog.list"
|
||||
:footer-props="search_dialog.footer"
|
||||
:items-per-page="10"
|
||||
:server-items-length="search_dialog.count"
|
||||
:page.sync="search_dialog.page"
|
||||
:options.sync="options"
|
||||
@click:row="search_select"
|
||||
:loading="search_dialog.loading"
|
||||
></v-data-table>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-app>
|
||||
</div>
|
||||
</asp:Content>
|
||||
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" runat="Server">
|
||||
<style>
|
||||
.code-textarea textarea {
|
||||
font-family: 'Courier New', monospace !important;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
/* 增加表格每列高度,讓內容不擁擠 */
|
||||
.v-data-table.balance-reconcile-table .v-data-table__wrapper tr {
|
||||
}
|
||||
.v-data-table.balance-reconcile-table .v-data-table__wrapper td {
|
||||
min-height: 110px !important;
|
||||
height: 110px !important;
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
</style>
|
||||
<script src="draft-utils.js"></script>
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#balance-app',
|
||||
vuetify: new Vuetify(),
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
headers: [
|
||||
{ text: '信眾', value: 'follower' },
|
||||
{ text: '入帳帳戶/日期', value: 'bank_info' },
|
||||
{ text: '金額', value: 'amount_info' },
|
||||
{ text: '詳細資訊', value: 'detail_info', sortable: false },
|
||||
{ text: '選取項目', value: 'select_items', sortable: false, width: '200px' },
|
||||
{ text: '狀態 | 核對記錄', value: 'status_action', sortable: false, width: '280px' }
|
||||
],
|
||||
items: [],
|
||||
dialog: {
|
||||
show: false,
|
||||
selected: null,
|
||||
balanceAction: null,
|
||||
memo: '',
|
||||
loading: false,
|
||||
errorMessage: '',
|
||||
historyHeaders: [
|
||||
{ text: '法會', value: 'activity_name' },
|
||||
{ text: '項目', value: 'item_name' },
|
||||
{ text: '沖帳金額', value: 'reconcile_amount' },
|
||||
{ text: '沖帳日期', value: 'reconcile_date' }
|
||||
],
|
||||
historyItems: [
|
||||
{
|
||||
activity_name: '2025新春法會',
|
||||
item_name: '護持金',
|
||||
reconcile_amount: 12000,
|
||||
reconcile_date: '2025-01-15T00:00:00'
|
||||
}
|
||||
]
|
||||
},
|
||||
detailDialog: {
|
||||
show: false,
|
||||
selected: null,
|
||||
tab: 0,
|
||||
reconcileHeaders: [
|
||||
{ text: '法會活動', value: 'activity_name' },
|
||||
{ text: '報名項目', value: 'actitem_name' },
|
||||
{ text: '原始金額', value: 'price' },
|
||||
{ text: '沖帳金額', value: 'reconcile' },
|
||||
{ text: '報名日期', value: 'register_date' },
|
||||
{ text: '訂單編號', value: 'order_no' }
|
||||
],
|
||||
reconcileItems: []
|
||||
},
|
||||
statusOptions: [
|
||||
{ text: '沖帳有剩餘', value: '90' },
|
||||
{ text: '未聯絡', value: '91' },
|
||||
{ text: '已聯絡', value: '92' },
|
||||
{ text: '餘額核銷', value: '95' }
|
||||
],
|
||||
search_dialog: {
|
||||
controls: {
|
||||
search5: {
|
||||
id: 'search5',
|
||||
title: '活動品項',
|
||||
text_prop: 'subject',
|
||||
value_prop: 'num',
|
||||
keys: [
|
||||
{ id: 'subject', title: '項目名稱', value: '' },
|
||||
{ id: 'kindTxt', title: '項目分類' },
|
||||
{ id: 'num', visible: false },
|
||||
],
|
||||
api_url: '../../api/activity/GetOrderList',
|
||||
columns: [
|
||||
{ id: 'subject', title: '項目名稱' },
|
||||
{ id: 'kindTxt', title: '項目分類' },
|
||||
{ id: 'price', title: '價格' },
|
||||
],
|
||||
selected: {},
|
||||
currentRow: null, // 儲存當前正在編輯的行
|
||||
select(vueInstance, selectedItem) {
|
||||
// 當選擇項目時的處理邏輯
|
||||
if (this.currentRow) {
|
||||
// 將選中的 actItem 的 num 儲存到 balance_act_item 欄位
|
||||
this.currentRow.balance_act_item = selectedItem.num;
|
||||
// 顯示項目名稱給用戶看
|
||||
this.currentRow.selected_items_text = selectedItem.subject;
|
||||
// 如果有價格,也可以顯示
|
||||
if (selectedItem.price) {
|
||||
this.currentRow.selected_items_text += ` (NT$${Number(selectedItem.price).toLocaleString()})`;
|
||||
}
|
||||
console.log('已選擇項目:', selectedItem.subject, '項目編號:', selectedItem.num);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
show: false,
|
||||
current: {},
|
||||
list: [],
|
||||
count: 0,
|
||||
page: 1,
|
||||
loading: false,
|
||||
footer: {
|
||||
showFirstLastPage: true,
|
||||
disableItemsPerPage: true,
|
||||
itemsPerPageAllText: '',
|
||||
itemsPerPageText: '',
|
||||
},
|
||||
},
|
||||
options: {},
|
||||
payTypeText: {
|
||||
1: '現金',
|
||||
2: '匯款',
|
||||
3: '支票'
|
||||
},
|
||||
search_is_reconcile: "Y",
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadTableData();
|
||||
this.search_dialog.current = this.search_dialog.controls.search5; // 設定預設搜尋控制項
|
||||
},
|
||||
filters: {
|
||||
currency(val) {
|
||||
if (!val) return '0';
|
||||
return Number(val).toLocaleString();
|
||||
},
|
||||
date(val) {
|
||||
if (!val) return '';
|
||||
const date = new Date(val);
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 使用全域 DraftUtils 工具函數
|
||||
loadTableData() {
|
||||
this.loading = true;
|
||||
axios.get('../../api/transfer_register/balance_reconcile_list')
|
||||
.then(res => {
|
||||
this.items = res.data.map(item => ({
|
||||
...item,
|
||||
// 初始化表單欄位
|
||||
selected_items_text: item.balance_act_item ? (item.balance_actitem_name || '已選擇項目') : '', // 如果已有選中項目,顯示項目名稱
|
||||
new_status: item.check_status,
|
||||
balance_act_item: item.balance_act_item, // 保持現有的選中項目
|
||||
verify_note: item.verify_note || '' // 初始化核對記錄欄位
|
||||
}));
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('載入資料失敗:', err);
|
||||
alert('載入資料失敗,請重新整理頁面');
|
||||
this.items = [];
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
getStatusColor(status) {
|
||||
switch(status) {
|
||||
case '90': return 'warning';
|
||||
case '99': return 'success';
|
||||
default: return 'grey';
|
||||
}
|
||||
},
|
||||
getStatusText(status) {
|
||||
switch(status) {
|
||||
case '90': return '沖帳有剩餘';
|
||||
case '91': return '未聯絡';
|
||||
case '92': return '已聯絡';
|
||||
case '95': return '餘額核銷';
|
||||
case '99': return '沖帳完成';
|
||||
default: return '未知狀態';
|
||||
}
|
||||
},
|
||||
showBalanceDialog(item) {
|
||||
this.dialog.selected = item;
|
||||
this.dialog.balanceAction = null;
|
||||
this.dialog.memo = '';
|
||||
this.dialog.errorMessage = '';
|
||||
this.dialog.show = true;
|
||||
|
||||
// 載入歷史記錄
|
||||
if (item.draft) {
|
||||
const detailItems = window.DraftUtils.getDraftField(item.draft, 'pro_order_detail_items');
|
||||
if (detailItems && Array.isArray(detailItems)) {
|
||||
this.dialog.historyItems = detailItems.map(item => ({
|
||||
activity_name: item.activity_name || '未知活動',
|
||||
item_name: item.actitem_name || '未知項目',
|
||||
reconcile_amount: item.reconcile || 0,
|
||||
reconcile_date: item.register_date || '2025-01-15T00:00:00'
|
||||
}));
|
||||
} else {
|
||||
this.dialog.historyItems = [];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
confirmBalance() {
|
||||
if (!this.dialog.balanceAction) {
|
||||
this.dialog.errorMessage = '請選擇處理方式';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.dialog.memo.trim()) {
|
||||
this.dialog.errorMessage = '請輸入處理說明';
|
||||
return;
|
||||
}
|
||||
|
||||
this.dialog.loading = true;
|
||||
this.dialog.errorMessage = '';
|
||||
|
||||
// 模擬 API 調用
|
||||
setTimeout(() => {
|
||||
alert(`餘額處理完成:${this.getActionText(this.dialog.balanceAction)}`);
|
||||
this.dialog.show = false;
|
||||
this.dialog.loading = false;
|
||||
// 這裡之後會重新載入資料
|
||||
}, 1500);
|
||||
},
|
||||
getActionText(action) {
|
||||
switch(action) {
|
||||
case 'transfer': return '轉入下次活動';
|
||||
case 'refund_cash': return '現金退費';
|
||||
case 'refund_bank': return '銀行轉帳退費';
|
||||
case 'balance_off': return '結餘處理';
|
||||
default: return '未知操作';
|
||||
}
|
||||
},
|
||||
showDetailDialog(item) {
|
||||
this.detailDialog.selected = item;
|
||||
this.detailDialog.tab = 0; // 重置到第一個tab
|
||||
|
||||
// 解析沖帳明細 JSON 並轉為表格資料
|
||||
this.loadReconcileItems(item.draft, item.id);
|
||||
|
||||
this.detailDialog.show = true;
|
||||
},
|
||||
async loadReconcileItems(draft, transferRegisterId) {
|
||||
this.detailDialog.reconcileItems = [];
|
||||
|
||||
if (!draft) return;
|
||||
|
||||
const detailItems = window.DraftUtils.getDraftField(draft, 'pro_order_detail_items');
|
||||
if (detailItems && Array.isArray(detailItems)) {
|
||||
// 使用新格式的 pro_order_detail_items
|
||||
this.detailDialog.reconcileItems = detailItems.map((item, index) => {
|
||||
return {
|
||||
pro_order_detail_num: item.pro_order_detail_num,
|
||||
activity_name: item.activity_name || '未知活動',
|
||||
actitem_name: item.actitem_name || '未知項目',
|
||||
price: item.price || 0,
|
||||
reconcile: item.reconcile,
|
||||
register_date: item.register_date,
|
||||
order_no: item.order_no || ''
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// 如果沒有詳細資料,嘗試使用 API 取得資料
|
||||
try {
|
||||
const response = await axios.get(`../../api/transfer_register/reconcile_detail?transfer_register_id=${transferRegisterId}`);
|
||||
this.detailDialog.reconcileItems = response.data;
|
||||
} catch (error) {
|
||||
console.error('載入沖帳明細失敗:', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
search_show(curr) {
|
||||
this.search_dialog.current = curr;
|
||||
this.search_clear();
|
||||
this.search_dialog.show = true;
|
||||
},
|
||||
search_clear() {
|
||||
if (!this.search_dialog.current.keys) return;
|
||||
this.search_dialog.current.keys.forEach((t, i) => { t.value = '' });
|
||||
this.search_get();
|
||||
},
|
||||
search_get() {
|
||||
if (!this.search_dialog.current.keys) return;
|
||||
let api_url = this.search_dialog.current.api_url;
|
||||
let keys = this.search_dialog.current.keys;
|
||||
|
||||
this.search_dialog.page = this.options.page ?? 1;
|
||||
let params = { page: this.search_dialog.page, pageSize: 10 };
|
||||
var search = {};
|
||||
keys.forEach((t, i) => {
|
||||
search[t.id] = t.value;
|
||||
});
|
||||
|
||||
// 只有活動品項 Dialog 查詢時才帶 is_reconcile
|
||||
if (this.search_dialog.current.id === 'search5' && this.search_is_reconcile === 'Y') {
|
||||
search.is_reconcile = 'Y';
|
||||
}
|
||||
|
||||
console.log("search_get", api_url, search, params, this.options);
|
||||
this.search_dialog.loading = true;
|
||||
|
||||
axios.post(api_url, search, { params: params })
|
||||
.then(response => {
|
||||
this.search_dialog.list = response.data.list;
|
||||
this.search_dialog.count = response.data.count;
|
||||
this.search_dialog.loading = false;
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
this.search_dialog.list = [];
|
||||
this.search_dialog.count = 0;
|
||||
this.search_dialog.loading = false;
|
||||
alert("錯誤:" + error);
|
||||
});
|
||||
},
|
||||
search_headers() {
|
||||
if (!this.search_dialog.current.columns) return;
|
||||
let r = [];
|
||||
this.search_dialog.current.columns.forEach((t, i) => {
|
||||
r.push({
|
||||
text: t.title,
|
||||
width: t.width,
|
||||
align: 'start',
|
||||
sortable: false,
|
||||
value: t.id,
|
||||
});
|
||||
});
|
||||
return r;
|
||||
},
|
||||
search_select(row) {
|
||||
let curr = this.search_dialog.current;
|
||||
curr.selected = row;
|
||||
|
||||
if (curr.select instanceof Function) {
|
||||
curr.select(this, row);
|
||||
}
|
||||
this.search_dialog.show = false;
|
||||
console.log('Selected row:', row);
|
||||
},
|
||||
showItemSelectDialog(item) {
|
||||
// 設定當前正在編輯的行
|
||||
this.search_dialog.controls.search5.currentRow = item;
|
||||
this.search_dialog.current = this.search_dialog.controls.search5;
|
||||
this.search_is_reconcile = "Y"; // 預設勾選
|
||||
this.search_clear();
|
||||
this.search_get(); // Dialog 一打開就查詢
|
||||
this.search_dialog.show = true;
|
||||
},
|
||||
processBalance(item) {
|
||||
if (!item.new_status) {
|
||||
alert('請選擇狀態');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!item.balance_act_item) {
|
||||
alert('請先選擇項目');
|
||||
return;
|
||||
}
|
||||
|
||||
// 準備更新資料
|
||||
const updateData = {
|
||||
id: item.id,
|
||||
check_status: item.new_status,
|
||||
balance_act_item: item.balance_act_item,
|
||||
verify_note: item.verify_note || ''
|
||||
};
|
||||
|
||||
// 發送到後端更新
|
||||
axios.post('../../api/transfer_register/update_balance', updateData)
|
||||
.then(response => {
|
||||
const statusText = this.statusOptions.find(opt => opt.value === item.new_status)?.text || '';
|
||||
alert(`核銷餘額處理完成\n信眾:${item.follower}\n狀態:${statusText}\n項目:${item.selected_items_text}`);
|
||||
|
||||
// 如果狀態是「餘額核銷」(95),則從表格中移除該列
|
||||
if (item.new_status === '95') {
|
||||
const index = this.items.findIndex(i => i.id === item.id);
|
||||
if (index !== -1) {
|
||||
this.items.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
// 如果狀態不是「餘額核銷」,則只更新該列的狀態
|
||||
const index = this.items.findIndex(i => i.id === item.id);
|
||||
if (index !== -1) {
|
||||
this.items[index].check_status = item.new_status;
|
||||
this.items[index].verify_note = item.verify_note || '';
|
||||
this.items[index].balance_act_item = item.balance_act_item;
|
||||
// 確保 selected_items_text 也得到更新
|
||||
this.items[index].selected_items_text = item.selected_items_text;
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('更新失敗:', error);
|
||||
alert('更新失敗,請重試');
|
||||
});
|
||||
},
|
||||
canUpdate(item) {
|
||||
// 所有狀態都必須選取項目才可按
|
||||
if (!item.balance_act_item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 狀態為「餘額核銷」時,必須選取項目才可按
|
||||
if (item.new_status === '95') {
|
||||
return true;
|
||||
}
|
||||
// 狀態為「未聯絡」或「已聯絡」時,皆可按
|
||||
if (item.new_status === '91' || item.new_status === '92') {
|
||||
return true;
|
||||
}
|
||||
// 其他狀態不可按
|
||||
return false;
|
||||
},
|
||||
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</asp:Content>
|
||||
10
web/admin/transfer/balance_reconcile.aspx.cs
Normal file
10
web/admin/transfer/balance_reconcile.aspx.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Web.UI;
|
||||
|
||||
public partial class admin_transfer_balance_reconcile : MyWeb.config
|
||||
{
|
||||
protected void Page_Load(object sender, EventArgs e)
|
||||
{
|
||||
// 頁面初始化(暫無邏輯)
|
||||
}
|
||||
}
|
||||
317
web/admin/transfer/balance_reconcile_query.aspx
Normal file
317
web/admin/transfer/balance_reconcile_query.aspx
Normal file
@@ -0,0 +1,317 @@
|
||||
<%@ Page Title="餘額核銷查詢" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" EnableEventValidation="false" CodeFile="balance_reconcile_query.aspx.cs" Inherits="admin_transfer_balance_reconcile_query" %>
|
||||
<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="balance-query-app">
|
||||
<v-app>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<v-card-title class="bg-info white--text text-center">
|
||||
<h5 class="mb-0">餘額核銷查詢</h5>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-row class="mb-0 mt-4">
|
||||
<v-col cols="12" md="2">
|
||||
<v-text-field v-model="query.start_date" label="起始日" type="date" dense outlined></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="2">
|
||||
<v-text-field v-model="query.end_date" label="結束日" type="date" dense outlined></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="3">
|
||||
<v-text-field v-model="query.activity_name" label="法會" dense outlined readonly>
|
||||
<template v-slot:append>
|
||||
<v-btn icon @click="showActivityDialog"><v-icon>mdi-magnify</v-icon></v-btn>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="3">
|
||||
<v-text-field v-model="query.follower_name" label="信眾" dense outlined readonly>
|
||||
<template v-slot:append>
|
||||
<v-btn icon @click="showFollowerDialog"><v-icon>mdi-magnify</v-icon></v-btn>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="2" class="d-flex justify-end">
|
||||
<v-btn color="primary" class="mr-2" @click="search">查詢</v-btn>
|
||||
<v-btn color="grey" @click="reset">重設</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="items"
|
||||
:loading="loading"
|
||||
loading-text="載入中..."
|
||||
class="elevation-1 balance-reconcile-table"
|
||||
item-key="id"
|
||||
:footer-props="{ 'items-per-page-options': [10, 20, 50] }"
|
||||
>
|
||||
<template v-slot:item.follower="{ item }">
|
||||
<div>
|
||||
<div class="font-weight-bold">{{ item.follower }}</div>
|
||||
<div class="caption text--secondary" v-if="item.phone">{{ item.phone }}</div>
|
||||
<div class="caption text--secondary" v-if="item.phone2">{{ item.phone2 }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.bank_info="{ item }">
|
||||
<div>
|
||||
<div class="font-weight-bold">{{ item.acc_name }}</div>
|
||||
<div class="caption text--secondary">{{ item.check_date | date }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.amount_info="{ item }">
|
||||
<div>
|
||||
<div class="font-weight-bold text-primary">入帳金額:{{ item.check_amount || 0 }}</div>
|
||||
<div class="text-success">核銷金額:{{ (item.remain_amount || 0) }}</div>
|
||||
<!-- 狀態95不會有待核銷,不顯示這一行 -->
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.remain_amount="{ item }">
|
||||
<div class="text-success">{{ item.remain_amount || 0 }}</div>
|
||||
</template>
|
||||
<template v-slot:item.select_items="{ item }">
|
||||
<div style="min-width: 120px;">
|
||||
{{ item.balance_actitem_name || '未選擇項目' }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.detail_info="{ item }">
|
||||
<v-btn color="info" outlined @click="showDetailDialog(item)">
|
||||
<v-icon small class="mr-1">mdi-information-outline</v-icon> 詳細
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-slot:item.verify_note="{ item }">
|
||||
<div style="white-space: pre-line;">{{ item.verify_note }}</div>
|
||||
</template>
|
||||
<template v-slot:item.check_date="{ item }">
|
||||
<span>{{ item.check_date | date }}</span>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 法會選取 Dialog -->
|
||||
<v-dialog v-model="activityDialog.show" max-width="700px">
|
||||
<v-card>
|
||||
<v-card-title class="grey lighten-2">
|
||||
<v-icon color="primary" class="mr-2">mdi-table</v-icon>
|
||||
選擇法會
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="activityDialog.show = false"><v-icon>mdi-close</v-icon></v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text class="mt-4">
|
||||
<v-text-field v-model="activityDialog.search" label="搜尋法會" dense outlined @keyup.enter="searchActivity"></v-text-field>
|
||||
<v-data-table
|
||||
:headers="activityDialog.headers"
|
||||
:items="activityDialog.items"
|
||||
:loading="activityDialog.loading"
|
||||
item-key="num"
|
||||
class="elevation-1 mt-2"
|
||||
@click:row="selectActivity"
|
||||
hide-default-footer
|
||||
></v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 信眾選取 Dialog -->
|
||||
<v-dialog v-model="followerDialog.show" max-width="700px">
|
||||
<v-card>
|
||||
<v-card-title class="grey lighten-2">
|
||||
<v-icon color="primary" class="mr-2">mdi-account</v-icon>
|
||||
選擇信眾
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="followerDialog.show = false"><v-icon>mdi-close</v-icon></v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text class="mt-4">
|
||||
<v-text-field v-model="followerDialog.search" label="搜尋信眾" dense outlined @keyup.enter="searchFollower"></v-text-field>
|
||||
<v-data-table
|
||||
:headers="followerDialog.headers"
|
||||
:items="followerDialog.items"
|
||||
:loading="followerDialog.loading"
|
||||
item-key="num"
|
||||
class="elevation-1 mt-2"
|
||||
@click:row="selectFollower"
|
||||
hide-default-footer
|
||||
></v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 詳細資訊對話框 -->
|
||||
<v-dialog v-model="dialog.show" max-width="900px">
|
||||
<v-card>
|
||||
<v-card-title class="grey lighten-2">
|
||||
<v-icon color="info" class="mr-2">mdi-information-outline</v-icon>
|
||||
詳細資訊
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="dialog.show = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<div v-if="dialog.selected">
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<h6 class="mb-3 text-primary">匯款人資訊</h6>
|
||||
<div class="mb-2"><span class="font-weight-bold">姓名:</span>{{ dialog.selected.name }}</div>
|
||||
<div class="mb-2"><span class="font-weight-bold">電話:</span>{{ dialog.selected.phone }}</div>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<h6 class="mb-3 text-success">入帳資訊</h6>
|
||||
<div class="mb-2"><span class="font-weight-bold">入帳帳戶:</span>{{ dialog.selected.acc_name }}</div>
|
||||
<div class="mb-2"><span class="font-weight-bold">入帳日期:</span>{{ dialog.selected.check_date | date }}</div>
|
||||
<div class="mb-2"><span class="font-weight-bold">入帳金額:</span>{{ dialog.selected.check_amount }}</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-divider class="my-4"></v-divider>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<h6 class="mb-3 text-purple">活動資訊</h6>
|
||||
<div class="mb-2"><span class="font-weight-bold">活動名稱:</span>{{ dialog.selected.activity_name || '-' }}</div>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<h6 class="mb-3 text-info">核對記錄</h6>
|
||||
<div class="mb-2"><span class="font-weight-bold">核對記錄:</span>{{ dialog.selected.verify_note || '-' }}</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-container>
|
||||
</v-app>
|
||||
</div>
|
||||
</asp:Content>
|
||||
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" runat="Server">
|
||||
<style>
|
||||
.v-data-table.balance-reconcile-table .v-data-table__wrapper tbody tr {
|
||||
min-height: 90px !important;
|
||||
height: 90px !important;
|
||||
}
|
||||
.v-data-table.balance-reconcile-table .v-data-table__wrapper tbody td {
|
||||
min-height: 90px !important;
|
||||
height: 90px !important;
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#balance-query-app',
|
||||
vuetify: new Vuetify(),
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
query: {
|
||||
start_date: '',
|
||||
end_date: '',
|
||||
activity_num: '',
|
||||
activity_name: '',
|
||||
follower_num: '',
|
||||
follower_name: ''
|
||||
},
|
||||
headers: [
|
||||
{ text: '信眾', value: 'follower' },
|
||||
{ text: '入帳帳戶/日期', value: 'bank_info' },
|
||||
{ text: '核銷日期', value: 'check_date', sortable: true, width: '120px' },
|
||||
{ text: '核銷項目', value: 'select_items', sortable: false, width: '120px' },
|
||||
{ text: '核銷金額', value: 'reconcile_amount', sortable: false, width: '120px' },
|
||||
{ text: '詳細資訊', value: 'detail_info', sortable: false },
|
||||
// { text: '狀態', value: 'status', sortable: false, width: '100px' }, // 移除狀態欄
|
||||
{ text: '核對記錄', value: 'verify_note', sortable: false, width: '180px' },
|
||||
],
|
||||
items: [],
|
||||
dialog: {
|
||||
show: false,
|
||||
selected: null
|
||||
},
|
||||
activityDialog: {
|
||||
show: false,
|
||||
search: '',
|
||||
loading: false,
|
||||
items: [],
|
||||
headers: [
|
||||
{ text: '編號', value: 'num' },
|
||||
{ text: '名稱', value: 'subject' }
|
||||
]
|
||||
},
|
||||
followerDialog: {
|
||||
show: false,
|
||||
search: '',
|
||||
loading: false,
|
||||
items: [],
|
||||
headers: [
|
||||
{ text: '編號', value: 'num' },
|
||||
{ text: '姓名', value: 'u_name' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
date(val) {
|
||||
if (!val) return '';
|
||||
const date = new Date(val);
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 使用全域 DraftUtils 工具函數
|
||||
search() {
|
||||
this.loading = true;
|
||||
// 串接查詢 API,帶入所有查詢條件
|
||||
axios.get('../../api/transfer_register/balance_reconcile_query', { params: this.query })
|
||||
.then(res => { this.items = res.data; })
|
||||
.catch(() => { this.items = []; })
|
||||
.finally(() => { this.loading = false; });
|
||||
},
|
||||
reset() {
|
||||
this.query.start_date = '';
|
||||
this.query.end_date = '';
|
||||
this.query.activity_num = '';
|
||||
this.query.activity_name = '';
|
||||
this.query.follower_num = '';
|
||||
this.query.follower_name = '';
|
||||
this.items = [];
|
||||
},
|
||||
showDetailDialog(item) {
|
||||
this.dialog.selected = item;
|
||||
this.dialog.show = true;
|
||||
},
|
||||
showActivityDialog() {
|
||||
this.activityDialog.show = true;
|
||||
this.searchActivity();
|
||||
},
|
||||
searchActivity() {
|
||||
this.activityDialog.loading = true;
|
||||
axios.get('../../api/activity', { params: { keyword: this.activityDialog.search } })
|
||||
.then(res => { this.activityDialog.items = res.data; })
|
||||
.catch(() => { this.activityDialog.items = []; })
|
||||
.finally(() => { this.activityDialog.loading = false; });
|
||||
},
|
||||
selectActivity(row) {
|
||||
this.query.activity_num = row.num;
|
||||
this.query.activity_name = row.subject;
|
||||
this.activityDialog.show = false;
|
||||
},
|
||||
showFollowerDialog() {
|
||||
this.followerDialog.show = true;
|
||||
this.searchFollower();
|
||||
},
|
||||
searchFollower() {
|
||||
this.followerDialog.loading = true;
|
||||
axios.post('../../api/follower/GetList', { u_name: this.followerDialog.search }, { params: { page: 1, pageSize: 10 } })
|
||||
.then(res => { this.followerDialog.items = res.data.list; })
|
||||
.catch(() => { this.followerDialog.items = []; })
|
||||
.finally(() => { this.followerDialog.loading = false; });
|
||||
},
|
||||
selectFollower(row) {
|
||||
this.query.follower_num = row.num;
|
||||
this.query.follower_name = row.u_name;
|
||||
this.followerDialog.show = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</asp:Content>
|
||||
10
web/admin/transfer/balance_reconcile_query.aspx.cs
Normal file
10
web/admin/transfer/balance_reconcile_query.aspx.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Web.UI;
|
||||
|
||||
public partial class admin_transfer_balance_reconcile_query : MyWeb.config
|
||||
{
|
||||
protected void Page_Load(object sender, EventArgs e)
|
||||
{
|
||||
// 頁面初始化(暫無邏輯)
|
||||
}
|
||||
}
|
||||
257
web/admin/transfer/draft-utils.js
Normal file
257
web/admin/transfer/draft-utils.js
Normal file
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
* Draft 欄位操作工具函數
|
||||
* 確保 transfer_draft 和 pro_order_detail_items 的操作互不影響
|
||||
*/
|
||||
|
||||
// 解析 draft 欄位,返回標準化的物件格式
|
||||
function getDraftObject(draft) {
|
||||
if (!draft || !draft.trim()) return {};
|
||||
try {
|
||||
const draftObj = JSON.parse(draft);
|
||||
// 確保是物件格式
|
||||
if (typeof draftObj === 'object' && !Array.isArray(draftObj)) {
|
||||
return draftObj;
|
||||
} else if (Array.isArray(draftObj)) {
|
||||
// 舊格式:轉換為新格式
|
||||
return { transfer_draft: draftObj };
|
||||
}
|
||||
return {};
|
||||
} catch (e) {
|
||||
console.error('解析 draft JSON 失敗:', e);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// 安全地取得 draft 物件中的特定欄位
|
||||
function getDraftField(draft, fieldName) {
|
||||
const draftObj = getDraftObject(draft);
|
||||
return draftObj[fieldName] || null;
|
||||
}
|
||||
|
||||
// 安全地更新 draft 物件中的特定欄位,保持其他欄位不變
|
||||
function updateDraftField(draft, fieldName, newValue) {
|
||||
const draftObj = getDraftObject(draft);
|
||||
return {
|
||||
...draftObj,
|
||||
[fieldName]: newValue
|
||||
};
|
||||
}
|
||||
|
||||
// 檢查是否有 transfer_draft 資料
|
||||
function hasTransferDraft(draft) {
|
||||
const transferDraft = getDraftField(draft, 'transfer_draft');
|
||||
return transferDraft && Array.isArray(transferDraft) && transferDraft.length > 0;
|
||||
}
|
||||
|
||||
// 檢查是否有 pro_order_detail_items 資料
|
||||
function hasProOrderDetailItems(draft) {
|
||||
const detailItems = getDraftField(draft, 'pro_order_detail_items');
|
||||
return detailItems && Array.isArray(detailItems) && detailItems.length > 0;
|
||||
}
|
||||
|
||||
// 清除特定的 draft 欄位
|
||||
function clearDraftField(draft, fieldName) {
|
||||
const draftObj = getDraftObject(draft);
|
||||
const newDraftObj = { ...draftObj };
|
||||
delete newDraftObj[fieldName];
|
||||
return newDraftObj;
|
||||
}
|
||||
|
||||
// 合併兩個 draft 物件
|
||||
function mergeDraftObjects(draft1, draft2) {
|
||||
const obj1 = getDraftObject(draft1);
|
||||
const obj2 = getDraftObject(draft2);
|
||||
return {
|
||||
...obj1,
|
||||
...obj2
|
||||
};
|
||||
}
|
||||
|
||||
// 驗證 draft 物件格式
|
||||
function validateDraftFormat(draft) {
|
||||
if (!draft || !draft.trim()) return { valid: true, format: 'empty' };
|
||||
|
||||
try {
|
||||
const draftObj = JSON.parse(draft);
|
||||
|
||||
if (Array.isArray(draftObj)) {
|
||||
return { valid: true, format: 'legacy_array' };
|
||||
} else if (typeof draftObj === 'object') {
|
||||
const hasTransferDraft = draftObj.transfer_draft && Array.isArray(draftObj.transfer_draft);
|
||||
const hasDetailItems = draftObj.pro_order_detail_items && Array.isArray(draftObj.pro_order_detail_items);
|
||||
|
||||
if (hasTransferDraft || hasDetailItems) {
|
||||
return {
|
||||
valid: true,
|
||||
format: 'new_object',
|
||||
hasTransferDraft,
|
||||
hasDetailItems
|
||||
};
|
||||
} else {
|
||||
return { valid: false, error: '無效的物件格式' };
|
||||
}
|
||||
} else {
|
||||
return { valid: false, error: '不是有效的 JSON 物件或陣列' };
|
||||
}
|
||||
} catch (e) {
|
||||
return { valid: false, error: 'JSON 解析失敗' };
|
||||
}
|
||||
}
|
||||
|
||||
// 取得 draft 的摘要資訊
|
||||
function getDraftSummary(draft) {
|
||||
if (!draft || !draft.trim()) return { hasData: false };
|
||||
|
||||
const draftObj = getDraftObject(draft);
|
||||
const transferDraft = draftObj.transfer_draft || [];
|
||||
const detailItems = draftObj.pro_order_detail_items || [];
|
||||
|
||||
return {
|
||||
hasData: transferDraft.length > 0 || detailItems.length > 0,
|
||||
transferDraftCount: transferDraft.length,
|
||||
detailItemsCount: detailItems.length,
|
||||
totalReconcileAmount: detailItems.reduce((sum, item) => sum + (Number(item.reconcile) || 0), 0),
|
||||
format: validateDraftFormat(draft).format
|
||||
};
|
||||
}
|
||||
|
||||
// 將舊格式轉換為新格式
|
||||
function migrateLegacyFormat(draft) {
|
||||
if (!draft || !draft.trim()) return '';
|
||||
|
||||
try {
|
||||
const draftObj = JSON.parse(draft);
|
||||
if (Array.isArray(draftObj)) {
|
||||
// 舊格式:轉換為新格式
|
||||
const newDraftObj = { transfer_draft: draftObj };
|
||||
return JSON.stringify(newDraftObj);
|
||||
}
|
||||
// 已經是物件格式,直接返回
|
||||
return draft;
|
||||
} catch (e) {
|
||||
console.error('遷移舊格式失敗:', e);
|
||||
return draft;
|
||||
}
|
||||
}
|
||||
|
||||
// 清理空的 draft 欄位
|
||||
function cleanEmptyDraft(draft) {
|
||||
if (!draft || !draft.trim()) return '';
|
||||
|
||||
try {
|
||||
const draftObj = JSON.parse(draft);
|
||||
const cleaned = {};
|
||||
|
||||
if (draftObj.transfer_draft && Array.isArray(draftObj.transfer_draft) && draftObj.transfer_draft.length > 0) {
|
||||
cleaned.transfer_draft = draftObj.transfer_draft;
|
||||
}
|
||||
|
||||
if (draftObj.pro_order_detail_items && Array.isArray(draftObj.pro_order_detail_items) && draftObj.pro_order_detail_items.length > 0) {
|
||||
cleaned.pro_order_detail_items = draftObj.pro_order_detail_items;
|
||||
}
|
||||
|
||||
return Object.keys(cleaned).length > 0 ? JSON.stringify(cleaned) : '';
|
||||
} catch (e) {
|
||||
console.error('清理 draft 失敗:', e);
|
||||
return draft;
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化 draft JSON 為可讀字串
|
||||
function formatDraftJson(draft) {
|
||||
if (!draft) return '無記錄';
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(draft), null, 2);
|
||||
} catch(e) {
|
||||
return draft;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用範例:
|
||||
/*
|
||||
// 1. 更新 transfer_draft
|
||||
const currentDraft = item.draft || '';
|
||||
const newTransferDraft = [
|
||||
{ pro_order_detail_num: "123", reconcile: 5000 }
|
||||
];
|
||||
const updatedDraft = updateDraftField(currentDraft, 'transfer_draft', newTransferDraft);
|
||||
|
||||
// 2. 更新 pro_order_detail_items
|
||||
const newDetailItems = [
|
||||
{
|
||||
pro_order_detail_num: "123",
|
||||
reconcile: 5000,
|
||||
activity_name: "法會",
|
||||
actitem_name: "護持金"
|
||||
}
|
||||
];
|
||||
const updatedDraft = updateDraftField(currentDraft, 'pro_order_detail_items', newDetailItems);
|
||||
|
||||
// 3. 檢查是否有暫存資料
|
||||
if (hasTransferDraft(item.draft)) {
|
||||
console.log('有暫存資料');
|
||||
}
|
||||
|
||||
// 4. 取得特定欄位
|
||||
const transferDraft = getDraftField(item.draft, 'transfer_draft');
|
||||
const detailItems = getDraftField(item.draft, 'pro_order_detail_items');
|
||||
*/
|
||||
|
||||
// 檢查是否有 follower_list(共同支付人清單)
|
||||
function hasFollowerList(draft) {
|
||||
const followerList = getDraftField(draft, 'follower_list');
|
||||
return followerList && Array.isArray(followerList) && followerList.length > 0;
|
||||
}
|
||||
|
||||
// 取得 follower_list
|
||||
function getFollowerList(draft) {
|
||||
const followerList = getDraftField(draft, 'follower_list');
|
||||
return followerList && Array.isArray(followerList) ? followerList : [];
|
||||
}
|
||||
|
||||
// 更新 follower_list
|
||||
function updateFollowerList(draft, followerList) {
|
||||
return updateDraftField(draft, 'follower_list', followerList);
|
||||
}
|
||||
|
||||
// 如果是在瀏覽器環境中,將函數掛載到全域
|
||||
if (typeof window !== 'undefined') {
|
||||
window.DraftUtils = {
|
||||
getDraftObject,
|
||||
getDraftField,
|
||||
updateDraftField,
|
||||
hasTransferDraft,
|
||||
hasProOrderDetailItems,
|
||||
hasFollowerList,
|
||||
getFollowerList,
|
||||
updateFollowerList,
|
||||
clearDraftField,
|
||||
mergeDraftObjects,
|
||||
validateDraftFormat,
|
||||
getDraftSummary,
|
||||
migrateLegacyFormat,
|
||||
cleanEmptyDraft,
|
||||
formatDraftJson
|
||||
};
|
||||
}
|
||||
|
||||
// 如果是在 Node.js 環境中,匯出函數
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = {
|
||||
getDraftObject,
|
||||
getDraftField,
|
||||
updateDraftField,
|
||||
hasTransferDraft,
|
||||
hasProOrderDetailItems,
|
||||
hasFollowerList,
|
||||
getFollowerList,
|
||||
updateFollowerList,
|
||||
clearDraftField,
|
||||
mergeDraftObjects,
|
||||
validateDraftFormat,
|
||||
getDraftSummary,
|
||||
migrateLegacyFormat,
|
||||
cleanEmptyDraft,
|
||||
formatDraftJson
|
||||
};
|
||||
}
|
||||
682
web/admin/transfer/group_reconcile.aspx
Normal file
682
web/admin/transfer/group_reconcile.aspx
Normal file
@@ -0,0 +1,682 @@
|
||||
<%@ Page Title="共同-沖帳流程" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" EnableEventValidation="false" CodeFile="group_reconcile.aspx.cs" Inherits="admin_transfer_group_reconcile" %>
|
||||
<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="group-reconcile-app">
|
||||
<v-app>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<v-card-title class="bg-primary white--text text-center">
|
||||
<h5 class="mb-0">共同 - 沖帳流程</h5>
|
||||
</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.follower="{ item }">
|
||||
<span :title="item.f_num">{{ item.follower }}</span>
|
||||
</template>
|
||||
<template v-slot:item.activity_info="{ item }">
|
||||
<div>
|
||||
<div class="font-weight-bold">{{ item.activity_name }}</div>
|
||||
<div class="caption text--secondary">
|
||||
<span v-if="hasFollowerList(item.draft)" style="margin-right: 4px;">👥</span>
|
||||
{{ item.acc_name }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.check_date="{ item }">
|
||||
<span>{{ item.check_date | date }}</span>
|
||||
</template>
|
||||
<template v-slot:item.check_memo="{ item }">
|
||||
<span>{{ item.check_memo }}</span>
|
||||
</template>
|
||||
<template v-slot:item.verify_note="{ item }">
|
||||
<span>{{ item.verify_note }}</span>
|
||||
</template>
|
||||
<template v-slot:item.follower_selection="{ item }">
|
||||
<div>
|
||||
<v-btn
|
||||
small
|
||||
color="primary"
|
||||
outlined
|
||||
@click="showFollowerSelection(item)"
|
||||
class="mb-1"
|
||||
>
|
||||
<v-icon left small>mdi-account-multiple</v-icon>
|
||||
選擇支付人
|
||||
</v-btn>
|
||||
<div v-if="hasFollowerList(item.draft)" class="caption text-success mt-1">
|
||||
<v-icon small color="success">mdi-check-circle</v-icon>
|
||||
已選擇 {{ getFollowerCount(item.draft) }} 位
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<v-btn
|
||||
small
|
||||
color="success"
|
||||
@click="showGroupReconcileDialog(item)"
|
||||
:disabled="!hasFollowerList(item.draft)"
|
||||
>
|
||||
<v-icon left small>mdi-receipt</v-icon>
|
||||
{{ hasFollowerList(item.draft) ? '共同沖帳' : '請先選擇支付人' }}
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-container>
|
||||
|
||||
<!-- 選擇共同支付人對話框 -->
|
||||
<v-dialog v-model="followerDialog.show" max-width="800px">
|
||||
<v-card>
|
||||
<v-card-title class="grey lighten-2">
|
||||
<v-icon color="primary" class="mr-2">mdi-account-multiple</v-icon>
|
||||
選擇共同支付人
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="followerDialog.show = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text class="mt-4">
|
||||
<div v-if="followerDialog.selected">
|
||||
<h6 class="mb-3">法會:{{ followerDialog.selected.activity_name }}</h6>
|
||||
<v-text-field
|
||||
v-model="followerDialog.search"
|
||||
label="搜尋信眾"
|
||||
dense
|
||||
outlined
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
@input="searchActivityFollowers"
|
||||
></v-text-field>
|
||||
<v-data-table
|
||||
:headers="followerDialog.headers"
|
||||
:items="followerDialog.items"
|
||||
:loading="followerDialog.loading"
|
||||
item-key="f_num"
|
||||
class="elevation-1 mt-2"
|
||||
show-select
|
||||
v-model="followerDialog.selectedFollowers"
|
||||
hide-default-footer
|
||||
:disable-pagination="true"
|
||||
>
|
||||
<template v-slot:item.follower_name="{ item }">
|
||||
<div>
|
||||
<div class="font-weight-bold">{{ item.follower_name }}</div>
|
||||
<div class="caption text--secondary">編號: {{ item.f_num }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.total_due="{ item }">
|
||||
<span class="text-danger">{{ item.total_due | currency }}</span>
|
||||
</template>
|
||||
<template v-slot:item.item_count="{ item }">
|
||||
<span class="text-info">{{ item.item_count }} 項</span>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-actions class="pa-4">
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="grey" @click="followerDialog.show = false">取消</v-btn>
|
||||
<v-btn color="primary" @click="confirmFollowerSelection" :disabled="followerDialog.selectedFollowers.length === 0">
|
||||
確認選擇 ({{ followerDialog.selectedFollowers.length }})
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 共同沖帳明細對話框 -->
|
||||
<v-dialog v-model="reconcileDialog.show" max-width="1200px">
|
||||
<v-card>
|
||||
<v-card-title class="grey lighten-2">
|
||||
<v-icon color="success" class="mr-2">mdi-receipt</v-icon>
|
||||
共同沖帳明細
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="reconcileDialog.show = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="mb-0 pb-0">
|
||||
<div v-if="reconcileDialog.selected">
|
||||
<div class="row my-2">
|
||||
<h6 class="col">共同支付人:{{ getSelectedFollowersText() }}</h6>
|
||||
<div class="col">
|
||||
<span class="font-weight-bold">入帳金額:</span>
|
||||
<span class="text-primary">{{ reconcileDialog.selected.check_amount | currency }}</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<span class="font-weight-bold">已沖金額:</span>
|
||||
<span :class="{'text-danger': isOverPaid, 'text-dark': !isOverPaid}">{{ sumReconcile | currency }}</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<span class="font-weight-bold">未繳餘款:</span>
|
||||
<span class="text-danger">{{ remainDue | currency }}</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<span class="font-weight-bold">入帳後餘額:</span>
|
||||
<span class="text-success">{{ overPaid | currency }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<v-data-table
|
||||
:headers="reconcileDialog.headers"
|
||||
:items="reconcileDialog.items"
|
||||
class="elevation-1 mt-3"
|
||||
hide-default-footer
|
||||
:disable-pagination="true"
|
||||
>
|
||||
<template v-slot:item.follower_info="{ item }">
|
||||
<div>
|
||||
<div class="font-weight-bold text-primary">{{ item.follower_name }}</div>
|
||||
<div class="text-muted" style="font-size:12px;">{{ item.order_no }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.activity_name="{ item }">
|
||||
<div>
|
||||
<div>{{ item.activity_name }}</div>
|
||||
<div class="text-muted" style="font-size:12px;">{{ item.reg_time | date }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.paid="{ item }">
|
||||
<span>{{ item.paid | currency }}</span>
|
||||
</template>
|
||||
<template v-slot:item.due="{ item }">
|
||||
<span>{{ item.due | currency }}</span>
|
||||
</template>
|
||||
<template v-slot:item.reconcile="{ item, index }">
|
||||
<v-text-field
|
||||
v-model="item.reconcile"
|
||||
type="number"
|
||||
dense
|
||||
outlined
|
||||
hide-details="auto"
|
||||
:rules="[
|
||||
v => !isNaN(Number(v)) || '請輸入數字',
|
||||
v => Number(v) >= 0 || '不可小於 0',
|
||||
v => Number(v) <= Number(item.due) || `不可大於待繳金額 ${item.due}`
|
||||
]"
|
||||
@blur="validateAndUpdateReconcile($event.target.value, item, index)"
|
||||
></v-text-field>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-actions class="pa-4">
|
||||
<div>
|
||||
<div v-if="hasUnallocated && remainDue > 0" class="text-dark mb-2">
|
||||
⚠️ 尚有未分配的入帳金額,請確認是否全部分配
|
||||
</div>
|
||||
<div v-if="hasUnallocated && remainDue === 0" class="text-info mb-2">
|
||||
ℹ️ 已無未繳項目,剩餘入帳金額將成為餘額
|
||||
</div>
|
||||
<div v-if="!canConfirm && buttonErrorMessage" class="text-danger">
|
||||
❌ {{ buttonErrorMessage }}
|
||||
</div>
|
||||
</div>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="info" @click="redistributeReconcile" class="mr-2">重新分配</v-btn>
|
||||
<v-btn color="orange" @click="saveDraft" class="mr-2">暫存</v-btn>
|
||||
<v-btn color="primary" @click="confirmGroupReconcile" :disabled="!canConfirm">確認沖帳</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-app>
|
||||
</div>
|
||||
</asp:Content>
|
||||
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" runat="Server">
|
||||
<script src="draft-utils.js"></script>
|
||||
<script>
|
||||
// 確保 DraftUtils 已載入
|
||||
if (typeof window.DraftUtils === 'undefined') {
|
||||
console.warn('DraftUtils 未載入,使用備用方案');
|
||||
window.DraftUtils = {
|
||||
hasFollowerList: function(draft) { return false; },
|
||||
getFollowerList: function(draft) { return []; },
|
||||
updateFollowerList: function(draft, list) {
|
||||
return { follower_list: list };
|
||||
},
|
||||
getDraftField: function(draft, field) { return null; },
|
||||
updateDraftField: function(draft, field, value) {
|
||||
return { [field]: value };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
new Vue({
|
||||
el: '#group-reconcile-app',
|
||||
vuetify: new Vuetify(),
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
headers: [
|
||||
{ text: '匯款人', value: 'follower' },
|
||||
{ text: '法會/入帳帳戶', value: 'activity_info' },
|
||||
{ text: '入帳日期', value: 'check_date' },
|
||||
{ text: '帳簿備註', value: 'check_memo' },
|
||||
{ text: '入帳金額', value: 'check_amount' },
|
||||
{ text: '核對記錄', value: 'verify_note' },
|
||||
{ text: '選擇共同支付人', value: 'follower_selection', sortable: false },
|
||||
{ text: '共同沖帳', value: 'actions', sortable: false }
|
||||
],
|
||||
items: [],
|
||||
followerDialog: {
|
||||
show: false,
|
||||
selected: null,
|
||||
search: '',
|
||||
loading: false,
|
||||
items: [],
|
||||
selectedFollowers: [],
|
||||
headers: [
|
||||
{ text: '信眾', value: 'follower_name' },
|
||||
{ text: '待繳金額', value: 'total_due' },
|
||||
{ text: '項目數', value: 'item_count' }
|
||||
]
|
||||
},
|
||||
reconcileDialog: {
|
||||
show: false,
|
||||
selected: null,
|
||||
headers: [
|
||||
{ text: '信眾/報名單號', value: 'follower_info' },
|
||||
{ text: '法會/報名日期', value: 'activity_name' },
|
||||
{ text: '項目', value: 'actitem_name' },
|
||||
{ text: '應繳金額', value: 'price', sortable: false },
|
||||
{ text: '已繳金額', value: 'paid', sortable: false },
|
||||
{ text: '待繳金額', value: 'due', sortable: false },
|
||||
{ text: '沖帳金額', value: 'reconcile', sortable: false }
|
||||
],
|
||||
items: [],
|
||||
loading: false,
|
||||
errorMessage: ''
|
||||
},
|
||||
hasUnallocated: false,
|
||||
draftDataChanged: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sumReconcile() {
|
||||
return this.reconcileDialog.items.reduce((sum, item) => sum + (Number(item.reconcile) || 0), 0);
|
||||
},
|
||||
remainDue() {
|
||||
const totalDue = this.reconcileDialog.items.reduce((sum, item) => sum + (Number(item.due) || 0), 0);
|
||||
return totalDue - this.sumReconcile;
|
||||
},
|
||||
overPaid() {
|
||||
if (!this.reconcileDialog.selected) return 0;
|
||||
return this.reconcileDialog.selected.check_amount - this.sumReconcile;
|
||||
},
|
||||
isOverPaid() {
|
||||
if (!this.reconcileDialog.selected) return false;
|
||||
return this.sumReconcile > this.reconcileDialog.selected.check_amount;
|
||||
},
|
||||
canConfirm() {
|
||||
if (!this.reconcileDialog.selected) return false;
|
||||
|
||||
const hasReconcile = this.reconcileDialog.items.some(item => Number(item.reconcile) > 0);
|
||||
if (!hasReconcile) return false;
|
||||
|
||||
const validAmounts = this.reconcileDialog.items.every(item => {
|
||||
const amount = Number(item.reconcile) || 0;
|
||||
return amount >= 0 && amount <= Number(item.due);
|
||||
});
|
||||
if (!validAmounts) return false;
|
||||
|
||||
if (this.sumReconcile > this.reconcileDialog.selected.check_amount) return false;
|
||||
|
||||
if (this.hasUnallocated && this.remainDue > 0) return false;
|
||||
|
||||
return true;
|
||||
},
|
||||
buttonErrorMessage() {
|
||||
if (!this.reconcileDialog.selected) return '';
|
||||
|
||||
const hasReconcile = this.reconcileDialog.items.some(item => Number(item.reconcile) > 0);
|
||||
if (!hasReconcile) return '請至少輸入一筆沖帳金額';
|
||||
|
||||
const invalidItem = this.reconcileDialog.items.find(item => {
|
||||
const amount = Number(item.reconcile) || 0;
|
||||
return amount < 0 || amount > Number(item.due);
|
||||
});
|
||||
if (invalidItem) return '沖帳金額超出可沖帳範圍';
|
||||
|
||||
if (this.sumReconcile > this.reconcileDialog.selected.check_amount) {
|
||||
return '已沖金額不可大於入帳金額';
|
||||
}
|
||||
|
||||
if (this.hasUnallocated && this.remainDue > 0) {
|
||||
return '尚有未繳項目,請將入帳金額完全分配';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadTableData();
|
||||
},
|
||||
filters: {
|
||||
currency(val) {
|
||||
if (!val) return '0';
|
||||
return Number(val).toLocaleString();
|
||||
},
|
||||
date(val) {
|
||||
if (!val) return '';
|
||||
const date = new Date(val);
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 檢查是否有 follower_list
|
||||
hasFollowerList(draft) {
|
||||
return window.DraftUtils && window.DraftUtils.hasFollowerList && window.DraftUtils.hasFollowerList(draft);
|
||||
},
|
||||
// 取得已選擇的支付人數量
|
||||
getFollowerCount(draft) {
|
||||
if (!window.DraftUtils || !window.DraftUtils.getFollowerList) return 0;
|
||||
const followerList = window.DraftUtils.getFollowerList(draft);
|
||||
return followerList ? followerList.length : 0;
|
||||
},
|
||||
loadTableData() {
|
||||
this.loading = true;
|
||||
axios.get('../../api/transfer_register/group_reconcile_list')
|
||||
.then(res => {
|
||||
this.items = res.data;
|
||||
})
|
||||
.catch(() => {
|
||||
this.items = [];
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
showGroupReconcileDialog(item) {
|
||||
// 檢查是否已有選擇的共同支付人
|
||||
const followerList = window.DraftUtils.getFollowerList(item.draft);
|
||||
if (followerList && followerList.length > 0) {
|
||||
// 直接顯示沖帳明細
|
||||
this.showReconcileDetail(item, followerList);
|
||||
} else {
|
||||
// 先選擇共同支付人
|
||||
this.showFollowerSelection(item);
|
||||
}
|
||||
},
|
||||
showFollowerSelection(item) {
|
||||
this.followerDialog.selected = item;
|
||||
this.followerDialog.selectedFollowers = [];
|
||||
this.followerDialog.items = [];
|
||||
this.followerDialog.show = true;
|
||||
this.searchActivityFollowers();
|
||||
},
|
||||
searchActivityFollowers() {
|
||||
if (!this.followerDialog.selected || !this.followerDialog.selected.activity_num) return;
|
||||
|
||||
this.followerDialog.loading = true;
|
||||
axios.get('../../api/transfer_register/activity_followers', {
|
||||
params: { activity_num: this.followerDialog.selected.activity_num }
|
||||
})
|
||||
.then(res => {
|
||||
let items = res.data;
|
||||
// 如果有搜尋關鍵字,過濾結果
|
||||
if (this.followerDialog.search && this.followerDialog.search.trim()) {
|
||||
const keyword = this.followerDialog.search.trim().toLowerCase();
|
||||
items = items.filter(item =>
|
||||
item.follower_name.toLowerCase().includes(keyword)
|
||||
);
|
||||
}
|
||||
this.followerDialog.items = items;
|
||||
})
|
||||
.catch(() => {
|
||||
this.followerDialog.items = [];
|
||||
})
|
||||
.finally(() => {
|
||||
this.followerDialog.loading = false;
|
||||
});
|
||||
},
|
||||
confirmFollowerSelection() {
|
||||
if (this.followerDialog.selectedFollowers.length === 0) {
|
||||
alert('請選擇至少一位共同支付人');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.followerDialog.selected) {
|
||||
alert('系統錯誤:未選擇記錄');
|
||||
return;
|
||||
}
|
||||
|
||||
// 儲存選擇的共同支付人到 draft
|
||||
const followerList = this.followerDialog.selectedFollowers.map(f => ({
|
||||
f_num: f.f_num,
|
||||
f_name: f.follower_name,
|
||||
activity_num: this.followerDialog.selected.activity_num
|
||||
}));
|
||||
|
||||
const draftData = window.DraftUtils.updateFollowerList(this.followerDialog.selected.draft, followerList);
|
||||
this.updateFollowerDraftToDB(this.followerDialog.selected.id, draftData)
|
||||
.then(() => {
|
||||
this.followerDialog.selected.draft = JSON.stringify(draftData);
|
||||
this.followerDialog.show = false;
|
||||
// 重新載入資料以更新顯示
|
||||
this.loadTableData();
|
||||
// 顯示沖帳明細
|
||||
this.showReconcileDetail(this.followerDialog.selected, followerList);
|
||||
})
|
||||
.catch(err => {
|
||||
alert('儲存失敗,請重試');
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
showReconcileDetail(item, followerList) {
|
||||
this.reconcileDialog.selected = item;
|
||||
this.reconcileDialog.items = [];
|
||||
this.reconcileDialog.show = true;
|
||||
|
||||
// 取得所選信眾的訂單明細
|
||||
const fNums = followerList.map(f => f.f_num).join(',');
|
||||
axios.get('../../api/transfer_register/group_follower_orders', {
|
||||
params: {
|
||||
activity_num: item.activity_num,
|
||||
f_nums: fNums
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
this.reconcileDialog.items = res.data.map(item => ({
|
||||
...item,
|
||||
reconcile: 0 // 初始化沖帳金額
|
||||
}));
|
||||
this.autoDistributeReconcile();
|
||||
})
|
||||
.catch(() => {
|
||||
this.reconcileDialog.items = [];
|
||||
this.updateSumReconcile();
|
||||
});
|
||||
},
|
||||
getSelectedFollowersText() {
|
||||
if (!this.reconcileDialog.selected) return '';
|
||||
const followerList = window.DraftUtils.getFollowerList(this.reconcileDialog.selected.draft);
|
||||
return followerList.map(f => f.f_name).join('、');
|
||||
},
|
||||
autoDistributeReconcile() {
|
||||
// 先進先出分配沖帳金額
|
||||
let remainAmount = this.reconcileDialog.selected ? this.reconcileDialog.selected.check_amount : 0;
|
||||
|
||||
// 先將所有項目的 reconcile 清為 0
|
||||
this.reconcileDialog.items.forEach(item => {
|
||||
this.$set(item, 'reconcile', 0);
|
||||
});
|
||||
|
||||
// 依報名日期排序(先進先出)
|
||||
const sortedItems = [...this.reconcileDialog.items].sort((a, b) =>
|
||||
new Date(a.reg_time) - new Date(b.reg_time)
|
||||
);
|
||||
|
||||
// 逐項分配
|
||||
sortedItems.forEach(item => {
|
||||
if (remainAmount > 0) {
|
||||
const canPay = Math.min(item.due, remainAmount);
|
||||
const originalItem = this.reconcileDialog.items.find(i => i === item);
|
||||
if (originalItem) {
|
||||
this.$set(originalItem, 'reconcile', canPay);
|
||||
}
|
||||
remainAmount -= canPay;
|
||||
}
|
||||
});
|
||||
|
||||
this.updateSumReconcile();
|
||||
},
|
||||
redistributeReconcile() {
|
||||
this.autoDistributeReconcile();
|
||||
},
|
||||
updateSumReconcile() {
|
||||
const maxTotal = this.reconcileDialog.selected ? (this.reconcileDialog.selected.check_amount || 0) : 0;
|
||||
this.hasUnallocated = maxTotal > this.sumReconcile;
|
||||
},
|
||||
saveDraft() {
|
||||
try {
|
||||
// 組成新的資料
|
||||
const followerList = window.DraftUtils.getFollowerList(this.reconcileDialog.selected.draft);
|
||||
const reconcileData = this.reconcileDialog.items
|
||||
.filter(item => Number(item.reconcile) > 0)
|
||||
.map(item => ({
|
||||
f_num: item.f_num,
|
||||
follower_name: item.follower_name,
|
||||
pro_order_detail_num: item.num,
|
||||
reconcile: Number(item.reconcile)
|
||||
}));
|
||||
|
||||
const draftData = {
|
||||
transfer_draft: [],
|
||||
pro_order_detail_items: reconcileData,
|
||||
follower_list: followerList
|
||||
};
|
||||
|
||||
this.updateDraftToDB(this.reconcileDialog.selected.id, draftData)
|
||||
.then(() => {
|
||||
alert('暫存成功!');
|
||||
this.reconcileDialog.selected.draft = JSON.stringify(draftData);
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
alert('暫存失敗:資料格式錯誤');
|
||||
}
|
||||
},
|
||||
updateFollowerDraftToDB(id, draftData) {
|
||||
const selectedItem = this.followerDialog.selected;
|
||||
const updateData = {
|
||||
id: id,
|
||||
activity_num: selectedItem.activity_num,
|
||||
name: selectedItem.name,
|
||||
phone: selectedItem.phone,
|
||||
pay_type: selectedItem.pay_type,
|
||||
account_last5: selectedItem.account_last5,
|
||||
amount: selectedItem.amount,
|
||||
pay_mode: selectedItem.pay_mode,
|
||||
note: selectedItem.note,
|
||||
proof_img: selectedItem.proof_img,
|
||||
status: selectedItem.status,
|
||||
f_num_match: selectedItem.f_num_match,
|
||||
f_num: selectedItem.f_num,
|
||||
acc_num: selectedItem.acc_num,
|
||||
check_date: selectedItem.check_date,
|
||||
check_amount: selectedItem.check_amount,
|
||||
check_memo: selectedItem.check_memo,
|
||||
check_status: selectedItem.check_status,
|
||||
acc_kind: selectedItem.acc_kind,
|
||||
member_num: selectedItem.member_num,
|
||||
verify_time: selectedItem.verify_time,
|
||||
verify_note: selectedItem.verify_note,
|
||||
draft: JSON.stringify(draftData)
|
||||
};
|
||||
|
||||
return axios.put(`../../api/transfer_register/${id}`, updateData);
|
||||
},
|
||||
updateDraftToDB(id, draftData) {
|
||||
const updateData = {
|
||||
id: id,
|
||||
activity_num: this.reconcileDialog.selected.activity_num,
|
||||
name: this.reconcileDialog.selected.name,
|
||||
phone: this.reconcileDialog.selected.phone,
|
||||
pay_type: this.reconcileDialog.selected.pay_type,
|
||||
account_last5: this.reconcileDialog.selected.account_last5,
|
||||
amount: this.reconcileDialog.selected.amount,
|
||||
pay_mode: this.reconcileDialog.selected.pay_mode,
|
||||
note: this.reconcileDialog.selected.note,
|
||||
proof_img: this.reconcileDialog.selected.proof_img,
|
||||
status: this.reconcileDialog.selected.status,
|
||||
f_num_match: this.reconcileDialog.selected.f_num_match,
|
||||
f_num: this.reconcileDialog.selected.f_num,
|
||||
acc_num: this.reconcileDialog.selected.acc_num,
|
||||
check_date: this.reconcileDialog.selected.check_date,
|
||||
check_amount: this.reconcileDialog.selected.check_amount,
|
||||
check_memo: this.reconcileDialog.selected.check_memo,
|
||||
check_status: this.reconcileDialog.selected.check_status,
|
||||
acc_kind: this.reconcileDialog.selected.acc_kind,
|
||||
member_num: this.reconcileDialog.selected.member_num,
|
||||
verify_time: this.reconcileDialog.selected.verify_time,
|
||||
verify_note: this.reconcileDialog.selected.verify_note,
|
||||
draft: JSON.stringify(draftData)
|
||||
};
|
||||
|
||||
return axios.put(`../../api/transfer_register/${id}`, updateData);
|
||||
},
|
||||
confirmGroupReconcile() {
|
||||
if (!this.canConfirm) return;
|
||||
|
||||
this.reconcileDialog.loading = true;
|
||||
const overPayment = this.reconcileDialog.selected.check_amount - this.sumReconcile;
|
||||
const postData = {
|
||||
transfer_register_id: this.reconcileDialog.selected.id,
|
||||
details: this.reconcileDialog.items
|
||||
.filter(item => item.reconcile > 0)
|
||||
.map(item => ({
|
||||
f_num: item.f_num,
|
||||
pro_order_detail_num: item.num,
|
||||
amount: Number(item.reconcile),
|
||||
reg_time: new Date().toISOString()
|
||||
})),
|
||||
over_payment: overPayment
|
||||
};
|
||||
|
||||
axios.post('../../api/transfer_register/group_reconcile', postData)
|
||||
.then(res => {
|
||||
const message = res.data && res.data.message ? res.data.message : '共同沖帳完成';
|
||||
alert(message);
|
||||
|
||||
this.reconcileDialog.show = false;
|
||||
this.loadTableData(); // 重新載入清單
|
||||
})
|
||||
.catch(err => {
|
||||
let errorMessage = '沖帳失敗,請聯繫系統管理員';
|
||||
if (err.response && err.response.data) {
|
||||
if (typeof err.response.data === 'string') {
|
||||
errorMessage = err.response.data;
|
||||
} else if (err.response.data.message) {
|
||||
errorMessage = err.response.data.message;
|
||||
}
|
||||
}
|
||||
alert(errorMessage);
|
||||
})
|
||||
.finally(() => {
|
||||
this.reconcileDialog.loading = false;
|
||||
});
|
||||
},
|
||||
validateAndUpdateReconcile(value, item, index) {
|
||||
let numValue = Number(value);
|
||||
if (isNaN(numValue)) {
|
||||
numValue = 0;
|
||||
}
|
||||
numValue = Math.round(numValue);
|
||||
|
||||
this.$set(item, 'reconcile', numValue);
|
||||
this.updateSumReconcile();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</asp:Content>
|
||||
10
web/admin/transfer/group_reconcile.aspx.cs
Normal file
10
web/admin/transfer/group_reconcile.aspx.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Web.UI;
|
||||
|
||||
public partial class admin_transfer_group_reconcile : MyWeb.config
|
||||
{
|
||||
protected void Page_Load(object sender, EventArgs e)
|
||||
{
|
||||
// 頁面初始化(暫無邏輯)
|
||||
}
|
||||
}
|
||||
176
web/admin/transfer/index.aspx
Normal file
176
web/admin/transfer/index.aspx
Normal file
@@ -0,0 +1,176 @@
|
||||
<%@ Page Title="匯款/沖帳管理" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" EnableEventValidation="false" CodeFile="index.aspx.cs" Inherits="admin_transfer_index" %>
|
||||
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" runat="Server">
|
||||
<link rel="stylesheet" href="../../js/_bootstrap-icons-1.8.1/bootstrap-icons.css">
|
||||
<style>
|
||||
.function-icon {
|
||||
font-size: 2em;
|
||||
line-height: 1;
|
||||
align-content: center;
|
||||
}
|
||||
.external-link-icon {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
</style>
|
||||
</asp:Content>
|
||||
<asp:Content ID="Content3" ContentPlaceHolderID="page_nav" runat="Server">
|
||||
<h2 class="mb-3">匯款/沖帳管理</h2>
|
||||
</asp:Content>
|
||||
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
|
||||
<div id="content" class="container py-4">
|
||||
<div class="row">
|
||||
<!-- 第一欄:匯款登錄與核對 -->
|
||||
<div class="col-lg-4 mb-4">
|
||||
<h5 class="text-primary mb-3">
|
||||
<i class="bi bi-upload"></i> 匯款登錄與核對
|
||||
</h5>
|
||||
<div class="list-group">
|
||||
<a href="register.aspx" class="list-group-item list-group-item-action" target="_blank">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="d-flex">
|
||||
<i class="bi bi-plus-circle text-success me-3 function-icon"></i>
|
||||
<div>
|
||||
<div>
|
||||
登錄匯款資料
|
||||
<i class="bi bi-box-arrow-up-right text-muted ms-1 external-link-icon"></i>
|
||||
</div>
|
||||
<small class="text-muted">報名者自行填寫匯款相關資訊</small>
|
||||
</div>
|
||||
</div>
|
||||
<span class="badge bg-primary">報名者</span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="verify.aspx" class="list-group-item list-group-item-action d-none">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="d-flex">
|
||||
<i class="bi bi-person-check text-info me-3 function-icon"></i>
|
||||
<div>
|
||||
<div>出納核對匯款人</div>
|
||||
<small class="text-muted">核對匯款人身份與報名資料</small>
|
||||
</div>
|
||||
</div>
|
||||
<span class="badge bg-info">出納</span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="verify1.aspx" class="list-group-item list-group-item-action">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="d-flex">
|
||||
<i class="bi bi-person-check text-info me-3 function-icon"></i>
|
||||
<div>
|
||||
<div>出納核對匯款人(階段1)</div>
|
||||
<small class="text-muted">初步核對匯款人身份資料</small>
|
||||
</div>
|
||||
</div>
|
||||
<span class="badge bg-info">出納</span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="verify2.aspx" class="list-group-item list-group-item-action">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="d-flex">
|
||||
<i class="bi bi-currency-dollar text-warning me-3 function-icon"></i>
|
||||
<div>
|
||||
<div>出納核對金額(階段2)</div>
|
||||
<small class="text-muted">核對匯款金額與入帳資料</small>
|
||||
</div>
|
||||
</div>
|
||||
<span class="badge bg-warning text-dark">出納</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第二欄:沖帳流程 -->
|
||||
<div class="col-lg-4 mb-4">
|
||||
<h5 class="text-primary mb-3">
|
||||
<i class="bi bi-receipt"></i> 沖帳流程
|
||||
</h5>
|
||||
<div class="list-group">
|
||||
<a href="personal_reconcile.aspx" class="list-group-item list-group-item-action">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="d-flex">
|
||||
<i class="bi bi-person text-primary me-3 function-icon"></i>
|
||||
<div>
|
||||
<div>個人-沖帳流程</div>
|
||||
<small class="text-muted">處理個人匯款的沖帳作業</small>
|
||||
</div>
|
||||
</div>
|
||||
<span class="badge bg-success">會計</span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="group_reconcile.aspx" class="list-group-item list-group-item-action">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="d-flex">
|
||||
<i class="bi bi-people text-success me-3 function-icon"></i>
|
||||
<div>
|
||||
<div>共同-沖帳流程</div>
|
||||
<small class="text-muted">處理多人共同支付的沖帳作業</small>
|
||||
</div>
|
||||
</div>
|
||||
<span class="badge bg-success">會計</span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="balance_reconcile.aspx" class="list-group-item list-group-item-action">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="d-flex">
|
||||
<i class="bi bi-calculator text-danger me-3 function-icon"></i>
|
||||
<div>
|
||||
<div>餘額核銷</div>
|
||||
<small class="text-muted">處理沖帳後剩餘金額的核銷</small>
|
||||
</div>
|
||||
</div>
|
||||
<span class="badge bg-danger">會計</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第三欄:查詢功能 -->
|
||||
<div class="col-lg-4 mb-4">
|
||||
<h5 class="text-primary mb-3">
|
||||
<i class="bi bi-search"></i> 查詢功能
|
||||
</h5>
|
||||
<div class="list-group">
|
||||
<a href="verify_order_record_query.aspx" class="list-group-item list-group-item-action">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="d-flex">
|
||||
<i class="bi bi-journal-check text-info me-3 function-icon"></i>
|
||||
<div>
|
||||
<div>沖帳查詢</div>
|
||||
<small class="text-muted">查詢所有沖帳記錄與明細</small>
|
||||
</div>
|
||||
</div>
|
||||
<span class="badge bg-info">會計</span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="balance_reconcile_query.aspx" class="list-group-item list-group-item-action">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="d-flex">
|
||||
<i class="bi bi-file-text text-secondary me-3 function-icon"></i>
|
||||
<div>
|
||||
<div>餘額核銷查詢</div>
|
||||
<small class="text-muted">查詢已完成的餘額核銷記錄</small>
|
||||
</div>
|
||||
</div>
|
||||
<span class="badge bg-secondary">會計</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 統計資訊 -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info">
|
||||
<h6 class="alert-heading">
|
||||
<i class="bi bi-info-circle"></i> 系統說明
|
||||
</h6>
|
||||
<ul class="mb-0">
|
||||
<li><strong>匯款登錄與核對</strong>:處理報名者匯款資料的登錄與出納核對作業</li>
|
||||
<li><strong>沖帳流程</strong>:處理個人與共同支付的沖帳作業,以及剩餘金額的核銷</li>
|
||||
<li><strong>查詢功能</strong>:提供各類沖帳記錄的查詢與統計功能</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</asp:Content>
|
||||
10
web/admin/transfer/index.aspx.cs
Normal file
10
web/admin/transfer/index.aspx.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Web.UI;
|
||||
|
||||
public partial class admin_transfer_index : MyWeb.config
|
||||
{
|
||||
protected void Page_Load(object sender, EventArgs e)
|
||||
{
|
||||
// 頁面初始化(暫無邏輯)
|
||||
}
|
||||
}
|
||||
529
web/admin/transfer/personal_reconcile.aspx
Normal file
529
web/admin/transfer/personal_reconcile.aspx
Normal file
@@ -0,0 +1,529 @@
|
||||
<%@ Page Title="個人-沖帳流程" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" EnableEventValidation="false" CodeFile="personal_reconcile.aspx.cs" Inherits="admin_transfer_personal_reconcile" %>
|
||||
<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="reconcile-app">
|
||||
<v-app>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<v-card-title class="bg-primary white--text text-center">
|
||||
<h5 class="mb-0">個人 - 沖帳流程</h5>
|
||||
</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.follower="{ item }">
|
||||
<span :title="item.f_num">{{ item.follower }}</span>
|
||||
</template>
|
||||
<template v-slot:item.acc_name="{ item }">
|
||||
<span>
|
||||
<span v-if="hasTransferDraft(item.draft)" style="margin-right: 4px;">📝</span>{{ item.acc_name }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-slot:item.check_date="{ item }">
|
||||
<span>{{ item.check_date | date }}</span>
|
||||
</template>
|
||||
<template v-slot:item.check_memo="{ item }">
|
||||
<span>{{ item.check_memo }}</span>
|
||||
</template>
|
||||
<template v-slot:item.check_status="{ item }">
|
||||
<span>{{ item.check_status }}</span>
|
||||
</template>
|
||||
<template v-slot:item.verify_note="{ item }">
|
||||
<span>{{ item.verify_note }}</span>
|
||||
</template>
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<v-btn
|
||||
small
|
||||
color="success"
|
||||
@click="showReconcileDialog(item)"
|
||||
>
|
||||
沖帳
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-container>
|
||||
|
||||
<!-- 沖帳明細對話框 -->
|
||||
<v-dialog v-model="dialog.show" max-width="960px">
|
||||
<v-card>
|
||||
<v-card-title class="grey lighten-2">
|
||||
<span v-if="dialog.selected && hasTransferDraft(dialog.selected.draft)" style="margin-right: 8px;">📝</span>沖帳明細
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="dialog.show = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="mb-0 pb-0">
|
||||
<div v-if="dialog.selected">
|
||||
<div class="row my-2">
|
||||
<h6 class="col">信眾:{{ dialog.selected.follower }}</h6>
|
||||
<div class="col">
|
||||
<span class="font-weight-bold">入帳金額:</span>
|
||||
<span class="text-primary">{{ dialog.selected.check_amount | currency }}</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<span class="font-weight-bold">已沖金額:</span>
|
||||
<span :class="{'text-danger': isOverPaid, 'text-dark': !isOverPaid}">{{ sumReconcile | currency }}</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<span class="font-weight-bold">未繳餘款:</span>
|
||||
<span class="text-danger">{{ remainDue | currency }}</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<span class="font-weight-bold">入帳後餘額:</span>
|
||||
<span class="text-success">{{ overPaid | currency }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<v-data-table
|
||||
:headers="dialog.headers"
|
||||
:items="dialog.items"
|
||||
class="elevation-1 mt-3"
|
||||
hide-default-footer
|
||||
:disable-pagination="true"
|
||||
>
|
||||
<template v-slot:item.activity_name="{ item }">
|
||||
<div>
|
||||
<div>{{ item.activity_name }}</div>
|
||||
<div class="text-muted" style="font-size:12px;">{{ item.order_no }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.paid="{ item }">
|
||||
<span>{{ item.paid | currency }}</span>
|
||||
</template>
|
||||
<template v-slot:item.due="{ item }">
|
||||
<span>{{ item.due | currency }}</span>
|
||||
</template>
|
||||
<template v-slot:item.reg_time="{ item }">
|
||||
<span>{{ item.reg_time | date }}</span>
|
||||
</template>
|
||||
<template v-slot:item.reconcile="{ item, index }">
|
||||
<v-text-field
|
||||
v-model="item.reconcile"
|
||||
type="number"
|
||||
dense
|
||||
outlined
|
||||
hide-details="auto"
|
||||
:rules="[
|
||||
v => !isNaN(Number(v)) || '請輸入數字',
|
||||
v => Number(v) >= 0 || '不可小於 0',
|
||||
v => Number(v) <= Number(item.due) || `不可大於待繳金額 ${item.due}`
|
||||
]"
|
||||
@blur="validateAndUpdateReconcile($event.target.value, item, index)"
|
||||
></v-text-field>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-actions class="pa-4">
|
||||
<div>
|
||||
<div v-if="hasUnallocated && remainDue > 0" class="text-dark mb-2">
|
||||
⚠️ 尚有未分配的入帳金額,請確認是否全部分配
|
||||
</div>
|
||||
<div v-if="hasUnallocated && remainDue === 0" class="text-info mb-2">
|
||||
ℹ️ 已無未繳項目,剩餘入帳金額將成為餘額
|
||||
</div>
|
||||
<div v-if="!canConfirm && buttonErrorMessage" class="text-danger">
|
||||
❌ {{ buttonErrorMessage }}
|
||||
</div>
|
||||
</div>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="info" @click="redistributeReconcile" class="mr-2">重新分配</v-btn>
|
||||
<v-btn color="orange" @click="saveDraft" class="mr-2">暫存</v-btn>
|
||||
<v-btn color="primary" @click="confirmReconcile" :disabled="!canConfirm">確認沖帳</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-app>
|
||||
</div>
|
||||
</asp:Content>
|
||||
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" runat="Server">
|
||||
<script src="draft-utils.js"></script>
|
||||
<script>
|
||||
// 確保 DraftUtils 已載入
|
||||
if (typeof window.DraftUtils === 'undefined') {
|
||||
console.warn('DraftUtils 未載入,使用備用方案');
|
||||
window.DraftUtils = {
|
||||
hasTransferDraft: function(draft) { return false; },
|
||||
getDraftField: function(draft, field) { return null; },
|
||||
updateDraftField: function(draft, field, value) {
|
||||
return { [field]: value };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
new Vue({
|
||||
el: '#reconcile-app',
|
||||
vuetify: new Vuetify(),
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
headers: [
|
||||
//{ text: '信眾#', value: 'f_num' },
|
||||
{ text: '信眾', value: 'follower' },
|
||||
{ text: '入帳銀行/帳戶', value: 'acc_name' },
|
||||
{ text: '入帳日期', value: 'check_date' },
|
||||
{ text: '帳簿備註', value: 'check_memo' },
|
||||
{ text: '入帳金額', value: 'check_amount' },
|
||||
//{ text: '狀態', value: 'check_status' },
|
||||
{ text: '核對記錄', value: 'verify_note' },
|
||||
{ text: '沖帳', value: 'actions', sortable: false }
|
||||
],
|
||||
items: [],
|
||||
dialog: {
|
||||
show: false,
|
||||
selected: null,
|
||||
headers: [
|
||||
{ text: '法會/報名單號', value: 'activity_name' },
|
||||
{ text: '報名日期', value: 'reg_time' },
|
||||
{ text: '項目', value: 'actitem_name' },
|
||||
{ text: '應繳金額', value: 'price', sortable: false },
|
||||
{ text: '已繳金額', value: 'paid', sortable: false },
|
||||
{ text: '待繳金額', value: 'due', sortable: false },
|
||||
{ text: '沖帳金額', value: 'reconcile', sortable: false }
|
||||
],
|
||||
items: [],
|
||||
loading: false,
|
||||
errorMessage: ''
|
||||
},
|
||||
hasUnallocated: false,
|
||||
draftDataChanged: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sumReconcile() {
|
||||
return this.dialog.items.reduce((sum, item) => sum + (Number(item.reconcile) || 0), 0);
|
||||
},
|
||||
remainDue() {
|
||||
const totalDue = this.dialog.items.reduce((sum, item) => sum + (Number(item.due) || 0), 0);
|
||||
return totalDue - this.sumReconcile;
|
||||
},
|
||||
overPaid() {
|
||||
if (!this.dialog.selected) return 0;
|
||||
return this.dialog.selected.check_amount - this.sumReconcile;
|
||||
},
|
||||
isOverPaid() {
|
||||
if (!this.dialog.selected) return false;
|
||||
return this.sumReconcile > this.dialog.selected.check_amount;
|
||||
},
|
||||
canConfirm() {
|
||||
if (!this.dialog.selected) return false;
|
||||
|
||||
const hasReconcile = this.dialog.items.some(item => Number(item.reconcile) > 0);
|
||||
if (!hasReconcile) return false;
|
||||
|
||||
const validAmounts = this.dialog.items.every(item => {
|
||||
const amount = Number(item.reconcile) || 0;
|
||||
return amount >= 0 && amount <= Number(item.due);
|
||||
});
|
||||
if (!validAmounts) return false;
|
||||
|
||||
if (this.sumReconcile > this.dialog.selected.check_amount) return false;
|
||||
|
||||
// 有未分配金額時,只有在還有未繳餘款的情況下才禁用按鈕
|
||||
// 如果未繳餘款為 0,表示沒有更多項目可沖帳,應允許確認
|
||||
if (this.hasUnallocated && this.remainDue > 0) return false;
|
||||
|
||||
return true;
|
||||
},
|
||||
buttonErrorMessage() {
|
||||
if (!this.dialog.selected) return '';
|
||||
|
||||
const hasReconcile = this.dialog.items.some(item => Number(item.reconcile) > 0);
|
||||
if (!hasReconcile) return '請至少輸入一筆沖帳金額';
|
||||
|
||||
const invalidItem = this.dialog.items.find(item => {
|
||||
const amount = Number(item.reconcile) || 0;
|
||||
return amount < 0 || amount > Number(item.due);
|
||||
});
|
||||
if (invalidItem) return '沖帳金額超出可沖帳範圍';
|
||||
|
||||
if (this.sumReconcile > this.dialog.selected.check_amount) {
|
||||
return '已沖金額不可大於入帳金額';
|
||||
}
|
||||
|
||||
if (this.hasUnallocated && this.remainDue > 0) {
|
||||
return '尚有未繳項目,請將入帳金額完全分配';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadTableData();
|
||||
},
|
||||
filters: {
|
||||
currency(val) {
|
||||
if (!val) return '0';
|
||||
return Number(val).toLocaleString();
|
||||
},
|
||||
date(val) {
|
||||
if (!val) return '';
|
||||
const date = new Date(val);
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 檢查是否有 DraftUtils 可用
|
||||
hasTransferDraft(draft) {
|
||||
return window.DraftUtils && window.DraftUtils.hasTransferDraft && window.DraftUtils.hasTransferDraft(draft);
|
||||
},
|
||||
// 使用全域 DraftUtils 工具函數
|
||||
showError(message) {
|
||||
this.dialog.errorMessage = message;
|
||||
},
|
||||
clearError() {
|
||||
this.dialog.errorMessage = '';
|
||||
},
|
||||
loadTableData() {
|
||||
this.loading = true;
|
||||
axios.get('../../api/transfer_register/personal_reconcile_list')
|
||||
.then(res => {
|
||||
this.items = res.data;
|
||||
})
|
||||
.catch(() => {
|
||||
this.items = [];
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
showReconcileDialog(item) {
|
||||
this.dialog.selected = item;
|
||||
this.dialog.items = [];
|
||||
this.dialog.show = true;
|
||||
// 依 f_num 呼叫 API 取得訂單明細
|
||||
if (item.f_num) {
|
||||
axios.get('../../api/transfer_register/follower_orders', { params: { f_num: item.f_num } })
|
||||
.then(res => {
|
||||
this.dialog.items = res.data;
|
||||
this.loadFromDraftOrAutoDistribute();
|
||||
})
|
||||
.catch(() => {
|
||||
this.dialog.items = [];
|
||||
this.updateSumReconcile();
|
||||
});
|
||||
} else {
|
||||
this.dialog.items = [];
|
||||
this.updateSumReconcile();
|
||||
}
|
||||
},
|
||||
autoDistributeReconcile() {
|
||||
// 先進先出分配沖帳金額
|
||||
let remainAmount = this.dialog.selected ? this.dialog.selected.check_amount : 0;
|
||||
|
||||
// 先將所有項目的 reconcile 清為 0
|
||||
this.dialog.items.forEach(item => {
|
||||
this.$set(item, 'reconcile', 0);
|
||||
});
|
||||
|
||||
// 依報名日期排序(先進先出)
|
||||
const sortedItems = [...this.dialog.items].sort((a, b) =>
|
||||
new Date(a.reg_time) - new Date(b.reg_time)
|
||||
);
|
||||
|
||||
// 逐項分配
|
||||
sortedItems.forEach(item => {
|
||||
if (remainAmount > 0) {
|
||||
const canPay = Math.min(item.due, remainAmount);
|
||||
const originalItem = this.dialog.items.find(i => i === item);
|
||||
if (originalItem) {
|
||||
this.$set(originalItem, 'reconcile', canPay);
|
||||
}
|
||||
remainAmount -= canPay;
|
||||
}
|
||||
});
|
||||
|
||||
this.updateSumReconcile();
|
||||
},
|
||||
redistributeReconcile() {
|
||||
// 重新依先進先出原則分配沖帳金額
|
||||
this.autoDistributeReconcile();
|
||||
},
|
||||
updateSumReconcile() {
|
||||
// 只更新 hasUnallocated 狀態
|
||||
const maxTotal = this.dialog.selected ? (this.dialog.selected.check_amount || 0) : 0;
|
||||
this.hasUnallocated = maxTotal > this.sumReconcile;
|
||||
},
|
||||
loadFromDraftOrAutoDistribute() {
|
||||
const draft = this.dialog.selected ? this.dialog.selected.draft : null;
|
||||
|
||||
if (draft && draft.trim() && window.DraftUtils && window.DraftUtils.getDraftField) {
|
||||
const transferDraft = window.DraftUtils.getDraftField(draft, 'transfer_draft');
|
||||
if (transferDraft && Array.isArray(transferDraft)) {
|
||||
this.loadFromDraft(transferDraft);
|
||||
} else {
|
||||
this.autoDistributeReconcile();
|
||||
}
|
||||
} else {
|
||||
this.autoDistributeReconcile();
|
||||
}
|
||||
},
|
||||
loadFromDraft(draftData) {
|
||||
this.draftDataChanged = false;
|
||||
|
||||
// 先將所有項目的 reconcile 設為 0
|
||||
this.dialog.items.forEach(item => {
|
||||
this.$set(item, 'reconcile', 0);
|
||||
});
|
||||
|
||||
// 從 draft 資料填入沖帳金額
|
||||
draftData.forEach(draftItem => {
|
||||
const matchedItem = this.dialog.items.find(item =>
|
||||
item.num === draftItem.pro_order_detail_num
|
||||
);
|
||||
|
||||
if (matchedItem) {
|
||||
this.$set(matchedItem, 'reconcile', draftItem.reconcile || 0);
|
||||
} else {
|
||||
// 找不到對應的項目,標記資料已改動
|
||||
this.draftDataChanged = true;
|
||||
}
|
||||
});
|
||||
|
||||
this.updateSumReconcile();
|
||||
|
||||
// 如果有資料改動,提醒用戶
|
||||
if (this.draftDataChanged) {
|
||||
setTimeout(() => {
|
||||
alert('資料已改動,請注意金額一致性');
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
saveDraft() {
|
||||
try {
|
||||
// 組成新的 transfer_draft 資料
|
||||
const transferDraftData = this.dialog.items
|
||||
.filter(item => Number(item.reconcile) > 0)
|
||||
.map(item => ({
|
||||
pro_order_detail_num: item.num,
|
||||
reconcile: Number(item.reconcile)
|
||||
}));
|
||||
|
||||
// 使用工具函數安全地更新 draft 物件
|
||||
const currentDraft = this.dialog.selected.draft || '';
|
||||
let draftJson = '';
|
||||
|
||||
if (window.DraftUtils && window.DraftUtils.updateDraftField) {
|
||||
const newDraftObj = window.DraftUtils.updateDraftField(currentDraft, 'transfer_draft', transferDraftData);
|
||||
draftJson = JSON.stringify(newDraftObj);
|
||||
} else {
|
||||
// 如果 DraftUtils 不可用,使用簡單的格式
|
||||
draftJson = JSON.stringify({ transfer_draft: transferDraftData });
|
||||
}
|
||||
|
||||
// 更新 draft 欄位
|
||||
const updateData = {
|
||||
id: this.dialog.selected.id,
|
||||
activity_num: this.dialog.selected.activity_num,
|
||||
name: this.dialog.selected.name,
|
||||
phone: this.dialog.selected.phone,
|
||||
pay_type: this.dialog.selected.pay_type,
|
||||
account_last5: this.dialog.selected.account_last5,
|
||||
amount: this.dialog.selected.amount,
|
||||
pay_mode: this.dialog.selected.pay_mode,
|
||||
note: this.dialog.selected.note,
|
||||
proof_img: this.dialog.selected.proof_img,
|
||||
status: this.dialog.selected.status,
|
||||
f_num_match: this.dialog.selected.f_num_match,
|
||||
f_num: this.dialog.selected.f_num,
|
||||
acc_num: this.dialog.selected.acc_num,
|
||||
check_date: this.dialog.selected.check_date,
|
||||
check_amount: this.dialog.selected.check_amount,
|
||||
check_memo: this.dialog.selected.check_memo,
|
||||
check_status: this.dialog.selected.check_status,
|
||||
acc_kind: this.dialog.selected.acc_kind,
|
||||
member_num: this.dialog.selected.member_num,
|
||||
verify_time: this.dialog.selected.verify_time,
|
||||
verify_note: this.dialog.selected.verify_note,
|
||||
draft: draftJson // 更新 draft 欄位
|
||||
};
|
||||
|
||||
axios.put(`../../api/transfer_register/${this.dialog.selected.id}`, updateData)
|
||||
.then(() => {
|
||||
alert('暫存成功!');
|
||||
// 更新本地資料
|
||||
this.dialog.selected.draft = draftJson;
|
||||
})
|
||||
.catch(err => {
|
||||
alert('暫存失敗:' + (err.response?.data?.message || err.message));
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
alert('暫存失敗:資料格式錯誤');
|
||||
}
|
||||
},
|
||||
confirmReconcile() {
|
||||
if (!this.canConfirm) {
|
||||
this.showError(this.buttonErrorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
this.dialog.loading = true;
|
||||
const over_payment = this.dialog.selected.check_amount - this.sumReconcile;
|
||||
const postData = {
|
||||
transfer_register_id: this.dialog.selected.id,
|
||||
details: this.dialog.items
|
||||
.filter(item => item.reconcile > 0)
|
||||
.map(item => ({
|
||||
pro_order_detail_num: item.num,
|
||||
amount: Number(item.reconcile),
|
||||
reg_time: new Date().toISOString(),
|
||||
demo: `${this.dialog.selected.check_memo || ''}`
|
||||
})),
|
||||
over_payment: over_payment
|
||||
};
|
||||
|
||||
axios.post('../../api/transfer_register/reconcile', postData)
|
||||
.then(res => {
|
||||
// 顯示成功訊息
|
||||
const message = res.data && res.data.message ? res.data.message : '沖帳完成';
|
||||
msgtop(message, 'success');
|
||||
|
||||
this.dialog.show = false;
|
||||
this.loadTableData(); // 重新載入清單
|
||||
})
|
||||
.catch(err => {
|
||||
let errorMessage = '沖帳失敗,請聯繫系統管理員';
|
||||
if (err.response && err.response.data) {
|
||||
if (typeof err.response.data === 'string') {
|
||||
errorMessage = err.response.data;
|
||||
} else if (err.response.data.message) {
|
||||
errorMessage = err.response.data.message;
|
||||
if (err.response.data.exceptionMessage) {
|
||||
errorMessage += `\\n${err.response.data.exceptionMessage}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
alert(errorMessage);
|
||||
})
|
||||
.finally(() => {
|
||||
this.dialog.loading = false;
|
||||
});
|
||||
},
|
||||
validateAndUpdateReconcile(value, item, index) {
|
||||
let numValue = Number(value);
|
||||
if (isNaN(numValue)) {
|
||||
numValue = 0;
|
||||
}
|
||||
numValue = Math.round(numValue);
|
||||
|
||||
this.$set(item, 'reconcile', numValue);
|
||||
this.updateSumReconcile();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</asp:Content>
|
||||
10
web/admin/transfer/personal_reconcile.aspx.cs
Normal file
10
web/admin/transfer/personal_reconcile.aspx.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Web.UI;
|
||||
|
||||
public partial class admin_transfer_personal_reconcile : MyWeb.config
|
||||
{
|
||||
protected void Page_Load(object sender, EventArgs e)
|
||||
{
|
||||
// 頁面初始化(暫無邏輯)
|
||||
}
|
||||
}
|
||||
206
web/admin/transfer/register.aspx
Normal file
206
web/admin/transfer/register.aspx
Normal file
@@ -0,0 +1,206 @@
|
||||
<%@ Page Title="登錄匯款資料" Language="C#" AutoEventWireup="true" EnableEventValidation="false" CodeFile="register.aspx.cs" Inherits="admin_transfer_register" %>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-TW">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>報名者登錄匯款資料</title>
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Vue2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
|
||||
<!-- Axios -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||
<style>
|
||||
body { background-color: #f8f9fa; }
|
||||
.card { border: none; }
|
||||
.card-header { border-radius: 0.5rem 0.5rem 0 0 !important; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="container py-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-md-8 col-lg-6">
|
||||
<div class="card shadow">
|
||||
<div class="card-header bg-primary text-white text-center">
|
||||
<h4 class="mb-0">登錄匯款資料</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="transferForm" @submit.prevent="submitForm" enctype="multipart/form-data" autocomplete="off">
|
||||
<!-- 法會名稱 -->
|
||||
<div class="mb-3">
|
||||
<label for="activity_num" class="form-label">法會名稱 <span class="text-danger">*</span></label>
|
||||
<select class="form-select" id="activity_num" v-model="formData.activity_num" required>
|
||||
<option value="">請選擇</option>
|
||||
<option v-for="activity in activities" :key="activity.num" :value="activity.num">
|
||||
{{activity.subject}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- 姓名 -->
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">姓名 <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="name" v-model="formData.name" required maxlength="50" placeholder="請輸入姓名" @blur="onNameBlur">
|
||||
</div>
|
||||
<!-- 電話 -->
|
||||
<div class="mb-3">
|
||||
<label for="phone" class="form-label">電話 <span class="text-danger">*</span></label>
|
||||
<input type="tel" class="form-control" id="phone" v-model="formData.phone" required maxlength="30" placeholder="請輸入聯絡電話">
|
||||
</div>
|
||||
<!-- 支付方式 -->
|
||||
<div class="mb-3">
|
||||
<label for="pay_type" class="form-label">支付方式 <span class="text-danger">*</span></label>
|
||||
<select class="form-select" id="pay_type" v-model="formData.pay_type" required>
|
||||
<option value="">請選擇</option>
|
||||
<option v-for="(desc, value) in payTypes" :key="value" :value="value">
|
||||
{{desc}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- 帳號後五碼 -->
|
||||
<div class="mb-3">
|
||||
<label for="account_last5" class="form-label">帳號後五碼(若無免填)</label>
|
||||
<input type="text" class="form-control" id="account_last5" v-model="formData.account_last5" maxlength="10" placeholder="如有匯款請填寫">
|
||||
</div>
|
||||
<!-- 支付金額 -->
|
||||
<div class="mb-3">
|
||||
<label for="amount" class="form-label">支付金額 <span class="text-danger">*</span></label>
|
||||
<input type="number" class="form-control" id="amount" v-model="formData.amount" required min="1" step="1" placeholder="請輸入金額">
|
||||
</div>
|
||||
<!-- 支付型態 -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">支付型態 <span class="text-danger">*</span></label>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="pay_mode" id="pay_mode1" value="個人" v-model="formData.pay_mode" checked>
|
||||
<label class="form-check-label" for="pay_mode1">個人支付</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="pay_mode" id="pay_mode2" value="共同" v-model="formData.pay_mode">
|
||||
<label class="form-check-label" for="pay_mode2">共同支付</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 備註 -->
|
||||
<div class="mb-3">
|
||||
<label for="note" class="form-label">備註</label>
|
||||
<textarea class="form-control" id="note" v-model="formData.note" rows="2" maxlength="200" placeholder="如有分攤人名、金額、項目等請說明"></textarea>
|
||||
</div>
|
||||
<!-- 憑證上傳 -->
|
||||
<div class="mb-3">
|
||||
<label for="proof_img" class="form-label">上傳匯款憑證(可拍照)</label>
|
||||
<input class="form-control" type="file" id="proof_img" @change="handleFileUpload" accept="image/*" capture="environment">
|
||||
</div>
|
||||
<!-- 送出/關閉 -->
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" class="btn btn-primary btn-lg" :disabled="isSubmitting || !canSubmit">
|
||||
{{ isSubmitting ? '處理中...' : '送出資料' }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" @click="closeWindow">離開</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-3 text-muted small">
|
||||
<ul>
|
||||
<li>如一位組頭幫多人匯款,請在備註欄分拆明細(人名、金額、項目等)。</li>
|
||||
<li>送出後直接新增記錄,不顯示查核。</li>
|
||||
<li>如無姓名/電話資料,系統會提示。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap Bundle with Popper -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
activities: [],
|
||||
formData: {
|
||||
activity_num: '',
|
||||
name: '',
|
||||
phone: '',
|
||||
pay_type: '',
|
||||
account_last5: '',
|
||||
amount: '',
|
||||
pay_mode: '個人',
|
||||
note: '',
|
||||
proof_img: null
|
||||
},
|
||||
payTypes: {
|
||||
'1': '現金',
|
||||
'2': '匯款',
|
||||
'3': '支票'
|
||||
},
|
||||
isSubmitting: false,
|
||||
canSubmit: true
|
||||
},
|
||||
created() {
|
||||
this.loadActivities();
|
||||
},
|
||||
methods: {
|
||||
loadActivities() {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
axios.get(`../../api/activity?startDate=${today}&endDate=${today}`)
|
||||
.then(response => {
|
||||
this.activities = response.data;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('載入活動列表失敗:', error);
|
||||
alert('載入活動列表失敗,請重新整理頁面');
|
||||
});
|
||||
},
|
||||
handleFileUpload(event) {
|
||||
this.formData.proof_img = event.target.files[0];
|
||||
},
|
||||
submitForm() {
|
||||
if (this.isSubmitting || !this.canSubmit) return;
|
||||
this.isSubmitting = true;
|
||||
const formData = new FormData();
|
||||
Object.keys(this.formData).forEach(key => {
|
||||
if (this.formData[key] !== null) {
|
||||
formData.append(key, this.formData[key]);
|
||||
}
|
||||
});
|
||||
axios.post('../../api/transfer_register', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
alert('資料已成功送出!');
|
||||
this.formData = {
|
||||
activity_num: '',
|
||||
name: '',
|
||||
phone: '',
|
||||
pay_type: '',
|
||||
account_last5: '',
|
||||
amount: '',
|
||||
pay_mode: '個人',
|
||||
note: '',
|
||||
proof_img: null
|
||||
};
|
||||
this.canSubmit = false;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('送出失敗:', error);
|
||||
alert('送出失敗 :' + error.response.data.message);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isSubmitting = false;
|
||||
});
|
||||
},
|
||||
closeWindow() {
|
||||
window.close();
|
||||
},
|
||||
onNameBlur() {
|
||||
this.canSubmit = !!this.formData.name.trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
20
web/admin/transfer/register.aspx.cs
Normal file
20
web/admin/transfer/register.aspx.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Web.UI;
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Data.OleDb;
|
||||
|
||||
public partial class admin_transfer_register : Page//MyWeb.config
|
||||
{
|
||||
protected void Page_Load(object sender, EventArgs e)
|
||||
{
|
||||
if (!IsPostBack)
|
||||
{
|
||||
// 檢查是否為彈出視窗
|
||||
//if (Request.QueryString["popup"] != "1")
|
||||
//{
|
||||
// Response.Redirect("~/admin/transfer/index.aspx");
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
574
web/admin/transfer/verify.aspx
Normal file
574
web/admin/transfer/verify.aspx
Normal file
@@ -0,0 +1,574 @@
|
||||
<%@ 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>
|
||||
10
web/admin/transfer/verify.aspx.cs
Normal file
10
web/admin/transfer/verify.aspx.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Web.UI;
|
||||
|
||||
public partial class admin_transfer_verify : MyWeb.config
|
||||
{
|
||||
protected void Page_Load(object sender, EventArgs e)
|
||||
{
|
||||
// 頁面初始化(暫無邏輯)
|
||||
}
|
||||
}
|
||||
341
web/admin/transfer/verify1.aspx
Normal file
341
web/admin/transfer/verify1.aspx
Normal file
@@ -0,0 +1,341 @@
|
||||
<%@ Page Title="出納核對匯款人" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" EnableEventValidation="false" CodeFile="verify1.aspx.cs" Inherits="admin_transfer_verify1" %>
|
||||
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" runat="Server">
|
||||
</asp:Content>
|
||||
<asp:Content ID="Content3" ContentPlaceHolderID="page_nav" runat="Server">
|
||||
<h5 class="mb-0">出納 - 核對匯款人(階段1)</h5>
|
||||
</asp:Content>
|
||||
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
|
||||
<div id="verify-app">
|
||||
<v-app>
|
||||
<v-container>
|
||||
<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="saveData">確認送出</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>
|
||||
|
||||
<!-- 信眾選擇對話框 -->
|
||||
<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>
|
||||
new Vue({
|
||||
el: '#verify-app',
|
||||
vuetify: new Vuetify(),
|
||||
data() {
|
||||
return {
|
||||
headers: [
|
||||
{ text: '匯款人資訊', value: 'info' },
|
||||
{ text: '對應信眾', value: 'f_num' },
|
||||
{ text: '選擇信眾', value: 'actions', sortable: false },
|
||||
{ text: '狀態 | 核對記錄', value: 'status' },
|
||||
],
|
||||
items: [],
|
||||
activities: [],
|
||||
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' }
|
||||
],
|
||||
items: [],
|
||||
selected: null,
|
||||
current_item: null
|
||||
}
|
||||
};
|
||||
},
|
||||
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;
|
||||
}
|
||||
},
|
||||
async selectFollower(follower) {
|
||||
if (this.follower_dialog.current_item) {
|
||||
try {
|
||||
// 先關閉對話框,避免重複點擊
|
||||
this.follower_dialog.show = false;
|
||||
|
||||
// 直接使用點選的信眾資料,因為已經包含了解密後的電話和手機
|
||||
// 從 follower_dialog.items 中找到對應的信眾資料
|
||||
const selectedFollower = this.follower_dialog.items.find(item => item.num === follower.num);
|
||||
|
||||
if (selectedFollower) {
|
||||
// 更新當前項目的 f_num
|
||||
this.follower_dialog.current_item.f_num = follower.num;
|
||||
// 更新 follower 物件,包含解碼後的電話和手機資訊
|
||||
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);
|
||||
alert('取得信眾詳細資料失敗,請重試');
|
||||
// 如果發生錯誤,至少更新基本資訊
|
||||
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;
|
||||
// 取得活動清單
|
||||
const actRes = await axios.get('../../api/activity');
|
||||
this.activities = actRes.data;
|
||||
// 取得階段1的待處理資料
|
||||
const res = await axios.get('../../api/transfer_register/pending');
|
||||
this.items = res.data.map(item => ({
|
||||
...item,
|
||||
status: item.status ? String(item.status) : ''
|
||||
}));
|
||||
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
|
||||
}));
|
||||
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);
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadData();
|
||||
}
|
||||
});
|
||||
</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;
|
||||
}
|
||||
|
||||
/* 針對所有 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>
|
||||
10
web/admin/transfer/verify1.aspx.cs
Normal file
10
web/admin/transfer/verify1.aspx.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Web.UI;
|
||||
|
||||
public partial class admin_transfer_verify1 : MyWeb.config
|
||||
{
|
||||
protected void Page_Load(object sender, EventArgs e)
|
||||
{
|
||||
// 頁面初始化(暫無邏輯)
|
||||
}
|
||||
}
|
||||
337
web/admin/transfer/verify2.aspx
Normal file
337
web/admin/transfer/verify2.aspx
Normal file
@@ -0,0 +1,337 @@
|
||||
<%@ Page Title="出納核對金額" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" EnableEventValidation="false" CodeFile="verify2.aspx.cs" Inherits="admin_transfer_verify2" %>
|
||||
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" runat="Server">
|
||||
</asp:Content>
|
||||
<asp:Content ID="Content3" ContentPlaceHolderID="page_nav" runat="Server">
|
||||
<h5 class="mb-0">出納 - 核對金額(階段2)</h5>
|
||||
</asp:Content>
|
||||
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
|
||||
<div id="verify-app">
|
||||
<v-app>
|
||||
<v-container>
|
||||
<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="submitData">送出</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="items"
|
||||
:loading="loading"
|
||||
loading-text="載入中..."
|
||||
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-app>
|
||||
</div>
|
||||
</asp:Content>
|
||||
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" runat="Server">
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#verify-app',
|
||||
vuetify: new Vuetify(),
|
||||
data() {
|
||||
return {
|
||||
headers: [
|
||||
{ text: '匯款人資訊', value: 'info' },
|
||||
{ text: '匯款備註/相片', value: 'note' },
|
||||
{ text: '入帳銀行/帳戶 | 支付資訊/帳號後5碼', value: 'acc_num' },
|
||||
{ text: '入帳日期', value: 'check_date' },
|
||||
{ text: '入帳金額', value: 'check_amount' },
|
||||
{ text: '備註/狀態 | 核對記錄', value: 'check_memo' }
|
||||
],
|
||||
items: [],
|
||||
activities: [],
|
||||
loading: false,
|
||||
checkStatusOptions: [
|
||||
{ text: '', value: '' },
|
||||
{ text: '未核對', value: '1' },
|
||||
{ text: '核對', value: '2' },
|
||||
{ text: '金額不符', value: '3' },
|
||||
{ text: '其他問題', value: '4' },
|
||||
{ text: '作廢', value: '5' }
|
||||
],
|
||||
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 : '';
|
||||
},
|
||||
async loadData() {
|
||||
this.loading = true;
|
||||
// 取得活動清單
|
||||
const actRes = await axios.get('../../api/activity');
|
||||
this.activities = actRes.data;
|
||||
// 取得階段2的待處理資料(已通過階段1的資料)
|
||||
const res = await axios.get('../../api/transfer_register/step2_list');
|
||||
this.items = res.data.map(item => ({
|
||||
...item,
|
||||
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 submitData() {
|
||||
// 組出要更新的資料
|
||||
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('送出成功!');
|
||||
// 重新載入資料
|
||||
await this.loadData();
|
||||
} else {
|
||||
alert('送出失敗,請重試 :' + res.data.message);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('送出失敗:', e);
|
||||
alert('送出失敗,請再試一次!');
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadData();
|
||||
}
|
||||
});
|
||||
</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>
|
||||
10
web/admin/transfer/verify2.aspx.cs
Normal file
10
web/admin/transfer/verify2.aspx.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Web.UI;
|
||||
|
||||
public partial class admin_transfer_verify2 : MyWeb.config
|
||||
{
|
||||
protected void Page_Load(object sender, EventArgs e)
|
||||
{
|
||||
// 頁面初始化(暫無邏輯)
|
||||
}
|
||||
}
|
||||
398
web/admin/transfer/verify_order_record_query.aspx
Normal file
398
web/admin/transfer/verify_order_record_query.aspx
Normal file
@@ -0,0 +1,398 @@
|
||||
<%@ Page Title="沖帳查詢" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" EnableEventValidation="false" CodeFile="verify_order_record_query.aspx.cs" Inherits="admin_transfer_verify_order_record_query" %>
|
||||
<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-query-app">
|
||||
<v-app>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<v-card-title class="bg-primary white--text text-center">
|
||||
<h5 class="mb-0">沖帳查詢</h5>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-row class="mb-0 mt-4">
|
||||
<v-col cols="12" md="2">
|
||||
<v-text-field v-model="query.start_date" label="起始日" type="date" dense outlined></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="2">
|
||||
<v-text-field v-model="query.end_date" label="結束日" type="date" dense outlined></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="3">
|
||||
<v-text-field v-model="query.activity_name" label="法會" dense outlined readonly>
|
||||
<template v-slot:append>
|
||||
<v-btn icon @click="showActivityDialog"><v-icon>mdi-magnify</v-icon></v-btn>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="3">
|
||||
<v-text-field v-model="query.follower_name" label="信眾" dense outlined readonly>
|
||||
<template v-slot:append>
|
||||
<v-btn icon @click="showFollowerDialog"><v-icon>mdi-magnify</v-icon></v-btn>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="2" class="d-flex justify-end">
|
||||
<v-btn color="primary" class="mr-2" @click="search">查詢</v-btn>
|
||||
<v-btn color="grey" @click="reset">重設</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="items"
|
||||
:loading="loading"
|
||||
loading-text="載入中..."
|
||||
class="elevation-1 verify-query-table"
|
||||
item-key="transfer_id"
|
||||
:footer-props="{ 'items-per-page-options': [10, 20, 50] }"
|
||||
:expanded.sync="expanded"
|
||||
show-expand
|
||||
>
|
||||
<template v-slot:item.follower="{ item }">
|
||||
<div>
|
||||
<div class="font-weight-bold">{{ item.follower }}</div>
|
||||
<div class="caption text--secondary">{{ item.transfer_name }}</div>
|
||||
<div class="caption text--secondary">{{ item.transfer_phone }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.activity_info="{ item }">
|
||||
<div>
|
||||
<div class="font-weight-bold">{{ item.activity_name }}</div>
|
||||
<div class="caption text--secondary">{{ item.acc_name }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.transfer_info="{ item }">
|
||||
<div>
|
||||
<div class="font-weight-bold text-primary">入帳金額:{{ item.transfer_check_amount | currency }}</div>
|
||||
<div class="text-success">沖帳日期:{{ item.transfer_check_date | date }}</div>
|
||||
<div v-if="item.transfer_remain_amount > 0" class="text-warning">
|
||||
剩餘金額:{{ item.transfer_remain_amount | currency }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.status_info="{ item }">
|
||||
<div>
|
||||
<v-chip small :color="getStatusColor(item.transfer_check_status)" text-color="white">
|
||||
{{ getStatusText(item.transfer_check_status) }}
|
||||
</v-chip>
|
||||
<div class="caption text--secondary mt-1">{{ item.transfer_check_memo }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<v-btn color="info" outlined small @click="showDetailDialog(item)">
|
||||
<v-icon small class="mr-1">mdi-information-outline</v-icon> 詳細
|
||||
</v-btn>
|
||||
</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-data-table
|
||||
:headers="detailHeaders"
|
||||
:items="item.pro_order_records"
|
||||
class="elevation-1"
|
||||
hide-default-footer
|
||||
:disable-pagination="true"
|
||||
dense
|
||||
>
|
||||
<template v-slot:item.order_info="{ item }">
|
||||
<div>
|
||||
<div class="font-weight-bold">{{ item.pro_order_detail?.pro_order?.order_no }}</div>
|
||||
<div class="caption text--secondary">{{ item.pro_order_detail?.actitem_name }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.payment_info="{ item }">
|
||||
<div>
|
||||
<div class="font-weight-bold">{{ item.payment_name }}</div>
|
||||
<div class="caption text--secondary">{{ item.bank_code }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.amount_info="{ item }">
|
||||
<div>
|
||||
<div class="font-weight-bold text-success">{{ item.price | currency }}</div>
|
||||
<div class="caption text--secondary">{{ item.pay_date | date }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item.memo="{ item }">
|
||||
<div style="white-space: pre-line;">{{ item.reconcile_memo }}</div>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</div>
|
||||
</td>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 法會選取 Dialog -->
|
||||
<v-dialog v-model="activityDialog.show" max-width="700px">
|
||||
<v-card>
|
||||
<v-card-title class="grey lighten-2">
|
||||
<v-icon color="primary" class="mr-2">mdi-table</v-icon>
|
||||
選擇法會
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="activityDialog.show = false"><v-icon>mdi-close</v-icon></v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text class="mt-4">
|
||||
<v-text-field v-model="activityDialog.search" label="搜尋法會" dense outlined @keyup.enter="searchActivity"></v-text-field>
|
||||
<v-data-table
|
||||
:headers="activityDialog.headers"
|
||||
:items="activityDialog.items"
|
||||
:loading="activityDialog.loading"
|
||||
item-key="num"
|
||||
class="elevation-1 mt-2"
|
||||
@click:row="selectActivity"
|
||||
hide-default-footer
|
||||
></v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 信眾選取 Dialog -->
|
||||
<v-dialog v-model="followerDialog.show" max-width="700px">
|
||||
<v-card>
|
||||
<v-card-title class="grey lighten-2">
|
||||
<v-icon color="primary" class="mr-2">mdi-account</v-icon>
|
||||
選擇信眾
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="followerDialog.show = false"><v-icon>mdi-close</v-icon></v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text class="mt-4">
|
||||
<v-text-field v-model="followerDialog.search" label="搜尋信眾" dense outlined @keyup.enter="searchFollower"></v-text-field>
|
||||
<v-data-table
|
||||
:headers="followerDialog.headers"
|
||||
:items="followerDialog.items"
|
||||
:loading="followerDialog.loading"
|
||||
item-key="num"
|
||||
class="elevation-1 mt-2"
|
||||
@click:row="selectFollower"
|
||||
hide-default-footer
|
||||
></v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 詳細資訊對話框 -->
|
||||
<v-dialog v-model="dialog.show" max-width="900px">
|
||||
<v-card>
|
||||
<v-card-title class="grey lighten-2">
|
||||
<v-icon color="info" class="mr-2">mdi-information-outline</v-icon>
|
||||
詳細資訊
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="dialog.show = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<div v-if="dialog.selected">
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<h6 class="mb-3 text-primary">匯款人資訊</h6>
|
||||
<div class="mb-2"><span class="font-weight-bold">姓名:</span>{{ dialog.selected.transfer_name }}</div>
|
||||
<div class="mb-2"><span class="font-weight-bold">電話:</span>{{ dialog.selected.transfer_phone }}</div>
|
||||
<div class="mb-2"><span class="font-weight-bold">信眾:</span>{{ dialog.selected.follower }}</div>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<h6 class="mb-3 text-success">入帳資訊</h6>
|
||||
<div class="mb-2"><span class="font-weight-bold">入帳帳戶:</span>{{ dialog.selected.acc_name }}</div>
|
||||
<div class="mb-2"><span class="font-weight-bold">入帳日期:</span>{{ dialog.selected.transfer_check_date | date }}</div>
|
||||
<div class="mb-2"><span class="font-weight-bold">入帳金額:</span>{{ dialog.selected.transfer_check_amount | currency }}</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-divider class="my-4"></v-divider>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<h6 class="mb-3 text-purple">活動資訊</h6>
|
||||
<div class="mb-2"><span class="font-weight-bold">活動名稱:</span>{{ dialog.selected.activity_name || '-' }}</div>
|
||||
<div class="mb-2"><span class="font-weight-bold">支付方式:</span>{{ payTypeText[dialog.selected.transfer_pay_type] || dialog.selected.transfer_pay_type }}</div>
|
||||
<div class="mb-2"><span class="font-weight-bold">帳號後5碼:</span>{{ dialog.selected.transfer_account_last5 || '-' }}</div>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<h6 class="mb-3 text-info">核對記錄</h6>
|
||||
<div class="mb-2"><span class="font-weight-bold">核對記錄:</span>{{ dialog.selected.transfer_verify_note || '-' }}</div>
|
||||
<div class="mb-2"><span class="font-weight-bold">帳簿備註:</span>{{ dialog.selected.transfer_check_memo || '-' }}</div>
|
||||
<div v-if="dialog.selected.transfer_proof_img" class="mb-2">
|
||||
<span class="font-weight-bold">證明圖片:</span>
|
||||
<a :href="'../../upload/transfer_proof/' + dialog.selected.transfer_proof_img" target="_blank">查看相片</a>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-container>
|
||||
</v-app>
|
||||
</div>
|
||||
</asp:Content>
|
||||
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" runat="Server">
|
||||
<style>
|
||||
.v-data-table.verify-query-table .v-data-table__wrapper tbody tr {
|
||||
min-height: 90px !important;
|
||||
height: 90px !important;
|
||||
}
|
||||
.v-data-table.verify-query-table .v-data-table__wrapper tbody td {
|
||||
min-height: 90px !important;
|
||||
height: 90px !important;
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#verify-query-app',
|
||||
vuetify: new Vuetify(),
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
expanded: [],
|
||||
query: {
|
||||
start_date: '',
|
||||
end_date: '',
|
||||
activity_num: '',
|
||||
activity_name: '',
|
||||
follower_num: '',
|
||||
follower_name: ''
|
||||
},
|
||||
headers: [
|
||||
{ text: '信眾/匯款人', value: 'follower' },
|
||||
{ text: '法會/入帳帳戶', value: 'activity_info' },
|
||||
{ text: '入帳資訊', value: 'transfer_info' },
|
||||
{ text: '狀態/備註', value: 'status_info' },
|
||||
{ text: '操作', value: 'actions', sortable: false }
|
||||
],
|
||||
detailHeaders: [
|
||||
{ text: '訂單/項目', value: 'order_info' },
|
||||
{ text: '付款機構', value: 'payment_info' },
|
||||
{ text: '沖帳金額/日期', value: 'amount_info' },
|
||||
{ text: '備註', value: 'memo' }
|
||||
],
|
||||
items: [],
|
||||
dialog: {
|
||||
show: false,
|
||||
selected: null
|
||||
},
|
||||
activityDialog: {
|
||||
show: false,
|
||||
search: '',
|
||||
loading: false,
|
||||
items: [],
|
||||
headers: [
|
||||
{ text: '編號', value: 'num' },
|
||||
{ text: '名稱', value: 'subject' }
|
||||
]
|
||||
},
|
||||
followerDialog: {
|
||||
show: false,
|
||||
search: '',
|
||||
loading: false,
|
||||
items: [],
|
||||
headers: [
|
||||
{ text: '編號', value: 'num' },
|
||||
{ text: '姓名', value: 'u_name' }
|
||||
]
|
||||
},
|
||||
payTypeText: {
|
||||
1: '現金',
|
||||
2: '匯款',
|
||||
3: '支票'
|
||||
}
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
currency(val) {
|
||||
if (!val) return '0';
|
||||
return Number(val).toLocaleString();
|
||||
},
|
||||
date(val) {
|
||||
if (!val) return '';
|
||||
const date = new Date(val);
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
search() {
|
||||
this.loading = true;
|
||||
// 串接查詢 API,帶入所有查詢條件
|
||||
axios.get('../../api/transfer_register/verify_order_record_query', { params: this.query })
|
||||
.then(res => {
|
||||
this.items = res.data;
|
||||
this.expanded = []; // 重置展開狀態
|
||||
})
|
||||
.catch(() => {
|
||||
this.items = [];
|
||||
this.expanded = [];
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
reset() {
|
||||
this.query.start_date = '';
|
||||
this.query.end_date = '';
|
||||
this.query.activity_num = '';
|
||||
this.query.activity_name = '';
|
||||
this.query.follower_num = '';
|
||||
this.query.follower_name = '';
|
||||
this.items = [];
|
||||
this.expanded = [];
|
||||
},
|
||||
showDetailDialog(item) {
|
||||
this.dialog.selected = item;
|
||||
this.dialog.show = true;
|
||||
},
|
||||
showActivityDialog() {
|
||||
this.activityDialog.show = true;
|
||||
this.searchActivity();
|
||||
},
|
||||
searchActivity() {
|
||||
this.activityDialog.loading = true;
|
||||
axios.get('../../api/activity', { params: { keyword: this.activityDialog.search } })
|
||||
.then(res => { this.activityDialog.items = res.data; })
|
||||
.catch(() => { this.activityDialog.items = []; })
|
||||
.finally(() => { this.activityDialog.loading = false; });
|
||||
},
|
||||
selectActivity(row) {
|
||||
this.query.activity_num = row.num;
|
||||
this.query.activity_name = row.subject;
|
||||
this.activityDialog.show = false;
|
||||
},
|
||||
showFollowerDialog() {
|
||||
this.followerDialog.show = true;
|
||||
this.searchFollower();
|
||||
},
|
||||
searchFollower() {
|
||||
this.followerDialog.loading = true;
|
||||
axios.post('../..../..../../api/follower/GetList', { u_name: this.followerDialog.search }, { params: { page: 1, pageSize: 10 } })
|
||||
.then(res => { this.followerDialog.items = res.data.list; })
|
||||
.catch(() => { this.followerDialog.items = []; })
|
||||
.finally(() => { this.followerDialog.loading = false; });
|
||||
},
|
||||
selectFollower(row) {
|
||||
this.query.follower_num = row.num;
|
||||
this.query.follower_name = row.u_name;
|
||||
this.followerDialog.show = false;
|
||||
},
|
||||
getStatusColor(status) {
|
||||
switch(status) {
|
||||
case '90': return 'warning';
|
||||
case '99': return 'success';
|
||||
default: return 'grey';
|
||||
}
|
||||
},
|
||||
getStatusText(status) {
|
||||
switch(status) {
|
||||
case '90': return '沖帳有剩餘';
|
||||
case '99': return '沖帳完成';
|
||||
default: return '未知狀態';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</asp:Content>
|
||||
10
web/admin/transfer/verify_order_record_query.aspx.cs
Normal file
10
web/admin/transfer/verify_order_record_query.aspx.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Web.UI;
|
||||
|
||||
public partial class admin_transfer_verify_order_record_query : MyWeb.config
|
||||
{
|
||||
protected void Page_Load(object sender, EventArgs e)
|
||||
{
|
||||
// 頁面初始化(暫無邏輯)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user