整理代码

This commit is contained in:
BBIT-Kai
2026-05-22 18:00:14 +08:00
parent d57ea3960c
commit 01b63c4fe4
22 changed files with 969 additions and 218 deletions
@@ -46,9 +46,9 @@ import kotlin.uuid.Uuid
object BlueInvoiceDao { object BlueInvoiceDao {
fun listBatchNos(userId: Uuid): List<String> = fun listBatchNos(userId: Uuid, enterpriseId: Uuid?, digitalAccountId: Uuid?): List<String> =
OpenInvoiceBatchTable.selectAll() OpenInvoiceBatchTable.selectAll()
.where { OpenInvoiceBatchTable.userId eq userId } .where { batchScopeWhere(userId, enterpriseId, digitalAccountId) }
.orderBy(OpenInvoiceBatchTable.createdAt, SortOrder.DESC) .orderBy(OpenInvoiceBatchTable.createdAt, SortOrder.DESC)
.map { it[OpenInvoiceBatchTable.batchNo] } .map { it[OpenInvoiceBatchTable.batchNo] }
@@ -69,14 +69,7 @@ object BlueInvoiceDao {
isSuccess: Boolean? = null, isSuccess: Boolean? = null,
batchNo: String? = null, batchNo: String? = null,
): PageResult<InvoiceHistoryItem> { ): PageResult<InvoiceHistoryItem> {
val conditions = mutableListOf<Op<Boolean>>() val conditions = mutableListOf(invoiceScopeWhere(userId, enterpriseId, digitalAccountId))
if (digitalAccountId != null) {
conditions.add(HistoryInvoiceBasicTable.digitalAccountId eq digitalAccountId)
} else if (enterpriseId != null) {
conditions.add(HistoryInvoiceBasicTable.enterpriseId eq enterpriseId)
} else {
conditions.add(HistoryInvoiceBasicTable.userId eq userId)
}
conditions.add(HistoryInvoiceBasicTable.deletedAt.isNull()) conditions.add(HistoryInvoiceBasicTable.deletedAt.isNull())
// 发票类型筛选:前端传 BLUE/RED,库中存 1/2 // 发票类型筛选:前端传 BLUE/RED,库中存 1/2
@@ -105,7 +98,7 @@ object BlueInvoiceDao {
val matchedSerialNos = (OpenInvoiceBatchItemTable innerJoin OpenInvoiceBatchTable) val matchedSerialNos = (OpenInvoiceBatchItemTable innerJoin OpenInvoiceBatchTable)
.selectAll() .selectAll()
.where { .where {
(OpenInvoiceBatchTable.userId eq userId) and batchScopeWhere(userId, enterpriseId, digitalAccountId) and
(OpenInvoiceBatchTable.batchNo like "%$normalizedBatchNo%") (OpenInvoiceBatchTable.batchNo like "%$normalizedBatchNo%")
} }
.map { it[OpenInvoiceBatchItemTable.invoiceReqSerialNo] } .map { it[OpenInvoiceBatchItemTable.invoiceReqSerialNo] }
@@ -138,7 +131,7 @@ object BlueInvoiceDao {
(OpenInvoiceBatchItemTable innerJoin OpenInvoiceBatchTable) (OpenInvoiceBatchItemTable innerJoin OpenInvoiceBatchTable)
.selectAll() .selectAll()
.where { .where {
(OpenInvoiceBatchTable.userId eq userId) and batchScopeWhere(userId, enterpriseId, digitalAccountId) and
(OpenInvoiceBatchItemTable.invoiceReqSerialNo inList serialNos) (OpenInvoiceBatchItemTable.invoiceReqSerialNo inList serialNos)
} }
.associate { row -> .associate { row ->
@@ -451,18 +444,6 @@ object BlueInvoiceDao {
it[HistoryInvoiceBasicTable.invDeletedFlag] = req.invDeletedFlag ?: "0" it[HistoryInvoiceBasicTable.invDeletedFlag] = req.invDeletedFlag ?: "0"
} }
/**
* 根据流水号查询记录的 userId
*/
fun findUserIdBySerialNo(invoiceReqSerialNo: String): Uuid {
val row = HistoryInvoiceBasicTable.selectAll()
.where { HistoryInvoiceBasicTable.invoiceReqSerialNo eq invoiceReqSerialNo }
.singleOrNull()
?: throw com.bbit.ticket.entity.common.BizException("NOT_FOUND", "发票记录不存在")
return row[HistoryInvoiceBasicTable.userId]
?: throw com.bbit.ticket.entity.common.BizException("NOT_FOUND", "发票记录不存在用户信息")
}
fun findInvoiceScopeBySerialNo(invoiceReqSerialNo: String): InvoiceScope { fun findInvoiceScopeBySerialNo(invoiceReqSerialNo: String): InvoiceScope {
val row = HistoryInvoiceBasicTable.selectAll() val row = HistoryInvoiceBasicTable.selectAll()
.where { HistoryInvoiceBasicTable.invoiceReqSerialNo eq invoiceReqSerialNo } .where { HistoryInvoiceBasicTable.invoiceReqSerialNo eq invoiceReqSerialNo }
@@ -528,10 +509,15 @@ object BlueInvoiceDao {
.distinct() .distinct()
} }
fun invoiceDownloadUrl(userId: Uuid, invoiceReqSerialNo: String): InvoiceDownloadUrlResponse? { fun invoiceDownloadUrl(
userId: Uuid,
enterpriseId: Uuid?,
digitalAccountId: Uuid?,
invoiceReqSerialNo: String,
): InvoiceDownloadUrlResponse? {
val row = HistoryInvoiceBasicTable.selectAll() val row = HistoryInvoiceBasicTable.selectAll()
.where { .where {
(HistoryInvoiceBasicTable.userId eq userId) and invoiceScopeWhere(userId, enterpriseId, digitalAccountId) and
(HistoryInvoiceBasicTable.invoiceReqSerialNo eq invoiceReqSerialNo) and (HistoryInvoiceBasicTable.invoiceReqSerialNo eq invoiceReqSerialNo) and
HistoryInvoiceBasicTable.deletedAt.isNull() HistoryInvoiceBasicTable.deletedAt.isNull()
} }
@@ -543,14 +529,35 @@ object BlueInvoiceDao {
/** /**
* 查询发票完整详情(含商品明细、差额征税凭证、关联单据) * 查询发票完整详情(含商品明细、差额征税凭证、关联单据)
*/ */
fun invoiceDetail(userId: Uuid, invoiceReqSerialNo: String): InvoiceDetailResponse? { fun invoiceDetail(
userId: Uuid,
enterpriseId: Uuid?,
digitalAccountId: Uuid?,
invoiceReqSerialNo: String,
): InvoiceDetailResponse? {
val basicRow = HistoryInvoiceBasicTable.selectAll().where { val basicRow = HistoryInvoiceBasicTable.selectAll().where {
(HistoryInvoiceBasicTable.userId eq userId) and (HistoryInvoiceBasicTable.invoiceReqSerialNo eq invoiceReqSerialNo) invoiceScopeWhere(userId, enterpriseId, digitalAccountId) and
(HistoryInvoiceBasicTable.invoiceReqSerialNo eq invoiceReqSerialNo) and
HistoryInvoiceBasicTable.deletedAt.isNull()
}.singleOrNull() ?: return null }.singleOrNull() ?: return null
return buildInvoiceDetailResponse(basicRow) return buildInvoiceDetailResponse(basicRow)
} }
private fun invoiceScopeWhere(userId: Uuid, enterpriseId: Uuid?, digitalAccountId: Uuid?): Op<Boolean> =
when {
digitalAccountId != null -> HistoryInvoiceBasicTable.digitalAccountId eq digitalAccountId
enterpriseId != null -> HistoryInvoiceBasicTable.enterpriseId eq enterpriseId
else -> HistoryInvoiceBasicTable.userId eq userId
}
private fun batchScopeWhere(userId: Uuid, enterpriseId: Uuid?, digitalAccountId: Uuid?): Op<Boolean> =
when {
digitalAccountId != null -> OpenInvoiceBatchTable.digitalAccountId eq digitalAccountId
enterpriseId != null -> OpenInvoiceBatchTable.enterpriseId eq enterpriseId
else -> OpenInvoiceBatchTable.userId eq userId
}
/** /**
* 根据主表行构建发票完整详情响应 * 根据主表行构建发票完整详情响应
*/ */
@@ -92,19 +92,19 @@ object EnterpriseManageDao {
.where { (PtDigitalAccountTable.id eq id) and PtDigitalAccountTable.deletedAt.isNull() } .where { (PtDigitalAccountTable.id eq id) and PtDigitalAccountTable.deletedAt.isNull() }
.singleOrNull() .singleOrNull()
fun digitalAccountByApiKey(apiKey: String): ResultRow? = fun digitalAccountByTaxpayerAndAccount(taxpayerNum: String, account: String): ResultRow? =
PtDigitalAccountTable.selectAll() PtDigitalAccountTable.selectAll()
.where { .where {
(PtDigitalAccountTable.apiKey eq apiKey) and (PtDigitalAccountTable.taxpayerNum eq taxpayerNum) and
(PtDigitalAccountTable.status eq "ENABLED") and (PtDigitalAccountTable.account eq account) and
PtDigitalAccountTable.deletedAt.isNull() PtDigitalAccountTable.deletedAt.isNull()
} }
.singleOrNull() .singleOrNull()
fun digitalAccountForUser(userId: Uuid): ResultRow? = fun digitalAccountByApiKey(apiKey: String): ResultRow? =
PtDigitalAccountTable.selectAll() PtDigitalAccountTable.selectAll()
.where { .where {
(PtDigitalAccountTable.platformUserId eq userId) and (PtDigitalAccountTable.apiKey eq apiKey) and
(PtDigitalAccountTable.status eq "ENABLED") and (PtDigitalAccountTable.status eq "ENABLED") and
PtDigitalAccountTable.deletedAt.isNull() PtDigitalAccountTable.deletedAt.isNull()
} }
@@ -183,7 +183,6 @@ object EnterpriseManageDao {
it[PtDigitalAccountTable.name] = name it[PtDigitalAccountTable.name] = name
it[PtDigitalAccountTable.identityType] = identityType it[PtDigitalAccountTable.identityType] = identityType
it[PtDigitalAccountTable.platformUserId] = platformUserId it[PtDigitalAccountTable.platformUserId] = platformUserId
it[PtDigitalAccountTable.apiKey] = apiKey
it[updatedAt] = now it[updatedAt] = now
} }
return id return id
@@ -201,6 +200,13 @@ object EnterpriseManageDao {
}[PtDigitalAccountTable.id] }[PtDigitalAccountTable.id]
} }
fun updateDigitalAccountStatus(digitalAccountId: Uuid, status: String) {
PtDigitalAccountTable.update({ PtDigitalAccountTable.id eq digitalAccountId }) {
it[PtDigitalAccountTable.status] = status
it[updatedAt] = OffsetDateTime.now()
}
}
fun bindDigitalAccountUser(digitalAccountId: Uuid, userId: Uuid) { fun bindDigitalAccountUser(digitalAccountId: Uuid, userId: Uuid) {
PtDigitalAccountTable.update({ PtDigitalAccountTable.id eq digitalAccountId }) { PtDigitalAccountTable.update({ PtDigitalAccountTable.id eq digitalAccountId }) {
it[platformUserId] = userId it[platformUserId] = userId
@@ -3,6 +3,7 @@
package com.bbit.ticket.dao.piaotong package com.bbit.ticket.dao.piaotong
import com.bbit.ticket.database.piaotong.HistoryInvoiceBasicTable import com.bbit.ticket.database.piaotong.HistoryInvoiceBasicTable
import org.jetbrains.exposed.v1.core.Op
import org.jetbrains.exposed.v1.core.and import org.jetbrains.exposed.v1.core.and
import org.jetbrains.exposed.v1.core.eq import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.isNull import org.jetbrains.exposed.v1.core.isNull
@@ -41,14 +42,14 @@ object HistoryDao {
/** /**
* 根据历史记录 ID 查询蓝票基本信息 * 根据历史记录 ID 查询蓝票基本信息
*/ */
fun findByHistory(historyId: Uuid?, userId: Uuid): HistoryRow { fun findByHistory(historyId: Uuid?, userId: Uuid, enterpriseId: Uuid?, digitalAccountId: Uuid?): HistoryRow {
if (historyId == null) { if (historyId == null) {
throw com.bbit.ticket.entity.common.BizException("NOT_FOUND", "历史记录 ID 为空") throw com.bbit.ticket.entity.common.BizException("NOT_FOUND", "历史记录 ID 为空")
} }
val row = HistoryInvoiceBasicTable.selectAll() val row = HistoryInvoiceBasicTable.selectAll()
.where { .where {
(HistoryInvoiceBasicTable.id eq historyId) and (HistoryInvoiceBasicTable.id eq historyId) and
(HistoryInvoiceBasicTable.userId eq userId) and scopeWhere(userId, enterpriseId, digitalAccountId) and
HistoryInvoiceBasicTable.deletedAt.isNull() HistoryInvoiceBasicTable.deletedAt.isNull()
} }
.singleOrNull() .singleOrNull()
@@ -79,4 +80,11 @@ object HistoryDao {
) )
} }
private fun scopeWhere(userId: Uuid, enterpriseId: Uuid?, digitalAccountId: Uuid?): Op<Boolean> =
when {
digitalAccountId != null -> HistoryInvoiceBasicTable.digitalAccountId eq digitalAccountId
enterpriseId != null -> HistoryInvoiceBasicTable.enterpriseId eq enterpriseId
else -> HistoryInvoiceBasicTable.userId eq userId
}
} }
@@ -7,6 +7,7 @@ import com.bbit.ticket.database.piaotong.HistoryInvoiceRedTable
import com.bbit.ticket.entity.request.QuickRedInvoiceRequest import com.bbit.ticket.entity.request.QuickRedInvoiceRequest
import com.bbit.ticket.entity.response.InvoiceDownloadUrlResponse import com.bbit.ticket.entity.response.InvoiceDownloadUrlResponse
import com.bbit.ticket.entity.response.RedInvoiceInfoResponse import com.bbit.ticket.entity.response.RedInvoiceInfoResponse
import org.jetbrains.exposed.v1.core.Op
import org.jetbrains.exposed.v1.core.and import org.jetbrains.exposed.v1.core.and
import org.jetbrains.exposed.v1.core.eq import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.isNull import org.jetbrains.exposed.v1.core.isNull
@@ -66,7 +67,7 @@ object RedInvoiceDao {
it[HistoryInvoiceBasicTable.buyerBankAccount] = blueRow?.get(HistoryInvoiceBasicTable.buyerBankAccount) it[HistoryInvoiceBasicTable.buyerBankAccount] = blueRow?.get(HistoryInvoiceBasicTable.buyerBankAccount)
// ---- 金额 ---- // ---- 金额 ----
val redAmount = req.amount?.toBigDecimalOrNull() ?: BigDecimal.ZERO val redAmount = req.amount.toBigDecimalOrNull() ?: BigDecimal.ZERO
it[HistoryInvoiceBasicTable.noTaxAmount] = redAmount it[HistoryInvoiceBasicTable.noTaxAmount] = redAmount
it[HistoryInvoiceBasicTable.taxAmount] = BigDecimal.ZERO it[HistoryInvoiceBasicTable.taxAmount] = BigDecimal.ZERO
it[HistoryInvoiceBasicTable.amountWithTax] = redAmount it[HistoryInvoiceBasicTable.amountWithTax] = redAmount
@@ -89,7 +90,7 @@ object RedInvoiceDao {
it[HistoryInvoiceRedTable.historyId] = historyId it[HistoryInvoiceRedTable.historyId] = historyId
it[HistoryInvoiceRedTable.invoiceReqSerialNo] = req.invoiceReqSerialNo it[HistoryInvoiceRedTable.invoiceReqSerialNo] = req.invoiceReqSerialNo
it[HistoryInvoiceRedTable.redReason] = req.redReason it[HistoryInvoiceRedTable.redReason] = req.redReason
it[HistoryInvoiceRedTable.amount] = req.amount?.toBigDecimalOrNull() it[HistoryInvoiceRedTable.amount] = req.amount.toBigDecimalOrNull()
it[HistoryInvoiceRedTable.invoiceCode] = req.invoiceCode it[HistoryInvoiceRedTable.invoiceCode] = req.invoiceCode
it[HistoryInvoiceRedTable.invoiceNo] = req.invoiceNo it[HistoryInvoiceRedTable.invoiceNo] = req.invoiceNo
it[HistoryInvoiceRedTable.invoiceKind] = req.invoiceKind it[HistoryInvoiceRedTable.invoiceKind] = req.invoiceKind
@@ -114,11 +115,24 @@ object RedInvoiceDao {
/** /**
* 根据流水号查询红票申请信息 * 根据流水号查询红票申请信息
*/ */
fun findRedInfoBySerialNo(userId: Uuid, invoiceReqSerialNo: String): RedInvoiceInfoResponse? { fun findRedInfoBySerialNo(
userId: Uuid,
enterpriseId: Uuid?,
digitalAccountId: Uuid?,
invoiceReqSerialNo: String,
): RedInvoiceInfoResponse? {
val canAccess = HistoryInvoiceBasicTable.selectAll()
.where {
scopeWhere(userId, enterpriseId, digitalAccountId) and
(HistoryInvoiceBasicTable.invoiceReqSerialNo eq invoiceReqSerialNo) and
HistoryInvoiceBasicTable.deletedAt.isNull()
}
.any()
if (!canAccess) return null
val row = HistoryInvoiceRedTable.selectAll() val row = HistoryInvoiceRedTable.selectAll()
.where { .where {
(HistoryInvoiceRedTable.userId eq userId) and HistoryInvoiceRedTable.invoiceReqSerialNo eq invoiceReqSerialNo
(HistoryInvoiceRedTable.invoiceReqSerialNo eq invoiceReqSerialNo)
} }
.singleOrNull() ?: return null .singleOrNull() ?: return null
@@ -130,10 +144,15 @@ object RedInvoiceDao {
) )
} }
fun invoiceDownloadUrl(userId: Uuid, invoiceReqSerialNo: String): InvoiceDownloadUrlResponse? { fun invoiceDownloadUrl(
userId: Uuid,
enterpriseId: Uuid?,
digitalAccountId: Uuid?,
invoiceReqSerialNo: String,
): InvoiceDownloadUrlResponse? {
val row = HistoryInvoiceBasicTable.selectAll() val row = HistoryInvoiceBasicTable.selectAll()
.where { .where {
(HistoryInvoiceBasicTable.userId eq userId) and scopeWhere(userId, enterpriseId, digitalAccountId) and
(HistoryInvoiceBasicTable.invoiceReqSerialNo eq invoiceReqSerialNo) and (HistoryInvoiceBasicTable.invoiceReqSerialNo eq invoiceReqSerialNo) and
(HistoryInvoiceBasicTable.invoiceType eq "2") and (HistoryInvoiceBasicTable.invoiceType eq "2") and
HistoryInvoiceBasicTable.deletedAt.isNull() HistoryInvoiceBasicTable.deletedAt.isNull()
@@ -142,4 +161,11 @@ object RedInvoiceDao {
return InvoiceDownloadUrlResponse(row[HistoryInvoiceBasicTable.downloadUrl]) return InvoiceDownloadUrlResponse(row[HistoryInvoiceBasicTable.downloadUrl])
} }
private fun scopeWhere(userId: Uuid, enterpriseId: Uuid?, digitalAccountId: Uuid?): Op<Boolean> =
when {
digitalAccountId != null -> HistoryInvoiceBasicTable.digitalAccountId eq digitalAccountId
enterpriseId != null -> HistoryInvoiceBasicTable.enterpriseId eq enterpriseId
else -> HistoryInvoiceBasicTable.userId eq userId
}
} }
@@ -49,3 +49,8 @@ data class UpdateInvoiceSettingRequest(
val address: String = "", val address: String = "",
val phone: String = "", val phone: String = "",
) )
@Serializable
data class UpdateDigitalAccountStatusRequest(
val status: String,
)
@@ -0,0 +1,155 @@
package com.bbit.ticket.entity.request
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* 发票数据获取请求参数
*
* 用于查询企业开具/取得的发票信息
*/
@Serializable
data class InvoiceQueryRequest(
/**
* 纳税人识别号
*
* 长度:15-20
* 仅支持大写字母和数字
*/
@SerialName("taxpayerNum")
val taxpayerNum: String,
/**
* 查询类型
*
* 1:开具发票
* 2:取得发票
*/
@SerialName("queryType")
val queryType: String,
/**
* 发票来源
*
* 0:全部
* 1:增值税发票管理系统
* 2:电子发票服务平台
*/
@SerialName("invSource")
val invSource: String? = null,
/**
* 发票种类代码
*
* 81:电子发票(增值税专用发票)
* 82:电子发票(普通发票)
* 10:增值税电子普通发票
* 08:增值税电子专用发票
* 04:增值税普通发票
* 01:增值税专用发票
*/
@SerialName("invoiceKind")
val invoiceKind: String? = null,
/**
* 发票状态
*
* 空字符串:全部
* 01:正常
* 02:已作废
* 03:已红冲-全额
* 04:已红冲-部分
*/
@SerialName("invoiceState")
val invoiceState: String? = null,
/**
* 数电号码
*
* 查询数电发票时必填
* 与 invoiceCode 至少传一个
*/
@SerialName("electronicInvoiceNo")
val electronicInvoiceNo: String? = null,
/**
* 发票代码
*
* 长度:10 或 12
* 查询税控系统发票时必填
*/
@SerialName("invoiceCode")
val invoiceCode: String? = null,
/**
* 发票号码
*
* 长度:8
* 查询税控系统发票时必填
*/
@SerialName("invoiceNo")
val invoiceNo: String? = null,
/**
* 开票日期
*
* 格式:yyyyMMdd
* 示例:20221123
*/
@SerialName("invoiceDate")
val invoiceDate: String,
/**
* 对方纳税人税号
*/
@SerialName("reciprocalTaxpayerNum")
val reciprocalTaxpayerNum: String? = null,
/**
* 对方纳税人名称
*
* 长度:4-100
*/
@SerialName("reciprocalTaxpayerName")
val reciprocalTaxpayerName: String? = null,
/**
* 电子税局登录账号
*/
@SerialName("account")
val account: String? = null
)
@Serializable
data class InvoiceQuerySubmitRequest(
@SerialName("digitalAccountId")
val digitalAccountId: String? = null,
@SerialName("queryType")
val queryType: String,
@SerialName("invSource")
val invSource: String? = null,
@SerialName("invoiceKind")
val invoiceKind: String? = null,
@SerialName("invoiceState")
val invoiceState: String? = null,
@SerialName("electronicInvoiceNo")
val electronicInvoiceNo: String? = null,
@SerialName("invoiceCode")
val invoiceCode: String? = null,
@SerialName("invoiceDate")
val invoiceDate: String,
@SerialName("reciprocalTaxpayerNum")
val reciprocalTaxpayerNum: String? = null,
@SerialName("reciprocalTaxpayerName")
val reciprocalTaxpayerName: String? = null,
)
@@ -0,0 +1,348 @@
package com.bbit.ticket.entity.response
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* 发票数据获取响应报文
*/
@Serializable
data class InvoiceQueryResponse(
/**
* 获取结果代码
*
* 0000:获取成功
* 其他:获取失败,失败原因见 resultMsg
*/
@SerialName("resultCode")
val resultCode: String,
/**
* 获取结果描述
*
* 成功或失败时返回的说明信息
*/
@SerialName("resultMsg")
val resultMsg: String? = null,
/**
* 发票列表
*
* 兼容后续批量查询场景
*/
@SerialName("invoiceList")
val invoiceList: List<InvoiceInfo> = emptyList()
)
/**
* 发票信息
*/
@Serializable
data class InvoiceInfo(
/**
* 购买方名称
*/
@SerialName("buyerName")
val buyerName: String,
/**
* 购买方纳税人识别号
*/
@SerialName("buyerTaxpayerNum")
val buyerTaxpayerNum: String? = null,
/**
* 购买方地址电话
*/
@SerialName("buyerAddress")
val buyerAddress: String? = null,
/**
* 购买方开户行及账号
*/
@SerialName("buyerBankName")
val buyerBankName: String? = null,
/**
* 销售方纳税人识别号
*/
@SerialName("sellerTaxpayerNum")
val sellerTaxpayerNum: String,
/**
* 销售方名称
*/
@SerialName("sellerName")
val sellerName: String,
/**
* 销售方地址电话
*/
@SerialName("sellerAddress")
val sellerAddress: String? = null,
/**
* 销售方开户行及账号
*/
@SerialName("sellerBankName")
val sellerBankName: String? = null,
/**
* 发票种类代码
*
* 81:电子发票(增值税专用发票)
* 82:电子发票(普通发票)
* 10:增值税电子普通发票
* 08:增值税电子专用发票
* 04:增值税普通发票
* 01:增值税专用发票
*/
@SerialName("invoiceKind")
val invoiceKind: String,
/**
* 开票日期
*
* 格式:yyyy-MM-dd HH:mm:ss
*/
@SerialName("invoiceDate")
val invoiceDate: String,
/**
* 发票代码
*
* 税控系统发票可能返回
*/
@SerialName("invoiceCode")
val invoiceCode: String? = null,
/**
* 发票号码
*
* 税控系统发票可能返回
*/
@SerialName("invoiceNo")
val invoiceNo: String? = null,
/**
* 数电发票号码
*/
@SerialName("electronicInvoiceNo")
val electronicInvoiceNo: String? = null,
/**
* 不含税金额
*
* 保留小数点后 2 位
*/
@SerialName("noTaxAmount")
val noTaxAmount: String,
/**
* 税额
*
* 保留小数点后 2 位
*/
@SerialName("taxAmount")
val taxAmount: String,
/**
* 价税合计
*
* 保留小数点后 2 位
*/
@SerialName("amountWithTax")
val amountWithTax: String,
/**
* 作废标志
*
* NOT_DESTROY:未作废
* ALREADY_DESTROY:已作废
* DESTROYING:作废中
* DESTROY_FAIL:作废失败
*/
@SerialName("invalidFlag")
val invalidFlag: String,
/**
* 冲红标志
*
* NOT_RED:未冲红
* ALREADY_RED:已冲红
* REDING:冲红中
* RED_FAIL:冲红失败
* PART_RED:部分冲红
*/
@SerialName("redFlag")
val redFlag: String,
/**
* 开票人名称
*/
@SerialName("drawerName")
val drawerName: String,
/**
* 收款人
*/
@SerialName("casherName")
val casherName: String? = null,
/**
* 复核人
*/
@SerialName("reviewerName")
val reviewerName: String? = null,
/**
* 备注
*
* 数电发票一般限制 200 字符
* 税控发票按 GBK 字节长度限制 240 字节
*/
@SerialName("remark")
val remark: String? = null,
/**
* 项目明细列表
*/
@SerialName("itemList")
val itemList: List<InvoiceItem> = emptyList()
)
/**
* 发票项目明细
*/
@Serializable
data class InvoiceItem(
/**
* 货物或服务名称
*/
@SerialName("goodsName")
val goodsName: String,
/**
* 税收分类编码
*/
@SerialName("taxClassificationCode")
val taxClassificationCode: String,
/**
* 规格型号
*/
@SerialName("specificationModel")
val specificationModel: String? = null,
/**
* 单位
*/
@SerialName("meteringUnit")
val meteringUnit: String? = null,
/**
* 数量
*
* 保留小数点后 8 位
*/
@SerialName("quantity")
val quantity: String? = null,
/**
* 单价
*
* 保留小数点后 8 位
*/
@SerialName("unitPrice")
val unitPrice: String? = null,
/**
* 含税标识
*
* 0:不含税
* 1:含税
*/
@SerialName("taxIncludeFlag")
val taxIncludeFlag: String,
/**
* 项目金额
*
* 保留小数点后 2 位
*/
@SerialName("itemAmount")
val itemAmount: String,
/**
* 税率
*
* 保留小数点后 2 位,例如 0.13
*/
@SerialName("taxRate")
val taxRate: String,
/**
* 项目税额
*
* 保留小数点后 2 位
*/
@SerialName("taxRateAmount")
val taxRateAmount: String,
/**
* 扣除额
*
* 差额发票有值,保留小数点后 2 位
*/
@SerialName("deduction")
val deduction: String? = null,
/**
* 优惠政策标识
*
* 0:不使用
* 1:使用
*/
@SerialName("preferentialPolicyFlag")
val preferentialPolicyFlag: String? = null,
/**
* 零税率标识
*
* 空:非零税率
* 1:免税
* 2:不征税
* 3:普通零税率
*/
@SerialName("zeroTaxFlag")
val zeroTaxFlag: String? = null,
/**
* 增值税特殊管理
*
* 例如:免税、不征税、简易征收等
*/
@SerialName("vatSpecialManage")
val vatSpecialManage: String? = null,
/**
* 发票行性质
*
* 0:正常行
* 1:折扣行
* 2:被折扣行
*/
@SerialName("itemProperty")
val itemProperty: String,
/**
* 项目序号
*
* 用于表示项目明细的先后顺序
*/
@SerialName("itemNo")
val itemNo: String
)
@@ -8,6 +8,7 @@ import com.bbit.ticket.entity.request.GetLoginSmsCodeRequest
import com.bbit.ticket.entity.request.QueryRealNameAuthQrStatusRequest import com.bbit.ticket.entity.request.QueryRealNameAuthQrStatusRequest
import com.bbit.ticket.entity.request.SmsLoginRequest import com.bbit.ticket.entity.request.SmsLoginRequest
import com.bbit.ticket.entity.request.TaxBureauAuthReq import com.bbit.ticket.entity.request.TaxBureauAuthReq
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
@@ -73,6 +74,17 @@ fun Route.registerPTAuthRoutes() {
} }
} }
put("/digital-accounts/{id}/status") {
call.respondPt("更新数电账号状态失败") {
val id = call.parameters["id"] ?: throw IllegalArgumentException("缺少数电账号ID")
PTConfigService.updateDigitalAccountStatus(
call.requireCurrentUser(),
id,
call.receive<UpdateDigitalAccountStatusRequest>(),
)
}
}
get("/preset") { get("/preset") {
call.respondPtOrEmptyObject("查询预设数据失败") { call.respondPtOrEmptyObject("查询预设数据失败") {
PTConfigService.getEnterpriseInfo(call.requireCurrentUser()) PTConfigService.getEnterpriseInfo(call.requireCurrentUser())
@@ -115,13 +127,17 @@ fun Route.registerPTAuthRoutes() {
post("/send-sms-code") { post("/send-sms-code") {
call.respondPt("发送登录短信验证码失败") { call.respondPt("发送登录短信验证码失败") {
PTAuthService.sendLoginSmsCode(call.receive<GetLoginSmsCodeRequest>()) val req = call.receive<GetLoginSmsCodeRequest>()
PTConfigService.requireDigitalAccountForLogin(call.requireCurrentUser(), req.taxpayerNum, req.account)
PTAuthService.sendLoginSmsCode(req)
} }
} }
post("/sms-login") { post("/sms-login") {
call.respondPt("短信验证码登录失败") { call.respondPt("短信验证码登录失败") {
PTAuthService.smsLogin(call.receive<SmsLoginRequest>()) val req = call.receive<SmsLoginRequest>()
PTConfigService.requireDigitalAccountForLogin(call.requireCurrentUser(), req.taxpayerNum, req.account)
PTAuthService.smsLogin(req)
} }
} }
} }
@@ -4,6 +4,7 @@ package com.bbit.ticket.route.piaotong
import com.bbit.ticket.entity.common.BizException import com.bbit.ticket.entity.common.BizException
import com.bbit.ticket.entity.request.AskInvoiceRequest import com.bbit.ticket.entity.request.AskInvoiceRequest
import com.bbit.ticket.entity.request.InvoiceQuerySubmitRequest
import com.bbit.ticket.entity.request.QueryInvoiceRequest import com.bbit.ticket.entity.request.QueryInvoiceRequest
import com.bbit.ticket.entity.request.RedCreateRequest import com.bbit.ticket.entity.request.RedCreateRequest
import com.bbit.ticket.service.piaotong.PTBlueService import com.bbit.ticket.service.piaotong.PTBlueService
@@ -57,7 +58,7 @@ fun Route.registerPTInvoiceRoutes() {
val invoiceReqSerialNo = call.requiredQueryParameter("invoiceReqSerialNo", "请传入发票请求流水号") val invoiceReqSerialNo = call.requiredQueryParameter("invoiceReqSerialNo", "请传入发票请求流水号")
?: return@get ?: return@get
call.respondPt("查询发票详情失败") { call.respondPt("查询发票详情失败") {
PTBlueService.getInvoiceDetail(call.requireCurrentUser().id, invoiceReqSerialNo) PTBlueService.getInvoiceDetail(call.requireCurrentUser(), invoiceReqSerialNo)
?: throw BizException("-1", "未找到该发票记录") ?: throw BizException("-1", "未找到该发票记录")
} }
} }
@@ -66,7 +67,7 @@ fun Route.registerPTInvoiceRoutes() {
val invoiceReqSerialNo = call.requiredQueryParameter("invoiceReqSerialNo", "请传入发票请求流水号") val invoiceReqSerialNo = call.requiredQueryParameter("invoiceReqSerialNo", "请传入发票请求流水号")
?: return@get ?: return@get
call.respondPt("查询发票下载地址失败") { call.respondPt("查询发票下载地址失败") {
PTBlueService.getInvoiceDownloadUrl(call.requireCurrentUser().id, invoiceReqSerialNo) PTBlueService.getInvoiceDownloadUrl(call.requireCurrentUser(), invoiceReqSerialNo)
?: throw BizException("-1", "未找到该发票记录") ?: throw BizException("-1", "未找到该发票记录")
} }
} }
@@ -75,7 +76,7 @@ fun Route.registerPTInvoiceRoutes() {
val invoiceReqSerialNo = call.requiredQueryParameter("invoiceReqSerialNo", "请传入发票请求流水号") val invoiceReqSerialNo = call.requiredQueryParameter("invoiceReqSerialNo", "请传入发票请求流水号")
?: return@get ?: return@get
call.respondPtPdf("预览票样失败", "$invoiceReqSerialNo.pdf") { call.respondPtPdf("预览票样失败", "$invoiceReqSerialNo.pdf") {
PTBlueService.getInvoicePreview(call.requireCurrentUser().id, invoiceReqSerialNo) PTBlueService.getInvoicePreview(call.requireCurrentUser(), invoiceReqSerialNo)
?: throw BizException("-1", "未找到票样地址") ?: throw BizException("-1", "未找到票样地址")
} }
} }
@@ -84,7 +85,7 @@ fun Route.registerPTInvoiceRoutes() {
val invoiceReqSerialNo = call.requiredQueryParameter("invoiceReqSerialNo", "请传入发票请求流水号") val invoiceReqSerialNo = call.requiredQueryParameter("invoiceReqSerialNo", "请传入发票请求流水号")
?: return@get ?: return@get
call.respondPt("查询红票下载地址失败") { call.respondPt("查询红票下载地址失败") {
PTRedService.getRedInvoiceDownloadUrl(call.requireCurrentUser().id, invoiceReqSerialNo) PTRedService.getRedInvoiceDownloadUrl(call.requireCurrentUser(), invoiceReqSerialNo)
?: throw BizException("-1", "未找到该红票记录") ?: throw BizException("-1", "未找到该红票记录")
} }
} }
@@ -93,7 +94,7 @@ fun Route.registerPTInvoiceRoutes() {
val invoiceReqSerialNo = call.requiredQueryParameter("invoiceReqSerialNo", "请传入发票请求流水号") val invoiceReqSerialNo = call.requiredQueryParameter("invoiceReqSerialNo", "请传入发票请求流水号")
?: return@get ?: return@get
call.respondPtPdf("预览红票票样失败", "$invoiceReqSerialNo.pdf") { call.respondPtPdf("预览红票票样失败", "$invoiceReqSerialNo.pdf") {
PTRedService.getRedInvoicePreview(call.requireCurrentUser().id, invoiceReqSerialNo) PTRedService.getRedInvoicePreview(call.requireCurrentUser(), invoiceReqSerialNo)
?: throw BizException("-1", "未找到票样地址") ?: throw BizException("-1", "未找到票样地址")
} }
} }
@@ -120,7 +121,7 @@ fun Route.registerPTInvoiceRoutes() {
val invoiceReqSerialNo = call.requiredQueryParameter("invoiceReqSerialNo", "请传入发票请求流水号") val invoiceReqSerialNo = call.requiredQueryParameter("invoiceReqSerialNo", "请传入发票请求流水号")
?: return@get ?: return@get
call.respondPt("查询红票申请信息失败") { call.respondPt("查询红票申请信息失败") {
PTRedService.getRedInvoiceInfo(call.requireCurrentUser().id, invoiceReqSerialNo) PTRedService.getRedInvoiceInfo(call.requireCurrentUser(), invoiceReqSerialNo)
?: throw BizException("-1", "未找到红票申请信息") ?: throw BizException("-1", "未找到红票申请信息")
} }
} }
@@ -13,86 +13,45 @@ import com.bbit.ticket.entity.request.TaxBureauAuthReq
import com.bbit.ticket.entity.response.AuthQrcodeResponse import com.bbit.ticket.entity.response.AuthQrcodeResponse
import com.bbit.ticket.entity.response.AuthQrcodeStatusResponse import com.bbit.ticket.entity.response.AuthQrcodeStatusResponse
import com.bbit.ticket.entity.response.DigitalAccountInfo import com.bbit.ticket.entity.response.DigitalAccountInfo
import com.bbit.ticket.entity.response.EnterpriseTaxInfo
import com.bbit.ticket.entity.response.LoginSmsCodeResponse import com.bbit.ticket.entity.response.LoginSmsCodeResponse
import com.bbit.ticket.entity.response.QueryEnterpriseBankAccountResponse import com.bbit.ticket.entity.response.QueryEnterpriseBankAccountResponse
import com.bbit.ticket.entity.response.QueryEnterpriseInfoResponse import com.bbit.ticket.entity.response.QueryEnterpriseInfoResponse
import com.bbit.ticket.entity.response.SMSLoginResponse import com.bbit.ticket.entity.response.SMSLoginResponse
import com.bbit.ticket.entity.response.TaxBureauAccountAuthContent import com.bbit.ticket.entity.response.TaxBureauAccountAuthContent
import com.bbit.ticket.utils.net.PTClient import com.bbit.ticket.utils.net.PTApi
object PTAuthService { object PTAuthService {
/**
* 查询数电账号认证状态 2.8
*/
suspend fun getTaxBureauAccountAuthStatus(req: TaxBureauAuthReq): TaxBureauAccountAuthContent { suspend fun getTaxBureauAccountAuthStatus(req: TaxBureauAuthReq): TaxBureauAccountAuthContent {
val res = return PTApi.getTaxBureauAccountAuthStatus(req)
PTClient.ptPost<TaxBureauAuthReq, TaxBureauAccountAuthContent>("getTaxBureauAccountAuthStatus.pt", req)
return res
} }
/**
* 获取实名认证二维码 2.6
*
* @return AuthQrcodeResponse 包含 base64 二维码图片
*/
suspend fun getAuthenticationQrcode(req: AuthQrcodeRequest): AuthQrcodeResponse { suspend fun getAuthenticationQrcode(req: AuthQrcodeRequest): AuthQrcodeResponse {
return PTClient.ptPost<AuthQrcodeRequest, AuthQrcodeResponse>("getAuthenticationQrcode.pt", req) return PTApi.getAuthenticationQrcode(req)
} }
/**
* 查询认证二维码扫码状态 2.7
*
* @return AuthQrcodeStatusResponse 包含 scanStatus、msg
*/
suspend fun queryAuthQrcodeScanStatus(req: QueryRealNameAuthQrStatusRequest): AuthQrcodeStatusResponse { suspend fun queryAuthQrcodeScanStatus(req: QueryRealNameAuthQrStatusRequest): AuthQrcodeStatusResponse {
return PTClient.ptPost<QueryRealNameAuthQrStatusRequest, AuthQrcodeStatusResponse>( return PTApi.queryAuthQrcodeScanStatus(req)
"queryAuthQrcodeScanStatus.pt",
req
)
} }
/**
* 发送登录短信验证码 2.4
*
* @return LoginSmsCodeResponse 包含脱敏手机号、有效时长
*/
suspend fun sendLoginSmsCode(req: GetLoginSmsCodeRequest): LoginSmsCodeResponse { suspend fun sendLoginSmsCode(req: GetLoginSmsCodeRequest): LoginSmsCodeResponse {
return PTClient.ptPost<GetLoginSmsCodeRequest, LoginSmsCodeResponse>("sendLoginSmsCode.pt", req) return PTApi.sendLoginSmsCode(req)
} }
/**
* 短信验证码登录 2.5
*
* @return SMSLoginResponse 包含登录结果
*/
suspend fun smsLogin(req: SmsLoginRequest): SMSLoginResponse { suspend fun smsLogin(req: SmsLoginRequest): SMSLoginResponse {
return PTClient.ptPost<SmsLoginRequest, SMSLoginResponse>("smsLogin.pt", req) return PTApi.smsLogin(req)
} }
/**
* 查询企业状态 2.47
*/
suspend fun getEnterpriseInfo(req: QueryEnterpriseInfoRequest): QueryEnterpriseInfoResponse { suspend fun getEnterpriseInfo(req: QueryEnterpriseInfoRequest): QueryEnterpriseInfoResponse {
return PTClient.ptPost<QueryEnterpriseInfoRequest, QueryEnterpriseInfoResponse>("getEnterpriseInfo.pt", req) return PTApi.getEnterpriseInfo(req)
} }
/**
* 查询数电账号列表 2.45
*/
suspend fun getListTaxBureauAccount(req: QueryDigitalAccountListRequest): List<DigitalAccountInfo> { suspend fun getListTaxBureauAccount(req: QueryDigitalAccountListRequest): List<DigitalAccountInfo> {
return PTClient.ptPost<QueryDigitalAccountListRequest, List<DigitalAccountInfo>>( return PTApi.listTaxBureauAccount(req)
"listTaxBureauAccount.pt",
req
)
} }
/**
* 查询企业开户行及账号 2.49
*/
suspend fun queryEnterpriseBankInfo(req: QueryEnterpriseBankAccountRequest): QueryEnterpriseBankAccountResponse { suspend fun queryEnterpriseBankInfo(req: QueryEnterpriseBankAccountRequest): QueryEnterpriseBankAccountResponse {
return PTClient.ptPost<QueryEnterpriseBankAccountRequest, QueryEnterpriseBankAccountResponse>("queryEnterpriseBankInfo.pt", req) return PTApi.queryEnterpriseBankInfo(req)
} }
} }
@@ -5,17 +5,19 @@ package com.bbit.ticket.service.piaotong
import com.bbit.ticket.dao.piaotong.BlueInvoiceDao import com.bbit.ticket.dao.piaotong.BlueInvoiceDao
import com.bbit.ticket.entity.common.PageResult import com.bbit.ticket.entity.common.PageResult
import com.bbit.ticket.entity.request.AskInvoiceRequest import com.bbit.ticket.entity.request.AskInvoiceRequest
import com.bbit.ticket.entity.request.InvoiceQueryRequest
import com.bbit.ticket.entity.request.InvoiceQuerySubmitRequest
import com.bbit.ticket.entity.request.QueryInvoiceRequest import com.bbit.ticket.entity.request.QueryInvoiceRequest
import com.bbit.ticket.entity.response.GetInvoiceInfoResponse
import com.bbit.ticket.entity.response.InvoiceDownloadUrlResponse import com.bbit.ticket.entity.response.InvoiceDownloadUrlResponse
import com.bbit.ticket.entity.response.QueryInvoiceResult import com.bbit.ticket.entity.response.QueryInvoiceResult
import com.bbit.ticket.entity.response.InvoiceCreateResponse
import com.bbit.ticket.entity.response.InvoiceDetailResponse import com.bbit.ticket.entity.response.InvoiceDetailResponse
import com.bbit.ticket.entity.response.InvoiceHistoryItem import com.bbit.ticket.entity.response.InvoiceHistoryItem
import com.bbit.ticket.entity.response.InvoiceQueryResponse
import com.bbit.ticket.utils.CurrentUser import com.bbit.ticket.utils.CurrentUser
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsBytes import io.ktor.client.statement.bodyAsBytes
import com.bbit.ticket.utils.plugins.dbQuery import com.bbit.ticket.utils.plugins.dbQuery
import com.bbit.ticket.utils.net.PTApi
import com.bbit.ticket.utils.net.PTClient import com.bbit.ticket.utils.net.PTClient
import com.bbit.ticket.utils.parseUuid import com.bbit.ticket.utils.parseUuid
import kotlin.uuid.Uuid import kotlin.uuid.Uuid
@@ -23,7 +25,13 @@ import kotlin.uuid.Uuid
object PTBlueService { object PTBlueService {
suspend fun listBatchNos(user: CurrentUser): List<String> = suspend fun listBatchNos(user: CurrentUser): List<String> =
dbQuery { BlueInvoiceDao.listBatchNos(user.id) } dbQuery {
BlueInvoiceDao.listBatchNos(
user.id,
user.enterpriseId,
if (user.isDigitalOperator) user.digitalAccountId else null,
)
}
/** /**
* 查询票通同步发票信息(支持插入和更新) * 查询票通同步发票信息(支持插入和更新)
@@ -38,8 +46,7 @@ object PTBlueService {
enterpriseId: Uuid? = null, enterpriseId: Uuid? = null,
digitalAccountId: Uuid? = null, digitalAccountId: Uuid? = null,
): QueryInvoiceResult { ): QueryInvoiceResult {
val res = PTClient.ptPost<QueryInvoiceRequest, GetInvoiceInfoResponse>( val res = PTApi.queryInvoiceInfo(
"queryInvoiceInfo.pt",
QueryInvoiceRequest(taxpayerNum = taxpayerNum, invoiceReqSerialNo = invoiceReqSerialNo) QueryInvoiceRequest(taxpayerNum = taxpayerNum, invoiceReqSerialNo = invoiceReqSerialNo)
) )
dbQuery { BlueInvoiceDao.upsertInvoiceInfo(userId, res, enterpriseId, digitalAccountId) } dbQuery { BlueInvoiceDao.upsertInvoiceInfo(userId, res, enterpriseId, digitalAccountId) }
@@ -77,7 +84,7 @@ object PTBlueService {
): String { ): String {
dbQuery { BlueInvoiceDao.addInvoice(userId, req, enterpriseId, digitalAccountId) } dbQuery { BlueInvoiceDao.addInvoice(userId, req, enterpriseId, digitalAccountId) }
try { try {
PTClient.ptPost<AskInvoiceRequest, InvoiceCreateResponse>("invoiceBlue.pt", req) PTApi.invoiceBlue(req)
} catch (e: Exception) { } catch (e: Exception) {
dbQuery { BlueInvoiceDao.markInvoiceFailed(userId, req.invoiceReqSerialNo, e.message) } dbQuery { BlueInvoiceDao.markInvoiceFailed(userId, req.invoiceReqSerialNo, e.message) }
throw e throw e
@@ -89,9 +96,6 @@ object PTBlueService {
return "操作成功" return "操作成功"
} }
/**
* 分页查询开票历史(支持筛选)
*/
suspend fun getInvoiceBlueHistory( suspend fun getInvoiceBlueHistory(
user: CurrentUser, user: CurrentUser,
page: Int, page: Int,
@@ -117,10 +121,30 @@ object PTBlueService {
* 查询发票完整详情 * 查询发票完整详情
*/ */
suspend fun getInvoiceDetail(userId: Uuid, invoiceReqSerialNo: String): InvoiceDetailResponse? = suspend fun getInvoiceDetail(userId: Uuid, invoiceReqSerialNo: String): InvoiceDetailResponse? =
dbQuery { BlueInvoiceDao.invoiceDetail(userId, invoiceReqSerialNo) } dbQuery { BlueInvoiceDao.invoiceDetail(userId, null, null, invoiceReqSerialNo) }
suspend fun getInvoiceDetail(user: CurrentUser, invoiceReqSerialNo: String): InvoiceDetailResponse? =
dbQuery {
BlueInvoiceDao.invoiceDetail(
user.id,
user.enterpriseId,
if (user.isDigitalOperator) user.digitalAccountId else null,
invoiceReqSerialNo,
)
}
suspend fun getInvoiceDownloadUrl(userId: Uuid, invoiceReqSerialNo: String): InvoiceDownloadUrlResponse? = suspend fun getInvoiceDownloadUrl(userId: Uuid, invoiceReqSerialNo: String): InvoiceDownloadUrlResponse? =
dbQuery { BlueInvoiceDao.invoiceDownloadUrl(userId, invoiceReqSerialNo) } dbQuery { BlueInvoiceDao.invoiceDownloadUrl(userId, null, null, invoiceReqSerialNo) }
suspend fun getInvoiceDownloadUrl(user: CurrentUser, invoiceReqSerialNo: String): InvoiceDownloadUrlResponse? =
dbQuery {
BlueInvoiceDao.invoiceDownloadUrl(
user.id,
user.enterpriseId,
if (user.isDigitalOperator) user.digitalAccountId else null,
invoiceReqSerialNo,
)
}
suspend fun getInvoicePreview(userId: Uuid, invoiceReqSerialNo: String): ByteArray? { suspend fun getInvoicePreview(userId: Uuid, invoiceReqSerialNo: String): ByteArray? {
val downloadUrl = getInvoiceDownloadUrl(userId, invoiceReqSerialNo)?.downloadUrl val downloadUrl = getInvoiceDownloadUrl(userId, invoiceReqSerialNo)?.downloadUrl
@@ -129,6 +153,13 @@ object PTBlueService {
return PTClient.client.get(downloadUrl).bodyAsBytes() return PTClient.client.get(downloadUrl).bodyAsBytes()
} }
suspend fun getInvoicePreview(user: CurrentUser, invoiceReqSerialNo: String): ByteArray? {
val downloadUrl = getInvoiceDownloadUrl(user, invoiceReqSerialNo)?.downloadUrl
?.takeIf { it.isNotBlank() }
?: return null
return PTClient.client.get(downloadUrl).bodyAsBytes()
}
/** /**
* 查询并更新发票状态(复用 syncInvoiceFromPT * 查询并更新发票状态(复用 syncInvoiceFromPT
*/ */
@@ -11,12 +11,12 @@ import com.bbit.ticket.entity.request.QueryDigitalAccountListRequest
import com.bbit.ticket.entity.request.QueryEnterpriseBankAccountRequest import com.bbit.ticket.entity.request.QueryEnterpriseBankAccountRequest
import com.bbit.ticket.entity.request.QueryEnterpriseInfoRequest import com.bbit.ticket.entity.request.QueryEnterpriseInfoRequest
import com.bbit.ticket.entity.request.TaxRegister import com.bbit.ticket.entity.request.TaxRegister
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.entity.response.BankInfo import com.bbit.ticket.entity.response.BankInfo
import com.bbit.ticket.entity.response.DigitalAccountInfo import com.bbit.ticket.entity.response.DigitalAccountInfo
import com.bbit.ticket.entity.response.DigitalAccountManageItem import com.bbit.ticket.entity.response.DigitalAccountManageItem
import com.bbit.ticket.entity.response.EnterpriseManageResponse import com.bbit.ticket.entity.response.EnterpriseManageResponse
import com.bbit.ticket.entity.response.EtaxRegisterResponse
import com.bbit.ticket.entity.response.OpenApiStatisticsItem import com.bbit.ticket.entity.response.OpenApiStatisticsItem
import com.bbit.ticket.utils.plugins.dbQuery import com.bbit.ticket.utils.plugins.dbQuery
import com.bbit.ticket.entity.common.BizException import com.bbit.ticket.entity.common.BizException
@@ -24,7 +24,7 @@ import com.bbit.ticket.entity.common.ErrorCode
import com.bbit.ticket.service.system.PasswordService import com.bbit.ticket.service.system.PasswordService
import com.bbit.ticket.utils.ApiKeyUtil import com.bbit.ticket.utils.ApiKeyUtil
import com.bbit.ticket.utils.CurrentUser import com.bbit.ticket.utils.CurrentUser
import com.bbit.ticket.utils.net.PTClient import com.bbit.ticket.utils.net.PTApi
import com.bbit.ticket.utils.parseUuid import com.bbit.ticket.utils.parseUuid
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import kotlin.uuid.Uuid import kotlin.uuid.Uuid
@@ -109,8 +109,7 @@ object PTConfigService {
?: throw BizException(ErrorCode.BAD_REQUEST.code, "企业信息不存在", HttpStatusCode.BadRequest) ?: throw BizException(ErrorCode.BAD_REQUEST.code, "企业信息不存在", HttpStatusCode.BadRequest)
} }
PTClient.ptPost<TaxRegister, EtaxRegisterResponse>( PTApi.registerDigitalAccount(
"registerUser.pt",
TaxRegister( TaxRegister(
taxpayerNum = enterprise.taxpayerNum, taxpayerNum = enterprise.taxpayerNum,
account = req.account, account = req.account,
@@ -147,6 +146,28 @@ object PTConfigService {
} }
} }
suspend fun updateDigitalAccountStatus(
user: CurrentUser,
digitalAccountId: String,
req: UpdateDigitalAccountStatusRequest,
): String = dbQuery {
if (!user.isSuperAdmin && !user.isEnterpriseAdmin) {
throw BizException(ErrorCode.FORBIDDEN.code, "无权操作数电账号", HttpStatusCode.Forbidden)
}
val status = req.status.trim().uppercase()
if (status !in setOf("ENABLED", "DISABLED")) {
throw BizException(ErrorCode.BAD_REQUEST.code, "账号状态必须是 ENABLED 或 DISABLED")
}
val targetId = parseUuid(digitalAccountId, "digitalAccountId")
val row = EnterpriseManageDao.digitalAccount(targetId)
?: throw BizException(ErrorCode.BAD_REQUEST.code, "数电账号不存在", HttpStatusCode.NotFound)
if (row[PtDigitalAccountTable.enterpriseId] != requireEnterpriseId(user)) {
throw BizException(ErrorCode.FORBIDDEN.code, "无权操作该数电账号", HttpStatusCode.Forbidden)
}
EnterpriseManageDao.updateDigitalAccountStatus(targetId, status)
if (status == "ENABLED") "数电账号已恢复" else "数电账号已禁用"
}
private fun ensureDigitalOperatorAccount( private fun ensureDigitalOperatorAccount(
enterpriseId: Uuid, enterpriseId: Uuid,
taxpayerNum: String, taxpayerNum: String,
@@ -220,6 +241,22 @@ object PTConfigService {
if (!user.isSuperAdmin && row[PtDigitalAccountTable.enterpriseId] != requireEnterpriseId(user)) { if (!user.isSuperAdmin && row[PtDigitalAccountTable.enterpriseId] != requireEnterpriseId(user)) {
throw BizException(ErrorCode.FORBIDDEN.code, "无权操作该数电账号", HttpStatusCode.Forbidden) throw BizException(ErrorCode.FORBIDDEN.code, "无权操作该数电账号", HttpStatusCode.Forbidden)
} }
if (row[PtDigitalAccountTable.status] != "ENABLED") {
throw BizException(ErrorCode.FORBIDDEN.code, "数电账号已禁用", HttpStatusCode.Forbidden)
}
EnterpriseManageDao.run { row.toDigitalAccountItem() }
}
suspend fun requireDigitalAccountForLogin(user: CurrentUser, taxpayerNum: String, account: String): DigitalAccountManageItem =
dbQuery {
val row = EnterpriseManageDao.digitalAccountByTaxpayerAndAccount(taxpayerNum.trim(), account.trim())
?: throw BizException(ErrorCode.BAD_REQUEST.code, "数电账号不存在", HttpStatusCode.NotFound)
if (!user.isSuperAdmin && row[PtDigitalAccountTable.enterpriseId] != requireEnterpriseId(user)) {
throw BizException(ErrorCode.FORBIDDEN.code, "无权操作该数电账号", HttpStatusCode.Forbidden)
}
if (row[PtDigitalAccountTable.status] != "ENABLED") {
throw BizException(ErrorCode.FORBIDDEN.code, "数电账号已禁用", HttpStatusCode.Forbidden)
}
EnterpriseManageDao.run { row.toDigitalAccountItem() } EnterpriseManageDao.run { row.toDigitalAccountItem() }
} }
} }
@@ -7,10 +7,10 @@ import com.bbit.ticket.dao.piaotong.RedInvoiceDao
import com.bbit.ticket.entity.request.QuickRedInvoiceRequest import com.bbit.ticket.entity.request.QuickRedInvoiceRequest
import com.bbit.ticket.entity.request.RedCreateRequest import com.bbit.ticket.entity.request.RedCreateRequest
import com.bbit.ticket.entity.response.InvoiceDownloadUrlResponse import com.bbit.ticket.entity.response.InvoiceDownloadUrlResponse
import com.bbit.ticket.entity.response.QuickRedInvoiceResponse
import com.bbit.ticket.entity.response.RedInvoiceInfoResponse import com.bbit.ticket.entity.response.RedInvoiceInfoResponse
import com.bbit.ticket.utils.CurrentUser import com.bbit.ticket.utils.CurrentUser
import com.bbit.ticket.utils.plugins.dbQuery import com.bbit.ticket.utils.plugins.dbQuery
import com.bbit.ticket.utils.net.PTApi
import com.bbit.ticket.utils.net.PTClient import com.bbit.ticket.utils.net.PTClient
import com.bbit.ticket.utils.parseUuid import com.bbit.ticket.utils.parseUuid
import io.ktor.client.request.get import io.ktor.client.request.get
@@ -25,14 +25,19 @@ import kotlin.uuid.Uuid
object PTRedService { object PTRedService {
/** /**
* 红票接口调用 * 红票接口调用 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, null)
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 {
HistoryDao.findByHistory(historyId, user.id) HistoryDao.findByHistory(
historyId,
user.id,
user.enterpriseId,
if (user.isDigitalOperator) user.digitalAccountId else null,
)
} }
val req = QuickRedInvoiceRequest( val req = QuickRedInvoiceRequest(
taxpayerNum = account.taxpayerNum, taxpayerNum = account.taxpayerNum,
@@ -49,26 +54,50 @@ object PTRedService {
takerTel = req.takerTel, takerTel = req.takerTel,
takerEmail = req.takerEmail, takerEmail = req.takerEmail,
) )
PTClient.ptPost<QuickRedInvoiceRequest, QuickRedInvoiceResponse>("invoiceRed.pt", req) PTApi.invoiceRed(req)
dbQuery { RedInvoiceDao.addRedInvoice(user.id, historyId, req) } dbQuery { RedInvoiceDao.addRedInvoice(user.id, historyId, req) }
val enterpriseId = parseUuid(account.enterpriseId, "enterpriseId") val enterpriseId = parseUuid(account.enterpriseId, "enterpriseId")
val digitalAccountId = parseUuid(account.id, "digitalAccountId") val digitalAccountId = parseUuid(account.id, "digitalAccountId")
PTBlueService.syncInvoiceFromPT(user.id, his.invoiceReqSerialNo, account.taxpayerNum, enterpriseId, digitalAccountId) PTBlueService.syncInvoiceFromPT(
PTBlueService.syncInvoiceFromPT(user.id, invoiceReqSerialNo, account.taxpayerNum, enterpriseId, digitalAccountId) user.id,
his.invoiceReqSerialNo,
account.taxpayerNum,
enterpriseId,
digitalAccountId
)
PTBlueService.syncInvoiceFromPT(
user.id,
invoiceReqSerialNo,
account.taxpayerNum,
enterpriseId,
digitalAccountId
)
return "操作成功" return "操作成功"
} }
/** /**
* 查询红票申请信息 * 查询红票申请信息
*/ */
suspend fun getRedInvoiceInfo(userId: Uuid, invoiceReqSerialNo: String): RedInvoiceInfoResponse? = suspend fun getRedInvoiceInfo(user: CurrentUser, invoiceReqSerialNo: String): RedInvoiceInfoResponse? =
dbQuery { RedInvoiceDao.findRedInfoBySerialNo(userId, invoiceReqSerialNo) } dbQuery {
RedInvoiceDao.findRedInfoBySerialNo(
user.id, user.enterpriseId, if (user.isDigitalOperator) user.digitalAccountId else null,
invoiceReqSerialNo,
)
}
suspend fun getRedInvoiceDownloadUrl(userId: Uuid, invoiceReqSerialNo: String): InvoiceDownloadUrlResponse? = suspend fun getRedInvoiceDownloadUrl(user: CurrentUser, invoiceReqSerialNo: String): InvoiceDownloadUrlResponse? =
dbQuery { RedInvoiceDao.invoiceDownloadUrl(userId, invoiceReqSerialNo) } dbQuery {
RedInvoiceDao.invoiceDownloadUrl(
user.id,
user.enterpriseId,
if (user.isDigitalOperator) user.digitalAccountId else null,
invoiceReqSerialNo,
)
}
suspend fun getRedInvoicePreview(userId: Uuid, invoiceReqSerialNo: String): ByteArray? { suspend fun getRedInvoicePreview(user: CurrentUser, invoiceReqSerialNo: String): ByteArray? {
val downloadUrl = getRedInvoiceDownloadUrl(userId, invoiceReqSerialNo)?.downloadUrl val downloadUrl = getRedInvoiceDownloadUrl(user, invoiceReqSerialNo)?.downloadUrl
?.takeIf { it.isNotBlank() } ?.takeIf { it.isNotBlank() }
?: return null ?: return null
return PTClient.client.get(downloadUrl).bodyAsBytes() return PTClient.client.get(downloadUrl).bodyAsBytes()
@@ -13,12 +13,10 @@ import com.bbit.ticket.entity.common.system.LoginRequest
import com.bbit.ticket.entity.common.system.LoginResponse import com.bbit.ticket.entity.common.system.LoginResponse
import com.bbit.ticket.entity.common.system.MeResponse import com.bbit.ticket.entity.common.system.MeResponse
import com.bbit.ticket.entity.request.EnterpriseRegisterRequest import com.bbit.ticket.entity.request.EnterpriseRegisterRequest
import com.bbit.ticket.entity.request.TaxRegisterInfo
import com.bbit.ticket.entity.request.toTaxRegisterInfo import com.bbit.ticket.entity.request.toTaxRegisterInfo
import com.bbit.ticket.entity.response.EnterpriseTaxInfo
import com.bbit.ticket.utils.plugins.dbQuery import com.bbit.ticket.utils.plugins.dbQuery
import com.bbit.ticket.utils.CurrentUser import com.bbit.ticket.utils.CurrentUser
import com.bbit.ticket.utils.net.PTClient import com.bbit.ticket.utils.net.PTApi
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.ExperimentalUuidApi
@@ -92,7 +90,7 @@ object AuthService {
throw BizException(ErrorCode.DATA_CONFLICT.code, "企业已存在", HttpStatusCode.Conflict) throw BizException(ErrorCode.DATA_CONFLICT.code, "企业已存在", HttpStatusCode.Conflict)
} }
} }
PTClient.ptPost<TaxRegisterInfo, EnterpriseTaxInfo>("register.pt", registerInfo) PTApi.registerEnterprise(registerInfo)
dbQuery { dbQuery {
val enterpriseId = EnterpriseManageDao.createEnterprise(registerInfo) val enterpriseId = EnterpriseManageDao.createEnterprise(registerInfo)
@@ -2,7 +2,7 @@ package com.bbit.ticket.utils.bootstrap
object Global { object Global {
val isDev = false val isDev = true
// 请求基础地址 // 请求基础地址
var baseUrl: String var baseUrl: String
@@ -2,9 +2,14 @@
package com.bbit.ticket.utils.bootstrap package com.bbit.ticket.utils.bootstrap
import com.bbit.ticket.dao.piaotong.EnterpriseManageDao
import com.bbit.ticket.database.piaotong.PtEnterpriseTable
import com.bbit.ticket.database.system.* import com.bbit.ticket.database.system.*
import com.bbit.ticket.utils.plugins.dbQuery import com.bbit.ticket.entity.request.QueryEnterpriseInfoRequest
import com.bbit.ticket.entity.request.TaxRegisterInfo
import com.bbit.ticket.service.system.PasswordService import com.bbit.ticket.service.system.PasswordService
import com.bbit.ticket.utils.net.PTApi
import com.bbit.ticket.utils.plugins.dbQuery
import org.jetbrains.exposed.v1.core.and import org.jetbrains.exposed.v1.core.and
import org.jetbrains.exposed.v1.core.eq import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.inList import org.jetbrains.exposed.v1.core.inList
@@ -26,6 +31,7 @@ object SeedData {
const val ADMIN_INIT_PASSWORD = "Admin@123456" const val ADMIN_INIT_PASSWORD = "Admin@123456"
private const val DEFAULT_ORG_CODE = "DEFAULT_ORG" private const val DEFAULT_ORG_CODE = "DEFAULT_ORG"
private const val DEFAULT_ADMIN_TAXPAYER_NUM = "500102201007206608"
private const val SUPER_ADMIN_ROLE_CODE = "SUPER_ADMIN" private const val SUPER_ADMIN_ROLE_CODE = "SUPER_ADMIN"
private const val ENTERPRISE_ADMIN_ROLE_CODE = "ENTERPRISE_ADMIN" private const val ENTERPRISE_ADMIN_ROLE_CODE = "ENTERPRISE_ADMIN"
private const val DIGITAL_OPERATOR_ROLE_CODE = "DIGITAL_OPERATOR" private const val DIGITAL_OPERATOR_ROLE_CODE = "DIGITAL_OPERATOR"
@@ -38,6 +44,7 @@ object SeedData {
val now = OffsetDateTime.now() val now = OffsetDateTime.now()
val orgId = upsertDefaultOrg(now) val orgId = upsertDefaultOrg(now)
val roleId = upsertSuperAdminRole(now) val roleId = upsertSuperAdminRole(now)
val defaultEnterpriseId = ensureDefaultAdminEnterprise()
val enterpriseAdminRoleId = upsertBusinessRole( val enterpriseAdminRoleId = upsertBusinessRole(
code = ENTERPRISE_ADMIN_ROLE_CODE, code = ENTERPRISE_ADMIN_ROLE_CODE,
name = "企业管理员", name = "企业管理员",
@@ -50,9 +57,10 @@ object SeedData {
description = "数电账号对应的平台开票员角色", description = "数电账号对应的平台开票员角色",
now = now, now = now,
) )
val adminId = upsertAdminUser(orgId, now) val adminId = upsertAdminUser(orgId, defaultEnterpriseId, now)
upsertUserRole(adminId, roleId) upsertUserRole(adminId, roleId)
val menuIds = upsertMenus(now) val menuIds = upsertMenus(now)
disableObsoleteMenus(now)
bindRoleMenus(roleId, menuIds.values.toList()) bindRoleMenus(roleId, menuIds.values.toList())
bindRoleMenus(enterpriseAdminRoleId, enterpriseAdminMenuIds(menuIds)) bindRoleMenus(enterpriseAdminRoleId, enterpriseAdminMenuIds(menuIds))
bindRoleMenus(digitalOperatorRoleId, digitalOperatorMenuIds(menuIds)) bindRoleMenus(digitalOperatorRoleId, digitalOperatorMenuIds(menuIds))
@@ -156,7 +164,40 @@ object SeedData {
// Admin user // Admin user
// ========================================================= // =========================================================
private suspend fun upsertAdminUser(orgId: Uuid, now: OffsetDateTime): Uuid = dbQuery { private suspend fun ensureDefaultAdminEnterprise(): Uuid {
val existingId = dbQuery {
EnterpriseManageDao.findEnterpriseByTaxpayerNum(DEFAULT_ADMIN_TAXPAYER_NUM)?.get(PtEnterpriseTable.id)
}
val ptEnterprise = PTApi.getEnterpriseInfo(QueryEnterpriseInfoRequest(DEFAULT_ADMIN_TAXPAYER_NUM))
if (existingId != null) {
dbQuery { EnterpriseManageDao.updateEnterpriseFromPt(existingId, ptEnterprise) }
return existingId
}
val enterpriseInfo = TaxRegisterInfo(
taxpayerNum = DEFAULT_ADMIN_TAXPAYER_NUM,
enterpriseName = ptEnterprise.enterpriseName.ifBlank { DEFAULT_ADMIN_TAXPAYER_NUM },
legalPersonName = null,
contactsName = null,
contactsEmail = null,
contactsPhone = null,
regionCode = ptEnterprise.regionCode,
cityName = ptEnterprise.cityName,
enterpriseAddress = ptEnterprise.enterpriseAddress,
taxRegistrationCertificate = null,
)
return dbQuery {
val enterpriseId = EnterpriseManageDao.findEnterpriseByTaxpayerNum(DEFAULT_ADMIN_TAXPAYER_NUM)
?.get(PtEnterpriseTable.id)
?: EnterpriseManageDao.createEnterprise(enterpriseInfo)
EnterpriseManageDao.updateEnterpriseFromPt(enterpriseId, ptEnterprise)
enterpriseId
}
}
private suspend fun upsertAdminUser(orgId: Uuid, enterpriseId: Uuid, now: OffsetDateTime): Uuid = dbQuery {
val existing = SysUserTable.selectAll() val existing = SysUserTable.selectAll()
.where { (SysUserTable.username eq ADMIN_USERNAME) and SysUserTable.deletedAt.isNull() } .where { (SysUserTable.username eq ADMIN_USERNAME) and SysUserTable.deletedAt.isNull() }
.singleOrNull() .singleOrNull()
@@ -166,6 +207,7 @@ object SeedData {
SysUserTable.update({ SysUserTable.id eq id }) { SysUserTable.update({ SysUserTable.id eq id }) {
it[SysUserTable.nickname] = "系统管理员" it[SysUserTable.nickname] = "系统管理员"
it[SysUserTable.orgId] = orgId it[SysUserTable.orgId] = orgId
it[SysUserTable.enterpriseId] = enterpriseId
it[SysUserTable.status] = "ENABLED" it[SysUserTable.status] = "ENABLED"
it[SysUserTable.userType] = "SYSTEM" it[SysUserTable.userType] = "SYSTEM"
it[SysUserTable.updatedAt] = now it[SysUserTable.updatedAt] = now
@@ -180,6 +222,7 @@ object SeedData {
it[SysUserTable.passwordHash] = PasswordService.hash(ADMIN_INIT_PASSWORD) it[SysUserTable.passwordHash] = PasswordService.hash(ADMIN_INIT_PASSWORD)
it[SysUserTable.nickname] = "系统管理员" it[SysUserTable.nickname] = "系统管理员"
it[SysUserTable.orgId] = orgId it[SysUserTable.orgId] = orgId
it[SysUserTable.enterpriseId] = enterpriseId
it[SysUserTable.status] = "ENABLED" it[SysUserTable.status] = "ENABLED"
it[SysUserTable.userType] = "SYSTEM" it[SysUserTable.userType] = "SYSTEM"
it[SysUserTable.tokenVersion] = 1 it[SysUserTable.tokenVersion] = 1
@@ -234,15 +277,15 @@ object SeedData {
catalog("logs", "日志管理", "LogsRoot", "Logs", 30), catalog("logs", "日志管理", "LogsRoot", "Logs", 30),
subMenu("logs_operation", "logs", "操作日志", "LogsOperation", "/logs/operation", "logs/operation/index", "ScrollText", "log:operation:view", 10), subMenu("logs_operation", "logs", "操作日志", "LogsOperation", "/logs/operation", "logs/operation/index", "ScrollText", "log:operation:view", 10),
subMenu("logs_api_access", "logs", "接口日志", "LogsApiAccess", "/logs/api-access", "logs/api-access/index", "Waypoints", "log:api-access:view", 20), subMenu("logs_api_access", "logs", "接口日志", "LogsApiAccess", "/logs/api-access", "logs/api-access/index", "Waypoints", "log:api-access:view", 20),
subMenu("openapi_statistics", "logs", "OpenAPI", "OpenApiStatistics", "/logs/openapi", "statistics/openapi/index", "Waypoints", "openapi:statistics:view", 30),
catalog("basic_info", "基础信息", "BasicInfoRoot", "Building2", 40), catalog("basic_info", "基础信息", "BasicInfoRoot", "Building2", 40),
subMenu("enterprise_info", "basic_info", "企业信息", "EnterpriseInfo", "/enterprise/info", "piaotong/index", "Building2", "enterprise:info:view", 10), subMenu("enterprise_info", "basic_info", "企业信息", "EnterpriseInfo", "/enterprise/info", "piaotong/index", "Building2", "enterprise:info:view", 10),
subMenu("digital_account", "basic_info", "数电账号", "DigitalAccountManage", "/enterprise/digital-accounts", "piaotong/digital-accounts/index", "Users", "digital-account:view", 20), subMenu("digital_account", "basic_info", "数电账号", "DigitalAccountManage", "/enterprise/digital-accounts", "piaotong/digital-accounts/index", "Users", "digital-account:view", 20),
button("digital_account_status", "digital_account", "禁用/恢复数电账号", "DigitalAccountStatus", "digital-account:status", 1),
subMenu("invoice_setting", "basic_info", "开票设置", "InvoiceSetting", "/enterprise/invoice-setting", "piaotong/invoice-setting/index", "SlidersHorizontal", "invoice-setting:view", 30), subMenu("invoice_setting", "basic_info", "开票设置", "InvoiceSetting", "/enterprise/invoice-setting", "piaotong/invoice-setting/index", "SlidersHorizontal", "invoice-setting:view", 30),
catalog("invoice_service", "开票服务", "InvoiceServiceRoot", "Receipt", 50), catalog("invoice_service", "开票服务", "InvoiceServiceRoot", "Receipt", 50),
subMenu("piaotong_invoice_issue", "invoice_service", "开具蓝票", "PiaoTongInvoiceIssue", "/piaotong/invoice-issue", "piaotong/invoice-issue/index", "FilePlus", "piaotong:invoice-issue:view", 10), subMenu("piaotong_invoice_issue", "invoice_service", "开具蓝票", "PiaoTongInvoiceIssue", "/piaotong/invoice-issue", "piaotong/invoice-issue/index", "FilePlus", "piaotong:invoice-issue:view", 10),
subMenu("piaotong_invoice_history", "invoice_service", "开票历史", "PiaoTongInvoiceHistory", "/piaotong/invoice-history", "piaotong/invoice-history/index", "History", "piaotong:invoice-history:view", 20), subMenu("piaotong_invoice_history", "invoice_service", "开票历史", "PiaoTongInvoiceHistory", "/piaotong/invoice-history", "piaotong/invoice-history/index", "History", "piaotong:invoice-history:view", 20),
catalog("statistics_info", "统计信息", "StatisticsInfoRoot", "ChartNoAxesColumn", 60),
subMenu("openapi_statistics", "statistics_info", "OpenAPI", "OpenApiStatistics", "/statistics/openapi", "statistics/openapi/index", "Waypoints", "openapi:statistics:view", 10),
) )
val idMap = mutableMapOf<String, Uuid>() val idMap = mutableMapOf<String, Uuid>()
@@ -260,11 +303,11 @@ object SeedData {
"basic_info", "basic_info",
"enterprise_info", "enterprise_info",
"digital_account", "digital_account",
"digital_account_status",
"invoice_setting", "invoice_setting",
"invoice_service", "invoice_service",
"piaotong_invoice_issue", "piaotong_invoice_issue",
"piaotong_invoice_history", "piaotong_invoice_history",
"statistics_info",
"openapi_statistics", "openapi_statistics",
).mapNotNull { menuIds[it] } ).mapNotNull { menuIds[it] }
@@ -276,7 +319,6 @@ object SeedData {
"invoice_service", "invoice_service",
"piaotong_invoice_issue", "piaotong_invoice_issue",
"piaotong_invoice_history", "piaotong_invoice_history",
"statistics_info",
"openapi_statistics", "openapi_statistics",
).mapNotNull { menuIds[it] } ).mapNotNull { menuIds[it] }
@@ -351,6 +393,16 @@ object SeedData {
} }
} }
private suspend fun disableObsoleteMenus(now: OffsetDateTime) = dbQuery {
SysMenuTable.update({
(SysMenuTable.name inList listOf("StatisticsInfoRoot")) and SysMenuTable.deletedAt.isNull()
}) {
it[SysMenuTable.visible] = false
it[SysMenuTable.status] = "DISABLED"
it[SysMenuTable.updatedAt] = now
}
}
// ========================================================= // =========================================================
// Dicts // Dicts
// ========================================================= // =========================================================
@@ -0,0 +1,89 @@
package com.bbit.ticket.utils.net
import com.bbit.ticket.entity.request.AskInvoiceRequest
import com.bbit.ticket.entity.request.AuthQrcodeRequest
import com.bbit.ticket.entity.request.GetLoginSmsCodeRequest
import com.bbit.ticket.entity.request.InvoiceQueryRequest
import com.bbit.ticket.entity.request.QueryDigitalAccountListRequest
import com.bbit.ticket.entity.request.QueryEnterpriseBankAccountRequest
import com.bbit.ticket.entity.request.QueryEnterpriseInfoRequest
import com.bbit.ticket.entity.request.QueryInvoiceRequest
import com.bbit.ticket.entity.request.QueryRealNameAuthQrStatusRequest
import com.bbit.ticket.entity.request.QuickRedInvoiceRequest
import com.bbit.ticket.entity.request.SmsLoginRequest
import com.bbit.ticket.entity.request.TaxBureauAuthReq
import com.bbit.ticket.entity.request.TaxRegister
import com.bbit.ticket.entity.request.TaxRegisterInfo
import com.bbit.ticket.entity.response.AuthQrcodeResponse
import com.bbit.ticket.entity.response.AuthQrcodeStatusResponse
import com.bbit.ticket.entity.response.DigitalAccountInfo
import com.bbit.ticket.entity.response.EnterpriseTaxInfo
import com.bbit.ticket.entity.response.EtaxRegisterResponse
import com.bbit.ticket.entity.response.GetInvoiceInfoResponse
import com.bbit.ticket.entity.response.InvoiceCreateResponse
import com.bbit.ticket.entity.response.InvoiceQueryResponse
import com.bbit.ticket.entity.response.LoginSmsCodeResponse
import com.bbit.ticket.entity.response.QueryEnterpriseBankAccountResponse
import com.bbit.ticket.entity.response.QueryEnterpriseInfoResponse
import com.bbit.ticket.entity.response.QuickRedInvoiceResponse
import com.bbit.ticket.entity.response.SMSLoginResponse
import com.bbit.ticket.entity.response.TaxBureauAccountAuthContent
object PTApi {
/** 企业注册 2.2*/
suspend fun registerEnterprise(req: TaxRegisterInfo): EnterpriseTaxInfo =
PTClient.ptPost("register.pt", req)
/** 登记数电账号 2.3*/
suspend fun registerDigitalAccount(req: TaxRegister): EtaxRegisterResponse =
PTClient.ptPost("registerUser.pt", req)
/** 发送登录短信验证码 2.4 */
suspend fun sendLoginSmsCode(req: GetLoginSmsCodeRequest): LoginSmsCodeResponse =
PTClient.ptPost("sendLoginSmsCode.pt", req)
/** 短信验证码登录 2.5 */
suspend fun smsLogin(req: SmsLoginRequest): SMSLoginResponse =
PTClient.ptPost("smsLogin.pt", req)
/** 获取实名认证二维码 2.6 */
suspend fun getAuthenticationQrcode(req: AuthQrcodeRequest): AuthQrcodeResponse =
PTClient.ptPost("getAuthenticationQrcode.pt", req)
/** 查询认证二维码扫码状态 2.7 */
suspend fun queryAuthQrcodeScanStatus(req: QueryRealNameAuthQrStatusRequest): AuthQrcodeStatusResponse =
PTClient.ptPost("queryAuthQrcodeScanStatus.pt", req)
/** 查询数电账号认证状态 2.8 */
suspend fun getTaxBureauAccountAuthStatus(req: TaxBureauAuthReq): TaxBureauAccountAuthContent =
PTClient.ptPost("getTaxBureauAccountAuthStatus.pt", req)
/** 蓝票接口调用 2.9 */
suspend fun invoiceBlue(req: AskInvoiceRequest): InvoiceCreateResponse =
PTClient.ptPost("invoiceBlue.pt", req)
/** 红票接口调用 2.10 */
suspend fun invoiceRed(req: QuickRedInvoiceRequest): QuickRedInvoiceResponse =
PTClient.ptPost("invoiceRed.pt", req)
/** 开票统计及授信额度查询 2.20 */
suspend fun queryAllEleBlueInvStatistics(req: InvoiceQueryRequest): InvoiceQueryResponse =
PTClient.ptPost("queryAllEleBlueInvStatistics.pt", req)
/** 查询票通同步发票信息 2.12*/
suspend fun queryInvoiceInfo(req: QueryInvoiceRequest): GetInvoiceInfoResponse =
PTClient.ptPost("queryInvoiceInfo.pt", req)
/** 查询数电账号列表 2.45 */
suspend fun listTaxBureauAccount(req: QueryDigitalAccountListRequest): List<DigitalAccountInfo> =
PTClient.ptPost("listTaxBureauAccount.pt", req)
/** 查询企业状态 2.47 */
suspend fun getEnterpriseInfo(req: QueryEnterpriseInfoRequest): QueryEnterpriseInfoResponse =
PTClient.ptPost("getEnterpriseInfo.pt", req)
/** 查询企业开户行及账号 2.49 */
suspend fun queryEnterpriseBankInfo(req: QueryEnterpriseBankAccountRequest): QueryEnterpriseBankAccountResponse =
PTClient.ptPost("queryEnterpriseBankInfo.pt", req)
}
+4
View File
@@ -172,6 +172,10 @@ export function createDigitalAccountApi(
return http.post('/pt/digital-accounts', payload) return http.post('/pt/digital-accounts', payload)
} }
export function updateDigitalAccountStatusApi(id: string, status: string): Promise<string> {
return http.put(`/pt/digital-accounts/${id}/status`, { status })
}
export interface PresetData { export interface PresetData {
bankName: string bankName: string
bankAccount: string bankAccount: string
@@ -110,6 +110,7 @@ import {
NIcon, NIcon,
NInput, NInput,
NModal, NModal,
NPopconfirm,
NSelect, NSelect,
NSpace, NSpace,
NTag, NTag,
@@ -122,7 +123,8 @@ import {
listDigitalAccountsApi, listDigitalAccountsApi,
refreshDigitalAccountsApi, refreshDigitalAccountsApi,
sendLoginSmsCodeApi, sendLoginSmsCodeApi,
smsLoginApi smsLoginApi,
updateDigitalAccountStatusApi
} from '@/api/piaotong' } from '@/api/piaotong'
import type { DigitalAccountItem } from '@/api/piaotong' import type { DigitalAccountItem } from '@/api/piaotong'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
@@ -144,6 +146,7 @@ const selected = ref<DigitalAccountItem | null>(null)
const createFormRef = ref<FormInst | null>(null) const createFormRef = ref<FormInst | null>(null)
const canManage = computed(() => authStore.user?.userType !== 'DIGITAL_OPERATOR') const canManage = computed(() => authStore.user?.userType !== 'DIGITAL_OPERATOR')
const canUpdateStatus = computed(() => authStore.hasPermission('digital-account:status'))
const createForm = reactive({ const createForm = reactive({
account: '', account: '',
@@ -197,6 +200,17 @@ const columns: DataTableColumns<DigitalAccountItem> = [
) )
}, },
{ title: '最近认证', key: 'lastAuthSuccTime', minWidth: 160 }, { title: '最近认证', key: 'lastAuthSuccTime', minWidth: 160 },
{
title: '平台状态',
key: 'status',
width: 110,
render: (row) =>
h(
NTag,
{ type: row.status === 'ENABLED' ? 'success' : 'warning' },
{ default: () => (row.status === 'ENABLED' ? '可用' : '禁用') }
)
},
{ {
title: 'API Key', title: 'API Key',
key: 'apiKey', key: 'apiKey',
@@ -207,19 +221,49 @@ const columns: DataTableColumns<DigitalAccountItem> = [
title: '操作', title: '操作',
key: 'actions', key: 'actions',
fixed: 'right', fixed: 'right',
width: 220, width: 300,
render: (row) => render: (row) =>
h('div', { class: 'row-actions' }, [ h('div', { class: 'row-actions' }, [
h( h(
NButton, NButton,
{ size: 'small', secondary: true, onClick: () => openSms(row) }, {
size: 'small',
secondary: true,
disabled: row.status !== 'ENABLED',
onClick: () => openSms(row)
},
{ icon: () => h(NIcon, { component: KeyRound }), default: () => '短信登录' } { icon: () => h(NIcon, { component: KeyRound }), default: () => '短信登录' }
), ),
h( h(
NButton, NButton,
{ size: 'small', tertiary: true, onClick: () => openQrcode(row) }, {
size: 'small',
tertiary: true,
disabled: row.status !== 'ENABLED',
onClick: () => openQrcode(row)
},
{ icon: () => h(NIcon, { component: ShieldCheck }), default: () => '认证' } { icon: () => h(NIcon, { component: ShieldCheck }), default: () => '认证' }
),
canUpdateStatus.value
? h(
NPopconfirm,
{
onPositiveClick: () => updateAccountStatus(row)
},
{
trigger: () =>
h(
NButton,
{ size: 'small', tertiary: true, type: row.status === 'ENABLED' ? 'warning' : 'success' },
{ default: () => (row.status === 'ENABLED' ? '禁用' : '恢复') }
),
default: () =>
row.status === 'ENABLED'
? `确认禁用数电账号 ${row.account} 吗?`
: `确认恢复数电账号 ${row.account} 吗?`
}
) )
: null
]) ])
} }
] ]
@@ -270,6 +314,13 @@ async function createAccount() {
} }
} }
async function updateAccountStatus(row: DigitalAccountItem) {
const nextStatus = row.status === 'ENABLED' ? 'DISABLED' : 'ENABLED'
await updateDigitalAccountStatusApi(row.id, nextStatus)
message.success(nextStatus === 'ENABLED' ? '数电账号已恢复' : '数电账号已禁用')
await load()
}
async function openSms(row: DigitalAccountItem) { async function openSms(row: DigitalAccountItem) {
selected.value = row selected.value = row
smsCode.value = '' smsCode.value = ''
@@ -400,7 +400,6 @@
style="color: #e74c3c; font-size: 12px" style="color: #e74c3c; font-size: 12px"
>农产品开票销方信息代表实际的购方信息</span >农产品开票销方信息代表实际的购方信息</span
> >
<span v-else class="section-hint">不填则使用平台默认配置</span>
</div> </div>
<div class="section-body"> <div class="section-body">
<n-grid item-responsive responsive="screen" cols="2 s:2 m:3 l:4 xl:5 2xl:6" :x-gap="12" :y-gap="4"> <n-grid item-responsive responsive="screen" cols="2 s:2 m:3 l:4 xl:5 2xl:6" :x-gap="12" :y-gap="4">
-50
View File
@@ -102,26 +102,6 @@
</div> </div>
</div> </div>
<div class="detail-section">
<div class="detail-section-title">税务信息</div>
<div class="detail-grid">
<div v-for="item in taxDetailItems" :key="item.label" class="detail-item">
<span>{{ item.label }}</span>
<strong>{{ item.value }}</strong>
</div>
</div>
</div>
<div class="detail-section">
<div class="detail-section-title">银行与预设信息</div>
<div class="detail-grid">
<div v-for="item in presetDetailItems" :key="item.label" class="detail-item">
<span>{{ item.label }}</span>
<strong>{{ item.value }}</strong>
</div>
</div>
</div>
<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">
@@ -342,36 +322,6 @@ const baseDetailItems = computed(() => {
] ]
}) })
const taxDetailItems = computed(() => {
const user = detailUser.value
if (!user) return []
return [
{ label: '纳税人识别号', value: displayValue(user.taxpayerNum) },
{ label: '数电账号', value: displayValue(user.account) },
{ label: '身份类型', value: displayValue(user.taxIdentityType) },
{ label: '联系人', value: displayValue(user.taxContactName) },
{ label: '联系人电话', value: displayValue(user.taxContactPhone) },
{ label: '联系人邮箱', value: displayValue(user.taxContactEmail) },
{ label: '法定代表人', value: displayValue(user.taxLegalPersonName) },
{ label: '企业名称', value: displayValue(user.taxEnterpriseName) },
{ label: '地区编码', value: displayValue(user.taxRegionCode) },
{ label: '城市', value: displayValue(user.taxCityName) },
{ label: '企业地址', value: displayValue(user.taxEnterpriseAddress) },
{ label: '税务登记证', value: displayValue(user.taxRegistrationCertificate) }
]
})
const presetDetailItems = computed(() => {
const user = detailUser.value
if (!user) return []
return [
{ label: '开户行', value: displayValue(user.bankName) },
{ label: '银行账号', value: displayValue(user.bankAccount) },
{ label: '预设地址', value: displayValue(user.presetAddress) },
{ label: '预设电话', value: displayValue(user.presetPhone) }
]
})
const systemDetailItems = computed(() => { const systemDetailItems = computed(() => {
const user = detailUser.value const user = detailUser.value
if (!user) return [] if (!user) return []
-20
View File
@@ -36,22 +36,6 @@ export interface UserDetail {
deletedAt?: string | null deletedAt?: string | null
deletedBy?: string | null deletedBy?: string | null
version: number version: number
taxpayerNum?: string | null
account?: string | null
taxIdentityType?: string | null
taxContactName?: string | null
taxContactPhone?: string | null
taxContactEmail?: string | null
taxLegalPersonName?: string | null
taxEnterpriseName?: string | null
taxRegionCode?: string | null
taxCityName?: string | null
taxEnterpriseAddress?: string | null
taxRegistrationCertificate?: string | null
bankName?: string | null
bankAccount?: string | null
presetAddress?: string | null
presetPhone?: string | null
} }
export interface UserRoleBrief { export interface UserRoleBrief {
@@ -90,10 +74,6 @@ export interface UpdateUserRequest {
email?: string email?: string
avatar?: string avatar?: string
orgId?: string orgId?: string
taxpayerNum?: string
account?: string
taxPassword?: string
taxIdentityType?: string
} }
export interface UpdateUserStatusRequest { export interface UpdateUserStatusRequest {