完善前端显示字段;去掉开放测试接口

This commit is contained in:
BBIT-Kai
2026-05-27 11:45:57 +08:00
parent a716c481da
commit bf87c09803
28 changed files with 327 additions and 213 deletions
+1
View File
@@ -1,3 +1,4 @@
web/node_modules/ web/node_modules/
web/dist/ web/dist/
log/ log/
server/src/main/resources/dist/
@@ -27,6 +27,8 @@ import com.bbit.ticket.service.openapi.OpenInvoiceTaskWorker
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import io.ktor.server.application.Application import io.ktor.server.application.Application
import io.ktor.server.auth.authenticate import io.ktor.server.auth.authenticate
import io.ktor.server.http.content.singlePageApplication
import io.ktor.server.http.content.vue
import io.ktor.server.netty.EngineMain import io.ktor.server.netty.EngineMain
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.get import io.ktor.server.routing.get
@@ -47,7 +49,7 @@ fun Application.module() {
configureCors() configureCors()
configureSecurity() configureSecurity()
configureDatabase() configureDatabase()
configureRedis() // configureRedis()
runBlocking { runBlocking {
DatabaseInitializer.initialize() DatabaseInitializer.initialize()
SeedData.seed() SeedData.seed()
@@ -58,6 +60,10 @@ fun Application.module() {
get("/health") { get("/health") {
call.respond(ok(mapOf("status" to "UP", "service" to AppConfig.app.name))) call.respond(ok(mapOf("status" to "UP", "service" to AppConfig.app.name)))
} }
singlePageApplication {
useResources = true
vue("dist")
}
route("/api") { route("/api") {
registerAuthRoutes() registerAuthRoutes()
registerUserRoutes() registerUserRoutes()
@@ -105,10 +105,33 @@ object EnterpriseManageDao {
} }
} }
fun digitalAccountsForEnterprise(enterpriseId: Uuid): List<DigitalAccountManageItem> = fun digitalAccountsForEnterprise(
PtDigitalAccountTable.selectAll() enterpriseId: Uuid,
.where { (PtDigitalAccountTable.enterpriseId eq enterpriseId) and PtDigitalAccountTable.deletedAt.isNull() } account: String?,
status: String?,
page: Int,
pageSize: Int,
): PageResult<DigitalAccountManageItem> {
val where = digitalAccountWhere(enterpriseId, account, status)
val total = PtDigitalAccountTable.selectAll().where { where }.count()
val rows = PtDigitalAccountTable.selectAll()
.where { where }
.orderBy(PtDigitalAccountTable.createdAt, SortOrder.DESC) .orderBy(PtDigitalAccountTable.createdAt, SortOrder.DESC)
.limit(pageSize)
.offset(pageOffset(page, pageSize))
.map { it.toDigitalAccountItem() }
return PageResult(rows, page, pageSize, total)
}
fun digitalAccountOptionsForEnterprise(
enterpriseId: Uuid,
account: String?,
limit: Int,
): List<DigitalAccountManageItem> =
PtDigitalAccountTable.selectAll()
.where { digitalAccountWhere(enterpriseId, account, "ENABLED") }
.orderBy(PtDigitalAccountTable.createdAt, SortOrder.DESC)
.limit(limit)
.map { it.toDigitalAccountItem() } .map { it.toDigitalAccountItem() }
fun digitalAccount(id: Uuid): ResultRow? = fun digitalAccount(id: Uuid): ResultRow? =
@@ -134,6 +157,18 @@ object EnterpriseManageDao {
} }
.singleOrNull() .singleOrNull()
private fun digitalAccountWhere(enterpriseId: Uuid, account: String?, status: String?): Op<Boolean> {
var where: Op<Boolean> =
(PtDigitalAccountTable.enterpriseId eq enterpriseId) and PtDigitalAccountTable.deletedAt.isNull()
account?.trim()?.takeIf { it.isNotEmpty() }?.let {
where = where and (PtDigitalAccountTable.account like "%$it%")
}
status?.trim()?.takeIf { it.isNotEmpty() }?.let {
where = where and (PtDigitalAccountTable.status eq it.uppercase())
}
return where
}
fun upsertDigitalAccount(enterpriseId: Uuid, item: DigitalAccountInfo): Uuid { fun upsertDigitalAccount(enterpriseId: Uuid, item: DigitalAccountInfo): Uuid {
val existing = PtDigitalAccountTable.selectAll() val existing = PtDigitalAccountTable.selectAll()
.where { .where {
@@ -1,4 +1,4 @@
package com.bbit.ticket.database.piaotong package com.bbit.ticket.database.piaotong
import org.jetbrains.exposed.v1.core.Table import org.jetbrains.exposed.v1.core.Table
import org.jetbrains.exposed.v1.javatime.timestampWithTimeZone import org.jetbrains.exposed.v1.javatime.timestampWithTimeZone
@@ -306,13 +306,13 @@ object HistoryInvoiceBasicTable : Table("history_invoice_basic") {
.nullable() .nullable()
/** /**
* 红状态 * 红状态
* *
* NOT_RED:未 * NOT_RED:未红
* ALREADY_RED:已 * ALREADY_RED:已红
* REDING红中 * REDING:红
* RED_FAIL红失败 * RED_FAIL:红失败
* PART_RED:部分 * PART_RED:部分红
*/ */
val redFlag = varchar("red_flag", 32) val redFlag = varchar("red_flag", 32)
.nullable() .nullable()
@@ -453,3 +453,4 @@ object HistoryInvoiceBasicTable : Table("history_invoice_basic") {
override val primaryKey = PrimaryKey(id) override val primaryKey = PrimaryKey(id)
} }
@@ -1,4 +1,4 @@
@file:OptIn(ExperimentalUuidApi::class) @file:OptIn(ExperimentalUuidApi::class)
package com.bbit.ticket.database.piaotong package com.bbit.ticket.database.piaotong
@@ -26,13 +26,13 @@ object HistoryInvoiceRedTable : Table("history_invoice_red") {
/** /**
* 发票代码 * 发票代码
* 需红的原发票代码,红增值税发票管理系统开具的发票或数电纸质发票时必填 * 需红的原发票代码,红增值税发票管理系统开具的发票或数电纸质发票时必填
*/ */
val invoiceCode = varchar("invoice_code", 12).nullable() val invoiceCode = varchar("invoice_code", 12).nullable()
/** /**
* 发票请求流水号 * 发票请求流水号
* 需红的原发票号码,红增值税发票管理系统开具的发票或数电纸质发票时必填 * 需红的原发票号码,红增值税发票管理系统开具的发票或数电纸质发票时必填
*/ */
val invoiceNo = varchar("invoice_no", 8).nullable() val invoiceNo = varchar("invoice_no", 8).nullable()
@@ -42,7 +42,7 @@ object HistoryInvoiceRedTable : Table("history_invoice_red") {
val invoiceReqSerialNo = varchar("invoice_req_serial_no", 20) val invoiceReqSerialNo = varchar("invoice_req_serial_no", 20)
/** /**
* 红原因 * 红原因
* 01:开票有误 * 01:开票有误
* 02:销货退回 * 02:销货退回
* 03:服务中止 * 03:服务中止
@@ -61,9 +61,9 @@ object HistoryInvoiceRedTable : Table("history_invoice_red") {
* 81:数电发票(增值税专用发票) * 81:数电发票(增值税专用发票)
* 82:数电发票(普通发票)。 * 82:数电发票(普通发票)。
* 默认蓝票的发票种类代码。 * 默认蓝票的发票种类代码。
* 此字段目的解决数电发票红增值税发票,只有企业不再使用增值税系统时才可以跨票种红。 * 此字段目的解决数电发票红增值税发票,只有企业不再使用增值税系统时才可以跨票种红
* 数电发票(普通发票)可以红数电发票(普通发票)、增值税电子普通发票、增值税纸质普通发票。 * 数电发票(普通发票)可以红数电发票(普通发票)、增值税电子普通发票、增值税纸质普通发票。
* 数电发票(增值税专用发票)可以红数电发票(增值税专用发票)、增值税电子专用发票、增值税纸质专用发票。 * 数电发票(增值税专用发票)可以红数电发票(增值税专用发票)、增值税电子专用发票、增值税纸质专用发票。
*/ */
val invoiceKind = varchar("invoice_kind", 2).nullable() val invoiceKind = varchar("invoice_kind", 2).nullable()
@@ -104,3 +104,4 @@ object HistoryInvoiceRedTable : Table("history_invoice_red") {
override val primaryKey = PrimaryKey(id) override val primaryKey = PrimaryKey(id)
} }
@@ -16,7 +16,6 @@ object OpenInvoiceTaskTable : Table("open_invoice_task") {
val taxAccount = varchar("tax_account", 64).nullable() val taxAccount = varchar("tax_account", 64).nullable()
val taskType = varchar("task_type", 32) val taskType = varchar("task_type", 32)
val sourceType = varchar("source_type", 32) val sourceType = varchar("source_type", 32)
val runMode = varchar("run_mode", 16)
val invoiceReqSerialNo = varchar("invoice_req_serial_no", 20) val invoiceReqSerialNo = varchar("invoice_req_serial_no", 20)
val batchNo = varchar("batch_no", 64).nullable() val batchNo = varchar("batch_no", 64).nullable()
val status = varchar("status", 32) val status = varchar("status", 32)
@@ -9,7 +9,6 @@ data class OpenInvoiceTaskSubmitResponse(
val invoiceReqSerialNo: String, val invoiceReqSerialNo: String,
val status: String, val status: String,
val taskType: String, val taskType: String,
val runMode: String,
) )
@Serializable @Serializable
@@ -37,7 +36,6 @@ data class OpenInvoiceTaskItem(
val account: String? = null, val account: String? = null,
val taskType: String, val taskType: String,
val sourceType: String, val sourceType: String,
val runMode: String,
val invoiceReqSerialNo: String, val invoiceReqSerialNo: String,
val batchNo: String? = null, val batchNo: String? = null,
val status: String, val status: String,
@@ -1,4 +1,4 @@
package com.bbit.ticket.entity.request package com.bbit.ticket.entity.request
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -28,7 +28,7 @@ data class CreateRedInvoiceRequest(
/** /**
* 蓝票代码 * 蓝票代码
* *
* 红税控发票或数电纸质发票时必填 * 红税控发票或数电纸质发票时必填
*/ */
@SerialName("blueInvoiceCode") @SerialName("blueInvoiceCode")
val blueInvoiceCode: String? = null, val blueInvoiceCode: String? = null,
@@ -36,7 +36,7 @@ data class CreateRedInvoiceRequest(
/** /**
* 蓝票号码 * 蓝票号码
* *
* 红税控发票或数电纸质发票时必填 * 红税控发票或数电纸质发票时必填
*/ */
@SerialName("blueInvoiceNo") @SerialName("blueInvoiceNo")
val blueInvoiceNo: String? = null, val blueInvoiceNo: String? = null,
@@ -1,4 +1,4 @@
package com.bbit.ticket.entity.request package com.bbit.ticket.entity.request
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -20,25 +20,25 @@ data class InitRedInvoiceConfirmRequest(
/** /**
* 税控蓝字发票代码(10或12位) * 税控蓝字发票代码(10或12位)
* 红增值税发票管理系统开具的发票或数电纸票时必填 * 红增值税发票管理系统开具的发票或数电纸票时必填
*/ */
val invoiceCode: String? = null, val invoiceCode: String? = null,
/** /**
* 税控蓝字发票号码(8位) * 税控蓝字发票号码(8位)
* 红增值税发票管理系统开具的发票或数电纸票时必填 * 红增值税发票管理系统开具的发票或数电纸票时必填
*/ */
val invoiceNo: String? = null, val invoiceNo: String? = null,
/** /**
* 蓝字数电票号码(20位) * 蓝字数电票号码(20位)
* 红数电发票时必填 * 红数电发票时必填
*/ */
val blueAllEleInvNo: String? = null, val blueAllEleInvNo: String? = null,
/** /**
* 蓝字发票开票日期(yyyyMMdd) * 蓝字发票开票日期(yyyyMMdd)
* 红非票通平台开具的发票时必填 * 红非票通平台开具的发票时必填
*/ */
val blueInvoiceDate: String? = null, val blueInvoiceDate: String? = null,
@@ -1,10 +1,10 @@
package com.bbit.ticket.entity.request package com.bbit.ticket.entity.request
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/** /**
* 快捷红数电发票请求 * 快捷红数电发票请求
*/ */
@Serializable @Serializable
data class QuickRedInvoiceRequest( data class QuickRedInvoiceRequest(
@@ -29,7 +29,7 @@ data class QuickRedInvoiceRequest(
/** /**
* 原发票代码 * 原发票代码
* *
* 红增值税系统发票或数电纸质发票时必填 * 红增值税系统发票或数电纸质发票时必填
*/ */
@SerialName("invoiceCode") @SerialName("invoiceCode")
val invoiceCode: String? = null, val invoiceCode: String? = null,
@@ -37,7 +37,7 @@ data class QuickRedInvoiceRequest(
/** /**
* 原发票号码 * 原发票号码
* *
* 红增值税系统发票或数电纸质发票时必填 * 红增值税系统发票或数电纸质发票时必填
*/ */
@SerialName("invoiceNo") @SerialName("invoiceNo")
val invoiceNo: String? = null, val invoiceNo: String? = null,
@@ -45,7 +45,7 @@ data class QuickRedInvoiceRequest(
/** /**
* 原数电发票号码 * 原数电发票号码
* *
* 红数电发票时必填 * 红数电发票时必填
*/ */
@SerialName("blueAllEleInvNo") @SerialName("blueAllEleInvNo")
val blueAllEleInvNo: String? = null, val blueAllEleInvNo: String? = null,
@@ -56,14 +56,14 @@ data class QuickRedInvoiceRequest(
* 格式:yyyyMMdd * 格式:yyyyMMdd
* *
* 场景: * 场景:
* 红非票通平台开具的发票时必填, * 红非票通平台开具的发票时必填,
* 税局会结合该日期与数电发票号码拉取原票 * 税局会结合该日期与数电发票号码拉取原票
*/ */
@SerialName("blueInvoiceDate") @SerialName("blueInvoiceDate")
val blueInvoiceDate: String? = null, val blueInvoiceDate: String? = null,
/** /**
* 红原因 * 红原因
* *
* 默认值:01 * 默认值:01
* *
@@ -108,7 +108,7 @@ data class QuickRedInvoiceRequest(
* *
* 默认取蓝票票种 * 默认取蓝票票种
* *
* 用于跨票种红场景 * 用于跨票种红场景
*/ */
@SerialName("invoiceKind") @SerialName("invoiceKind")
val invoiceKind: String? = null, val invoiceKind: String? = null,
@@ -1,4 +1,4 @@
package com.bbit.ticket.entity.request package com.bbit.ticket.entity.request
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -7,11 +7,11 @@ data class RedCreateRequest(
val historyId: String, val historyId: String,
/** /**
* 平台数电账号 ID。企业管理员发起红时用于指定开票员。 * 平台数电账号 ID。企业管理员发起红时用于指定开票员。
*/ */
val digitalAccountId: String? = null, val digitalAccountId: String? = null,
/** /**
* 红原因 * 红原因
* *
* 默认值:01 * 默认值:01
* *
@@ -26,3 +26,4 @@ data class RedCreateRequest(
val takerTel: String? = null, val takerTel: String? = null,
val takerEmail: String? = null, val takerEmail: String? = null,
) )
@@ -1,4 +1,4 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class) @file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
package com.bbit.ticket.entity.response package com.bbit.ticket.entity.response
@@ -100,7 +100,7 @@ data class InvoiceHistoryItem(
// ===== 发票状态标记 ===== // ===== 发票状态标记 =====
/** 作废状态 */ /** 作废状态 */
val invalidFlag: String? = null, val invalidFlag: String? = null,
/** 红状态 */ /** 红状态 */
val redFlag: String? = null, val redFlag: String? = null,
// ===== 人员信息 ===== // ===== 人员信息 =====
@@ -149,3 +149,4 @@ data class InvoiceHistoryItem(
/** 删除标记 */ /** 删除标记 */
val invDeletedFlag: String? = null, val invDeletedFlag: String? = null,
) )
@@ -1,4 +1,4 @@
package com.bbit.ticket.entity.response package com.bbit.ticket.entity.response
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -168,13 +168,13 @@ data class InvoiceInfo(
val invalidFlag: String, val invalidFlag: String,
/** /**
* 红标志 * 红标志
* *
* NOT_RED:未 * NOT_RED:未红
* ALREADY_RED:已 * ALREADY_RED:已红
* REDING红中 * REDING:红
* RED_FAIL红失败 * RED_FAIL:红失败
* PART_RED:部分 * PART_RED:部分红
*/ */
@SerialName("redFlag") @SerialName("redFlag")
val redFlag: String, val redFlag: String,
@@ -1,4 +1,4 @@
package com.bbit.ticket.entity.response package com.bbit.ticket.entity.response
import com.bbit.ticket.entity.request.InvoiceItem import com.bbit.ticket.entity.request.InvoiceItem
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
@@ -187,12 +187,12 @@ data class GetInvoiceInfoResponse(
val invalidFlag: String? = null, val invalidFlag: String? = null,
/** /**
* 红状态 * 红状态
* NOT_RED:未 * NOT_RED:未红
* ALREADY_RED:已 * ALREADY_RED:已红
* REDING红中 * REDING:红
* RED_FAIL红失败 * RED_FAIL:红失败
* PART_RED:部分 * PART_RED:部分红
*/ */
val redFlag: String? = null, val redFlag: String? = null,
@@ -1,10 +1,10 @@
package com.bbit.ticket.entity.response package com.bbit.ticket.entity.response
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/** /**
* 快捷红数电发票响应 * 快捷红数电发票响应
*/ */
@Serializable @Serializable
data class QuickRedInvoiceResponse( data class QuickRedInvoiceResponse(
@@ -1,16 +1,16 @@
package com.bbit.ticket.entity.response package com.bbit.ticket.entity.response
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/** /**
* 红票申请信息 * 红票申请信息
* *
* 对应 [HistoryInvoiceRedTable] 的红申请字段, * 对应 [HistoryInvoiceRedTable] 的红申请字段,
* 用于红票详情弹窗展示。 * 用于红票详情弹窗展示。
*/ */
@Serializable @Serializable
data class RedInvoiceInfoResponse( data class RedInvoiceInfoResponse(
/** 红原因:01=开票有误 02=销货退回 03=服务中止 04=销售折让 */ /** 红原因:01=开票有误 02=销货退回 03=服务中止 04=销售折让 */
val redReason: String, val redReason: String,
/** 收票人名称 */ /** 收票人名称 */
val takerName: String? = null, val takerName: String? = null,
@@ -19,3 +19,4 @@ data class RedInvoiceInfoResponse(
/** 收票人邮箱 */ /** 收票人邮箱 */
val takerEmail: String? = null, val takerEmail: String? = null,
) )
@@ -8,27 +8,13 @@ import io.ktor.server.routing.Route
import io.ktor.server.routing.post import io.ktor.server.routing.post
fun Route.registerOpenInvoiceTaskRoutes() { fun Route.registerOpenInvoiceTaskRoutes() {
post("/test") { post {
val principal = call.requireOpenApiPrincipal() val principal = call.requireOpenApiPrincipal()
val request = call.receive<OpenBlueInvoiceCreateRequest>() val request = call.receive<OpenBlueInvoiceCreateRequest>()
call.respondOpenApi(principal, "blue-invoice-task.test", null) { call.respondOpenApi(principal, "blue-invoice-task.create", null) {
OpenInvoiceTaskService.createIssueTask( OpenInvoiceTaskService.createIssueTask(
principal = principal, principal = principal,
request = request, request = request,
runMode = OpenInvoiceTaskService.MODE_SIMULATED,
sourceType = OpenInvoiceTaskService.SOURCE_TEST,
)
}
}
post("/production") {
val principal = call.requireOpenApiPrincipal()
val request = call.receive<OpenBlueInvoiceCreateRequest>()
call.respondOpenApi(principal, "blue-invoice-task.production", null) {
OpenInvoiceTaskService.createIssueTask(
principal = principal,
request = request,
runMode = OpenInvoiceTaskService.MODE_REAL,
) )
} }
} }
@@ -21,7 +21,6 @@ fun Route.registerOpenInvoiceTaskManageRoutes() {
digitalAccountId = call.request.queryParameters["digitalAccountId"], digitalAccountId = call.request.queryParameters["digitalAccountId"],
status = call.request.queryParameters["status"], status = call.request.queryParameters["status"],
sourceType = call.request.queryParameters["sourceType"], sourceType = call.request.queryParameters["sourceType"],
runMode = call.request.queryParameters["runMode"],
page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1, page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1,
pageSize = call.request.queryParameters["pageSize"]?.toIntOrNull() ?: 20, pageSize = call.request.queryParameters["pageSize"]?.toIntOrNull() ?: 20,
) )
@@ -12,6 +12,8 @@ import com.bbit.ticket.entity.request.UpdateDigitalAccountStatusRequest
import com.bbit.ticket.entity.request.UpdateInvoiceSettingRequest import com.bbit.ticket.entity.request.UpdateInvoiceSettingRequest
import com.bbit.ticket.service.piaotong.PTAuthService import com.bbit.ticket.service.piaotong.PTAuthService
import com.bbit.ticket.service.piaotong.PTConfigService import com.bbit.ticket.service.piaotong.PTConfigService
import com.bbit.ticket.utils.queryInt
import com.bbit.ticket.utils.queryString
import com.bbit.ticket.utils.requireCurrentUser import com.bbit.ticket.utils.requireCurrentUser
import io.ktor.server.request.receive import io.ktor.server.request.receive
import io.ktor.server.routing.Route import io.ktor.server.routing.Route
@@ -59,7 +61,23 @@ fun Route.registerPTAuthRoutes() {
get("/digital-accounts") { get("/digital-accounts") {
call.respondPt("查询数电账号失败") { call.respondPt("查询数电账号失败") {
PTConfigService.listDigitalAccounts(call.requireCurrentUser()) PTConfigService.listDigitalAccounts(
user = call.requireCurrentUser(),
account = call.queryString("account"),
status = call.queryString("status"),
page = call.queryInt("page", 1),
pageSize = call.queryInt("pageSize", 20),
)
}
}
get("/digital-accounts/options") {
call.respondPt("查询数电账号选项失败") {
PTConfigService.listDigitalAccountOptions(
user = call.requireCurrentUser(),
account = call.queryString("account"),
limit = call.queryInt("limit", 200),
)
} }
} }
@@ -56,9 +56,6 @@ object OpenInvoiceTaskService {
const val TASK_ISSUE_BLUE = "ISSUE_BLUE" const val TASK_ISSUE_BLUE = "ISSUE_BLUE"
const val TASK_QUERY_BLUE = "QUERY_BLUE" const val TASK_QUERY_BLUE = "QUERY_BLUE"
const val SOURCE_SINGLE = "SINGLE" const val SOURCE_SINGLE = "SINGLE"
const val SOURCE_TEST = "TEST"
const val MODE_REAL = "REAL"
const val MODE_SIMULATED = "SIMULATED"
private const val STATUS_PENDING = "PENDING" private const val STATUS_PENDING = "PENDING"
private const val STATUS_PROCESSING = "PROCESSING" private const val STATUS_PROCESSING = "PROCESSING"
@@ -75,7 +72,6 @@ object OpenInvoiceTaskService {
suspend fun createIssueTask( suspend fun createIssueTask(
principal: OpenApiPrincipal, principal: OpenApiPrincipal,
request: OpenBlueInvoiceCreateRequest, request: OpenBlueInvoiceCreateRequest,
runMode: String,
sourceType: String = SOURCE_SINGLE, sourceType: String = SOURCE_SINGLE,
batchNo: String? = null, batchNo: String? = null,
): OpenInvoiceTaskSubmitResponse { ): OpenInvoiceTaskSubmitResponse {
@@ -97,12 +93,6 @@ object OpenInvoiceTaskService {
.singleOrNull() .singleOrNull()
} }
if (existing != null) { if (existing != null) {
if (existing[OpenInvoiceTaskTable.runMode] != runMode) {
throw BizException(
ErrorCode.BAD_REQUEST.code,
"invoiceReqSerialNo 已存在 ${existing[OpenInvoiceTaskTable.runMode]} 任务,不能重复创建 $runMode 任务",
)
}
return existing.toSubmitResponse() return existing.toSubmitResponse()
} }
@@ -129,7 +119,6 @@ object OpenInvoiceTaskService {
it[taxAccount] = principal.taxAccount it[taxAccount] = principal.taxAccount
it[taskType] = TASK_ISSUE_BLUE it[taskType] = TASK_ISSUE_BLUE
it[OpenInvoiceTaskTable.sourceType] = sourceType it[OpenInvoiceTaskTable.sourceType] = sourceType
it[OpenInvoiceTaskTable.runMode] = runMode
it[OpenInvoiceTaskTable.invoiceReqSerialNo] = invoiceReqSerialNo it[OpenInvoiceTaskTable.invoiceReqSerialNo] = invoiceReqSerialNo
it[OpenInvoiceTaskTable.batchNo] = batchNo it[OpenInvoiceTaskTable.batchNo] = batchNo
it[status] = STATUS_PENDING it[status] = STATUS_PENDING
@@ -146,19 +135,12 @@ object OpenInvoiceTaskService {
} }
.single() .single()
} }
if (task[OpenInvoiceTaskTable.runMode] != runMode) {
throw BizException(
ErrorCode.BAD_REQUEST.code,
"invoiceReqSerialNo 已存在 ${task[OpenInvoiceTaskTable.runMode]} 任务,不能重复创建 $runMode 任务",
)
}
return OpenInvoiceTaskSubmitResponse( return OpenInvoiceTaskSubmitResponse(
taskId = task[OpenInvoiceTaskTable.id].toString(), taskId = task[OpenInvoiceTaskTable.id].toString(),
invoiceReqSerialNo = invoiceReqSerialNo, invoiceReqSerialNo = invoiceReqSerialNo,
status = task[OpenInvoiceTaskTable.status], status = task[OpenInvoiceTaskTable.status],
taskType = TASK_ISSUE_BLUE, taskType = TASK_ISSUE_BLUE,
runMode = runMode,
) )
} }
@@ -201,7 +183,6 @@ object OpenInvoiceTaskService {
digitalAccountId: String?, digitalAccountId: String?,
status: String?, status: String?,
sourceType: String?, sourceType: String?,
runMode: String?,
page: Int, page: Int,
pageSize: Int, pageSize: Int,
): PageResult<OpenInvoiceTaskItem> = dbQuery { ): PageResult<OpenInvoiceTaskItem> = dbQuery {
@@ -215,9 +196,6 @@ object OpenInvoiceTaskService {
sourceType?.takeIf { it.isNotBlank() }?.let { sourceType?.takeIf { it.isNotBlank() }?.let {
where = where and (OpenInvoiceTaskTable.sourceType eq it) where = where and (OpenInvoiceTaskTable.sourceType eq it)
} }
runMode?.takeIf { it.isNotBlank() }?.let {
where = where and (OpenInvoiceTaskTable.runMode eq it)
}
val accountRows = PtDigitalAccountTable.selectAll() val accountRows = PtDigitalAccountTable.selectAll()
.where { accountScope(user) } .where { accountScope(user) }
.associateBy { it[PtDigitalAccountTable.id] } .associateBy { it[PtDigitalAccountTable.id] }
@@ -290,18 +268,13 @@ object OpenInvoiceTaskService {
private suspend fun processIssueTask(task: ResultRow) { private suspend fun processIssueTask(task: ResultRow) {
val taskId = task[OpenInvoiceTaskTable.id] val taskId = task[OpenInvoiceTaskTable.id]
val runMode = task[OpenInvoiceTaskTable.runMode]
val request = task[OpenInvoiceTaskTable.requestBody] val request = task[OpenInvoiceTaskTable.requestBody]
?.let { myJson.decodeFromString<OpenBlueInvoiceCreateRequest>(it) } ?.let { myJson.decodeFromString<OpenBlueInvoiceCreateRequest>(it) }
?: return failTask(taskId, "REQUEST_BODY_NULL", "任务请求体为空") ?: return failTask(taskId, "REQUEST_BODY_NULL", "任务请求体为空")
val askRequest = request.toAskInvoiceRequest(task) val askRequest = request.toAskInvoiceRequest(task)
createHistoryPlaceholder(task, askRequest) createHistoryPlaceholder(task, askRequest)
try { try {
if (runMode == MODE_SIMULATED) {
delay(2000)
} else {
PTApi.invoiceBlue(askRequest) PTApi.invoiceBlue(askRequest)
}
completeIssueTask(task) completeIssueTask(task)
} catch (e: PTException) { } catch (e: PTException) {
failTask(taskId, e.code, e.message) failTask(taskId, e.code, e.message)
@@ -314,14 +287,8 @@ object OpenInvoiceTaskService {
} }
private suspend fun processQueryTask(task: ResultRow) { private suspend fun processQueryTask(task: ResultRow) {
val taskId = task[OpenInvoiceTaskTable.id]
val runMode = task[OpenInvoiceTaskTable.runMode]
val invoiceReqSerialNo = task[OpenInvoiceTaskTable.invoiceReqSerialNo] val invoiceReqSerialNo = task[OpenInvoiceTaskTable.invoiceReqSerialNo]
try { try {
val (code, message) = if (runMode == MODE_SIMULATED) {
delay(2000)
simulatedQueryCode(invoiceReqSerialNo, task[OpenInvoiceTaskTable.pollCount]) to null
} else {
val res = PTApi.queryInvoiceInfo( val res = PTApi.queryInvoiceInfo(
QueryInvoiceRequest( QueryInvoiceRequest(
taxpayerNum = task[OpenInvoiceTaskTable.taxpayerNum], taxpayerNum = task[OpenInvoiceTaskTable.taxpayerNum],
@@ -336,9 +303,7 @@ object OpenInvoiceTaskService {
task[OpenInvoiceTaskTable.digitalAccountId], task[OpenInvoiceTaskTable.digitalAccountId],
) )
} }
res.code to res.msg handleQueryCode(task, res.code, res.msg)
}
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) {
@@ -368,7 +333,6 @@ object OpenInvoiceTaskService {
it[taxAccount] = task[OpenInvoiceTaskTable.taxAccount] it[taxAccount] = task[OpenInvoiceTaskTable.taxAccount]
it[taskType] = TASK_QUERY_BLUE it[taskType] = TASK_QUERY_BLUE
it[sourceType] = task[OpenInvoiceTaskTable.sourceType] it[sourceType] = task[OpenInvoiceTaskTable.sourceType]
it[runMode] = task[OpenInvoiceTaskTable.runMode]
it[OpenInvoiceTaskTable.invoiceReqSerialNo] = invoiceReqSerialNo it[OpenInvoiceTaskTable.invoiceReqSerialNo] = invoiceReqSerialNo
it[batchNo] = task[OpenInvoiceTaskTable.batchNo] it[batchNo] = task[OpenInvoiceTaskTable.batchNo]
it[status] = STATUS_PENDING it[status] = STATUS_PENDING
@@ -660,15 +624,6 @@ object OpenInvoiceTaskService {
orderList = orderList, orderList = orderList,
) )
private fun simulatedQueryCode(invoiceReqSerialNo: String, pollCount: Int): String =
when {
invoiceReqSerialNo.contains("3999", ignoreCase = true) -> AUTH_REQUIRED_CODE
invoiceReqSerialNo.contains("FAIL", ignoreCase = true) -> "9999"
pollCount <= 0 -> "7777"
pollCount == 1 -> "6666"
else -> "0000"
}
private fun historyCode(code: String): String = private fun historyCode(code: String): String =
if (code.length <= 8) code else HISTORY_FAILED_CODE if (code.length <= 8) code else HISTORY_FAILED_CODE
@@ -706,7 +661,6 @@ object OpenInvoiceTaskService {
invoiceReqSerialNo = this[OpenInvoiceTaskTable.invoiceReqSerialNo], invoiceReqSerialNo = this[OpenInvoiceTaskTable.invoiceReqSerialNo],
status = this[OpenInvoiceTaskTable.status], status = this[OpenInvoiceTaskTable.status],
taskType = this[OpenInvoiceTaskTable.taskType], taskType = this[OpenInvoiceTaskTable.taskType],
runMode = this[OpenInvoiceTaskTable.runMode],
) )
private fun ResultRow.toTaskItem(account: String?, historyMessage: String?): OpenInvoiceTaskItem = private fun ResultRow.toTaskItem(account: String?, historyMessage: String?): OpenInvoiceTaskItem =
@@ -717,7 +671,6 @@ object OpenInvoiceTaskService {
account = account, account = account,
taskType = this[OpenInvoiceTaskTable.taskType], taskType = this[OpenInvoiceTaskTable.taskType],
sourceType = this[OpenInvoiceTaskTable.sourceType], sourceType = this[OpenInvoiceTaskTable.sourceType],
runMode = this[OpenInvoiceTaskTable.runMode],
invoiceReqSerialNo = this[OpenInvoiceTaskTable.invoiceReqSerialNo], invoiceReqSerialNo = this[OpenInvoiceTaskTable.invoiceReqSerialNo],
batchNo = this[OpenInvoiceTaskTable.batchNo], batchNo = this[OpenInvoiceTaskTable.batchNo],
status = this[OpenInvoiceTaskTable.status], status = this[OpenInvoiceTaskTable.status],
@@ -6,6 +6,7 @@ import com.bbit.ticket.dao.piaotong.EnterpriseManageDao
import com.bbit.ticket.dao.system.UserDao import com.bbit.ticket.dao.system.UserDao
import com.bbit.ticket.database.piaotong.PtDigitalAccountTable import com.bbit.ticket.database.piaotong.PtDigitalAccountTable
import com.bbit.ticket.database.system.SysUserTable import com.bbit.ticket.database.system.SysUserTable
import com.bbit.ticket.entity.common.PageResult
import com.bbit.ticket.entity.request.CreateDigitalAccountRequest import com.bbit.ticket.entity.request.CreateDigitalAccountRequest
import com.bbit.ticket.entity.request.QueryDigitalAccountListRequest import com.bbit.ticket.entity.request.QueryDigitalAccountListRequest
import com.bbit.ticket.entity.request.QueryEnterpriseBankAccountRequest import com.bbit.ticket.entity.request.QueryEnterpriseBankAccountRequest
@@ -62,15 +63,42 @@ object PTConfigService {
return PTAuthService.queryEnterpriseBankInfo(QueryEnterpriseBankAccountRequest(taxpayerNum)).bankList return PTAuthService.queryEnterpriseBankInfo(QueryEnterpriseBankAccountRequest(taxpayerNum)).bankList
} }
suspend fun listDigitalAccounts(user: CurrentUser): List<DigitalAccountManageItem> = dbQuery { suspend fun listDigitalAccounts(
user: CurrentUser,
account: String?,
status: String?,
page: Int,
pageSize: Int,
): PageResult<DigitalAccountManageItem> = dbQuery {
when { when {
user.isSuperAdmin || user.isEnterpriseAdmin -> { user.isSuperAdmin || user.isEnterpriseAdmin -> {
EnterpriseManageDao.digitalAccountsForEnterprise(requireEnterpriseId(user)) EnterpriseManageDao.digitalAccountsForEnterprise(requireEnterpriseId(user), account, status, page, pageSize)
} }
user.isDigitalOperator -> { user.isDigitalOperator -> {
val id = user.digitalAccountId val id = user.digitalAccountId
?: throw BizException(ErrorCode.BAD_REQUEST.code, "当前账号未绑定数电账号") ?: throw BizException(ErrorCode.BAD_REQUEST.code, "当前账号未绑定数电账号")
EnterpriseManageDao.digitalAccount(id)?.let { listOf(EnterpriseManageDao.run { it.toDigitalAccountItem() }) } val item = EnterpriseManageDao.digitalAccount(id)
?.let { EnterpriseManageDao.run { it.toDigitalAccountItem() } }
?.takeIf { account.isNullOrBlank() || it.account.contains(account.trim(), ignoreCase = true) }
?.takeIf { status.isNullOrBlank() || it.status == status.trim().uppercase() }
PageResult(listOfNotNull(item), page, pageSize, if (item == null) 0 else 1)
}
else -> PageResult(emptyList(), page, pageSize, 0)
}
}
suspend fun listDigitalAccountOptions(user: CurrentUser, account: String?, limit: Int): List<DigitalAccountManageItem> = dbQuery {
when {
user.isSuperAdmin || user.isEnterpriseAdmin -> {
EnterpriseManageDao.digitalAccountOptionsForEnterprise(requireEnterpriseId(user), account, limit.coerceIn(1, 200))
}
user.isDigitalOperator -> {
val id = user.digitalAccountId
?: throw BizException(ErrorCode.BAD_REQUEST.code, "当前账号未绑定数电账号")
EnterpriseManageDao.digitalAccount(id)
?.let { EnterpriseManageDao.run { it.toDigitalAccountItem() } }
?.takeIf { account.isNullOrBlank() || it.account.contains(account.trim(), ignoreCase = true) }
?.let { listOf(it) }
?: emptyList() ?: emptyList()
} }
else -> emptyList() else -> emptyList()
@@ -97,7 +125,7 @@ object PTConfigService {
) )
} }
} }
return listDigitalAccounts(user) return listDigitalAccountOptions(user, null, 200)
} }
suspend fun createDigitalAccount(user: CurrentUser, req: CreateDigitalAccountRequest): DigitalAccountManageItem { suspend fun createDigitalAccount(user: CurrentUser, req: CreateDigitalAccountRequest): DigitalAccountManageItem {
@@ -59,6 +59,7 @@ object DatabaseInitializer {
exec(it) exec(it)
} }
} }
exec("ALTER TABLE open_invoice_task DROP COLUMN IF EXISTS run_mode")
} }
logger.info("Database schema initialized") logger.info("Database schema initialized")
} }
@@ -2,7 +2,7 @@ package com.bbit.ticket.utils.bootstrap
object Global { object Global {
val isDev = true val isDev = false
// 请求基础地址 // 请求基础地址
var baseUrl: String var baseUrl: String
+20 -9
View File
@@ -1,4 +1,4 @@
import http from '@/api/http' import http from '@/api/http'
/** /**
* 账号状态 * 账号状态
@@ -158,8 +158,20 @@ export interface CreateDigitalAccountRequest {
platformPassword: string platformPassword: string
} }
export function listDigitalAccountsApi(): Promise<DigitalAccountItem[]> { export function listDigitalAccountsApi(params: {
return http.get('/pt/digital-accounts') page: number
pageSize: number
account?: string | null
status?: string | null
}): Promise<PageResult<DigitalAccountItem>> {
return http.get('/pt/digital-accounts', { params })
}
export function digitalAccountOptionsApi(params?: {
account?: string | null
limit?: number
}): Promise<DigitalAccountItem[]> {
return http.get('/pt/digital-accounts/options', { params })
} }
export function refreshDigitalAccountsApi(): Promise<DigitalAccountItem[]> { export function refreshDigitalAccountsApi(): Promise<DigitalAccountItem[]> {
@@ -244,7 +256,6 @@ export interface OpenInvoiceTaskItem {
account?: string | null account?: string | null
taskType: string taskType: string
sourceType: string sourceType: string
runMode: string
invoiceReqSerialNo: string invoiceReqSerialNo: string
batchNo?: string | null batchNo?: string | null
status: string status: string
@@ -271,7 +282,6 @@ export function openInvoiceTaskPageApi(params: {
digitalAccountId?: string digitalAccountId?: string
status?: string | null status?: string | null
sourceType?: string | null sourceType?: string | null
runMode?: string | null
}): Promise<PageResult<OpenInvoiceTaskItem>> { }): Promise<PageResult<OpenInvoiceTaskItem>> {
return http.get('/pt/openapi/tasks', { params }) return http.get('/pt/openapi/tasks', { params })
} }
@@ -458,9 +468,9 @@ export function invoiceIssueApi(payload: InvoiceRequest): Promise<string> {
export interface RedCreateRequest { export interface RedCreateRequest {
/** 蓝票历史记录 ID */ /** 蓝票历史记录 ID */
historyId: string historyId: string
/** 平台数电账号 ID。企业管理员红时用于指定开票员 */ /** 平台数电账号 ID。企业管理员红时用于指定开票员 */
digitalAccountId?: string | null digitalAccountId?: string | null
/** 红原因:01开票有误 02销货退回 03服务中止 04销售折让 */ /** 红原因:01开票有误 02销货退回 03服务中止 04销售折让 */
redReason: string redReason: string
/** 收票人名称 */ /** 收票人名称 */
takerName?: string takerName?: string
@@ -577,7 +587,7 @@ export interface InvoiceHistoryItem {
// ===== 发票状态标记 ===== // ===== 发票状态标记 =====
/** 作废状态 */ /** 作废状态 */
invalidFlag?: string invalidFlag?: string
/** 红状态 */ /** 红状态 */
redFlag?: string redFlag?: string
// ===== 人员信息 ===== // ===== 人员信息 =====
@@ -849,7 +859,7 @@ export function queryInvoiceApi(
/** 红票申请信息 */ /** 红票申请信息 */
export interface RedInvoiceInfo { export interface RedInvoiceInfo {
/** 红原因:01开票有误 02销货退回 03服务中止 04销售折让 */ /** 红原因:01开票有误 02销货退回 03服务中止 04销售折让 */
redReason: string redReason: string
/** 收票人名称 */ /** 收票人名称 */
takerName?: string takerName?: string
@@ -976,3 +986,4 @@ export function queryAuthStatusApi(payload: {
}): Promise<AuthQrcodeStatusResponse> { }): Promise<AuthQrcodeStatusResponse> {
return http.post('/pt/query-auth-status', payload) return http.post('/pt/query-auth-status', payload)
} }
@@ -4,6 +4,12 @@
<div class="page-toolbar"> <div class="page-toolbar">
<h2 class="page-toolbar-title">数电账号管理</h2> <h2 class="page-toolbar-title">数电账号管理</h2>
<div class="page-toolbar-actions"> <div class="page-toolbar-actions">
<n-input
v-model:value="accountKeyword"
clearable
placeholder="税局账号"
class="toolbar-filter-input"
/>
<n-button :loading="loading" @click="refreshAccounts"> <n-button :loading="loading" @click="refreshAccounts">
<template #icon><n-icon :component="RefreshCw" /></template> <template #icon><n-icon :component="RefreshCw" /></template>
刷新 刷新
@@ -22,9 +28,12 @@
:columns="columns" :columns="columns"
:data="accounts" :data="accounts"
:loading="loading" :loading="loading"
:pagination="{ pageSize: 10 }" :pagination="pagination"
:row-key="(row: DigitalAccountItem) => row.id" :row-key="(row: DigitalAccountItem) => row.id"
:scroll-x="1460" :scroll-x="1460"
remote
@update:page="onPageChange"
@update:page-size="onPageSizeChange"
/> />
</div> </div>
</section> </section>
@@ -133,8 +142,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, h, onMounted, reactive, ref } from 'vue' import { computed, h, onMounted, reactive, ref, watch } from 'vue'
import type { DataTableColumns, FormInst, FormRules } from 'naive-ui' import type { DataTableColumns, FormInst, FormRules, PaginationProps } from 'naive-ui'
import { import {
NAlert, NAlert,
NButton, NButton,
@@ -175,6 +184,8 @@ const authStore = useAuthStore()
const loading = ref(false) const loading = ref(false)
const saving = ref(false) const saving = ref(false)
const accounts = ref<DigitalAccountItem[]>([]) const accounts = ref<DigitalAccountItem[]>([])
const accountKeyword = ref('')
let accountSearchTimer: ReturnType<typeof setTimeout> | null = null
const showCreate = ref(false) const showCreate = ref(false)
const showSms = ref(false) const showSms = ref(false)
const showQrcode = ref(false) const showQrcode = ref(false)
@@ -195,6 +206,17 @@ const canUpdateStatus = computed(() => authStore.hasPermission('digital-account:
const qrcodeTypeLabel = computed(() => const qrcodeTypeLabel = computed(() =>
qrcodeType.value === '2' ? '国家网络身份认证 APP' : '电子税务局 APP' qrcodeType.value === '2' ? '国家网络身份认证 APP' : '电子税务局 APP'
) )
const pager = reactive({
page: 1,
pageSize: 10
})
const pagination = reactive<PaginationProps>({
page: pager.page,
pageSize: pager.pageSize,
itemCount: 0,
showSizePicker: true,
pageSizes: [10, 20, 50, 100]
})
const createForm = reactive({ const createForm = reactive({
account: '', account: '',
@@ -352,7 +374,17 @@ function renderEllipsisText(value?: string | null) {
async function load() { async function load() {
loading.value = true loading.value = true
try { try {
accounts.value = await listDigitalAccountsApi() const result = await listDigitalAccountsApi({
page: pager.page,
pageSize: pager.pageSize,
account: accountKeyword.value.trim() || null
})
accounts.value = result.items
pager.page = result.page
pager.pageSize = result.pageSize
pagination.page = result.page
pagination.pageSize = result.pageSize
pagination.itemCount = result.total
} finally { } finally {
loading.value = false loading.value = false
} }
@@ -361,13 +393,26 @@ async function load() {
async function refreshAccounts() { async function refreshAccounts() {
loading.value = true loading.value = true
try { try {
accounts.value = await refreshDigitalAccountsApi() await refreshDigitalAccountsApi()
message.success('数电账号已刷新') message.success('数电账号已刷新')
pager.page = 1
await load()
} finally { } finally {
loading.value = false loading.value = false
} }
} }
function onPageChange(page: number) {
pager.page = page
load()
}
function onPageSizeChange(pageSize: number) {
pager.page = 1
pager.pageSize = pageSize
load()
}
async function createAccount() { async function createAccount() {
await createFormRef.value?.validate() await createFormRef.value?.validate()
saving.value = true saving.value = true
@@ -451,6 +496,14 @@ async function loadQrcode() {
} }
onMounted(load) onMounted(load)
watch(accountKeyword, () => {
if (accountSearchTimer) clearTimeout(accountSearchTimer)
accountSearchTimer = setTimeout(() => {
pager.page = 1
load()
}, 300)
})
</script> </script>
<style scoped> <style scoped>
@@ -461,6 +514,10 @@ onMounted(load)
gap: 8px; gap: 8px;
} }
.toolbar-filter-input {
width: 240px;
}
.row-actions { .row-actions {
flex-wrap: nowrap; flex-wrap: nowrap;
white-space: nowrap; white-space: nowrap;
@@ -1,4 +1,4 @@
<template> <template>
<div class="page-shell invoice-history-page"> <div class="page-shell invoice-history-page">
<div class="stats-row"> <div class="stats-row">
<div v-for="s in stats" :key="s.label" class="stat-card"> <div v-for="s in stats" :key="s.label" class="stat-card">
@@ -173,9 +173,9 @@
}}</strong> }}</strong>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span>红状态</span> <span>状态</span>
<strong>{{ <strong>{{
redFlagMap[detailItem.redFlag || ''] || detailItem.redFlag || '未红' redFlagMap[detailItem.redFlag || ''] || detailItem.redFlag || '未红'
}}</strong> }}</strong>
</div> </div>
<div class="detail-item"> <div class="detail-item">
@@ -227,10 +227,10 @@
<template v-if="detailItem.invoiceType === 'RED' && redInfo"> <template v-if="detailItem.invoiceType === 'RED' && redInfo">
<div class="detail-section"> <div class="detail-section">
<div class="detail-section-title">红申请</div> <div class="detail-section-title">红申请</div>
<div class="detail-grid"> <div class="detail-grid">
<div class="detail-item"> <div class="detail-item">
<span>红原因</span> <span>红原因</span>
<strong>{{ redReasonMap[redInfo.redReason] || redInfo.redReason }}</strong> <strong>{{ redReasonMap[redInfo.redReason] || redInfo.redReason }}</strong>
</div> </div>
<div class="detail-item"> <div class="detail-item">
@@ -419,7 +419,7 @@
<n-modal <n-modal
v-model:show="showRedForm" v-model:show="showRedForm"
preset="card" preset="card"
title="发起红" title="发起红"
:style="{ width: '480px', maxWidth: '92vw' }" :style="{ width: '480px', maxWidth: '92vw' }"
:mask-closable="false" :mask-closable="false"
content-style="padding: 20px" content-style="padding: 20px"
@@ -441,11 +441,11 @@
clearable clearable
/> />
</n-form-item> </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"
:options="redReasonOptions" :options="redReasonOptions"
placeholder="请选择红原因" placeholder="请选择红原因"
/> />
</n-form-item> </n-form-item>
<n-form-item label="收票人名称" path="takerName"> <n-form-item label="收票人名称" path="takerName">
@@ -461,7 +461,7 @@
<div class="modal-actions"> <div class="modal-actions">
<n-button quaternary @click="showRedForm = false">取消</n-button> <n-button quaternary @click="showRedForm = false">取消</n-button>
<n-button type="error" :loading="redSubmitting" @click="handleRedSubmit" <n-button type="error" :loading="redSubmitting" @click="handleRedSubmit"
>确认红</n-button >确认红</n-button
> >
</div> </div>
</n-form> </n-form>
@@ -506,7 +506,7 @@ import {
invoiceHistoryApi, invoiceHistoryApi,
invoiceKindMap, invoiceKindMap,
invoiceStatusMap, invoiceStatusMap,
listDigitalAccountsApi, digitalAccountOptionsApi,
queryInvoiceApi, queryInvoiceApi,
redInvoiceCreateApi, redInvoiceCreateApi,
redInvoiceDownloadUrlApi, redInvoiceDownloadUrlApi,
@@ -539,11 +539,11 @@ const invalidFlagMap: Record<string, string> = {
} }
const redFlagMap: Record<string, string> = { const redFlagMap: Record<string, string> = {
NOT_RED: '未红', NOT_RED: '未红',
ALREADY_RED: '已红', ALREADY_RED: '已红',
REDING: '红中', REDING: '红中',
RED_FAIL: '红失败', RED_FAIL: '红失败',
PART_RED: '部分红' PART_RED: '部分红'
} }
const redInvoiceKindCodes = new Set(['81', '82', '83', '87']) const redInvoiceKindCodes = new Set(['81', '82', '83', '87'])
@@ -740,7 +740,7 @@ function getRowActions(row: InvoiceHistoryItem) {
} }
if (canCreateRedInvoice(row)) { if (canCreateRedInvoice(row)) {
actions.push({ label: '红', icon: RotateCcw, onClick: () => startRedTask(row) }) actions.push({ label: '红', icon: RotateCcw, onClick: () => startRedTask(row) })
} }
return actions return actions
@@ -751,7 +751,7 @@ function getActionsColumnWidth() {
详情: 58, 详情: 58,
刷新: 58, 刷新: 58,
查看票样: 86, 查看票样: 86,
: 58 : 58
} }
const rowWidths = dataSource.value.map((row) => { const rowWidths = dataSource.value.map((row) => {
const actions = getRowActions(row) const actions = getRowActions(row)
@@ -847,7 +847,7 @@ const columns = computed<DataTableColumns<InvoiceHistoryItem>>(() => {
: h('span', { style: 'color:#9ca3af' }, '-') : h('span', { style: 'color:#9ca3af' }, '-')
}, },
{ {
title: '红状态', title: '红状态',
key: 'redFlag', key: 'redFlag',
width: 110, width: 110,
render: (row: InvoiceHistoryItem) => { render: (row: InvoiceHistoryItem) => {
@@ -862,7 +862,7 @@ const columns = computed<DataTableColumns<InvoiceHistoryItem>>(() => {
}[row.status] }[row.status]
: undefined) : undefined)
if (!redFlag || redFlag === 'NOT_RED') { if (!redFlag || redFlag === 'NOT_RED') {
return h(NTag, { size: 'small', round: true }, () => '未红') return h(NTag, { size: 'small', round: true }, () => '未红')
} }
const typeMap: Record<string, 'error' | 'warning' | 'default'> = { const typeMap: Record<string, 'error' | 'warning' | 'default'> = {
ALREADY_RED: 'error', ALREADY_RED: 'error',
@@ -1013,14 +1013,14 @@ const digitalAccountOptions = computed(() =>
const redFormRules = { const redFormRules = {
digitalAccountId: [{ required: true, message: '请选择开票员', trigger: 'change' }], digitalAccountId: [{ required: true, message: '请选择开票员', trigger: 'change' }],
redReason: [{ required: true, message: '请选择红原因', trigger: 'change' }] redReason: [{ required: true, message: '请选择红原因', trigger: 'change' }]
} }
async function ensureDigitalAccountsLoaded() { async function ensureDigitalAccountsLoaded() {
if (digitalAccounts.value.length > 0) return if (digitalAccounts.value.length > 0) return
digitalAccountLoading.value = true digitalAccountLoading.value = true
try { try {
digitalAccounts.value = await listDigitalAccountsApi() digitalAccounts.value = await digitalAccountOptionsApi({ limit: 200 })
} finally { } finally {
digitalAccountLoading.value = false digitalAccountLoading.value = false
} }
@@ -1057,11 +1057,11 @@ async function handleRedSubmit() {
takerEmail: redForm.takerEmail || undefined takerEmail: redForm.takerEmail || undefined
} }
await redInvoiceCreateApi(payload) await redInvoiceCreateApi(payload)
message.success('红任务创建成功') message.success('红任务创建成功')
showRedForm.value = false showRedForm.value = false
fetchData() fetchData()
} catch { } catch {
message.error('创建红任务失败') message.error('创建红任务失败')
} finally { } finally {
redSubmitting.value = false redSubmitting.value = false
} }
@@ -1510,3 +1510,4 @@ onMounted(() => {
} }
} }
</style> </style>
@@ -931,7 +931,7 @@ import {
invoiceIssueApi, invoiceIssueApi,
getEnterpriseInfoApi, getEnterpriseInfoApi,
getPresetDataApi, getPresetDataApi,
listDigitalAccountsApi digitalAccountOptionsApi
} from '@/api/piaotong' } from '@/api/piaotong'
import type { DigitalAccountItem, InvoiceRequest, InvoiceItem, VariableLevyProof } from '@/api/piaotong' import type { DigitalAccountItem, InvoiceRequest, InvoiceItem, VariableLevyProof } from '@/api/piaotong'
import type { FormInst, FormRules } from 'naive-ui' import type { FormInst, FormRules } from 'naive-ui'
@@ -1574,7 +1574,7 @@ onMounted(async () => {
const [enterpriseInfo, presetData, accounts] = await Promise.all([ const [enterpriseInfo, presetData, accounts] = await Promise.all([
getEnterpriseInfoApi(), getEnterpriseInfoApi(),
getPresetDataApi(), getPresetDataApi(),
listDigitalAccountsApi() digitalAccountOptionsApi({ limit: 200 })
]) ])
digitalAccounts.value = accounts digitalAccounts.value = accounts
const defaultAccount = const defaultAccount =
+39 -23
View File
@@ -66,15 +66,6 @@
class="task-filter" class="task-filter"
@update:value="loadTasks(1)" @update:value="loadTasks(1)"
/> />
<n-select
v-model:value="taskRunMode"
clearable
size="small"
placeholder="全部模式"
:options="runModeOptions"
class="task-filter"
@update:value="loadTasks(1)"
/>
<n-button size="small" :loading="taskLoading" @click="loadTasks(taskPage)"> <n-button size="small" :loading="taskLoading" @click="loadTasks(taskPage)">
<template #icon><n-icon :component="RefreshCw" /></template> <template #icon><n-icon :component="RefreshCw" /></template>
刷新 刷新
@@ -122,7 +113,6 @@ const taskPageSize = ref(20)
const taskTotal = ref(0) const taskTotal = ref(0)
const taskStatus = ref<string | null>(null) const taskStatus = ref<string | null>(null)
const taskSourceType = ref<string | null>(null) const taskSourceType = ref<string | null>(null)
const taskRunMode = ref<string | null>(null)
const statusOptions = [ const statusOptions = [
{ label: '待处理', value: 'PENDING' }, { label: '待处理', value: 'PENDING' },
@@ -134,14 +124,35 @@ const statusOptions = [
const sourceTypeOptions = [ const sourceTypeOptions = [
{ label: '单笔', value: 'SINGLE' }, { label: '单笔', value: 'SINGLE' },
{ label: '批量', value: 'BATCH' }, { label: '批量', value: 'BATCH' }
{ label: '测试', value: 'TEST' }
] ]
const runModeOptions = [ const queueStatusLabelMap: Record<string, string> = {
{ label: '生产', value: 'REAL' }, RUNNING: '运行中',
{ label: '模拟', value: 'SIMULATED' } PAUSED: '已暂停'
] }
const taskStatusLabelMap: Record<string, string> = {
PENDING: '待处理',
PROCESSING: '处理中',
SUCCESS: '成功',
FAILED: '失败',
WAITING_AUTH: '需认证'
}
const taskTypeLabelMap: Record<string, string> = {
ISSUE_BLUE: '蓝票开具',
QUERY_BLUE: '蓝票查询'
}
const sourceTypeLabelMap: Record<string, string> = {
SINGLE: '单笔',
BATCH: '批量'
}
function labelOf(map: Record<string, string>, value?: string | null) {
return value ? map[value] || value : '-'
}
const totals = computed(() => const totals = computed(() =>
rows.value.reduce( rows.value.reduce(
@@ -177,7 +188,11 @@ const columns: DataTableColumns<OpenInvoiceTaskOverviewItem> = [
key: 'status', key: 'status',
width: 110, width: 110,
render: (row) => render: (row) =>
h(NTag, { type: statusTagType(row.status), size: 'small' }, { default: () => row.status }) h(
NTag,
{ type: statusTagType(row.status), size: 'small' },
{ default: () => labelOf(queueStatusLabelMap, row.status) }
)
}, },
{ title: '待处理', key: 'pending', width: 90 }, { title: '待处理', key: 'pending', width: 90 },
{ title: '处理中', key: 'processing', width: 90 }, { title: '处理中', key: 'processing', width: 90 },
@@ -265,15 +280,18 @@ function rowProps(row: OpenInvoiceTaskOverviewItem) {
const taskColumns: DataTableColumns<OpenInvoiceTaskItem> = [ const taskColumns: DataTableColumns<OpenInvoiceTaskItem> = [
{ title: '票号', key: 'invoiceReqSerialNo', minWidth: 170 }, { title: '票号', key: 'invoiceReqSerialNo', minWidth: 170 },
{ title: '任务', key: 'taskType', width: 120 }, { title: '任务', key: 'taskType', width: 120, render: (row) => labelOf(taskTypeLabelMap, row.taskType) },
{ title: '来源', key: 'sourceType', width: 90 }, { title: '来源', key: 'sourceType', width: 90, render: (row) => labelOf(sourceTypeLabelMap, row.sourceType) },
{ title: '模式', key: 'runMode', width: 90 },
{ {
title: '状态', title: '状态',
key: 'status', key: 'status',
width: 110, width: 110,
render: (row) => render: (row) =>
h(NTag, { type: statusTagType(row.status), size: 'small' }, { default: () => row.status }) h(
NTag,
{ type: statusTagType(row.status), size: 'small' },
{ default: () => labelOf(taskStatusLabelMap, row.status) }
)
}, },
{ title: 'PT码', key: 'ptCode', width: 90 }, { title: 'PT码', key: 'ptCode', width: 90 },
{ title: '错误', key: 'errorMessage', minWidth: 260, ellipsis: { tooltip: true } }, { title: '错误', key: 'errorMessage', minWidth: 260, ellipsis: { tooltip: true } },
@@ -307,7 +325,6 @@ async function loadTasks(page = 1) {
digitalAccountId: selected.value.digitalAccountId, digitalAccountId: selected.value.digitalAccountId,
status: taskStatus.value, status: taskStatus.value,
sourceType: taskSourceType.value, sourceType: taskSourceType.value,
runMode: taskRunMode.value,
page, page,
pageSize: taskPageSize.value pageSize: taskPageSize.value
}) })
@@ -326,7 +343,6 @@ function openDrawer(row: OpenInvoiceTaskOverviewItem) {
drawerVisible.value = true drawerVisible.value = true
taskStatus.value = null taskStatus.value = null
taskSourceType.value = null taskSourceType.value = null
taskRunMode.value = null
loadTasks(1) loadTasks(1)
} }