/**
* 電子發票工具執行器
*
* 權限規則:
* - 所有電子發票操作:僅 admin/assistant 可用
*/
import type { ToolResponse } from '@mcp-internal/shared'
import { getEInvoiceClient } from './client.js'
import type { Permission } from '../permission/types.js'
import { EINVOICE_ALLOWED_PERMISSIONS } from './types.js'
/** 電子發票工具定義 */
export const einvoiceToolDefinitions = [
{
name: 'einvoice_issue',
description: '開立電子發票(僅 admin/assistant 可用)',
inputSchema: {
type: 'object',
properties: {
invoiceDate: { type: 'string', description: '發票日期 (YYYY/MM/DD)' },
invoiceTime: { type: 'string', description: '發票時間 (HH:MM:SS)' },
taxType: { type: 'string', enum: ['1', '2', '3', '4', '9'], description: '稅別:1=應稅、2=零稅率、3=免稅、9=混合' },
allAmount: { type: 'number', description: '總金額(含稅)' },
items: {
type: 'array',
items: {
type: 'object',
properties: {
description: { type: 'string', description: '品名' },
quantity: { type: 'number', description: '數量' },
unitPrice: { type: 'number', description: '單價' },
amount: { type: 'number', description: '金額' },
unit: { type: 'string', description: '單位' },
},
required: ['description', 'quantity', 'unitPrice', 'amount'],
},
description: '發票明細',
},
buyerId: { type: 'string', description: '買受人統一編號(B2B)' },
companyName: { type: 'string', description: '公司名稱(B2B)' },
name: { type: 'string', description: '買受人姓名' },
email: { type: 'string', description: '買受人 Email' },
phone: { type: 'string', description: '買受人電話' },
address: { type: 'string', description: '買受人地址' },
carrierType: { type: 'string', enum: ['', 'EJ0113', '3J0002', 'CQ0001'], description: '載具類型' },
carrierId: { type: 'string', description: '載具號碼' },
donateMark: { type: 'string', enum: ['0', '1'], description: '捐贈:0=否、1=是' },
loveKey: { type: 'string', description: '愛心碼(3-7碼)' },
orderId: { type: 'string', description: '訂單編號' },
dataId: { type: 'string', description: '自訂發票編號' },
mainRemark: { type: 'string', description: '備註' },
},
required: ['invoiceDate', 'items', 'allAmount', 'taxType'],
},
},
{
name: 'einvoice_issue_allowance',
description: '開立折讓單(僅 admin/assistant 可用)',
inputSchema: {
type: 'object',
properties: {
invoiceNumber: { type: 'string', description: '原發票號碼' },
invoiceDate: { type: 'string', description: '原發票日期' },
allowanceDate: { type: 'string', description: '折讓日期' },
allowanceNumber: { type: 'string', description: '折讓單號碼' },
allowanceType: { type: 'string', enum: ['1', '2'], description: '1=買方、2=賣方' },
items: {
type: 'array',
items: {
type: 'object',
properties: {
description: { type: 'string' },
quantity: { type: 'number' },
unitPrice: { type: 'number' },
amount: { type: 'number' },
tax: { type: 'number' },
taxType: { type: 'string' },
},
required: ['description', 'quantity', 'unitPrice', 'amount', 'tax', 'taxType'],
},
description: '折讓明細',
},
},
required: ['invoiceNumber', 'invoiceDate', 'items'],
},
},
{
name: 'einvoice_cancel',
description: '作廢發票(未上傳財政部,僅 admin/assistant 可用)',
inputSchema: {
type: 'object',
properties: {
invoiceNumber: { type: 'string', description: '發票號碼' },
invoiceDate: { type: 'string', description: '發票日期' },
cancelReason: { type: 'string', description: '作廢原因' },
remark: { type: 'string', description: '備註' },
},
required: ['invoiceNumber', 'invoiceDate', 'cancelReason'],
},
},
{
name: 'einvoice_void',
description: '註銷發票(已上傳財政部,僅 admin/assistant 可用)',
inputSchema: {
type: 'object',
properties: {
invoiceNumber: { type: 'string', description: '發票號碼' },
invoiceDate: { type: 'string', description: '發票日期' },
voidReason: { type: 'string', description: '註銷原因' },
remark: { type: 'string', description: '備註' },
},
required: ['invoiceNumber', 'invoiceDate', 'voidReason'],
},
},
{
name: 'einvoice_cancel_allowance',
description: '作廢折讓單(僅 admin/assistant 可用)',
inputSchema: {
type: 'object',
properties: {
invoiceNumber: { type: 'string', description: '原發票號碼' },
invoiceDate: { type: 'string', description: '原發票日期' },
allowanceNumber: { type: 'string', description: '折讓單號碼' },
allowanceDate: { type: 'string', description: '折讓日期' },
cancelReason: { type: 'string', description: '作廢原因' },
remark: { type: 'string', description: '備註' },
},
required: ['invoiceNumber', 'invoiceDate', 'allowanceNumber', 'allowanceDate', 'cancelReason'],
},
},
{
name: 'einvoice_print_url',
description: '取得發票列印 URL(僅 admin/assistant 可用)',
inputSchema: {
type: 'object',
properties: {
invoiceNumber: { type: 'string', description: '發票號碼' },
invoiceDate: { type: 'string', description: '發票日期' },
randomNumber: { type: 'string', description: '隨機碼(B2C)或統編(B2B)' },
mode: { type: 'string', enum: ['web', 'epson'], description: '列印模式' },
autoPrint: { type: 'boolean', description: '自動列印' },
detailPrint: { type: 'boolean', description: '顯示明細' },
},
required: ['invoiceNumber', 'invoiceDate', 'randomNumber'],
},
},
{
name: 'einvoice_query',
description: '查詢發票(僅 admin/assistant 可用)',
inputSchema: {
type: 'object',
properties: {
invoiceNumber: { type: 'string', description: '發票號碼' },
invoiceDate: { type: 'string', description: '發票日期' },
randomNumber: { type: 'string', description: '隨機碼' },
dataId: { type: 'string', description: '自訂發票編號' },
},
},
},
{
name: 'einvoice_query_allowance',
description: '查詢折讓單(僅 admin/assistant 可用)',
inputSchema: {
type: 'object',
properties: {
invoiceNumber: { type: 'string', description: '原發票號碼' },
invoiceDate: { type: 'string', description: '日期' },
allowanceNumber: { type: 'string', description: '折讓單號碼' },
},
},
},
{
name: 'einvoice_list',
description: '列表發票(僅 admin/assistant 可用)',
inputSchema: {
type: 'object',
properties: {
startDate: { type: 'string', description: '開始日期 (YYYY/MM/DD)' },
endDate: { type: 'string', description: '結束日期 (YYYY/MM/DD),須同月份' },
searchKeyWord: { type: 'string', description: '搜尋關鍵字(統編/發票號碼/買受人)' },
invoiceTypes: { type: 'string', description: '發票類型' },
posSystemId: { type: 'string', description: 'POS 系統 ID' },
pageNumber: { type: 'number', description: '分頁頁數' },
},
required: ['startDate', 'endDate'],
},
},
]
/**
* 檢查電子發票權限
*/
export function checkEInvoicePermission(
userPermission: Permission
): { allowed: boolean; reason?: string } {
if (EINVOICE_ALLOWED_PERMISSIONS.includes(userPermission)) {
return { allowed: true }
}
return {
allowed: false,
reason: `電子發票操作需要 admin 或 assistant 權限,您的權限為 ${userPermission}`,
}
}
/**
* 執行電子發票工具
*/
export async function executeEInvoiceTool(
toolName: string,
args: any,
userPermission: Permission
): Promise<ToolResponse> {
const startTime = Date.now()
// 權限檢查
const permCheck = checkEInvoicePermission(userPermission)
if (!permCheck.allowed) {
return {
success: false,
error: {
code: 'PERMISSION_DENIED',
message: permCheck.reason || '權限不足',
},
metadata: {
executionTimeMs: Date.now() - startTime,
tool: toolName,
timestamp: new Date().toISOString(),
},
}
}
try {
const client = await getEInvoiceClient()
let result: any
switch (toolName) {
case 'einvoice_issue':
result = await client.issueInvoice({
invoiceDate: args.invoiceDate,
invoiceTime: args.invoiceTime,
items: args.items,
allAmount: args.allAmount,
taxType: args.taxType,
buyerId: args.buyerId,
companyName: args.companyName,
name: args.name,
email: args.email,
phone: args.phone,
address: args.address,
carrierType: args.carrierType,
carrierId: args.carrierId,
donateMark: args.donateMark,
loveKey: args.loveKey,
orderId: args.orderId,
dataId: args.dataId,
mainRemark: args.mainRemark,
})
break
case 'einvoice_issue_allowance':
result = await client.issueAllowance({
invoiceNumber: args.invoiceNumber,
invoiceDate: args.invoiceDate,
allowanceDate: args.allowanceDate,
allowanceNumber: args.allowanceNumber,
allowanceType: args.allowanceType,
items: args.items,
})
break
case 'einvoice_cancel':
result = await client.cancelInvoice({
invoiceNumber: args.invoiceNumber,
invoiceDate: args.invoiceDate,
cancelReason: args.cancelReason,
remark: args.remark,
})
break
case 'einvoice_void':
result = await client.voidInvoice({
invoiceNumber: args.invoiceNumber,
invoiceDate: args.invoiceDate,
voidReason: args.voidReason,
remark: args.remark,
})
break
case 'einvoice_cancel_allowance':
result = await client.cancelAllowance({
invoiceNumber: args.invoiceNumber,
invoiceDate: args.invoiceDate,
allowanceNumber: args.allowanceNumber,
allowanceDate: args.allowanceDate,
cancelReason: args.cancelReason,
remark: args.remark,
})
break
case 'einvoice_print_url':
result = await client.getPrintUrl({
invoiceNumber: args.invoiceNumber,
invoiceDate: args.invoiceDate,
randomNumber: args.randomNumber,
mode: args.mode,
autoPrint: args.autoPrint,
detailPrint: args.detailPrint,
})
break
case 'einvoice_query':
result = await client.queryInvoice({
invoiceNumber: args.invoiceNumber,
invoiceDate: args.invoiceDate,
randomNumber: args.randomNumber,
dataId: args.dataId,
})
break
case 'einvoice_query_allowance':
result = await client.queryAllowance({
invoiceNumber: args.invoiceNumber,
invoiceDate: args.invoiceDate,
allowanceNumber: args.allowanceNumber,
})
break
case 'einvoice_list':
result = await client.listInvoices({
startDate: args.startDate,
endDate: args.endDate,
searchKeyWord: args.searchKeyWord,
invoiceTypes: args.invoiceTypes,
posSystemId: args.posSystemId,
pageNumber: args.pageNumber,
})
break
default:
throw new Error(`未知的電子發票工具: ${toolName}`)
}
return {
success: true,
result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] },
metadata: {
executionTimeMs: Date.now() - startTime,
tool: toolName,
timestamp: new Date().toISOString(),
},
}
} catch (error) {
return {
success: false,
error: {
code: 'EXTERNAL_SERVICE_ERROR',
message: error instanceof Error ? error.message : String(error),
},
metadata: {
executionTimeMs: Date.now() - startTime,
tool: toolName,
timestamp: new Date().toISOString(),
},
}
}
}