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; } } }); }