修复企业管理员角色无法红冲的问题;修复操作列按钮高低不齐的问题
This commit is contained in:
@@ -6,6 +6,10 @@ import kotlinx.serialization.Serializable
|
|||||||
data class RedCreateRequest(
|
data class RedCreateRequest(
|
||||||
|
|
||||||
val historyId: String,
|
val historyId: String,
|
||||||
|
/**
|
||||||
|
* 平台数电账号 ID。企业管理员发起冲红时用于指定开票员。
|
||||||
|
*/
|
||||||
|
val digitalAccountId: String? = null,
|
||||||
/**
|
/**
|
||||||
* 冲红原因
|
* 冲红原因
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -222,12 +222,28 @@ object OpenInvoiceTaskService {
|
|||||||
.where { accountScope(user) }
|
.where { accountScope(user) }
|
||||||
.associateBy { it[PtDigitalAccountTable.id] }
|
.associateBy { it[PtDigitalAccountTable.id] }
|
||||||
val total = OpenInvoiceTaskTable.selectAll().where { where }.count()
|
val total = OpenInvoiceTaskTable.selectAll().where { where }.count()
|
||||||
val items = OpenInvoiceTaskTable.selectAll()
|
val taskRows = OpenInvoiceTaskTable.selectAll()
|
||||||
.where { where }
|
.where { where }
|
||||||
.orderBy(OpenInvoiceTaskTable.createdAt, SortOrder.DESC)
|
.orderBy(OpenInvoiceTaskTable.createdAt, SortOrder.DESC)
|
||||||
.limit(pageSize)
|
.limit(pageSize)
|
||||||
.offset(((page - 1).coerceAtLeast(0) * pageSize).toLong())
|
.offset(((page - 1).coerceAtLeast(0) * pageSize).toLong())
|
||||||
.map { it.toTaskItem(accountRows[it[OpenInvoiceTaskTable.digitalAccountId]]?.get(PtDigitalAccountTable.account)) }
|
.toList()
|
||||||
|
val historyMessages = taskRows
|
||||||
|
.map { it[OpenInvoiceTaskTable.invoiceReqSerialNo] }
|
||||||
|
.distinct()
|
||||||
|
.takeIf { it.isNotEmpty() }
|
||||||
|
?.let { serialNos ->
|
||||||
|
HistoryInvoiceBasicTable.selectAll()
|
||||||
|
.where { HistoryInvoiceBasicTable.invoiceReqSerialNo inList serialNos }
|
||||||
|
.associate { it[HistoryInvoiceBasicTable.invoiceReqSerialNo] to it[HistoryInvoiceBasicTable.msg] }
|
||||||
|
}
|
||||||
|
?: emptyMap()
|
||||||
|
val items = taskRows.map {
|
||||||
|
it.toTaskItem(
|
||||||
|
accountRows[it[OpenInvoiceTaskTable.digitalAccountId]]?.get(PtDigitalAccountTable.account),
|
||||||
|
historyMessages[it[OpenInvoiceTaskTable.invoiceReqSerialNo]],
|
||||||
|
)
|
||||||
|
}
|
||||||
PageResult(items, page, pageSize, total)
|
PageResult(items, page, pageSize, total)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,9 +318,9 @@ object OpenInvoiceTaskService {
|
|||||||
val runMode = task[OpenInvoiceTaskTable.runMode]
|
val runMode = task[OpenInvoiceTaskTable.runMode]
|
||||||
val invoiceReqSerialNo = task[OpenInvoiceTaskTable.invoiceReqSerialNo]
|
val invoiceReqSerialNo = task[OpenInvoiceTaskTable.invoiceReqSerialNo]
|
||||||
try {
|
try {
|
||||||
val code = if (runMode == MODE_SIMULATED) {
|
val (code, message) = if (runMode == MODE_SIMULATED) {
|
||||||
delay(2000)
|
delay(2000)
|
||||||
simulatedQueryCode(invoiceReqSerialNo, task[OpenInvoiceTaskTable.pollCount])
|
simulatedQueryCode(invoiceReqSerialNo, task[OpenInvoiceTaskTable.pollCount]) to null
|
||||||
} else {
|
} else {
|
||||||
val res = PTApi.queryInvoiceInfo(
|
val res = PTApi.queryInvoiceInfo(
|
||||||
QueryInvoiceRequest(
|
QueryInvoiceRequest(
|
||||||
@@ -320,9 +336,9 @@ object OpenInvoiceTaskService {
|
|||||||
task[OpenInvoiceTaskTable.digitalAccountId],
|
task[OpenInvoiceTaskTable.digitalAccountId],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
res.code
|
res.code to res.msg
|
||||||
}
|
}
|
||||||
handleQueryCode(task, code)
|
handleQueryCode(task, code, message)
|
||||||
} catch (e: PTException) {
|
} catch (e: PTException) {
|
||||||
retryOrFail(task, e.code, e.message)
|
retryOrFail(task, e.code, e.message)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -365,23 +381,23 @@ object OpenInvoiceTaskService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleQueryCode(task: ResultRow, code: String) {
|
private suspend fun handleQueryCode(task: ResultRow, code: String, message: String?) {
|
||||||
when (code) {
|
when (code) {
|
||||||
"0000" -> finishQueryTask(task, STATUS_SUCCESS, code, null)
|
"0000" -> finishQueryTask(task, STATUS_SUCCESS, code, null)
|
||||||
"9999" -> finishQueryTask(task, STATUS_FAILED, code, "开票失败")
|
"9999" -> finishQueryTask(task, STATUS_FAILED, code, message?.takeIf { it.isNotBlank() } ?: "开票失败")
|
||||||
AUTH_REQUIRED_CODE -> {
|
AUTH_REQUIRED_CODE -> {
|
||||||
val message = "需要登录/风险认证"
|
val authMessage = message?.takeIf { it.isNotBlank() } ?: "需要登录/风险认证"
|
||||||
dbQuery {
|
dbQuery {
|
||||||
OpenInvoiceTaskTable.update({ OpenInvoiceTaskTable.id eq task[OpenInvoiceTaskTable.id] }) {
|
OpenInvoiceTaskTable.update({ OpenInvoiceTaskTable.id eq task[OpenInvoiceTaskTable.id] }) {
|
||||||
it[status] = STATUS_WAITING_AUTH
|
it[status] = STATUS_WAITING_AUTH
|
||||||
it[ptCode] = code
|
it[ptCode] = code
|
||||||
it[errorMessage] = message
|
it[errorMessage] = authMessage
|
||||||
it[updatedAt] = OffsetDateTime.now()
|
it[updatedAt] = OffsetDateTime.now()
|
||||||
it[lockedBy] = null
|
it[lockedBy] = null
|
||||||
it[lockedAt] = null
|
it[lockedAt] = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pauseApiKey(task[OpenInvoiceTaskTable.apiKey], code, message)
|
pauseApiKey(task[OpenInvoiceTaskTable.apiKey], code, authMessage)
|
||||||
}
|
}
|
||||||
"7777", "6666" -> requeueQueryTask(task, code)
|
"7777", "6666" -> requeueQueryTask(task, code)
|
||||||
else -> requeueQueryTask(task, code)
|
else -> requeueQueryTask(task, code)
|
||||||
@@ -693,7 +709,7 @@ object OpenInvoiceTaskService {
|
|||||||
runMode = this[OpenInvoiceTaskTable.runMode],
|
runMode = this[OpenInvoiceTaskTable.runMode],
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun ResultRow.toTaskItem(account: String?): OpenInvoiceTaskItem =
|
private fun ResultRow.toTaskItem(account: String?, historyMessage: String?): OpenInvoiceTaskItem =
|
||||||
OpenInvoiceTaskItem(
|
OpenInvoiceTaskItem(
|
||||||
id = this[OpenInvoiceTaskTable.id].toString(),
|
id = this[OpenInvoiceTaskTable.id].toString(),
|
||||||
digitalAccountId = this[OpenInvoiceTaskTable.digitalAccountId].toString(),
|
digitalAccountId = this[OpenInvoiceTaskTable.digitalAccountId].toString(),
|
||||||
@@ -706,7 +722,8 @@ object OpenInvoiceTaskService {
|
|||||||
batchNo = this[OpenInvoiceTaskTable.batchNo],
|
batchNo = this[OpenInvoiceTaskTable.batchNo],
|
||||||
status = this[OpenInvoiceTaskTable.status],
|
status = this[OpenInvoiceTaskTable.status],
|
||||||
ptCode = this[OpenInvoiceTaskTable.ptCode],
|
ptCode = this[OpenInvoiceTaskTable.ptCode],
|
||||||
errorMessage = this[OpenInvoiceTaskTable.errorMessage],
|
errorMessage = this[OpenInvoiceTaskTable.errorMessage]
|
||||||
|
?: historyMessage?.takeIf { this[OpenInvoiceTaskTable.status] == STATUS_FAILED },
|
||||||
attemptCount = this[OpenInvoiceTaskTable.attemptCount],
|
attemptCount = this[OpenInvoiceTaskTable.attemptCount],
|
||||||
maxAttemptCount = this[OpenInvoiceTaskTable.maxAttemptCount],
|
maxAttemptCount = this[OpenInvoiceTaskTable.maxAttemptCount],
|
||||||
pollCount = this[OpenInvoiceTaskTable.pollCount],
|
pollCount = this[OpenInvoiceTaskTable.pollCount],
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ object PTRedService {
|
|||||||
* 红票接口调用 2.10
|
* 红票接口调用 2.10
|
||||||
*/
|
*/
|
||||||
suspend fun invoiceRed(user: CurrentUser, req: RedCreateRequest): String {
|
suspend fun invoiceRed(user: CurrentUser, req: RedCreateRequest): String {
|
||||||
val account = PTConfigService.requireDigitalAccountForAction(user, null)
|
val account = PTConfigService.requireDigitalAccountForAction(user, req.digitalAccountId)
|
||||||
val invoiceReqSerialNo = PTClient.ptDate()
|
val invoiceReqSerialNo = PTClient.ptDate()
|
||||||
val historyId = Uuid.parse(req.historyId)
|
val historyId = Uuid.parse(req.historyId)
|
||||||
val his = dbQuery {
|
val his = dbQuery {
|
||||||
|
|||||||
@@ -458,6 +458,8 @@ export function invoiceIssueApi(payload: InvoiceRequest): Promise<string> {
|
|||||||
export interface RedCreateRequest {
|
export interface RedCreateRequest {
|
||||||
/** 蓝票历史记录 ID */
|
/** 蓝票历史记录 ID */
|
||||||
historyId: string
|
historyId: string
|
||||||
|
/** 平台数电账号 ID。企业管理员冲红时用于指定开票员 */
|
||||||
|
digitalAccountId?: string | null
|
||||||
/** 冲红原因:01开票有误 02销货退回 03服务中止 04销售折让 */
|
/** 冲红原因:01开票有误 02销货退回 03服务中止 04销售折让 */
|
||||||
redReason: string
|
redReason: string
|
||||||
/** 收票人名称 */
|
/** 收票人名称 */
|
||||||
|
|||||||
@@ -431,6 +431,16 @@
|
|||||||
label-placement="top"
|
label-placement="top"
|
||||||
require-mark-placement="right-hanging"
|
require-mark-placement="right-hanging"
|
||||||
>
|
>
|
||||||
|
<n-form-item label="开票员" path="digitalAccountId">
|
||||||
|
<n-select
|
||||||
|
v-model:value="redForm.digitalAccountId"
|
||||||
|
:options="digitalAccountOptions"
|
||||||
|
:loading="digitalAccountLoading"
|
||||||
|
placeholder="请选择开票员"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
<n-form-item label="冲红原因" path="redReason">
|
<n-form-item label="冲红原因" path="redReason">
|
||||||
<n-select
|
<n-select
|
||||||
v-model:value="redForm.redReason"
|
v-model:value="redForm.redReason"
|
||||||
@@ -496,6 +506,7 @@ import {
|
|||||||
invoiceHistoryApi,
|
invoiceHistoryApi,
|
||||||
invoiceKindMap,
|
invoiceKindMap,
|
||||||
invoiceStatusMap,
|
invoiceStatusMap,
|
||||||
|
listDigitalAccountsApi,
|
||||||
queryInvoiceApi,
|
queryInvoiceApi,
|
||||||
redInvoiceCreateApi,
|
redInvoiceCreateApi,
|
||||||
redInvoiceDownloadUrlApi,
|
redInvoiceDownloadUrlApi,
|
||||||
@@ -507,6 +518,7 @@ import type {
|
|||||||
InvoiceDetailGoods,
|
InvoiceDetailGoods,
|
||||||
InvoiceDetailResponse,
|
InvoiceDetailResponse,
|
||||||
InvoiceDetailVoucher,
|
InvoiceDetailVoucher,
|
||||||
|
DigitalAccountItem,
|
||||||
InvoiceHistoryItem,
|
InvoiceHistoryItem,
|
||||||
RedCreateRequest,
|
RedCreateRequest,
|
||||||
RedInvoiceInfo
|
RedInvoiceInfo
|
||||||
@@ -973,9 +985,12 @@ async function refreshStatus(item: InvoiceHistoryItem) {
|
|||||||
const showRedForm = ref(false)
|
const showRedForm = ref(false)
|
||||||
const redSubmitting = ref(false)
|
const redSubmitting = ref(false)
|
||||||
const redFormRef = ref()
|
const redFormRef = ref()
|
||||||
|
const digitalAccountLoading = ref(false)
|
||||||
|
const digitalAccounts = ref<DigitalAccountItem[]>([])
|
||||||
|
|
||||||
const redForm = reactive({
|
const redForm = reactive({
|
||||||
historyId: '',
|
historyId: '',
|
||||||
|
digitalAccountId: null as string | null,
|
||||||
redReason: '01',
|
redReason: '01',
|
||||||
takerName: '',
|
takerName: '',
|
||||||
takerTel: '',
|
takerTel: '',
|
||||||
@@ -989,17 +1004,40 @@ const redReasonOptions = [
|
|||||||
{ label: '销售折让', value: '04' }
|
{ label: '销售折让', value: '04' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const digitalAccountOptions = computed(() =>
|
||||||
|
digitalAccounts.value.map((item) => ({
|
||||||
|
label: `${item.account}${item.name ? `(${item.name})` : ''}`,
|
||||||
|
value: item.id
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
const redFormRules = {
|
const redFormRules = {
|
||||||
|
digitalAccountId: [{ required: true, message: '请选择开票员', trigger: 'change' }],
|
||||||
redReason: [{ required: true, message: '请选择冲红原因', trigger: 'change' }]
|
redReason: [{ required: true, message: '请选择冲红原因', trigger: 'change' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
function startRedTask(item: InvoiceHistoryItem) {
|
async function ensureDigitalAccountsLoaded() {
|
||||||
|
if (digitalAccounts.value.length > 0) return
|
||||||
|
digitalAccountLoading.value = true
|
||||||
|
try {
|
||||||
|
digitalAccounts.value = await listDigitalAccountsApi()
|
||||||
|
} finally {
|
||||||
|
digitalAccountLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startRedTask(item: InvoiceHistoryItem) {
|
||||||
redForm.historyId = item.id
|
redForm.historyId = item.id
|
||||||
|
redForm.digitalAccountId = null
|
||||||
redForm.redReason = '01'
|
redForm.redReason = '01'
|
||||||
redForm.takerName = ''
|
redForm.takerName = ''
|
||||||
redForm.takerTel = ''
|
redForm.takerTel = ''
|
||||||
redForm.takerEmail = ''
|
redForm.takerEmail = ''
|
||||||
showRedForm.value = true
|
showRedForm.value = true
|
||||||
|
await ensureDigitalAccountsLoaded()
|
||||||
|
if (!redForm.digitalAccountId && digitalAccounts.value.length === 1) {
|
||||||
|
redForm.digitalAccountId = digitalAccounts.value[0].id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleRedSubmit() {
|
async function handleRedSubmit() {
|
||||||
@@ -1012,6 +1050,7 @@ async function handleRedSubmit() {
|
|||||||
try {
|
try {
|
||||||
const payload: RedCreateRequest = {
|
const payload: RedCreateRequest = {
|
||||||
historyId: redForm.historyId,
|
historyId: redForm.historyId,
|
||||||
|
digitalAccountId: redForm.digitalAccountId,
|
||||||
redReason: redForm.redReason,
|
redReason: redForm.redReason,
|
||||||
takerName: redForm.takerName || undefined,
|
takerName: redForm.takerName || undefined,
|
||||||
takerTel: redForm.takerTel || undefined,
|
takerTel: redForm.takerTel || undefined,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<n-drawer v-model:show="drawerVisible" :width="760">
|
<n-drawer v-model:show="drawerVisible" :width="980">
|
||||||
<n-drawer-content :title="drawerTitle" closable>
|
<n-drawer-content :title="drawerTitle" closable>
|
||||||
<div class="drawer-toolbar">
|
<div class="drawer-toolbar">
|
||||||
<n-select
|
<n-select
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
:data="tasks"
|
:data="tasks"
|
||||||
:loading="taskLoading"
|
:loading="taskLoading"
|
||||||
:pagination="taskPagination"
|
:pagination="taskPagination"
|
||||||
:scroll-x="980"
|
:scroll-x="1280"
|
||||||
remote
|
remote
|
||||||
@update:page="loadTasks"
|
@update:page="loadTasks"
|
||||||
@update:page-size="changeTaskPageSize"
|
@update:page-size="changeTaskPageSize"
|
||||||
@@ -276,10 +276,10 @@ const taskColumns: DataTableColumns<OpenInvoiceTaskItem> = [
|
|||||||
h(NTag, { type: statusTagType(row.status), size: 'small' }, { default: () => row.status })
|
h(NTag, { type: statusTagType(row.status), size: 'small' }, { default: () => row.status })
|
||||||
},
|
},
|
||||||
{ title: 'PT码', key: 'ptCode', width: 90 },
|
{ title: 'PT码', key: 'ptCode', width: 90 },
|
||||||
|
{ title: '错误', key: 'errorMessage', minWidth: 260, ellipsis: { tooltip: true } },
|
||||||
{ title: '查询次数', key: 'pollCount', width: 90, render: (row) => `${row.pollCount}/${row.maxPollCount}` },
|
{ title: '查询次数', key: 'pollCount', width: 90, render: (row) => `${row.pollCount}/${row.maxPollCount}` },
|
||||||
{ title: '重试', key: 'attemptCount', width: 90, render: (row) => `${row.attemptCount}/${row.maxAttemptCount}` },
|
{ title: '重试', key: 'attemptCount', width: 90, render: (row) => `${row.attemptCount}/${row.maxAttemptCount}` },
|
||||||
{ title: '下次执行', key: 'nextRunAt', minWidth: 150 },
|
{ title: '下次执行', key: 'nextRunAt', minWidth: 150 }
|
||||||
{ title: '错误', key: 'errorMessage', minWidth: 180 }
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const taskPagination = reactive<PaginationProps>({
|
const taskPagination = reactive<PaginationProps>({
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ function renderLabel(payload: { option: unknown }) {
|
|||||||
? h(NTag, { size: 'small', type: 'primary' }, { default: () => '基础内置' })
|
? h(NTag, { size: 'small', type: 'primary' }, { default: () => '基础内置' })
|
||||||
: null
|
: null
|
||||||
]),
|
]),
|
||||||
h(NSpace, { size: 6, class: 'menu-tree-actions' }, () => [
|
h(NSpace, { size: 6, align: 'center', class: 'menu-tree-actions table-action-buttons' }, () => [
|
||||||
h(
|
h(
|
||||||
NButton,
|
NButton,
|
||||||
{ size: 'small', tertiary: true, class: 'action-btn', onClick: () => openCreate(node.id) },
|
{ size: 'small', tertiary: true, class: 'action-btn', onClick: () => openCreate(node.id) },
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ const columns = computed<DataTableColumns<RoleItem>>(() => [
|
|||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
render: (r) =>
|
render: (r) =>
|
||||||
h(NSpace, { size: 6 }, () => [
|
h(NSpace, { size: 6, align: 'center', class: 'table-action-buttons' }, () => [
|
||||||
h(
|
h(
|
||||||
NButton,
|
NButton,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -660,7 +660,7 @@ const columns = computed<DataTableColumns<UserListItem>>(() => [
|
|||||||
render: (row) =>
|
render: (row) =>
|
||||||
h(
|
h(
|
||||||
NSpace,
|
NSpace,
|
||||||
{ size: 6 },
|
{ size: 6, align: 'center', class: 'table-action-buttons' },
|
||||||
{
|
{
|
||||||
default: () => [
|
default: () => [
|
||||||
h(
|
h(
|
||||||
|
|||||||
@@ -213,6 +213,40 @@ select {
|
|||||||
min-width: 68px;
|
min-width: 68px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-action-buttons,
|
||||||
|
.row-actions,
|
||||||
|
.table-actions,
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
line-height: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-action-buttons {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-action-buttons .n-button,
|
||||||
|
.row-actions .n-button,
|
||||||
|
.table-actions .n-button,
|
||||||
|
.action-buttons .n-button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-action-buttons .n-button__content,
|
||||||
|
.row-actions .n-button__content,
|
||||||
|
.table-actions .n-button__content,
|
||||||
|
.action-buttons .n-button__content {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.soft-stat {
|
.soft-stat {
|
||||||
border: 1px solid var(--app-border);
|
border: 1px solid var(--app-border);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
|||||||
Reference in New Issue
Block a user