feat: 积分商城-供应商-对账-发票5
This commit is contained in:
@@ -13,7 +13,7 @@
|
|||||||
<el-table-column label="价税合计" width="120"><template #default="{ row }">¥{{ row.invoiceAmount }}</template></el-table-column>
|
<el-table-column label="价税合计" width="120"><template #default="{ row }">¥{{ row.invoiceAmount }}</template></el-table-column>
|
||||||
<el-table-column label="开票日期" width="130"><template #default="{ row }">{{ dateText(row.invoiceDate) }}</template></el-table-column>
|
<el-table-column label="开票日期" width="130"><template #default="{ row }">{{ dateText(row.invoiceDate) }}</template></el-table-column>
|
||||||
<el-table-column prop="failureMessage" label="说明" min-width="180" show-overflow-tooltip><template #default="{ row }">{{ row.failureMessage || '-' }}</template></el-table-column>
|
<el-table-column prop="failureMessage" label="说明" min-width="180" show-overflow-tooltip><template #default="{ row }">{{ row.failureMessage || '-' }}</template></el-table-column>
|
||||||
<el-table-column label="操作" width="190" fixed="right"><template #default="{ row }"><el-button link type="primary" @click="openDetail(row)">详情</el-button><el-button v-if="row.hasFile" link type="primary" :disabled="fileLoading" @click="openFile(row)">预览</el-button><el-button v-if="row.hasFile" link type="primary" :disabled="fileLoading" @click="openFile(row, true)">下载</el-button></template></el-table-column>
|
<el-table-column label="操作" width="190" fixed="right"><template #default="{ row }"><el-button link type="primary" @click="openDetail(row)">详情</el-button><el-button link type="primary" :loading="pushingId === row.id" :disabled="pushingId !== null || ![2, 3].includes(row.invoiceStatus)" @click="pushInvoice(row)">推送发票</el-button></template></el-table-column>
|
||||||
<template #empty><el-empty :image-size="80" description="暂无发票记录" /></template>
|
<template #empty><el-empty :image-size="80" description="暂无发票记录" /></template>
|
||||||
</el-table>
|
</el-table>
|
||||||
<el-pagination v-model:current-page="query.pageNumber" :page-size="query.pageSize" :total="totalCount" layout="total, prev, pager, next, jumper" @current-change="load" />
|
<el-pagination v-model:current-page="query.pageNumber" :page-size="query.pageSize" :total="totalCount" layout="total, prev, pager, next, jumper" @current-change="load" />
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, reactive, ref } from 'vue'
|
import { onMounted, reactive, ref } from 'vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { postAdmin } from '@/api/admin'
|
import { postAdmin } from '@/api/admin'
|
||||||
type Invoice = { id: number; settlementId: string; invoiceIds: string; invoiceCode: string; invoiceStatus: number; invoiceDate: string | null; invoiceType: number | null; invoiceAmount: string; invoiceNakeAmount: string; invoiceTaxAmount: string; invoiceTaxRate: string; taxIdNumber: string; checkCode: string; orderIds: string; failureMessage: string; hasFile: boolean }
|
type Invoice = { id: number; settlementId: string; invoiceIds: string; invoiceCode: string; invoiceStatus: number; invoiceDate: string | null; invoiceType: number | null; invoiceAmount: string; invoiceNakeAmount: string; invoiceTaxAmount: string; invoiceTaxRate: string; taxIdNumber: string; checkCode: string; orderIds: string; failureMessage: string; hasFile: boolean }
|
||||||
const statuses: Record<number, string> = { 0: '未开票', 1: '开票中', 2: '成功', 3: '失败' }
|
const statuses: Record<number, string> = { 0: '未开票', 1: '开票中', 2: '成功', 3: '失败' }
|
||||||
@@ -48,6 +48,7 @@ const loading = ref(false)
|
|||||||
const current = ref<Invoice>()
|
const current = ref<Invoice>()
|
||||||
const detailVisible = ref(false)
|
const detailVisible = ref(false)
|
||||||
const fileLoading = ref(false)
|
const fileLoading = ref(false)
|
||||||
|
const pushingId = ref<number | null>(null)
|
||||||
const previewVisible = ref(false)
|
const previewVisible = ref(false)
|
||||||
const previewUrl = ref('')
|
const previewUrl = ref('')
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
@@ -61,6 +62,17 @@ const load = async () => {
|
|||||||
const search = () => { query.pageNumber = 1; return load() }
|
const search = () => { query.pageNumber = 1; return load() }
|
||||||
const reset = () => { query.settlementId = ''; query.invoiceStatus = undefined; return search() }
|
const reset = () => { query.settlementId = ''; query.invoiceStatus = undefined; return search() }
|
||||||
const openDetail = (row: Invoice) => { current.value = row; detailVisible.value = true }
|
const openDetail = (row: Invoice) => { current.value = row; detailVisible.value = true }
|
||||||
|
const pushInvoice = async (row: Invoice) => {
|
||||||
|
if (pushingId.value !== null) return
|
||||||
|
pushingId.value = row.id
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(`确认向工会推送结算单 ${row.settlementId} 的开票结果?`, '推送发票', { confirmButtonText: '确认推送', cancelButtonText: '取消' })
|
||||||
|
await postAdmin('merchant-invoice-push', { id: row.id })
|
||||||
|
ElMessage.success('发票推送成功')
|
||||||
|
} catch (error) {
|
||||||
|
if (error !== 'cancel' && error !== 'close') ElMessage.error(error instanceof Error ? error.message : '发票推送失败')
|
||||||
|
} finally { pushingId.value = null }
|
||||||
|
}
|
||||||
const openFile = async (row: Invoice, download = false) => {
|
const openFile = async (row: Invoice, download = false) => {
|
||||||
if (fileLoading.value) return
|
if (fileLoading.value) return
|
||||||
fileLoading.value = true
|
fileLoading.value = true
|
||||||
|
|||||||
@@ -13,6 +13,13 @@ export const merchantInvoiceListController = withErrorBoundary(async request =>
|
|||||||
return toHttpResponse(await merchantInvoiceService.list(await readJson<MerchantInvoiceListInput>(request)))
|
return toHttpResponse(await merchantInvoiceService.list(await readJson<MerchantInvoiceListInput>(request)))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const merchantInvoicePushController = withErrorBoundary(async request => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
if (!await adminAuthService.findSession(getBearerToken(request))) return toHttpResponse(serviceFail('登录已失效,请重新登录', 401, 'UNAUTHORIZED'))
|
||||||
|
return toHttpResponse(await merchantInvoiceService.push(await readJson<{ id?: number }>(request)))
|
||||||
|
})
|
||||||
|
|
||||||
export const merchantInvoiceFileController = withErrorBoundary(async request => {
|
export const merchantInvoiceFileController = withErrorBoundary(async request => {
|
||||||
const methodResponse = guardMethod(request, ['POST'])
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
if (methodResponse) return methodResponse
|
if (methodResponse) return methodResponse
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export { merchantInvoicePushController as default } from '../controllers/merchant-invoice.controller.js'
|
||||||
@@ -79,11 +79,12 @@ const app = new Koa()
|
|||||||
const router = new Router()
|
const router = new Router()
|
||||||
const port = Number(process.env.PORT || 8888)
|
const port = Number(process.env.PORT || 8888)
|
||||||
|
|
||||||
import { merchantInvoiceListController, merchantInvoiceFileController } from './controllers/merchant-invoice.controller.js'
|
import { merchantInvoiceListController, merchantInvoiceFileController, merchantInvoicePushController } from './controllers/merchant-invoice.controller.js'
|
||||||
|
|
||||||
const handlers: Record<string, FunctionHandler> = {
|
const handlers: Record<string, FunctionHandler> = {
|
||||||
'merchant-invoice-list': merchantInvoiceListController,
|
'merchant-invoice-list': merchantInvoiceListController,
|
||||||
'merchant-invoice-file': merchantInvoiceFileController,
|
'merchant-invoice-file': merchantInvoiceFileController,
|
||||||
|
'merchant-invoice-push': merchantInvoicePushController,
|
||||||
'address-default': addressDefaultController,
|
'address-default': addressDefaultController,
|
||||||
'address-delete': addressDeleteController,
|
'address-delete': addressDeleteController,
|
||||||
'address-list': addressListController,
|
'address-list': addressListController,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { bridgeFetchWithAuth } from '../utils/bridge-auth.js'
|
||||||
|
import { ORDER_EVENT_TYPE, ORDER_PUSH_STATUS } from '../constants/order.js'
|
||||||
import { prisma } from '../utils/prisma.js'
|
import { prisma } from '../utils/prisma.js'
|
||||||
import { serviceFail, serviceOk } from '../types/service-result.js'
|
import { serviceFail, serviceOk } from '../types/service-result.js'
|
||||||
import { supplierFileService } from './supplier-file.service.js'
|
import { supplierFileService } from './supplier-file.service.js'
|
||||||
@@ -28,6 +30,46 @@ export const merchantInvoiceService = {
|
|||||||
}))
|
}))
|
||||||
return serviceOk({ invoiceItems, totalCount, pageNumber, pageSize })
|
return serviceOk({ invoiceItems, totalCount, pageNumber, pageSize })
|
||||||
},
|
},
|
||||||
|
async push(input: { id?: number }) {
|
||||||
|
const id = Number(input.id)
|
||||||
|
if (!Number.isInteger(id) || id <= 0) return serviceFail('发票参数不正确')
|
||||||
|
const invoice = await prisma.supplierInvoiceInfo.findFirst({ where: { id, deleted: 0 } })
|
||||||
|
if (!invoice) return serviceFail('发票记录不存在', 404, 'INVOICE_NOT_FOUND')
|
||||||
|
if (![2, 3].includes(invoice.invoiceStatus)) return serviceFail('开票尚未结束,暂不能推送发票')
|
||||||
|
const now = new Date()
|
||||||
|
const pad = (value: number) => String(value).padStart(2, '0')
|
||||||
|
const body = {
|
||||||
|
supplierId: invoice.supplierId,
|
||||||
|
userId: invoice.userId || 'SYSTEM',
|
||||||
|
orderType: ORDER_EVENT_TYPE.INVOICE_INFO,
|
||||||
|
orderId: invoice.settlementId || invoice.invoiceBizId,
|
||||||
|
mainOrderId: invoice.mainOrderId || '',
|
||||||
|
operationTime: `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`,
|
||||||
|
time: now.getTime()
|
||||||
|
}
|
||||||
|
const apiPath = '/pointOrder/pushOrderInfoMsg'
|
||||||
|
const log = await prisma.supplierOrderPushLog.create({ data: {
|
||||||
|
supplierId: body.supplierId, userId: body.userId, mainOrderId: invoice.mainOrderId,
|
||||||
|
orderId: body.orderId, orderType: body.orderType, apiPath, requestBody: body, pushStatus: ORDER_PUSH_STATUS.PENDING
|
||||||
|
} })
|
||||||
|
try {
|
||||||
|
const result = await bridgeFetchWithAuth<{ resultCode?: number; message?: string }>(invoice.supplierId, apiPath, body)
|
||||||
|
const success = result.response.ok && result.payload.resultCode === 0
|
||||||
|
const message = result.payload.message || '发票推送失败'
|
||||||
|
await prisma.supplierOrderPushLog.update({ where: { id: log.id }, data: {
|
||||||
|
responseBody: result.payload, httpStatus: result.response.status, resultCode: result.payload.resultCode,
|
||||||
|
pushStatus: success ? ORDER_PUSH_STATUS.SUCCESS : ORDER_PUSH_STATUS.FAILED,
|
||||||
|
errorMessage: success ? null : message, lastPushTime: new Date()
|
||||||
|
} })
|
||||||
|
return success ? serviceOk({ message: '发票推送成功' }) : serviceFail(message, 502, 'INVOICE_PUSH_FAILED')
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : '发票推送失败'
|
||||||
|
await prisma.supplierOrderPushLog.update({ where: { id: log.id }, data: {
|
||||||
|
pushStatus: ORDER_PUSH_STATUS.FAILED, errorMessage: message, lastPushTime: new Date()
|
||||||
|
} })
|
||||||
|
return serviceFail(message, 502, 'INVOICE_PUSH_FAILED')
|
||||||
|
}
|
||||||
|
},
|
||||||
async file(input: MerchantInvoiceFileInput) {
|
async file(input: MerchantInvoiceFileInput) {
|
||||||
const id = Number(input.id)
|
const id = Number(input.id)
|
||||||
if (!Number.isInteger(id) || id <= 0) return serviceFail('发票参数不正确')
|
if (!Number.isInteger(id) || id <= 0) return serviceFail('发票参数不正确')
|
||||||
|
|||||||
Reference in New Issue
Block a user