642 lines
28 KiB
JavaScript
642 lines
28 KiB
JavaScript
let taxReceiptApp = null;
|
|
|
|
function CreateTaxReceiptApp() {
|
|
const loadMarker = document.getElementById('taxReceiptApp');
|
|
if (loadMarker) {
|
|
DoCreateTaxReceiptApp();
|
|
}
|
|
}
|
|
|
|
function DoCreateTaxReceiptApp() {
|
|
taxReceiptApp = new Vue({
|
|
el: '#taxReceiptApp',
|
|
mixins: [ToastMixin, ApiMixin],
|
|
data: {
|
|
activeTab: 0,
|
|
currentYear: '',
|
|
currentClosingMonth: '',
|
|
newClosingMonth: '',
|
|
loading: false,
|
|
message: '',
|
|
messageType: 'info',
|
|
summaryTable: {
|
|
startDate: '',
|
|
endDate: '',
|
|
identity: '',
|
|
paymentMethod: '',
|
|
identityOptions: [],
|
|
paymentMethodOptions: [],
|
|
dataList: [],
|
|
loading: false,
|
|
message: '',
|
|
messageType: 'info'
|
|
},
|
|
detailTable: {
|
|
startDate: '',
|
|
endDate: '',
|
|
identity: '',
|
|
certificateType: '',
|
|
identityOptions: [],
|
|
certificateTypeOptions: [],
|
|
dataList: [],
|
|
selectAll: false,
|
|
senderEmail: '',
|
|
emailSubject: '',
|
|
emailBody: '',
|
|
loading: false,
|
|
message: '',
|
|
messageType: 'info',
|
|
emailMessage: '',
|
|
emailMessageType: 'info',
|
|
previewData: [],
|
|
showEmailPreview: false
|
|
}
|
|
},
|
|
created() {
|
|
this.initializeData();
|
|
this.loadSummaryTableDropdowns();
|
|
this.loadDetailTableDropdowns();
|
|
// 確保預覽窗口初始為關閉狀態
|
|
this.detailTable.showEmailPreview = false;
|
|
this.detailTable.previewData = [];
|
|
},
|
|
mounted() {
|
|
this.loadClosingMonthData();
|
|
// 再次確保預覽窗口初始為關閉狀態
|
|
this.$nextTick(() => {
|
|
this.detailTable.showEmailPreview = false;
|
|
this.detailTable.previewData = [];
|
|
});
|
|
},
|
|
methods: {
|
|
initializeData() {
|
|
// 取得現行年度
|
|
const now = new Date();
|
|
this.currentYear = now.getFullYear().toString();
|
|
},
|
|
|
|
async loadClosingMonthData() {
|
|
try {
|
|
const result = await this.callApiWithErrorMsg(
|
|
'領據處理',
|
|
'GetClosingMonth',
|
|
{},
|
|
'載入關帳年月資料'
|
|
);
|
|
|
|
if (result && result.Data) {
|
|
this.currentClosingMonth = result.Data.currentClosingMonth || '';
|
|
// 初始化新關帳年月為目前月份
|
|
const now = new Date();
|
|
const year = now.getFullYear();
|
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
this.newClosingMonth = `${year}-${month}`;
|
|
}
|
|
} catch (error) {
|
|
console.error('載入關帳年月失敗:', error);
|
|
this.showMessage('載入關帳年月失敗: ' + (error.message || '請檢查網路連線'), 'danger');
|
|
}
|
|
},
|
|
|
|
validateMonth() {
|
|
if (!this.newClosingMonth) {
|
|
return;
|
|
}
|
|
|
|
// 驗證格式 YYYY-MM
|
|
const pattern = /^\d{4}-\d{2}$/;
|
|
if (!pattern.test(this.newClosingMonth)) {
|
|
this.showMessage('年月格式不正確,請使用 YYYY-MM 格式', 'danger');
|
|
this.newClosingMonth = '';
|
|
return;
|
|
}
|
|
|
|
// 驗證年份
|
|
const [year, month] = this.newClosingMonth.split('-');
|
|
const yearNum = parseInt(year);
|
|
const monthNum = parseInt(month);
|
|
|
|
// 月份範圍驗證
|
|
if (monthNum < 1 || monthNum > 12) {
|
|
this.showMessage('月份必須介於 01 到 12 之間', 'danger');
|
|
this.newClosingMonth = '';
|
|
return;
|
|
}
|
|
|
|
// 年份不能超過現在
|
|
const currentYear = new Date().getFullYear();
|
|
if (yearNum > currentYear) {
|
|
this.showMessage('年份不能超過現在', 'danger');
|
|
this.newClosingMonth = '';
|
|
return;
|
|
}
|
|
},
|
|
|
|
async saveClosingMonth() {
|
|
if (!this.newClosingMonth) {
|
|
this.showMessage('請輸入調整的關帳年月', 'warning');
|
|
return;
|
|
}
|
|
|
|
// 再次驗證
|
|
this.validateMonth();
|
|
if (!this.newClosingMonth) {
|
|
return;
|
|
}
|
|
|
|
// 確認操作
|
|
if (!confirm(`確認要將領據關帳年月調整為 ${this.newClosingMonth} 嗎?\n此操作將影響領據的修改權限。`)) {
|
|
return;
|
|
}
|
|
|
|
this.loading = true;
|
|
|
|
try {
|
|
const params = {
|
|
closingMonth: this.newClosingMonth
|
|
};
|
|
|
|
const result = await this.callApiWithErrorMsg(
|
|
'領據處理',
|
|
'UpdateClosingMonth',
|
|
params,
|
|
'保存關帳年月'
|
|
);
|
|
|
|
if (result && result.Success) {
|
|
this.showMessage('關帳年月已成功更新', 'success');
|
|
this.currentClosingMonth = this.newClosingMonth;
|
|
// 清除輸入欄位
|
|
setTimeout(() => {
|
|
this.clearForm();
|
|
}, 1500);
|
|
} else {
|
|
this.showMessage(result.Message || '保存失敗,請稍後重試', 'danger');
|
|
}
|
|
} catch (error) {
|
|
console.error('保存失敗:', error);
|
|
this.showMessage('保存失敗: ' + (error.message || '請檢查網路連線'), 'danger');
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
clearForm() {
|
|
this.newClosingMonth = '';
|
|
this.message = '';
|
|
},
|
|
|
|
showMessage(text, type = 'info') {
|
|
this.message = text;
|
|
this.messageType = type;
|
|
|
|
// 3 秒後自動隱藏訊息
|
|
if (type === 'success' || type === 'info') {
|
|
setTimeout(() => {
|
|
this.message = '';
|
|
}, 3000);
|
|
}
|
|
},
|
|
|
|
async loadSummaryTableDropdowns() {
|
|
try {
|
|
// 加載身分別選項
|
|
const identityResult = await this.callApiWithErrorMsg(
|
|
'領據處理',
|
|
'GetDropdownLists',
|
|
{ key: '身分別' },
|
|
'載入身分別選項'
|
|
);
|
|
if (identityResult && identityResult.Data) {
|
|
this.summaryTable.identityOptions = identityResult.Data.filter(item => item.Value !== '');
|
|
}
|
|
|
|
// 加載付款方式選項
|
|
const paymentResult = await this.callApiWithErrorMsg(
|
|
'領據處理',
|
|
'GetDropdownLists',
|
|
{ key: '付款方式' },
|
|
'載入付款方式選項'
|
|
);
|
|
if (paymentResult && paymentResult.Data) {
|
|
this.summaryTable.paymentMethodOptions = paymentResult.Data.filter(item => item.Value !== '');
|
|
}
|
|
} catch (error) {
|
|
console.error('加載下拉清單失敗:', error);
|
|
}
|
|
},
|
|
|
|
async searchSummaryTable() {
|
|
this.summaryTable.loading = true;
|
|
this.summaryTable.message = '';
|
|
this.summaryTable.dataList = [];
|
|
|
|
try {
|
|
const params = {
|
|
起日: this.summaryTable.startDate ? this.parseDate(this.summaryTable.startDate) : null,
|
|
迄日: this.summaryTable.endDate ? this.parseDate(this.summaryTable.endDate) : null,
|
|
身分別: this.summaryTable.identity || null,
|
|
付款方式: this.summaryTable.paymentMethod || null,
|
|
輸出方式: 'DATA'
|
|
};
|
|
|
|
const result = await this.callApiWithErrorMsg(
|
|
'領據處理',
|
|
'SearchSummaryTable',
|
|
params,
|
|
'查詢個人費用彙整表'
|
|
);
|
|
|
|
if (result && result.Success) {
|
|
this.summaryTable.dataList = result.Data || [];
|
|
if (this.summaryTable.dataList.length === 0) {
|
|
this.summaryTable.message = '查詢完成,但未找到符合條件的資料';
|
|
this.summaryTable.messageType = 'info';
|
|
} else {
|
|
this.summaryTable.message = `查詢完成,共找到 ${this.summaryTable.dataList.length} 筆資料`;
|
|
this.summaryTable.messageType = 'success';
|
|
}
|
|
} else {
|
|
this.summaryTable.message = result.Message || '查詢失敗,請稍後重試';
|
|
this.summaryTable.messageType = 'danger';
|
|
}
|
|
} catch (error) {
|
|
console.error('查詢失敗:', error);
|
|
this.summaryTable.message = '查詢失敗: ' + (error.message || '請檢查網路連線');
|
|
this.summaryTable.messageType = 'danger';
|
|
} finally {
|
|
this.summaryTable.loading = false;
|
|
}
|
|
},
|
|
|
|
async exportSummaryTable() {
|
|
if (this.summaryTable.dataList.length === 0) {
|
|
this.summaryTable.message = '沒有資料可以匯出,請先執行查詢';
|
|
this.summaryTable.messageType = 'warning';
|
|
return;
|
|
}
|
|
|
|
this.summaryTable.loading = true;
|
|
this.summaryTable.message = '';
|
|
|
|
try {
|
|
const params = {
|
|
起日: this.summaryTable.startDate ? this.parseDate(this.summaryTable.startDate) : null,
|
|
迄日: this.summaryTable.endDate ? this.parseDate(this.summaryTable.endDate) : null,
|
|
身分別: this.summaryTable.identity || null,
|
|
付款方式: this.summaryTable.paymentMethod || null,
|
|
輸出方式: 'EXPORT'
|
|
};
|
|
|
|
const result = await this.callApiWithErrorMsg(
|
|
'領據處理',
|
|
'SearchSummaryTable',
|
|
params,
|
|
'匯出個人費用彙整表'
|
|
);
|
|
|
|
if (result && result.Success && result.Data) {
|
|
// 下載 Excel 檔案
|
|
const excelData = result.Data;
|
|
if (excelData.excelData && excelData.fileName) {
|
|
this.downloadExcel(excelData.excelData, excelData.fileName);
|
|
this.summaryTable.message = '匯出成功';
|
|
this.summaryTable.messageType = 'success';
|
|
} else {
|
|
this.summaryTable.message = '匯出失敗:無法取得檔案';
|
|
this.summaryTable.messageType = 'danger';
|
|
}
|
|
} else {
|
|
this.summaryTable.message = result.Message || '匯出失敗,請稍後重試';
|
|
this.summaryTable.messageType = 'danger';
|
|
}
|
|
} catch (error) {
|
|
console.error('匯出失敗:', error);
|
|
this.summaryTable.message = '匯出失敗: ' + (error.message || '請檢查網路連線');
|
|
this.summaryTable.messageType = 'danger';
|
|
} finally {
|
|
this.summaryTable.loading = false;
|
|
}
|
|
},
|
|
|
|
parseDate(dateString) {
|
|
if (!dateString) return null;
|
|
const date = new Date(dateString);
|
|
return date.toISOString().split('T')[0];
|
|
},
|
|
|
|
formatCurrency(value) {
|
|
if (!value) return '0';
|
|
return new Intl.NumberFormat('zh-TW', {
|
|
style: 'currency',
|
|
currency: 'TWD',
|
|
minimumFractionDigits: 0,
|
|
maximumFractionDigits: 0
|
|
}).format(value);
|
|
},
|
|
|
|
downloadExcel(excelBase64, fileName) {
|
|
const binaryString = atob(excelBase64);
|
|
const bytes = new Uint8Array(binaryString.length);
|
|
for (let i = 0; i < binaryString.length; i++) {
|
|
bytes[i] = binaryString.charCodeAt(i);
|
|
}
|
|
const blob = new Blob([bytes], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
|
const url = window.URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = fileName;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
window.URL.revokeObjectURL(url);
|
|
document.body.removeChild(link);
|
|
},
|
|
|
|
async loadDetailTableDropdowns() {
|
|
try {
|
|
// 加載身分別選項
|
|
const identityResult = await this.callApiWithErrorMsg(
|
|
'領據處理',
|
|
'GetDropdownLists',
|
|
{ key: '身分別' },
|
|
'載入身分別選項'
|
|
);
|
|
if (identityResult && identityResult.Data) {
|
|
this.detailTable.identityOptions = identityResult.Data.filter(item => item.Value !== '');
|
|
}
|
|
|
|
// 加載憑證類別選項
|
|
const certResult = await this.callApiWithErrorMsg(
|
|
'領據處理',
|
|
'GetDropdownLists',
|
|
{ key: '憑證類別' },
|
|
'載入憑證類別選項'
|
|
);
|
|
if (certResult && certResult.Data) {
|
|
this.detailTable.certificateTypeOptions = certResult.Data.filter(item => item.Value !== '');
|
|
}
|
|
} catch (error) {
|
|
console.error('加載下拉清單失敗:', error);
|
|
}
|
|
},
|
|
|
|
async searchDetailTable() {
|
|
this.detailTable.loading = true;
|
|
this.detailTable.message = '';
|
|
this.detailTable.dataList = [];
|
|
this.detailTable.selectAll = false;
|
|
|
|
try {
|
|
const params = {
|
|
起日: this.detailTable.startDate ? this.parseDate(this.detailTable.startDate) : null,
|
|
迄日: this.detailTable.endDate ? this.parseDate(this.detailTable.endDate) : null,
|
|
身分別: this.detailTable.identity || null,
|
|
憑證類別: this.detailTable.certificateType || null,
|
|
輸出方式: 'DATA'
|
|
};
|
|
|
|
const result = await this.callApiWithErrorMsg(
|
|
'領據處理',
|
|
'SearchDetailTable',
|
|
params,
|
|
'查詢扣繳明細'
|
|
);
|
|
|
|
if (result && result.Success) {
|
|
var resultData = (result.Data || []).map(item => ({
|
|
...item,
|
|
selected: false,
|
|
showDetail: false
|
|
}));
|
|
this.detailTable.dataList = resultData;
|
|
|
|
if (this.detailTable.dataList.length === 0) {
|
|
this.detailTable.message = '查詢完成,但未找到符合條件的資料';
|
|
this.detailTable.messageType = 'info';
|
|
} else {
|
|
this.detailTable.message = `查詢完成,共找到 ${this.detailTable.dataList.length} 筆資料`;
|
|
this.detailTable.messageType = 'success';
|
|
}
|
|
} else {
|
|
this.detailTable.message = result.Message || '查詢失敗,請稍後重試';
|
|
this.detailTable.messageType = 'danger';
|
|
}
|
|
} catch (error) {
|
|
console.error('查詢失敗:', error);
|
|
this.detailTable.message = '查詢失敗: ' + (error.message || '請檢查網路連線');
|
|
this.detailTable.messageType = 'danger';
|
|
} finally {
|
|
this.detailTable.loading = false;
|
|
}
|
|
},
|
|
|
|
async exportDetailTable(kind) {
|
|
if (this.detailTable.dataList.length === 0) {
|
|
this.detailTable.message = '沒有資料可以匯出,請先執行查詢';
|
|
this.detailTable.messageType = 'warning';
|
|
return;
|
|
}
|
|
|
|
this.detailTable.loading = true;
|
|
this.detailTable.message = '';
|
|
|
|
try {
|
|
const params = {
|
|
起日: this.detailTable.startDate ? this.parseDate(this.detailTable.startDate) : null,
|
|
迄日: this.detailTable.endDate ? this.parseDate(this.detailTable.endDate) : null,
|
|
身分別: this.detailTable.identity || null,
|
|
憑證類別: this.detailTable.certificateType || null,
|
|
輸出方式: `EXPORT_${kind}`,
|
|
};
|
|
|
|
const result = await this.callApiWithErrorMsg(
|
|
'領據處理',
|
|
'SearchDetailTable',
|
|
params,
|
|
'匯出扣繳明細'
|
|
);
|
|
|
|
if (result && result.Success && result.Data) {
|
|
const excelData = result.Data;
|
|
if (excelData.excelData && excelData.fileName) {
|
|
this.downloadExcel(excelData.excelData, excelData.fileName);
|
|
this.detailTable.message = '匯出成功';
|
|
this.detailTable.messageType = 'success';
|
|
} else {
|
|
this.detailTable.message = '匯出失敗:無法取得檔案';
|
|
this.detailTable.messageType = 'danger';
|
|
}
|
|
} else {
|
|
this.detailTable.message = result.Message || '匯出失敗,請稍後重試';
|
|
this.detailTable.messageType = 'danger';
|
|
}
|
|
} catch (error) {
|
|
console.error('匯出失敗:', error);
|
|
this.detailTable.message = '匯出失敗: ' + (error.message || '請檢查網路連線');
|
|
this.detailTable.messageType = 'danger';
|
|
} finally {
|
|
this.detailTable.loading = false;
|
|
}
|
|
},
|
|
|
|
toggleSelectAll() {
|
|
this.detailTable.dataList.forEach(item => {
|
|
item.selected = this.detailTable.selectAll;
|
|
});
|
|
},
|
|
|
|
async previewEmail() {
|
|
// 檢查是否有選中的項目
|
|
const selectedItems = this.detailTable.dataList.filter(item => item.selected);
|
|
|
|
if (selectedItems.length === 0) {
|
|
this.detailTable.emailMessage = '請先勾選要發送的項目';
|
|
this.detailTable.emailMessageType = 'warning';
|
|
return;
|
|
}
|
|
|
|
// 檢查郵件設定
|
|
if (!this.detailTable.senderEmail) {
|
|
this.detailTable.emailMessage = '請輸入寄件人郵箱';
|
|
this.detailTable.emailMessageType = 'warning';
|
|
return;
|
|
}
|
|
|
|
if (!this.detailTable.emailSubject) {
|
|
this.detailTable.emailMessage = '請輸入郵件主旨';
|
|
this.detailTable.emailMessageType = 'warning';
|
|
return;
|
|
}
|
|
|
|
if (!this.detailTable.emailBody) {
|
|
this.detailTable.emailMessage = '請輸入郵件內文';
|
|
this.detailTable.emailMessageType = 'warning';
|
|
return;
|
|
}
|
|
|
|
this.detailTable.loading = true;
|
|
this.detailTable.emailMessage = '';
|
|
|
|
try {
|
|
// 提取選中項目的編號作為講師非講師編號
|
|
const 講師非講師編號 = selectedItems.map(item => item.講師_非講師編號);
|
|
|
|
const params = {
|
|
起日: this.detailTable.startDate ? this.parseDate(this.detailTable.startDate) : null,
|
|
迄日: this.detailTable.endDate ? this.parseDate(this.detailTable.endDate) : null,
|
|
身分別: this.detailTable.identity || null,
|
|
憑證類別: this.detailTable.certificateType || null,
|
|
輸出方式: 'PREVIEW',
|
|
講師非講師編號: 講師非講師編號,
|
|
寄件人郵箱: this.detailTable.senderEmail,
|
|
郵件主旨: this.detailTable.emailSubject,
|
|
郵件內文: this.detailTable.emailBody
|
|
};
|
|
|
|
const result = await this.callApiWithErrorMsg(
|
|
'領據處理',
|
|
'SendEmail',
|
|
params,
|
|
'預覽郵件'
|
|
);
|
|
|
|
if (result && result.Success && result.Data) {
|
|
this.detailTable.previewData = result.Data || [];
|
|
this.detailTable.showEmailPreview = true;
|
|
this.detailTable.emailMessage = '';
|
|
} else {
|
|
this.detailTable.emailMessage = result.Message || '預覽失敗,請稍後重試';
|
|
this.detailTable.emailMessageType = 'danger';
|
|
}
|
|
} catch (error) {
|
|
console.error('預覽失敗:', error);
|
|
this.detailTable.emailMessage = '預覽失敗: ' + (error.message || '請檢查網路連線');
|
|
this.detailTable.emailMessageType = 'danger';
|
|
} finally {
|
|
this.detailTable.loading = false;
|
|
}
|
|
},
|
|
|
|
closeEmailPreview() {
|
|
this.detailTable.showEmailPreview = false;
|
|
this.detailTable.previewData = [];
|
|
},
|
|
|
|
async sendEmail() {
|
|
// 檢查是否有選中的項目
|
|
const selectedItems = this.detailTable.dataList.filter(item => item.selected);
|
|
|
|
if (selectedItems.length === 0) {
|
|
this.detailTable.emailMessage = '請先勾選要發送的項目';
|
|
this.detailTable.emailMessageType = 'warning';
|
|
return;
|
|
}
|
|
|
|
if (!confirm(`確認要發送郵件給 ${selectedItems.length} 位收件者嗎?`)) {
|
|
return;
|
|
}
|
|
|
|
this.detailTable.loading = true;
|
|
this.detailTable.emailMessage = '';
|
|
|
|
try {
|
|
// 提取選中項目的編號作為講師非講師編號
|
|
const 講師非講師編號 = selectedItems.map(item => item.講師_非講師編號);
|
|
|
|
const params = {
|
|
起日: this.detailTable.startDate ? this.parseDate(this.detailTable.startDate) : null,
|
|
迄日: this.detailTable.endDate ? this.parseDate(this.detailTable.endDate) : null,
|
|
身分別: this.detailTable.identity || null,
|
|
憑證類別: this.detailTable.certificateType || null,
|
|
輸出方式: 'SENDMAIL',
|
|
講師非講師編號: 講師非講師編號,
|
|
寄件人郵箱: this.detailTable.senderEmail,
|
|
郵件主旨: this.detailTable.emailSubject,
|
|
郵件內文: this.detailTable.emailBody
|
|
};
|
|
|
|
const result = await this.callApiWithErrorMsg(
|
|
'領據處理',
|
|
'SendEmail',
|
|
params,
|
|
'發送郵件'
|
|
);
|
|
|
|
if (result && result.Success) {
|
|
this.detailTable.emailMessage = `郵件已排定發送給 ${selectedItems.length} 位收件者`;
|
|
this.detailTable.emailMessageType = 'success';
|
|
|
|
// 關閉預覽小窗
|
|
this.detailTable.showEmailPreview = false;
|
|
this.detailTable.previewData = [];
|
|
|
|
// 清空勾選
|
|
this.detailTable.dataList.forEach(item => {
|
|
item.selected = false;
|
|
});
|
|
this.detailTable.selectAll = false;
|
|
} else {
|
|
this.detailTable.emailMessage = result.Message || '發送失敗,請稍後重試';
|
|
this.detailTable.emailMessageType = 'danger';
|
|
}
|
|
} catch (error) {
|
|
console.error('發送失敗:', error);
|
|
this.detailTable.emailMessage = '發送失敗: ' + (error.message || '請檢查網路連線');
|
|
this.detailTable.emailMessageType = 'danger';
|
|
} finally {
|
|
this.detailTable.loading = false;
|
|
}
|
|
}
|
|
},
|
|
computed: {
|
|
canSendEmail() {
|
|
const hasSelected = this.detailTable.dataList.some(item => item.selected);
|
|
const hasEmail = this.detailTable.senderEmail && this.detailTable.senderEmail.trim().length > 0;
|
|
const hasSubject = this.detailTable.emailSubject && this.detailTable.emailSubject.trim().length > 0;
|
|
const hasBody = this.detailTable.emailBody && this.detailTable.emailBody.trim().length > 0;
|
|
return hasSelected && hasEmail && hasSubject && hasBody && !this.detailTable.loading;
|
|
}
|
|
}
|
|
});
|
|
}
|