开票历史模块
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
package com.bbit.ticket.bootstrap
|
||||
|
||||
import com.bbit.ticket.database.piaotong.HistoryInvoiceBasicTable
|
||||
import com.bbit.ticket.database.piaotong.HistoryInvoiceGoodsTable
|
||||
import com.bbit.ticket.database.piaotong.HistoryInvoiceOrderTable
|
||||
import com.bbit.ticket.database.piaotong.HistoryInvoiceVoucherTable
|
||||
import com.bbit.ticket.database.piaotong.InvoiceItemTable
|
||||
import com.bbit.ticket.database.piaotong.InvoiceOrderTable
|
||||
import com.bbit.ticket.database.system.SysApiAccessLogTable
|
||||
import com.bbit.ticket.database.system.SysDictItemTable
|
||||
import com.bbit.ticket.database.system.SysDictTypeTable
|
||||
@@ -33,7 +36,10 @@ object DatabaseInitializer {
|
||||
SysOperationLogTable,
|
||||
SysApiAccessLogTable,
|
||||
InvoiceItemTable,
|
||||
InvoiceOrderTable,
|
||||
HistoryInvoiceBasicTable,
|
||||
HistoryInvoiceGoodsTable,
|
||||
HistoryInvoiceVoucherTable,
|
||||
HistoryInvoiceOrderTable,
|
||||
)
|
||||
// 先通过 Exposed 生成迁移 SQL,再逐条执行,避免启动时静默跳过缺失表或字段。
|
||||
transaction {
|
||||
|
||||
@@ -2,31 +2,43 @@
|
||||
|
||||
package com.bbit.ticket.dao.piaotong
|
||||
|
||||
import com.bbit.ticket.database.piaotong.HistoryInvoiceBasicTable
|
||||
import com.bbit.ticket.database.piaotong.HistoryInvoiceGoodsTable
|
||||
import com.bbit.ticket.database.piaotong.HistoryInvoiceOrderTable
|
||||
import com.bbit.ticket.database.piaotong.HistoryInvoiceVoucherTable
|
||||
import com.bbit.ticket.database.piaotong.InvoiceItemTable
|
||||
import com.bbit.ticket.database.piaotong.InvoiceOrderTable
|
||||
import com.bbit.ticket.database.system.SysUserTable
|
||||
import com.bbit.ticket.entity.request.InvoiceRequest
|
||||
import com.bbit.ticket.entity.common.PageResult
|
||||
import com.bbit.ticket.entity.request.AskInvoiceRequest
|
||||
import com.bbit.ticket.entity.request.TaxRegisterInfo
|
||||
import com.bbit.ticket.entity.request.UpdateDigitalAccountRequest
|
||||
import com.bbit.ticket.entity.request.UpdateEnterpriseInfoRequest
|
||||
import com.bbit.ticket.entity.request.UpdatePresetDataRequest
|
||||
import com.bbit.ticket.entity.response.DigitalAccountResponse
|
||||
import com.bbit.ticket.entity.response.EnterpriseInfoResponse
|
||||
import com.bbit.ticket.entity.response.InvoiceDetailGoods
|
||||
import com.bbit.ticket.entity.response.InvoiceDetailOrder
|
||||
import com.bbit.ticket.entity.response.InvoiceDetailResponse
|
||||
import com.bbit.ticket.entity.response.InvoiceDetailVoucher
|
||||
import com.bbit.ticket.entity.response.InvoiceHistoryItem
|
||||
import com.bbit.ticket.entity.response.PresetDataResponse
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import com.bbit.ticket.utils.formatDateTime
|
||||
import org.jetbrains.exposed.v1.core.SortOrder
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.insertAndGetId
|
||||
import org.jetbrains.exposed.v1.jdbc.insertReturning
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import java.math.BigDecimal
|
||||
import java.text.DecimalFormat
|
||||
import java.time.LocalDateTime
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
private fun pageOffset(page: Int, pageSize: Int): Long = ((page - 1) * pageSize).toLong()
|
||||
|
||||
object EnterpriseTaxDao {
|
||||
|
||||
// =============================================
|
||||
@@ -129,36 +141,119 @@ object EnterpriseTaxDao {
|
||||
}
|
||||
}
|
||||
|
||||
fun addInvoice(userId: Uuid, req: InvoiceRequest) {
|
||||
// =============================================
|
||||
// 开票历史查询
|
||||
// =============================================
|
||||
|
||||
/**
|
||||
* 分页查询开票历史
|
||||
*/
|
||||
fun invoiceHistory(userId: Uuid, page: Int, pageSize: Int): PageResult<InvoiceHistoryItem> {
|
||||
val where = HistoryInvoiceBasicTable.userId eq userId
|
||||
val total = HistoryInvoiceBasicTable.selectAll().where { where }.count()
|
||||
val rows = HistoryInvoiceBasicTable.selectAll().where { where }
|
||||
.orderBy(HistoryInvoiceBasicTable.createdAt, SortOrder.DESC)
|
||||
.limit(pageSize)
|
||||
.offset(pageOffset(page, pageSize))
|
||||
.toList()
|
||||
return PageResult(
|
||||
items = rows.map { row ->
|
||||
InvoiceHistoryItem(
|
||||
id = row[HistoryInvoiceBasicTable.id],
|
||||
invoiceReqSerialNo = row[HistoryInvoiceBasicTable.invoiceReqSerialNo],
|
||||
taxpayerNum = row[HistoryInvoiceBasicTable.taxpayerNum],
|
||||
invoiceKindCode = row[HistoryInvoiceBasicTable.invoiceKindCode],
|
||||
buyerName = row[HistoryInvoiceBasicTable.buyerName],
|
||||
buyerTaxpayerNum = row[HistoryInvoiceBasicTable.buyerTaxpayerNum],
|
||||
buyerAddress = row[HistoryInvoiceBasicTable.buyerAddress],
|
||||
buyerTel = row[HistoryInvoiceBasicTable.buyerTel],
|
||||
buyerBankName = row[HistoryInvoiceBasicTable.buyerBankName],
|
||||
buyerBankAccount = row[HistoryInvoiceBasicTable.buyerBankAccount],
|
||||
amount = row[HistoryInvoiceBasicTable.amount].toPlainString(),
|
||||
taxAmount = row[HistoryInvoiceBasicTable.taxAmount].toPlainString(),
|
||||
totalAmount = row[HistoryInvoiceBasicTable.totalAmount].toPlainString(),
|
||||
invoiceNo = row[HistoryInvoiceBasicTable.invoiceNo],
|
||||
invoiceCode = row[HistoryInvoiceBasicTable.invoiceCode],
|
||||
electronicInvoiceNo = row[HistoryInvoiceBasicTable.electronicInvoiceNo],
|
||||
issuedAt = formatDateTime(row[HistoryInvoiceBasicTable.issuedAt]),
|
||||
status = row[HistoryInvoiceBasicTable.status],
|
||||
pdfUrl = row[HistoryInvoiceBasicTable.pdfUrl],
|
||||
ofdUrl = row[HistoryInvoiceBasicTable.ofdUrl],
|
||||
xmlUrl = row[HistoryInvoiceBasicTable.xmlUrl],
|
||||
tradeNo = row[HistoryInvoiceBasicTable.tradeNo],
|
||||
remark = row[HistoryInvoiceBasicTable.remark],
|
||||
definedData = row[HistoryInvoiceBasicTable.definedData],
|
||||
createdAt = formatDateTime(row[HistoryInvoiceBasicTable.createdAt]) ?: "",
|
||||
errorMessage = row[HistoryInvoiceBasicTable.errorMessage],
|
||||
)
|
||||
},
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
total = total,
|
||||
)
|
||||
}
|
||||
|
||||
fun addInvoice(userId: Uuid, req: AskInvoiceRequest) {
|
||||
val now = OffsetDateTime.now()
|
||||
val row = InvoiceOrderTable.insert {
|
||||
it[InvoiceOrderTable.userId] = userId
|
||||
it[InvoiceOrderTable.invoiceReqSerialNo] = req.invoiceReqSerialNo
|
||||
it[InvoiceOrderTable.taxpayerNum] = req.taxpayerNum
|
||||
it[InvoiceOrderTable.invoiceKindCode] = req.invoiceIssueKindCode
|
||||
it[InvoiceOrderTable.buyerName] = req.buyerName
|
||||
it[InvoiceOrderTable.buyerTaxpayerNum] = req.buyerTaxpayerNum
|
||||
it[InvoiceOrderTable.buyerAddress] = req.buyerAddress
|
||||
it[InvoiceOrderTable.buyerTel] = req.buyerTel
|
||||
it[InvoiceOrderTable.buyerBankName] = req.buyerBankName
|
||||
it[InvoiceOrderTable.buyerBankAccount] = req.buyerBankAccount
|
||||
it[InvoiceOrderTable.remark] = req.remark
|
||||
it[InvoiceOrderTable.definedData] = req.definedData
|
||||
it[InvoiceOrderTable.tradeNo] = req.tradeNo
|
||||
it[InvoiceOrderTable.taxAmount] = BigDecimal.ZERO
|
||||
it[InvoiceOrderTable.amount] = BigDecimal.ZERO
|
||||
it[InvoiceOrderTable.totalAmount] = BigDecimal.ZERO
|
||||
it[InvoiceOrderTable.requestJson] = Json.encodeToString(req)
|
||||
it[InvoiceOrderTable.status] = "PENDING"
|
||||
it[InvoiceOrderTable.createdAt] = now
|
||||
it[InvoiceOrderTable.createdBy] = userId
|
||||
|
||||
// 1. 插入 HistoryInvoiceBasicTable(基本信息历史快照)
|
||||
val basicRow = HistoryInvoiceBasicTable.insert {
|
||||
it[HistoryInvoiceBasicTable.userId] = userId
|
||||
it[HistoryInvoiceBasicTable.invoiceReqSerialNo] = req.invoiceReqSerialNo
|
||||
it[HistoryInvoiceBasicTable.taxpayerNum] = req.taxpayerNum
|
||||
it[HistoryInvoiceBasicTable.invoiceKindCode] = req.invoiceIssueKindCode
|
||||
it[HistoryInvoiceBasicTable.invoiceType] = "BLUE"
|
||||
it[HistoryInvoiceBasicTable.buyerName] = req.buyerName
|
||||
it[HistoryInvoiceBasicTable.buyerTaxpayerNum] = req.buyerTaxpayerNum
|
||||
it[HistoryInvoiceBasicTable.buyerAddress] = req.buyerAddress
|
||||
it[HistoryInvoiceBasicTable.buyerTel] = req.buyerTel
|
||||
it[HistoryInvoiceBasicTable.buyerBankName] = req.buyerBankName
|
||||
it[HistoryInvoiceBasicTable.buyerBankAccount] = req.buyerBankAccount
|
||||
it[HistoryInvoiceBasicTable.remark] = req.remark
|
||||
it[HistoryInvoiceBasicTable.definedData] = req.definedData
|
||||
it[HistoryInvoiceBasicTable.tradeNo] = req.tradeNo
|
||||
it[HistoryInvoiceBasicTable.amount] = req.itemList.sumOf { it.invoiceAmount.toBigDecimal() }
|
||||
it[HistoryInvoiceBasicTable.taxAmount] = req.itemList.sumOf { it.taxRateAmount?.toBigDecimalOrNull() ?: BigDecimal.ZERO }
|
||||
it[HistoryInvoiceBasicTable.totalAmount] = req.itemList.sumOf { item ->
|
||||
val amount = item.invoiceAmount.toBigDecimal()
|
||||
val tax = item.taxRateAmount?.toBigDecimalOrNull() ?: BigDecimal.ZERO
|
||||
amount + tax
|
||||
}
|
||||
it[HistoryInvoiceBasicTable.status] = "PENDING"
|
||||
it[HistoryInvoiceBasicTable.createdAt] = now
|
||||
it[HistoryInvoiceBasicTable.createdBy] = userId
|
||||
}
|
||||
val invoiceId = row[InvoiceOrderTable.id]
|
||||
val historyBasicId = basicRow[HistoryInvoiceBasicTable.id]
|
||||
|
||||
// 2. 插入商品明细(HistoryInvoiceGoodsTable + InvoiceItemTable)
|
||||
var lineNo = 1
|
||||
for (item in req.itemList) {
|
||||
// 历史商品明细
|
||||
HistoryInvoiceGoodsTable.insert {
|
||||
it[HistoryInvoiceGoodsTable.basicId] = historyBasicId
|
||||
it[HistoryInvoiceGoodsTable.lineNo] = lineNo
|
||||
it[HistoryInvoiceGoodsTable.goodsName] = item.goodsName
|
||||
it[HistoryInvoiceGoodsTable.taxClassificationCode] = item.taxClassificationCode
|
||||
it[HistoryInvoiceGoodsTable.specificationModel] = item.specificationModel
|
||||
it[HistoryInvoiceGoodsTable.meteringUnit] = item.meteringUnit
|
||||
it[HistoryInvoiceGoodsTable.quantity] = item.quantity?.toBigDecimalOrNull()
|
||||
it[HistoryInvoiceGoodsTable.unitPrice] = item.unitPrice?.toBigDecimalOrNull()
|
||||
it[HistoryInvoiceGoodsTable.invoiceAmount] = item.invoiceAmount.toBigDecimal()
|
||||
it[HistoryInvoiceGoodsTable.taxRateValue] = item.taxRateValue.toBigDecimal()
|
||||
it[HistoryInvoiceGoodsTable.taxRateAmount] = item.taxRateAmount?.toBigDecimalOrNull()
|
||||
it[HistoryInvoiceGoodsTable.includeTaxFlag] = item.includeTaxFlag == "1"
|
||||
it[HistoryInvoiceGoodsTable.discountAmount] = item.discountAmount?.toBigDecimalOrNull()
|
||||
it[HistoryInvoiceGoodsTable.zeroTaxFlag] = item.zeroTaxFlag
|
||||
it[HistoryInvoiceGoodsTable.preferentialPolicyFlag] = item.preferentialPolicyFlag
|
||||
it[HistoryInvoiceGoodsTable.vatSpecialManage] = item.vatSpecialManage
|
||||
it[HistoryInvoiceGoodsTable.deductionAmount] = item.deductionAmount?.toBigDecimalOrNull()
|
||||
it[HistoryInvoiceGoodsTable.createdAt] = now
|
||||
}
|
||||
|
||||
// 当前商品明细
|
||||
InvoiceItemTable.insert {
|
||||
it[InvoiceItemTable.invoiceId] = invoiceId
|
||||
it[InvoiceItemTable.lineNo] = lineNo++
|
||||
it[InvoiceItemTable.invoiceId] = historyBasicId
|
||||
it[InvoiceItemTable.lineNo] = lineNo
|
||||
it[InvoiceItemTable.goodsName] = item.goodsName
|
||||
it[InvoiceItemTable.taxClassificationCode] = item.taxClassificationCode
|
||||
it[InvoiceItemTable.specificationModel] = item.specificationModel
|
||||
@@ -176,6 +271,167 @@ object EnterpriseTaxDao {
|
||||
it[InvoiceItemTable.deductionAmount] = item.deductionAmount?.toBigDecimalOrNull()
|
||||
it[InvoiceItemTable.createdAt] = now
|
||||
}
|
||||
lineNo++
|
||||
}
|
||||
|
||||
// 3. 插入差额征税凭证明细(HistoryInvoiceVoucherTable)
|
||||
if (!req.variableLevyProofList.isNullOrEmpty()) {
|
||||
for (proof in req.variableLevyProofList) {
|
||||
HistoryInvoiceVoucherTable.insert {
|
||||
it[HistoryInvoiceVoucherTable.basicId] = historyBasicId
|
||||
it[HistoryInvoiceVoucherTable.proofType] = proof.proofType
|
||||
it[HistoryInvoiceVoucherTable.electronicInvoiceNo] = proof.electronicInvoiceNo
|
||||
it[HistoryInvoiceVoucherTable.invoiceCode] = proof.invoiceCode
|
||||
it[HistoryInvoiceVoucherTable.invoiceNo] = proof.invoiceNo
|
||||
it[HistoryInvoiceVoucherTable.proofNo] = proof.proofNo
|
||||
it[HistoryInvoiceVoucherTable.issueDate] = proof.issueDate
|
||||
it[HistoryInvoiceVoucherTable.proofAmount] = proof.proofAmount.toBigDecimal()
|
||||
it[HistoryInvoiceVoucherTable.deductionAmount] = proof.deductionAmount.toBigDecimal()
|
||||
it[HistoryInvoiceVoucherTable.proofRemark] = proof.proofRemark
|
||||
it[HistoryInvoiceVoucherTable.his_source] = proof.source
|
||||
it[HistoryInvoiceVoucherTable.createdAt] = now
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 插入关联单据(HistoryInvoiceOrderTable)
|
||||
if (!req.orderList.isNullOrEmpty()) {
|
||||
for (order in req.orderList) {
|
||||
HistoryInvoiceOrderTable.insert {
|
||||
it[HistoryInvoiceOrderTable.basicId] = historyBasicId
|
||||
it[HistoryInvoiceOrderTable.orderNo] = order.orderNo
|
||||
it[HistoryInvoiceOrderTable.createdAt] = now
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据票通查询结果更新发票状态
|
||||
*/
|
||||
fun updateInvoiceStatus(
|
||||
invoiceReqSerialNo: String,
|
||||
code: String,
|
||||
msg: String,
|
||||
invoiceNo: String?,
|
||||
invoiceCode: String?,
|
||||
electronicInvoiceNo: String?,
|
||||
invoiceDate: String?,
|
||||
tradeNo: String?,
|
||||
) {
|
||||
HistoryInvoiceBasicTable.update({
|
||||
HistoryInvoiceBasicTable.invoiceReqSerialNo eq invoiceReqSerialNo
|
||||
}) {
|
||||
val status = when (code) {
|
||||
"0000" -> "SUCCESS"
|
||||
"7777" -> "PROCESSING"
|
||||
"9999" -> "FAILED"
|
||||
"6666" -> "PENDING"
|
||||
else -> "PROCESSING"
|
||||
}
|
||||
it[HistoryInvoiceBasicTable.status] = status
|
||||
// 失败时将 msg 写入错误信息,成功时清空
|
||||
it[HistoryInvoiceBasicTable.errorMessage] = if (status == "FAILED") msg else null
|
||||
if (!invoiceNo.isNullOrBlank()) it[HistoryInvoiceBasicTable.invoiceNo] = invoiceNo
|
||||
if (!invoiceCode.isNullOrBlank()) it[HistoryInvoiceBasicTable.invoiceCode] = invoiceCode
|
||||
if (!electronicInvoiceNo.isNullOrBlank()) it[HistoryInvoiceBasicTable.electronicInvoiceNo] = electronicInvoiceNo
|
||||
if (!invoiceDate.isNullOrBlank()) {
|
||||
val localDt = LocalDateTime.parse(invoiceDate, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
||||
it[HistoryInvoiceBasicTable.issuedAt] = localDt.atZone(ZoneId.systemDefault()).toOffsetDateTime()
|
||||
}
|
||||
if (!tradeNo.isNullOrBlank()) it[HistoryInvoiceBasicTable.tradeNo] = tradeNo
|
||||
it[HistoryInvoiceBasicTable.updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询发票完整详情(含商品明细、差额征税凭证、关联单据)
|
||||
*/
|
||||
fun invoiceDetail(userId: Uuid, invoiceReqSerialNo: String): InvoiceDetailResponse? {
|
||||
val basicRow = HistoryInvoiceBasicTable.selectAll().where {
|
||||
(HistoryInvoiceBasicTable.userId eq userId) and
|
||||
(HistoryInvoiceBasicTable.invoiceReqSerialNo eq invoiceReqSerialNo)
|
||||
}.singleOrNull() ?: return null
|
||||
|
||||
val basicId = basicRow[HistoryInvoiceBasicTable.id]
|
||||
|
||||
// 查询商品明细
|
||||
val goodsRows = HistoryInvoiceGoodsTable.selectAll().where {
|
||||
HistoryInvoiceGoodsTable.basicId eq basicId
|
||||
}.orderBy(HistoryInvoiceGoodsTable.lineNo).toList()
|
||||
|
||||
// 查询差额征税凭证
|
||||
val voucherRows = HistoryInvoiceVoucherTable.selectAll().where {
|
||||
HistoryInvoiceVoucherTable.basicId eq basicId
|
||||
}.toList()
|
||||
|
||||
// 查询关联单据
|
||||
val orderRows = HistoryInvoiceOrderTable.selectAll().where {
|
||||
HistoryInvoiceOrderTable.basicId eq basicId
|
||||
}.toList()
|
||||
|
||||
return InvoiceDetailResponse(
|
||||
id = basicRow[HistoryInvoiceBasicTable.id],
|
||||
invoiceReqSerialNo = basicRow[HistoryInvoiceBasicTable.invoiceReqSerialNo],
|
||||
taxpayerNum = basicRow[HistoryInvoiceBasicTable.taxpayerNum],
|
||||
invoiceKindCode = basicRow[HistoryInvoiceBasicTable.invoiceKindCode],
|
||||
invoiceType = basicRow[HistoryInvoiceBasicTable.invoiceType],
|
||||
buyerName = basicRow[HistoryInvoiceBasicTable.buyerName],
|
||||
buyerTaxpayerNum = basicRow[HistoryInvoiceBasicTable.buyerTaxpayerNum],
|
||||
buyerAddress = basicRow[HistoryInvoiceBasicTable.buyerAddress],
|
||||
buyerTel = basicRow[HistoryInvoiceBasicTable.buyerTel],
|
||||
buyerBankName = basicRow[HistoryInvoiceBasicTable.buyerBankName],
|
||||
buyerBankAccount = basicRow[HistoryInvoiceBasicTable.buyerBankAccount],
|
||||
amount = basicRow[HistoryInvoiceBasicTable.amount].toPlainString(),
|
||||
taxAmount = basicRow[HistoryInvoiceBasicTable.taxAmount].toPlainString(),
|
||||
totalAmount = basicRow[HistoryInvoiceBasicTable.totalAmount].toPlainString(),
|
||||
invoiceNo = basicRow[HistoryInvoiceBasicTable.invoiceNo],
|
||||
invoiceCode = basicRow[HistoryInvoiceBasicTable.invoiceCode],
|
||||
electronicInvoiceNo = basicRow[HistoryInvoiceBasicTable.electronicInvoiceNo],
|
||||
issuedAt = formatDateTime(basicRow[HistoryInvoiceBasicTable.issuedAt]),
|
||||
status = basicRow[HistoryInvoiceBasicTable.status],
|
||||
errorMessage = basicRow[HistoryInvoiceBasicTable.errorMessage],
|
||||
pdfUrl = basicRow[HistoryInvoiceBasicTable.pdfUrl],
|
||||
ofdUrl = basicRow[HistoryInvoiceBasicTable.ofdUrl],
|
||||
xmlUrl = basicRow[HistoryInvoiceBasicTable.xmlUrl],
|
||||
tradeNo = basicRow[HistoryInvoiceBasicTable.tradeNo],
|
||||
remark = basicRow[HistoryInvoiceBasicTable.remark],
|
||||
definedData = basicRow[HistoryInvoiceBasicTable.definedData],
|
||||
createdAt = formatDateTime(basicRow[HistoryInvoiceBasicTable.createdAt]) ?: "",
|
||||
goodsList = goodsRows.map { row ->
|
||||
InvoiceDetailGoods(
|
||||
lineNo = row[HistoryInvoiceGoodsTable.lineNo],
|
||||
goodsName = row[HistoryInvoiceGoodsTable.goodsName],
|
||||
taxClassificationCode = row[HistoryInvoiceGoodsTable.taxClassificationCode],
|
||||
specificationModel = row[HistoryInvoiceGoodsTable.specificationModel],
|
||||
meteringUnit = row[HistoryInvoiceGoodsTable.meteringUnit],
|
||||
quantity = row[HistoryInvoiceGoodsTable.quantity]?.toPlainString(),
|
||||
unitPrice = row[HistoryInvoiceGoodsTable.unitPrice]?.toPlainString(),
|
||||
invoiceAmount = row[HistoryInvoiceGoodsTable.invoiceAmount].toPlainString(),
|
||||
taxRateValue = row[HistoryInvoiceGoodsTable.taxRateValue].toPlainString(),
|
||||
taxRateAmount = row[HistoryInvoiceGoodsTable.taxRateAmount]?.toPlainString(),
|
||||
includeTaxFlag = row[HistoryInvoiceGoodsTable.includeTaxFlag],
|
||||
)
|
||||
},
|
||||
voucherList = voucherRows.map { row ->
|
||||
InvoiceDetailVoucher(
|
||||
proofType = row[HistoryInvoiceVoucherTable.proofType],
|
||||
electronicInvoiceNo = row[HistoryInvoiceVoucherTable.electronicInvoiceNo],
|
||||
invoiceCode = row[HistoryInvoiceVoucherTable.invoiceCode],
|
||||
invoiceNo = row[HistoryInvoiceVoucherTable.invoiceNo],
|
||||
proofNo = row[HistoryInvoiceVoucherTable.proofNo],
|
||||
issueDate = row[HistoryInvoiceVoucherTable.issueDate],
|
||||
proofAmount = row[HistoryInvoiceVoucherTable.proofAmount].toPlainString(),
|
||||
deductionAmount = row[HistoryInvoiceVoucherTable.deductionAmount].toPlainString(),
|
||||
proofRemark = row[HistoryInvoiceVoucherTable.proofRemark],
|
||||
source = row[HistoryInvoiceVoucherTable.his_source],
|
||||
)
|
||||
},
|
||||
orderList = orderRows.map { row ->
|
||||
InvoiceDetailOrder(
|
||||
orderNo = row[HistoryInvoiceOrderTable.orderNo],
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+16
-64
@@ -5,8 +5,12 @@ import org.jetbrains.exposed.v1.javatime.timestampWithTimeZone
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* 发票基本信息表(历史)
|
||||
* 存储发票开具时的基本信息快照
|
||||
*/
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
object InvoiceOrderTable : Table("invoice_order") {
|
||||
object HistoryInvoiceBasicTable : Table("history_invoice_basic") {
|
||||
|
||||
val id = uuid("id").clientDefault { Uuid.Companion.random() }
|
||||
|
||||
@@ -30,29 +34,23 @@ object InvoiceOrderTable : Table("invoice_order") {
|
||||
* 发票种类
|
||||
* 81:电子发票(增值税专用发票)
|
||||
* 82:电子发票(普通发票)
|
||||
* 87:数电纸质发票(机动车销售统一发票)
|
||||
* 10:增值税电子普通发票
|
||||
* 08:增值税电子专用发票
|
||||
* 04:增值税普通发票
|
||||
* 01:增值税专用发票
|
||||
*
|
||||
*/
|
||||
val invoiceKindCode = varchar("invoice_kind_code", 8)
|
||||
|
||||
/**
|
||||
* 发票类型
|
||||
* BLUE:蓝票
|
||||
* RED:红票
|
||||
*/
|
||||
val invoiceType = varchar("invoice_type", 8).default("BLUE")
|
||||
|
||||
// =========================
|
||||
// 购买方信息(历史快照)
|
||||
// 购买方信息
|
||||
// =========================
|
||||
|
||||
/**
|
||||
* 购买方名称
|
||||
*/
|
||||
val buyerName = varchar("buyer_name", 200)
|
||||
|
||||
/**
|
||||
* 购买方税号
|
||||
*/
|
||||
val buyerTaxpayerNum = varchar("buyer_taxpayer_num", 32)
|
||||
.nullable()
|
||||
val buyerTaxpayerNum = varchar("buyer_taxpayer_num", 32).nullable()
|
||||
|
||||
val buyerAddress = varchar("buyer_address", 255).nullable()
|
||||
|
||||
@@ -85,82 +83,36 @@ object InvoiceOrderTable : Table("invoice_order") {
|
||||
// 开票结果
|
||||
// =========================
|
||||
|
||||
/**
|
||||
* 发票号码
|
||||
*/
|
||||
val invoiceNo = varchar("invoice_no", 64).nullable()
|
||||
|
||||
/**
|
||||
* 发票代码
|
||||
*/
|
||||
val invoiceCode = varchar("invoice_code", 64).nullable()
|
||||
|
||||
/**
|
||||
* 数电票号码
|
||||
*/
|
||||
val electronicInvoiceNo = varchar("electronic_invoice_no", 64).nullable()
|
||||
|
||||
/**
|
||||
* 开票时间
|
||||
*/
|
||||
val issuedAt = timestampWithTimeZone("issued_at").nullable()
|
||||
|
||||
/**
|
||||
* 开票状态
|
||||
*/
|
||||
val status = varchar("status", 32) .default("PENDING")
|
||||
val status = varchar("status", 32).default("PENDING")
|
||||
|
||||
/**
|
||||
* 第三方平台返回错误
|
||||
*/
|
||||
val errorMessage = text("error_message").nullable()
|
||||
|
||||
/**
|
||||
* 原始请求报文
|
||||
*/
|
||||
val requestJson = text("request_json").nullable()
|
||||
|
||||
/**
|
||||
* 原始响应报文
|
||||
*/
|
||||
val responseJson = text("response_json").nullable()
|
||||
|
||||
// =========================
|
||||
// 文件
|
||||
// =========================
|
||||
|
||||
/**
|
||||
* PDF地址
|
||||
*/
|
||||
val pdfUrl = text("pdf_url").nullable()
|
||||
|
||||
/**
|
||||
* OFD地址
|
||||
*/
|
||||
val ofdUrl = text("ofd_url").nullable()
|
||||
|
||||
/**
|
||||
* XML地址
|
||||
*/
|
||||
val xmlUrl = text("xml_url").nullable()
|
||||
|
||||
// =========================
|
||||
// 业务字段
|
||||
// =========================
|
||||
|
||||
/**
|
||||
* 订单号
|
||||
*/
|
||||
val tradeNo = varchar("trade_no", 128).nullable()
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
val remark = text("remark").nullable()
|
||||
|
||||
/**
|
||||
* 自定义透传数据
|
||||
*/
|
||||
val definedData = text("defined_data").nullable()
|
||||
|
||||
// =========================
|
||||
@@ -178,4 +130,4 @@ object InvoiceOrderTable : Table("invoice_order") {
|
||||
val deletedAt = timestampWithTimeZone("deleted_at").nullable()
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.bbit.ticket.database.piaotong
|
||||
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
import org.jetbrains.exposed.v1.javatime.timestampWithTimeZone
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* 发票商品明细表(历史)
|
||||
* 存储发票中的商品信息明细
|
||||
*/
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
object HistoryInvoiceGoodsTable : Table("history_invoice_goods") {
|
||||
|
||||
val id = uuid("id").clientDefault { Uuid.Companion.random() }
|
||||
|
||||
/**
|
||||
* 关联历史发票基本信息ID
|
||||
*/
|
||||
val basicId = uuid("basic_id").references(HistoryInvoiceBasicTable.id)
|
||||
|
||||
/**
|
||||
* 行号
|
||||
*/
|
||||
val lineNo = integer("line_no")
|
||||
|
||||
/**
|
||||
* 商品名称
|
||||
*/
|
||||
val goodsName = varchar("goods_name", 200)
|
||||
|
||||
/**
|
||||
* 税收分类编码
|
||||
*/
|
||||
val taxClassificationCode = varchar("tax_classification_code", 64)
|
||||
|
||||
/**
|
||||
* 规格型号
|
||||
*/
|
||||
val specificationModel = varchar("specification_model", 100).nullable()
|
||||
|
||||
/**
|
||||
* 单位
|
||||
*/
|
||||
val meteringUnit = varchar("metering_unit", 32).nullable()
|
||||
|
||||
/**
|
||||
* 数量
|
||||
*/
|
||||
val quantity = decimal("quantity", 18, 8).nullable()
|
||||
|
||||
/**
|
||||
* 单价
|
||||
*/
|
||||
val unitPrice = decimal("unit_price", 18, 8).nullable()
|
||||
|
||||
/**
|
||||
* 金额
|
||||
*/
|
||||
val invoiceAmount = decimal("invoice_amount", 18, 2)
|
||||
|
||||
/**
|
||||
* 税率
|
||||
*/
|
||||
val taxRateValue = decimal("tax_rate_value", 8, 4)
|
||||
|
||||
/**
|
||||
* 税额
|
||||
*/
|
||||
val taxRateAmount = decimal("tax_rate_amount", 18, 2).nullable()
|
||||
|
||||
/**
|
||||
* 是否含税
|
||||
*/
|
||||
val includeTaxFlag = bool("include_tax_flag").default(false)
|
||||
|
||||
/**
|
||||
* 折扣金额
|
||||
*/
|
||||
val discountAmount = decimal("discount_amount", 18, 2).nullable()
|
||||
|
||||
/**
|
||||
* 零税率标识
|
||||
*/
|
||||
val zeroTaxFlag = varchar("zero_tax_flag", 8).nullable()
|
||||
|
||||
/**
|
||||
* 优惠政策标识
|
||||
*/
|
||||
val preferentialPolicyFlag = varchar("preferential_policy_flag", 8).nullable()
|
||||
|
||||
/**
|
||||
* 增值税特殊管理
|
||||
*/
|
||||
val vatSpecialManage = varchar("vat_special_manage", 100).nullable()
|
||||
|
||||
/**
|
||||
* 差额扣除金额
|
||||
*/
|
||||
val deductionAmount = decimal("deduction_amount", 18, 2).nullable()
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
val createdAt = timestampWithTimeZone("created_at")
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.bbit.ticket.database.piaotong
|
||||
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
import org.jetbrains.exposed.v1.javatime.timestampWithTimeZone
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* 发票关联单据表(历史)
|
||||
* 存储合并开票时关联的业务单据信息
|
||||
*/
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
object HistoryInvoiceOrderTable : Table("history_invoice_order") {
|
||||
|
||||
val id = uuid("id").clientDefault { Uuid.Companion.random() }
|
||||
|
||||
/**
|
||||
* 关联历史发票基本信息ID
|
||||
*/
|
||||
val basicId = uuid("basic_id").references(HistoryInvoiceBasicTable.id)
|
||||
|
||||
/**
|
||||
* 业务单据号
|
||||
*/
|
||||
val orderNo = varchar("order_no", 128)
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
val createdAt = timestampWithTimeZone("created_at")
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package com.bbit.ticket.database.piaotong
|
||||
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
import org.jetbrains.exposed.v1.javatime.timestampWithTimeZone
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* 差额征税凭证明细表(历史)
|
||||
* 存储差额征税时的凭证明细信息
|
||||
*/
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
object HistoryInvoiceVoucherTable : Table("history_invoice_voucher") {
|
||||
|
||||
val id = uuid("id").clientDefault { Uuid.Companion.random() }
|
||||
|
||||
/**
|
||||
* 关联历史发票基本信息ID
|
||||
*/
|
||||
val basicId = uuid("basic_id").references(HistoryInvoiceBasicTable.id)
|
||||
|
||||
/**
|
||||
* 凭证类型
|
||||
* 01:数电票;02:增值税专用发票;03:增值税普通发票
|
||||
* 04:营业税发票;05:财政票据;06:法院裁决书
|
||||
* 07:契税完税凭证;08:其他发票类;09:其他扣除凭证
|
||||
*/
|
||||
val proofType = varchar("proof_type", 8)
|
||||
|
||||
/**
|
||||
* 数电票号码(凭证类型为01时必填)
|
||||
*/
|
||||
val electronicInvoiceNo = varchar("electronic_invoice_no", 64).nullable()
|
||||
|
||||
/**
|
||||
* 发票代码(凭证类型02/03/04时必填)
|
||||
*/
|
||||
val invoiceCode = varchar("invoice_code", 64).nullable()
|
||||
|
||||
/**
|
||||
* 发票号码(凭证类型02/03/04时必填)
|
||||
*/
|
||||
val invoiceNo = varchar("invoice_no", 64).nullable()
|
||||
|
||||
/**
|
||||
* 凭证号码
|
||||
*/
|
||||
val proofNo = varchar("proof_no", 64).nullable()
|
||||
|
||||
/**
|
||||
* 开具日期(格式yyyy-MM-dd)
|
||||
*/
|
||||
val issueDate = varchar("issue_date", 16).nullable()
|
||||
|
||||
/**
|
||||
* 凭证合计金额
|
||||
*/
|
||||
val proofAmount = decimal("proof_amount", 18, 2)
|
||||
|
||||
/**
|
||||
* 本次扣除金额
|
||||
*/
|
||||
val deductionAmount = decimal("deduction_amount", 18, 2)
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
val proofRemark = text("proof_remark").nullable()
|
||||
|
||||
/**
|
||||
* 来源
|
||||
*/
|
||||
val his_source = varchar("source", 32).nullable()
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
val createdAt = timestampWithTimeZone("created_at")
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.bbit.ticket.database.piaotong
|
||||
|
||||
import com.bbit.ticket.database.piaotong.HistoryInvoiceBasicTable
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
import org.jetbrains.exposed.v1.javatime.timestampWithTimeZone
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
@@ -13,7 +14,7 @@ object InvoiceItemTable : Table("invoice_item") {
|
||||
/**
|
||||
* 发票ID
|
||||
*/
|
||||
val invoiceId = uuid("invoice_id").references(InvoiceOrderTable.id)
|
||||
val invoiceId = uuid("invoice_id").references(HistoryInvoiceBasicTable.id)
|
||||
|
||||
/**
|
||||
* 行号
|
||||
|
||||
+1
-2
@@ -1,13 +1,12 @@
|
||||
package com.bbit.ticket.entity.request
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* 数电发票开票请求
|
||||
*/
|
||||
@Serializable
|
||||
data class InvoiceRequest(
|
||||
data class AskInvoiceRequest(
|
||||
|
||||
/**
|
||||
* 销方纳税人识别号(销售方税号)
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.bbit.ticket.entity.request
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* 发票申请请求
|
||||
*/
|
||||
@Serializable
|
||||
data class QueryInvoiceRequest(
|
||||
|
||||
/**
|
||||
* 纳税人识别号
|
||||
*
|
||||
* 一般为企业税号、统一社会信用代码
|
||||
*/
|
||||
val taxpayerNum: String,
|
||||
|
||||
/**
|
||||
* 发票申请流水号
|
||||
*
|
||||
* 用于标识一次唯一的开票申请请求
|
||||
*/
|
||||
val invoiceReqSerialNo: String
|
||||
)
|
||||
@@ -0,0 +1,104 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package com.bbit.ticket.entity.response
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* 发票完整详情(含商品明细、差额征税凭证、关联单据)
|
||||
*/
|
||||
@Serializable
|
||||
data class InvoiceDetailResponse(
|
||||
// ===== 基本信息 =====
|
||||
val id: Uuid,
|
||||
val invoiceReqSerialNo: String,
|
||||
val taxpayerNum: String,
|
||||
val invoiceKindCode: String,
|
||||
val invoiceType: String,
|
||||
|
||||
// ===== 购买方信息 =====
|
||||
val buyerName: String,
|
||||
val buyerTaxpayerNum: String? = null,
|
||||
val buyerAddress: String? = null,
|
||||
val buyerTel: String? = null,
|
||||
val buyerBankName: String? = null,
|
||||
val buyerBankAccount: String? = null,
|
||||
|
||||
// ===== 金额 =====
|
||||
val amount: String,
|
||||
val taxAmount: String,
|
||||
val totalAmount: String,
|
||||
|
||||
// ===== 开票结果 =====
|
||||
val invoiceNo: String? = null,
|
||||
val invoiceCode: String? = null,
|
||||
val electronicInvoiceNo: String? = null,
|
||||
val issuedAt: String? = null,
|
||||
val status: String,
|
||||
val errorMessage: String? = null,
|
||||
|
||||
// ===== 文件 =====
|
||||
val pdfUrl: String? = null,
|
||||
val ofdUrl: String? = null,
|
||||
val xmlUrl: String? = null,
|
||||
|
||||
// ===== 业务字段 =====
|
||||
val tradeNo: String? = null,
|
||||
val remark: String? = null,
|
||||
val definedData: String? = null,
|
||||
|
||||
// ===== 审计 =====
|
||||
val createdAt: String,
|
||||
|
||||
// ===== 子表数据 =====
|
||||
/** 商品明细 */
|
||||
val goodsList: List<InvoiceDetailGoods>,
|
||||
/** 差额征税凭证明细 */
|
||||
val voucherList: List<InvoiceDetailVoucher>,
|
||||
/** 关联单据 */
|
||||
val orderList: List<InvoiceDetailOrder>,
|
||||
)
|
||||
|
||||
/**
|
||||
* 发票商品明细(详情)
|
||||
*/
|
||||
@Serializable
|
||||
data class InvoiceDetailGoods(
|
||||
val lineNo: Int,
|
||||
val goodsName: String,
|
||||
val taxClassificationCode: String,
|
||||
val specificationModel: String? = null,
|
||||
val meteringUnit: String? = null,
|
||||
val quantity: String? = null,
|
||||
val unitPrice: String? = null,
|
||||
val invoiceAmount: String,
|
||||
val taxRateValue: String,
|
||||
val taxRateAmount: String? = null,
|
||||
val includeTaxFlag: Boolean = false,
|
||||
)
|
||||
|
||||
/**
|
||||
* 差额征税凭证明细(详情)
|
||||
*/
|
||||
@Serializable
|
||||
data class InvoiceDetailVoucher(
|
||||
val proofType: String,
|
||||
val electronicInvoiceNo: String? = null,
|
||||
val invoiceCode: String? = null,
|
||||
val invoiceNo: String? = null,
|
||||
val proofNo: String? = null,
|
||||
val issueDate: String? = null,
|
||||
val proofAmount: String,
|
||||
val deductionAmount: String,
|
||||
val proofRemark: String? = null,
|
||||
val source: String? = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* 关联单据(详情)
|
||||
*/
|
||||
@Serializable
|
||||
data class InvoiceDetailOrder(
|
||||
val orderNo: String,
|
||||
)
|
||||
@@ -0,0 +1,77 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package com.bbit.ticket.entity.response
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* 发票历史记录(列表页展示项)
|
||||
*/
|
||||
@Serializable
|
||||
data class InvoiceHistoryItem(
|
||||
val id: Uuid,
|
||||
|
||||
/** 发票请求流水号 */
|
||||
val invoiceReqSerialNo: String,
|
||||
/** 销方税号 */
|
||||
val taxpayerNum: String,
|
||||
/** 发票种类 */
|
||||
val invoiceKindCode: String,
|
||||
|
||||
// ===== 购买方信息 =====
|
||||
/** 购买方名称 */
|
||||
val buyerName: String,
|
||||
/** 购买方税号 */
|
||||
val buyerTaxpayerNum: String? = null,
|
||||
/** 购买方地址 */
|
||||
val buyerAddress: String? = null,
|
||||
/** 购买方电话 */
|
||||
val buyerTel: String? = null,
|
||||
/** 购买方开户银行 */
|
||||
val buyerBankName: String? = null,
|
||||
/** 购买方银行账号 */
|
||||
val buyerBankAccount: String? = null,
|
||||
|
||||
// ===== 金额 =====
|
||||
/** 不含税金额 */
|
||||
val amount: String,
|
||||
/** 税额 */
|
||||
val taxAmount: String,
|
||||
/** 含税总金额 */
|
||||
val totalAmount: String,
|
||||
|
||||
// ===== 开票结果 =====
|
||||
/** 发票号码 */
|
||||
val invoiceNo: String? = null,
|
||||
/** 发票代码 */
|
||||
val invoiceCode: String? = null,
|
||||
/** 数电票号码 */
|
||||
val electronicInvoiceNo: String? = null,
|
||||
/** 开票时间 */
|
||||
val issuedAt: String? = null,
|
||||
/** 开票状态 */
|
||||
val status: String,
|
||||
|
||||
// ===== 文件 =====
|
||||
/** PDF 地址 */
|
||||
val pdfUrl: String? = null,
|
||||
/** OFD 地址 */
|
||||
val ofdUrl: String? = null,
|
||||
/** XML 地址 */
|
||||
val xmlUrl: String? = null,
|
||||
|
||||
// ===== 业务字段 =====
|
||||
/** 订单号 */
|
||||
val tradeNo: String? = null,
|
||||
/** 备注 */
|
||||
val remark: String? = null,
|
||||
/** 自定义透传数据 */
|
||||
val definedData: String? = null,
|
||||
|
||||
// ===== 审计字段 =====
|
||||
/** 创建时间 */
|
||||
val createdAt: String,
|
||||
/** 错误信息 */
|
||||
val errorMessage: String? = null,
|
||||
)
|
||||
@@ -0,0 +1,227 @@
|
||||
package com.bbit.ticket.entity.response
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* 发票开具结果响应
|
||||
*/
|
||||
|
||||
@Serializable
|
||||
data class QueryInvoiceResponse(
|
||||
|
||||
/**
|
||||
* 销售方纳税人识别号
|
||||
*/
|
||||
@SerialName("taxpayerNum")
|
||||
val taxpayerNum: String,
|
||||
|
||||
/**
|
||||
* 发票请求流水号
|
||||
* 用于关联本次开票请求
|
||||
*/
|
||||
@SerialName("invoiceReqSerialNo")
|
||||
val invoiceReqSerialNo: String,
|
||||
|
||||
/**
|
||||
* 开票类型
|
||||
*
|
||||
* 1: 蓝票
|
||||
* 2: 红票
|
||||
*/
|
||||
@SerialName("invoiceType")
|
||||
val invoiceType: String? = null,
|
||||
|
||||
|
||||
/**
|
||||
* 发票种类代码
|
||||
*
|
||||
* 81: 数电票(增值税专用发票)
|
||||
* 82: 数电票(普通发票)
|
||||
* 83: 数电票(机动车销售统一发票)
|
||||
* 10: 增值税电子普通发票
|
||||
* 08: 增值税电子专用发票
|
||||
*/
|
||||
@SerialName("invoiceKind")
|
||||
val invoiceKind: String,
|
||||
|
||||
/**
|
||||
* 发票状态码
|
||||
*
|
||||
* 0000: 开票成功
|
||||
* 6666: 未开票
|
||||
* 7777: 开票中
|
||||
* 9999: 开票失败
|
||||
* 3999: 开票失败,需要扫码或短信认证
|
||||
* 4999: 红字发票确认单申请中
|
||||
* 5999: 红字发票确认单审核中
|
||||
*/
|
||||
@SerialName("code")
|
||||
val code: String,
|
||||
|
||||
/**
|
||||
* 发票状态描述
|
||||
* 一般为成功提示或失败原因
|
||||
*/
|
||||
@SerialName("msg")
|
||||
val msg: String,
|
||||
|
||||
/**
|
||||
* 数电票开票通道
|
||||
*
|
||||
* 0: RPA电子税局开具
|
||||
* 1: 乐企自用
|
||||
* 4: 乐企联用(腾讯)
|
||||
* 5: 乐企联用(支付宝)
|
||||
*/
|
||||
@SerialName("invIssueChannel")
|
||||
val invIssueChannel: String? = null,
|
||||
|
||||
/**
|
||||
* 开票人税局账号
|
||||
* 一般为手机号或身份证号
|
||||
*/
|
||||
@SerialName("account")
|
||||
val account: String? = null,
|
||||
|
||||
/**
|
||||
* 实名认证二维码图片
|
||||
* Base64字符串
|
||||
*/
|
||||
@SerialName("authenticationQrcode")
|
||||
val authenticationQrcode: String? = null,
|
||||
|
||||
/**
|
||||
* 实名认证二维码认证ID
|
||||
*/
|
||||
@SerialName("authId")
|
||||
val authId: String? = null,
|
||||
|
||||
/**
|
||||
* 订单号
|
||||
* 开票成功时通常会返回
|
||||
*/
|
||||
@SerialName("tradeNo")
|
||||
val tradeNo: String? = null,
|
||||
|
||||
/**
|
||||
* 自定义透传数据
|
||||
* 通常用于业务侧关联
|
||||
*/
|
||||
@SerialName("definedData")
|
||||
val definedData: String? = null,
|
||||
|
||||
/**
|
||||
* 发票二维码内容
|
||||
*/
|
||||
@SerialName("qrCode")
|
||||
val qrCode: String? = null,
|
||||
|
||||
/**
|
||||
* 发票代码
|
||||
*/
|
||||
@SerialName("invoiceCode")
|
||||
val invoiceCode: String? = null,
|
||||
|
||||
/**
|
||||
* 发票号码
|
||||
*/
|
||||
@SerialName("invoiceNo")
|
||||
val invoiceNo: String? = null,
|
||||
|
||||
/**
|
||||
* 数电发票号码
|
||||
* 通常 = 发票代码 + 发票号码
|
||||
*/
|
||||
@SerialName("electronicInvoiceNo")
|
||||
val electronicInvoiceNo: String? = null,
|
||||
|
||||
/**
|
||||
* 开票日期
|
||||
* 格式: yyyy-MM-dd HH:mm:ss
|
||||
*/
|
||||
@SerialName("invoiceDate")
|
||||
val invoiceDate: String? = null,
|
||||
|
||||
/**
|
||||
* 不含税金额
|
||||
* 单位:元
|
||||
* 保留两位小数
|
||||
*/
|
||||
@SerialName("noTaxAmount")
|
||||
val noTaxAmount: String? = null,
|
||||
|
||||
/**
|
||||
* 税额
|
||||
* 单位:元
|
||||
* 保留两位小数
|
||||
*/
|
||||
@SerialName("taxAmount")
|
||||
val taxAmount: String? = null,
|
||||
|
||||
/**
|
||||
* 发票版式文件类型
|
||||
*
|
||||
* pdf: PDF文件
|
||||
* ofd: OFD文件
|
||||
*/
|
||||
@SerialName("invoiceLayoutFileType")
|
||||
val invoiceLayoutFileType: String? = null,
|
||||
|
||||
/**
|
||||
* 发票版式文件
|
||||
* Base64编码后的文件流
|
||||
*/
|
||||
@SerialName("invoicePdf")
|
||||
val invoicePdf: String? = null,
|
||||
|
||||
/**
|
||||
* 发票XML文件
|
||||
* Base64字符串
|
||||
*/
|
||||
@SerialName("invoiceXml")
|
||||
val invoiceXml: String? = null,
|
||||
|
||||
/**
|
||||
* 发票下载地址
|
||||
*/
|
||||
@SerialName("downloadUrl")
|
||||
val downloadUrl: String? = null,
|
||||
|
||||
/**
|
||||
* 收费明细PDF文件
|
||||
* 常用于门诊/住院发票
|
||||
* Base64字符串
|
||||
*/
|
||||
@SerialName("chargeDetailPdf")
|
||||
val chargeDetailPdf: String? = null,
|
||||
|
||||
/**
|
||||
* 收费明细PDF下载地址
|
||||
*/
|
||||
@SerialName("chargeDetailDownloadUrl")
|
||||
val chargeDetailDownloadUrl: String? = null,
|
||||
|
||||
/**
|
||||
* 电子发票预览二维码URL
|
||||
* 用于扫码查看电子发票
|
||||
*/
|
||||
@SerialName("invPreviewQrcodePath")
|
||||
val invPreviewQrcodePath: String? = null,
|
||||
|
||||
/**
|
||||
* 电子发票预览二维码图片
|
||||
* Base64字符串
|
||||
*/
|
||||
@SerialName("invPreviewQrcode")
|
||||
val invPreviewQrcode: String? = null,
|
||||
|
||||
/**
|
||||
* 发票删除标志
|
||||
*
|
||||
* 0: 未删除
|
||||
* 1: 已删除
|
||||
*/
|
||||
@SerialName("invDeletedFlag")
|
||||
val invDeletedFlag: String? = null,
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.bbit.ticket.entity.response
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* 查询并刷新发票状态的结果
|
||||
*/
|
||||
@Serializable
|
||||
data class QueryInvoiceResult(
|
||||
/** 发票请求流水号 */
|
||||
val invoiceReqSerialNo: String,
|
||||
/** 刷新后的状态 */
|
||||
val status: String,
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.bbit.ticket.plugins
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
|
||||
val myJson = Json {
|
||||
explicitNulls = false
|
||||
ignoreUnknownKeys = true
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
}
|
||||
@@ -2,13 +2,11 @@
|
||||
|
||||
package com.bbit.ticket.route.piaotong
|
||||
|
||||
import com.bbit.ticket.bootstrap.Global
|
||||
import com.bbit.ticket.entity.common.PTException
|
||||
import com.bbit.ticket.entity.common.fail
|
||||
import com.bbit.ticket.entity.common.ok
|
||||
import com.bbit.ticket.entity.common.BizException
|
||||
import com.bbit.ticket.entity.common.ErrorCode
|
||||
import com.bbit.ticket.entity.request.InvoiceRequest
|
||||
import com.bbit.ticket.entity.request.AskInvoiceRequest
|
||||
import com.bbit.ticket.entity.request.QueryInvoiceRequest
|
||||
import com.bbit.ticket.entity.request.TaxBureauAuthReq
|
||||
import com.bbit.ticket.entity.request.TaxRegisterInfo
|
||||
import com.bbit.ticket.entity.request.TaxRegisterUserRequest
|
||||
@@ -18,7 +16,6 @@ import com.bbit.ticket.entity.request.UpdatePresetDataRequest
|
||||
import com.bbit.ticket.service.piaotong.PTAuthService
|
||||
import com.bbit.ticket.service.piaotong.PTConfigService
|
||||
import com.bbit.ticket.utils.requireCurrentUser
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.auth.authenticate
|
||||
import io.ktor.server.request.receive
|
||||
import io.ktor.server.response.respond
|
||||
@@ -168,7 +165,7 @@ fun Route.registerPTTestRoutes() {
|
||||
post("/invoiceBlue") {
|
||||
try {
|
||||
val currentUser = call.requireCurrentUser()
|
||||
val req = call.receive<InvoiceRequest>()
|
||||
val req = call.receive<AskInvoiceRequest>()
|
||||
val response = PTAuthService.invoiceBlue(req, currentUser.id)
|
||||
call.respond(ok(response))
|
||||
} catch (e: PTException) {
|
||||
@@ -181,6 +178,59 @@ fun Route.registerPTTestRoutes() {
|
||||
)
|
||||
}
|
||||
}
|
||||
get("/invoiceBlueHistory") {
|
||||
try {
|
||||
val currentUser = call.requireCurrentUser()
|
||||
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1
|
||||
val pageSize = call.request.queryParameters["pageSize"]?.toIntOrNull() ?: 20
|
||||
val response = PTAuthService.getInvoiceBlueHistory(currentUser.id, page, pageSize)
|
||||
call.respond(ok(response))
|
||||
} catch (e: Exception) {
|
||||
call.respond(fail(code = "-1", message = e.message ?: "查询开票历史失败"))
|
||||
}
|
||||
}
|
||||
|
||||
get("/invoiceDetail") {
|
||||
try {
|
||||
val currentUser = call.requireCurrentUser()
|
||||
val invoiceReqSerialNo = call.request.queryParameters["invoiceReqSerialNo"]
|
||||
if (invoiceReqSerialNo.isNullOrBlank()) {
|
||||
call.respond(fail(code = "-1", message = "请传入发票请求流水号"))
|
||||
return@get
|
||||
}
|
||||
val response = PTAuthService.getInvoiceDetail(currentUser.id, invoiceReqSerialNo)
|
||||
if (response == null) {
|
||||
call.respond(fail(code = "-1", message = "未找到该发票记录"))
|
||||
return@get
|
||||
}
|
||||
call.respond(ok(response))
|
||||
} catch (e: Exception) {
|
||||
call.respond(fail(code = "-1", message = e.message ?: "查询发票详情失败"))
|
||||
}
|
||||
}
|
||||
get("/queryInvoice"){
|
||||
try {
|
||||
val currentUser = call.requireCurrentUser()
|
||||
val invoiceReqSerialNo = call.request.queryParameters["invoiceReqSerialNo"]
|
||||
if (invoiceReqSerialNo.isNullOrBlank()) {
|
||||
call.respond(fail(code = "-1", message = "请传入发票请求流水号"))
|
||||
return@get
|
||||
}
|
||||
val response = PTAuthService.queryInvoice(QueryInvoiceRequest(
|
||||
taxpayerNum = currentUser.taxPayerNum ?: "",
|
||||
invoiceReqSerialNo = invoiceReqSerialNo
|
||||
))
|
||||
call.respond(ok(response))
|
||||
} catch (e: PTException) {
|
||||
call.respond(
|
||||
fail(
|
||||
code = e.code,
|
||||
message = e.message,
|
||||
traceId = e.serialNo
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ fun Route.registerDictRoutes() {
|
||||
}
|
||||
}
|
||||
}
|
||||
route("/api/system/dict-items") {
|
||||
route("/system/dict-items") {
|
||||
get {
|
||||
call.requirePermission("system:dict:view")
|
||||
val page = call.queryInt("page", 1)
|
||||
|
||||
@@ -3,18 +3,25 @@
|
||||
package com.bbit.ticket.service.piaotong
|
||||
|
||||
import com.bbit.ticket.dao.piaotong.EnterpriseTaxDao
|
||||
import com.bbit.ticket.entity.request.InvoiceRequest
|
||||
import com.bbit.ticket.entity.common.PageResult
|
||||
import com.bbit.ticket.entity.request.AskInvoiceRequest
|
||||
import com.bbit.ticket.entity.request.QueryInvoiceRequest
|
||||
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.request.TaxRegisterUserRequest
|
||||
import com.bbit.ticket.entity.response.QueryInvoiceResult
|
||||
import com.bbit.ticket.entity.response.EnterpriseTaxInfo
|
||||
import com.bbit.ticket.entity.response.EtaxRegisterResponse
|
||||
import com.bbit.ticket.entity.response.InvoiceCreateResponse
|
||||
import com.bbit.ticket.entity.response.InvoiceDetailResponse
|
||||
import com.bbit.ticket.entity.response.InvoiceHistoryItem
|
||||
import com.bbit.ticket.entity.response.QueryInvoiceResponse
|
||||
import com.bbit.ticket.entity.response.TaxBureauAccountAuthContent
|
||||
import com.bbit.ticket.plugins.dbQuery
|
||||
import com.bbit.ticket.utils.CurrentUser
|
||||
import com.bbit.ticket.utils.net.PTClient
|
||||
import io.ktor.server.util.url
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
object PTAuthService {
|
||||
@@ -62,28 +69,73 @@ object PTAuthService {
|
||||
body = req
|
||||
)
|
||||
dbQuery { EnterpriseTaxDao.updateEnterpriseInfo(userId, req) }
|
||||
return "操作成功,企业状态为审核中(待审核)"
|
||||
return "操作成功"
|
||||
}
|
||||
/**
|
||||
* 蓝票接口调用
|
||||
*/
|
||||
suspend fun invoiceBlue(req: InvoiceRequest, userId: Uuid): String {
|
||||
PTClient.ptPost<InvoiceRequest, InvoiceCreateResponse>(
|
||||
suspend fun invoiceBlue(req: AskInvoiceRequest, userId: Uuid): String {
|
||||
PTClient.ptPost<AskInvoiceRequest, InvoiceCreateResponse>(
|
||||
url = "invoiceBlue.pt",
|
||||
body = req
|
||||
)
|
||||
dbQuery { EnterpriseTaxDao.addInvoice(userId, req) }
|
||||
return "操作成功,企业状态为审核中(待审核)"
|
||||
return "操作成功"
|
||||
}
|
||||
/**
|
||||
* 红票接口调用
|
||||
*/
|
||||
suspend fun invoiceRed(req: InvoiceRequest, userId: Uuid): String {
|
||||
PTClient.ptPost<InvoiceRequest, InvoiceCreateResponse>(
|
||||
suspend fun invoiceRed(req: AskInvoiceRequest, userId: Uuid): String {
|
||||
PTClient.ptPost<AskInvoiceRequest, InvoiceCreateResponse>(
|
||||
url = "invoiceBlue.pt",
|
||||
body = req
|
||||
)
|
||||
dbQuery { EnterpriseTaxDao.addInvoice(userId, req) }
|
||||
return "操作成功,企业状态为审核中(待审核)"
|
||||
return "操作成功"
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询蓝票开票历史
|
||||
*/
|
||||
suspend fun getInvoiceBlueHistory(userId: Uuid, page: Int, pageSize: Int): PageResult<InvoiceHistoryItem> =
|
||||
dbQuery { EnterpriseTaxDao.invoiceHistory(userId, page, pageSize) }
|
||||
|
||||
/**
|
||||
* 查询发票完整详情
|
||||
*/
|
||||
suspend fun getInvoiceDetail(userId: Uuid, invoiceReqSerialNo: String): InvoiceDetailResponse? =
|
||||
dbQuery { EnterpriseTaxDao.invoiceDetail(userId, invoiceReqSerialNo) }
|
||||
|
||||
/**
|
||||
* 查询并更新发票状态
|
||||
* 调用票通平台查询实时状态,将结果更新到本地数据库
|
||||
*/
|
||||
suspend fun queryInvoice(req: QueryInvoiceRequest): QueryInvoiceResult {
|
||||
val res = PTClient.ptPost<QueryInvoiceRequest, QueryInvoiceResponse>(
|
||||
url = "queryInvoice.pt",
|
||||
body = req
|
||||
)
|
||||
dbQuery {
|
||||
EnterpriseTaxDao.updateInvoiceStatus(
|
||||
invoiceReqSerialNo = req.invoiceReqSerialNo,
|
||||
code = res.code,
|
||||
msg = res.msg,
|
||||
invoiceNo = res.invoiceNo,
|
||||
invoiceCode = res.invoiceCode,
|
||||
electronicInvoiceNo = res.electronicInvoiceNo,
|
||||
invoiceDate = res.invoiceDate,
|
||||
tradeNo = res.tradeNo,
|
||||
)
|
||||
}
|
||||
val newStatus = when (res.code) {
|
||||
"0000" -> "SUCCESS"
|
||||
"7777" -> "PROCESSING"
|
||||
"9999" -> "FAILED"
|
||||
"6666" -> "PENDING"
|
||||
else -> "PROCESSING"
|
||||
}
|
||||
return QueryInvoiceResult(
|
||||
invoiceReqSerialNo = req.invoiceReqSerialNo,
|
||||
status = newStatus
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.bbit.ticket.utils.net
|
||||
import com.bbit.ticket.bootstrap.Global
|
||||
import com.bbit.ticket.entity.common.PTException
|
||||
import com.bbit.ticket.entity.response.PTResponse
|
||||
import com.bbit.ticket.plugins.myJson
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.engine.cio.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
@@ -24,14 +25,7 @@ object PTClient {
|
||||
val client = HttpClient(CIO) {
|
||||
|
||||
install(ContentNegotiation) {
|
||||
json(
|
||||
Json {
|
||||
explicitNulls = false
|
||||
ignoreUnknownKeys = true
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
}
|
||||
)
|
||||
json(myJson)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +42,8 @@ object PTClient {
|
||||
|
||||
url {
|
||||
// ⚠️ 把 Req 转成 JSON 再拆成 query(统一协议口径)
|
||||
val json = Json.encodeToString(queryParams)
|
||||
val element = Json.parseToJsonElement(json).jsonObject
|
||||
val json = myJson.encodeToString(queryParams)
|
||||
val element = myJson.parseToJsonElement(json).jsonObject
|
||||
|
||||
element.forEach { (k, v) ->
|
||||
parameters.append(k, v.toString().trim('"'))
|
||||
@@ -63,7 +57,7 @@ object PTClient {
|
||||
|
||||
val decrypted = disposeResponse(response)
|
||||
|
||||
val result = Json.decodeFromString<PTResponse<JsonElement>>(decrypted)
|
||||
val result = myJson.decodeFromString<PTResponse<JsonElement>>(decrypted)
|
||||
|
||||
if (result.code != "0000") {
|
||||
throw PTException(
|
||||
@@ -73,7 +67,7 @@ object PTClient {
|
||||
)
|
||||
}
|
||||
|
||||
return Json.decodeFromJsonElement(result.content!!)
|
||||
return myJson.decodeFromJsonElement(result.content!!)
|
||||
}
|
||||
|
||||
suspend inline fun <reified Req, reified Resp> ptPost(
|
||||
@@ -85,11 +79,11 @@ object PTClient {
|
||||
val response = client.post(Global.baseUrl + url) {
|
||||
contentType(ContentType.Application.Json)
|
||||
headers.forEach { (k, v) -> header(k, v) }
|
||||
setBody(buildRequestData(Json.encodeToString(body)))
|
||||
setBody(buildRequestData(myJson.encodeToString(body)))
|
||||
}.bodyAsText()
|
||||
|
||||
val decrypted = disposeResponse(response)
|
||||
val result = Json.decodeFromString<PTResponse<JsonElement>>(decrypted)
|
||||
val result = myJson.decodeFromString<PTResponse<JsonElement>>(decrypted)
|
||||
if (result.code != "0000") {
|
||||
throw PTException(
|
||||
code = result.code,
|
||||
@@ -99,7 +93,7 @@ object PTClient {
|
||||
}
|
||||
|
||||
println("res = $result.content")
|
||||
return Json.decodeFromJsonElement<Resp>(result.content!!)
|
||||
return myJson.decodeFromJsonElement<Resp>(result.content!!)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,14 +117,14 @@ object PTClient {
|
||||
map["timestamp"] = sdf.format(Date())
|
||||
map["serialNo"] = ptDate()
|
||||
map["sign"] = RSAUtil.sign(RSAUtil.getSignatureContent(map), Global.ptPrivateKey) ?: ""
|
||||
return Json.encodeToString(map)
|
||||
return myJson.encodeToString(map)
|
||||
}
|
||||
|
||||
fun disposeResponse(
|
||||
jsonStr: String,
|
||||
): String {
|
||||
|
||||
val json = Json.parseToJsonElement(jsonStr).jsonObject
|
||||
val json = myJson.parseToJsonElement(jsonStr).jsonObject
|
||||
// 1. 转 Map(用于签名验证)
|
||||
val mutableMap = json
|
||||
.toMutableMap()
|
||||
@@ -161,7 +155,7 @@ object PTClient {
|
||||
?: "{}"
|
||||
|
||||
val contentElement = runCatching {
|
||||
Json.parseToJsonElement(plainContent)
|
||||
myJson.parseToJsonElement(plainContent)
|
||||
}.getOrElse {
|
||||
JsonObject(emptyMap())
|
||||
}
|
||||
|
||||
@@ -335,3 +335,193 @@ export interface InvoiceRequest {
|
||||
export function invoiceIssueApi(payload: InvoiceRequest): Promise<string> {
|
||||
return http.post('/pt/invoiceBlue', payload)
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// 开票历史
|
||||
// =============================================
|
||||
|
||||
/** 分页结果 */
|
||||
export interface PageResult<T> {
|
||||
items: T[]
|
||||
page: number
|
||||
pageSize: number
|
||||
total: number
|
||||
}
|
||||
|
||||
/** 发票历史记录 */
|
||||
export interface InvoiceHistoryItem {
|
||||
id: string
|
||||
/** 发票请求流水号 */
|
||||
invoiceReqSerialNo: string
|
||||
/** 销方税号 */
|
||||
taxpayerNum: string
|
||||
/** 发票种类 */
|
||||
invoiceKindCode: string
|
||||
/** 购买方名称 */
|
||||
buyerName: string
|
||||
/** 购买方税号 */
|
||||
buyerTaxpayerNum?: string
|
||||
/** 购买方地址 */
|
||||
buyerAddress?: string
|
||||
/** 购买方电话 */
|
||||
buyerTel?: string
|
||||
/** 购买方开户银行 */
|
||||
buyerBankName?: string
|
||||
/** 购买方银行账号 */
|
||||
buyerBankAccount?: string
|
||||
/** 不含税金额 */
|
||||
amount: string
|
||||
/** 税额 */
|
||||
taxAmount: string
|
||||
/** 含税总金额 */
|
||||
totalAmount: string
|
||||
/** 发票号码 */
|
||||
invoiceNo?: string
|
||||
/** 发票代码 */
|
||||
invoiceCode?: string
|
||||
/** 数电票号码 */
|
||||
electronicInvoiceNo?: string
|
||||
/** 开票时间 */
|
||||
issuedAt?: string
|
||||
/** 开票状态 */
|
||||
status: string
|
||||
/** PDF 地址 */
|
||||
pdfUrl?: string
|
||||
/** OFD 地址 */
|
||||
ofdUrl?: string
|
||||
/** XML 地址 */
|
||||
xmlUrl?: string
|
||||
/** 订单号 */
|
||||
tradeNo?: string
|
||||
/** 备注 */
|
||||
remark?: string
|
||||
/** 自定义透传数据 */
|
||||
definedData?: string
|
||||
/** 创建时间 */
|
||||
createdAt: string
|
||||
/** 错误信息 */
|
||||
errorMessage?: string
|
||||
}
|
||||
|
||||
/** 发票种类映射 */
|
||||
export const invoiceKindMap: Record<string, string> = {
|
||||
'81': '数电专票',
|
||||
'82': '数电普票',
|
||||
'87': '机动车发票',
|
||||
'10': '电子普票',
|
||||
'08': '电子专票',
|
||||
'04': '增值税普票',
|
||||
'01': '增值税专票'
|
||||
}
|
||||
|
||||
/** 开票状态映射 */
|
||||
export const invoiceStatusMap: Record<string, string> = {
|
||||
'PENDING': '待处理',
|
||||
'PROCESSING': '处理中',
|
||||
'SUCCESS': '开票成功',
|
||||
'FAILED': '开票失败'
|
||||
}
|
||||
|
||||
/** 开票状态颜色映射 */
|
||||
export const invoiceStatusColorMap: Record<string, string> = {
|
||||
'PENDING': '#faad14',
|
||||
'PROCESSING': '#409eff',
|
||||
'SUCCESS': '#52c41a',
|
||||
'FAILED': '#f56c6c'
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询蓝票开票历史
|
||||
*/
|
||||
export function invoiceHistoryApi(page: number, pageSize: number): Promise<PageResult<InvoiceHistoryItem>> {
|
||||
return http.get('/pt/invoiceBlueHistory', { params: { page, pageSize } })
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// 发票详情
|
||||
// =============================================
|
||||
|
||||
/** 发票商品明细(详情) */
|
||||
export interface InvoiceDetailGoods {
|
||||
lineNo: number
|
||||
goodsName: string
|
||||
taxClassificationCode: string
|
||||
specificationModel?: string
|
||||
meteringUnit?: string
|
||||
quantity?: string
|
||||
unitPrice?: string
|
||||
invoiceAmount: string
|
||||
taxRateValue: string
|
||||
taxRateAmount?: string
|
||||
includeTaxFlag: boolean
|
||||
}
|
||||
|
||||
/** 差额征税凭证明细(详情) */
|
||||
export interface InvoiceDetailVoucher {
|
||||
proofType: string
|
||||
electronicInvoiceNo?: string
|
||||
invoiceCode?: string
|
||||
invoiceNo?: string
|
||||
proofNo?: string
|
||||
issueDate?: string
|
||||
proofAmount: string
|
||||
deductionAmount: string
|
||||
proofRemark?: string
|
||||
source?: string
|
||||
}
|
||||
|
||||
/** 关联单据(详情) */
|
||||
export interface InvoiceDetailOrder {
|
||||
orderNo: string
|
||||
}
|
||||
|
||||
/** 发票完整详情 */
|
||||
export interface InvoiceDetailResponse {
|
||||
id: string
|
||||
invoiceReqSerialNo: string
|
||||
taxpayerNum: string
|
||||
invoiceKindCode: string
|
||||
invoiceType: string
|
||||
buyerName: string
|
||||
buyerTaxpayerNum?: string
|
||||
buyerAddress?: string
|
||||
buyerTel?: string
|
||||
buyerBankName?: string
|
||||
buyerBankAccount?: string
|
||||
amount: string
|
||||
taxAmount: string
|
||||
totalAmount: string
|
||||
invoiceNo?: string
|
||||
invoiceCode?: string
|
||||
electronicInvoiceNo?: string
|
||||
issuedAt?: string
|
||||
status: string
|
||||
errorMessage?: string
|
||||
pdfUrl?: string
|
||||
ofdUrl?: string
|
||||
xmlUrl?: string
|
||||
tradeNo?: string
|
||||
remark?: string
|
||||
definedData?: string
|
||||
createdAt: string
|
||||
/** 商品明细 */
|
||||
goodsList: InvoiceDetailGoods[]
|
||||
/** 差额征税凭证明细 */
|
||||
voucherList: InvoiceDetailVoucher[]
|
||||
/** 关联单据 */
|
||||
orderList: InvoiceDetailOrder[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询发票完整详情
|
||||
*/
|
||||
export function invoiceDetailApi(invoiceReqSerialNo: string): Promise<InvoiceDetailResponse> {
|
||||
return http.get('/pt/invoiceDetail', { params: { invoiceReqSerialNo } })
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询并刷新发票状态
|
||||
*/
|
||||
export function queryInvoiceApi(invoiceReqSerialNo: string): Promise<{ invoiceReqSerialNo: string; status: string }> {
|
||||
return http.get('/pt/queryInvoice', { params: { invoiceReqSerialNo } })
|
||||
}
|
||||
|
||||
@@ -1,35 +1,447 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="placeholder">
|
||||
<h2>开票历史</h2>
|
||||
<p>功能开发中,敬请期待</p>
|
||||
<div class="page-header">
|
||||
<h2 class="page-title">开票历史</h2>
|
||||
</div>
|
||||
|
||||
<div class="search-bar">
|
||||
<n-input
|
||||
v-model:value="query.keyword"
|
||||
placeholder="搜索购买方名称 / 流水号 / 发票号码"
|
||||
clearable
|
||||
style="width: 320px"
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
<n-button type="primary" @click="handleSearch">查询</n-button>
|
||||
<n-button @click="handleReset">重置</n-button>
|
||||
</div>
|
||||
|
||||
<n-data-table
|
||||
:columns="columns"
|
||||
:data="dataSource"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:bordered="true"
|
||||
:single-line="false"
|
||||
size="small"
|
||||
striped
|
||||
class="history-table"
|
||||
@update:page="handlePageChange"
|
||||
@update:page-size="handlePageSizeChange"
|
||||
/>
|
||||
|
||||
<!-- 查看详情弹窗 -->
|
||||
<n-modal
|
||||
v-model:show="showDetail"
|
||||
preset="card"
|
||||
title="发票详情"
|
||||
style="width: 900px; max-width: 92vw"
|
||||
:mask-closable="false"
|
||||
>
|
||||
<template v-if="detailItem">
|
||||
<n-spin :show="detailLoading">
|
||||
<!-- ===== 基本信息 ===== -->
|
||||
<n-descriptions :column="2" bordered size="small" label-placement="left">
|
||||
<n-descriptions-item label="发票请求流水号" span="2">
|
||||
{{ detailItem.invoiceReqSerialNo }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="销方税号">{{ detailItem.taxpayerNum }}</n-descriptions-item>
|
||||
<n-descriptions-item label="发票种类">{{ invoiceKindMap[detailItem.invoiceKindCode] || detailItem.invoiceKindCode }}</n-descriptions-item>
|
||||
<n-descriptions-item label="购买方名称">{{ detailItem.buyerName }}</n-descriptions-item>
|
||||
<n-descriptions-item label="购买方税号">{{ detailItem.buyerTaxpayerNum || '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item label="购买方地址">{{ detailItem.buyerAddress || '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item label="购买方电话">{{ detailItem.buyerTel || '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item label="购买方开户银行">{{ detailItem.buyerBankName || '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item label="购买方银行账号">{{ detailItem.buyerBankAccount || '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item label="不含税金额">{{ detailItem.amount }}</n-descriptions-item>
|
||||
<n-descriptions-item label="税额">{{ detailItem.taxAmount }}</n-descriptions-item>
|
||||
<n-descriptions-item label="含税总金额">
|
||||
<span style="font-weight: 600; color: #d4380d">{{ detailItem.totalAmount }}</span>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="发票号码">{{ detailItem.invoiceNo || '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item label="发票代码">{{ detailItem.invoiceCode || '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item label="数电票号码">{{ detailItem.electronicInvoiceNo || '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item label="开票时间">{{ detailItem.issuedAt || '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item label="状态">
|
||||
<n-tag :type="statusTagType(detailItem.status)" size="small">
|
||||
{{ invoiceStatusMap[detailItem.status] || detailItem.status }}
|
||||
</n-tag>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="订单号">{{ detailItem.tradeNo || '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item label="备注" span="2">{{ detailItem.remark || '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item v-if="detailItem.errorMessage" label="错误信息" span="2">
|
||||
<span style="color: #f56c6c">{{ detailItem.errorMessage }}</span>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="创建时间">{{ detailItem.createdAt }}</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
|
||||
<!-- 文件下载 -->
|
||||
<div v-if="detailItem.pdfUrl || detailItem.ofdUrl || detailItem.xmlUrl" class="file-links">
|
||||
<span class="file-links-label">文件下载:</span>
|
||||
<n-button v-if="detailItem.pdfUrl" text tag="a" :href="detailItem.pdfUrl" target="_blank" type="primary">PDF</n-button>
|
||||
<n-button v-if="detailItem.ofdUrl" text tag="a" :href="detailItem.ofdUrl" target="_blank" type="primary">OFD</n-button>
|
||||
<n-button v-if="detailItem.xmlUrl" text tag="a" :href="detailItem.xmlUrl" target="_blank" type="primary">XML</n-button>
|
||||
</div>
|
||||
|
||||
<!-- ===== 商品明细 ===== -->
|
||||
<div v-if="detailItem.goodsList.length > 0" class="detail-section">
|
||||
<div class="detail-section-title">商品明细</div>
|
||||
<n-data-table
|
||||
:data="detailItem.goodsList"
|
||||
:columns="goodsColumns"
|
||||
:bordered="true"
|
||||
:single-line="false"
|
||||
size="small"
|
||||
striped
|
||||
:pagination="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- ===== 差额征税凭证明细 ===== -->
|
||||
<div v-if="detailItem.voucherList.length > 0" class="detail-section">
|
||||
<div class="detail-section-title">差额征税凭证明细</div>
|
||||
<n-data-table
|
||||
:data="detailItem.voucherList"
|
||||
:columns="voucherColumns"
|
||||
:bordered="true"
|
||||
:single-line="false"
|
||||
size="small"
|
||||
striped
|
||||
:pagination="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- ===== 关联单据 ===== -->
|
||||
<div v-if="detailItem.orderList.length > 0" class="detail-section">
|
||||
<div class="detail-section-title">关联单据</div>
|
||||
<n-tag v-for="(ord, idx) in detailItem.orderList" :key="idx" style="margin-right: 8px; margin-bottom: 4px">
|
||||
{{ ord.orderNo }}
|
||||
</n-tag>
|
||||
</div>
|
||||
</n-spin>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h, onMounted, reactive, ref } from 'vue'
|
||||
import {
|
||||
NButton,
|
||||
NDataTable,
|
||||
NDescriptions,
|
||||
NDescriptionsItem,
|
||||
NInput,
|
||||
NModal,
|
||||
NSpin,
|
||||
NTag,
|
||||
useMessage
|
||||
} from 'naive-ui'
|
||||
import { Eye, RefreshCw } from 'lucide-vue-next'
|
||||
import { invoiceDetailApi, invoiceHistoryApi, queryInvoiceApi } from '@/api/piaotong'
|
||||
import type { InvoiceDetailGoods, InvoiceDetailOrder, InvoiceDetailResponse, InvoiceDetailVoucher, InvoiceHistoryItem } from '@/api/piaotong'
|
||||
import type { DataTableColumn } from 'naive-ui'
|
||||
|
||||
const invoiceKindMap: Record<string, string> = {
|
||||
'81': '数电专票',
|
||||
'82': '数电普票',
|
||||
'87': '机动车发票',
|
||||
'10': '电子普票',
|
||||
'08': '电子专票',
|
||||
'04': '增值税普票',
|
||||
'01': '增值税专票'
|
||||
}
|
||||
|
||||
const invoiceStatusMap: Record<string, string> = {
|
||||
'PENDING': '待处理',
|
||||
'PROCESSING': '处理中',
|
||||
'SUCCESS': '开票成功',
|
||||
'FAILED': '开票失败'
|
||||
}
|
||||
|
||||
function statusTagType(status: string): 'warning' | 'info' | 'success' | 'error' {
|
||||
switch (status) {
|
||||
case 'PENDING': return 'warning'
|
||||
case 'PROCESSING': return 'info'
|
||||
case 'SUCCESS': return 'success'
|
||||
case 'FAILED': return 'error'
|
||||
default: return 'info'
|
||||
}
|
||||
}
|
||||
|
||||
const message = useMessage()
|
||||
const loading = ref(false)
|
||||
const dataSource = ref<InvoiceHistoryItem[]>([])
|
||||
const showDetail = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const detailItem = ref<InvoiceDetailResponse | null>(null)
|
||||
/** 记录正在刷新状态的流水号 */
|
||||
const refreshingSet = reactive(new Set<string>())
|
||||
|
||||
const query = reactive({
|
||||
keyword: ''
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
pageCount: 1,
|
||||
itemCount: 0,
|
||||
showSizePicker: true,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
pageSlot: 7,
|
||||
prefix: ({ itemCount }: { itemCount: number }) => `共 ${itemCount} 条`
|
||||
})
|
||||
|
||||
/** 商品明细表格列 */
|
||||
const goodsColumns: DataTableColumn[] = [
|
||||
{ title: '行号', key: 'lineNo', width: 60, align: 'center' },
|
||||
{ title: '商品名称', key: 'goodsName', width: 160, ellipsis: { tooltip: true } },
|
||||
{ title: '税收分类编码', key: 'taxClassificationCode', width: 130, ellipsis: { tooltip: true } },
|
||||
{ title: '规格型号', key: 'specificationModel', width: 100, render: (r: InvoiceDetailGoods) => r.specificationModel || '-' },
|
||||
{ title: '单位', key: 'meteringUnit', width: 60, render: (r: InvoiceDetailGoods) => r.meteringUnit || '-' },
|
||||
{ title: '数量', key: 'quantity', width: 80, render: (r: InvoiceDetailGoods) => r.quantity || '-' },
|
||||
{ title: '单价', key: 'unitPrice', width: 100, render: (r: InvoiceDetailGoods) => r.unitPrice || '-' },
|
||||
{ title: '金额', key: 'invoiceAmount', width: 100, align: 'right' },
|
||||
{ title: '税率', key: 'taxRateValue', width: 70, render: (r: InvoiceDetailGoods) => `${(parseFloat(r.taxRateValue) * 100).toFixed(0)}%` },
|
||||
{ title: '税额', key: 'taxRateAmount', width: 100, align: 'right', render: (r: InvoiceDetailGoods) => r.taxRateAmount || '-' },
|
||||
{ title: '含税', key: 'includeTaxFlag', width: 60, align: 'center', render: (r: InvoiceDetailGoods) => r.includeTaxFlag ? '是' : '否' },
|
||||
]
|
||||
|
||||
/** 差额征税凭证表格列 */
|
||||
const voucherColumns: DataTableColumn[] = [
|
||||
{ title: '凭证类型', key: 'proofType', width: 120, render: (r: InvoiceDetailVoucher) => proofTypeMap[r.proofType] || r.proofType },
|
||||
{ title: '凭证号码', key: 'proofNo', width: 130, render: (r: InvoiceDetailVoucher) => r.proofNo || '-' },
|
||||
{ title: '开具日期', key: 'issueDate', width: 100, render: (r: InvoiceDetailVoucher) => r.issueDate || '-' },
|
||||
{ title: '凭证金额', key: 'proofAmount', width: 110, align: 'right' },
|
||||
{ title: '扣除金额', key: 'deductionAmount', width: 110, align: 'right' },
|
||||
{ title: '来源', key: 'source', width: 90, render: (r: InvoiceDetailVoucher) => r.source || '-' },
|
||||
]
|
||||
|
||||
const proofTypeMap: Record<string, string> = {
|
||||
'01': '数电票', '02': '增值税专票', '03': '增值税普票', '04': '营业税发票',
|
||||
'05': '财政票据', '06': '法院裁决书', '07': '契税完税凭证', '08': '其他发票类', '09': '其他扣除凭证'
|
||||
}
|
||||
|
||||
const columns: DataTableColumn[] = [
|
||||
{
|
||||
title: '流水号',
|
||||
key: 'invoiceReqSerialNo',
|
||||
width: 180,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: '购买方',
|
||||
key: 'buyerName',
|
||||
width: 140,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: '发票种类',
|
||||
key: 'invoiceKindCode',
|
||||
width: 100,
|
||||
render: (row: InvoiceHistoryItem) => invoiceKindMap[row.invoiceKindCode] || row.invoiceKindCode
|
||||
},
|
||||
{
|
||||
title: '不含税金额',
|
||||
key: 'amount',
|
||||
width: 120,
|
||||
align: 'right'
|
||||
},
|
||||
{
|
||||
title: '税额',
|
||||
key: 'taxAmount',
|
||||
width: 110,
|
||||
align: 'right'
|
||||
},
|
||||
{
|
||||
title: '含税总金额',
|
||||
key: 'totalAmount',
|
||||
width: 130,
|
||||
align: 'right',
|
||||
sorter: (a: InvoiceHistoryItem, b: InvoiceHistoryItem) =>
|
||||
parseFloat(a.totalAmount) - parseFloat(b.totalAmount),
|
||||
render: (row: InvoiceHistoryItem) =>
|
||||
h('span', { style: { fontWeight: 600, color: '#d4380d' } }, row.totalAmount)
|
||||
},
|
||||
{
|
||||
title: '发票号码',
|
||||
key: 'invoiceNo',
|
||||
width: 120,
|
||||
ellipsis: { tooltip: true },
|
||||
render: (row: InvoiceHistoryItem) => row.invoiceNo || '-'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
render: (row: InvoiceHistoryItem) =>
|
||||
h(NTag, { type: statusTagType(row.status), size: 'small' }, () =>
|
||||
invoiceStatusMap[row.status] || row.status
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '开票时间',
|
||||
key: 'issuedAt',
|
||||
width: 140,
|
||||
render: (row: InvoiceHistoryItem) => row.issuedAt || '-'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 200,
|
||||
fixed: 'right',
|
||||
render: (row: InvoiceHistoryItem) =>
|
||||
h('div', { style: 'display: flex; gap: 8px; align-items: center;' }, [
|
||||
h(
|
||||
NButton,
|
||||
{ size: 'small', type: 'primary', tertiary: true, onClick: () => showDetailInfo(row) },
|
||||
{ default: () => '查看详情' }
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'warning',
|
||||
secondary: true,
|
||||
disabled: refreshingSet.has(row.invoiceReqSerialNo),
|
||||
onClick: () => refreshStatus(row)
|
||||
},
|
||||
{ default: () => refreshingSet.has(row.invoiceReqSerialNo) ? '刷新中...' : '刷新状态', icon: () => h(RefreshCw, { size: 14 }) }
|
||||
)
|
||||
])
|
||||
}
|
||||
]
|
||||
|
||||
async function fetchData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await invoiceHistoryApi(pagination.page, pagination.pageSize)
|
||||
dataSource.value = res.items
|
||||
pagination.itemCount = res.total
|
||||
pagination.pageCount = Math.max(1, Math.ceil(res.total / pagination.pageSize))
|
||||
} catch {
|
||||
message.error('查询开票历史失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
pagination.page = page
|
||||
fetchData()
|
||||
}
|
||||
|
||||
function handlePageSizeChange(pageSize: number) {
|
||||
pagination.pageSize = pageSize
|
||||
pagination.page = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
pagination.page = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
query.keyword = ''
|
||||
pagination.page = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
async function showDetailInfo(item: InvoiceHistoryItem) {
|
||||
showDetail.value = true
|
||||
detailLoading.value = true
|
||||
try {
|
||||
const res = await invoiceDetailApi(item.invoiceReqSerialNo)
|
||||
detailItem.value = res
|
||||
} catch {
|
||||
detailItem.value = null
|
||||
useMessage().error('查询发票详情失败')
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshStatus(item: InvoiceHistoryItem) {
|
||||
refreshingSet.add(item.invoiceReqSerialNo)
|
||||
try {
|
||||
const res = await queryInvoiceApi(item.invoiceReqSerialNo)
|
||||
message.success(`状态已刷新: ${invoiceStatusMap[res.status] || res.status}`)
|
||||
// 重新加载列表数据更新此行状态
|
||||
await fetchData()
|
||||
} catch {
|
||||
message.error('刷新状态失败')
|
||||
} finally {
|
||||
refreshingSet.delete(item.invoiceReqSerialNo)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
min-height: 100%;
|
||||
background: #f7f8fa;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
text-align: center;
|
||||
color: #bbb;
|
||||
.page-header {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.placeholder h2 {
|
||||
margin: 0 0 8px;
|
||||
.page-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #999;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.placeholder p {
|
||||
margin: 0;
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.history-table {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.file-links {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.file-links-label {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.detail-section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
padding-left: 8px;
|
||||
border-left: 3px solid #409eff;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -448,13 +448,13 @@
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="金额 *" path="invoiceAmount">
|
||||
<n-input v-model:value="currentItem.invoiceAmount" :placeholder="amountPlaceholder" clearable />
|
||||
<n-form-item label="金额是否含税 *" path="includeTaxFlag">
|
||||
<n-select v-model:value="currentItem.includeTaxFlag" :options="includeTaxFlagOptions" placeholder="选择含税标示" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="含税标示 *" path="includeTaxFlag">
|
||||
<n-select v-model:value="currentItem.includeTaxFlag" :options="includeTaxFlagOptions" placeholder="选择含税标示" />
|
||||
<n-form-item label="金额 *" path="invoiceAmount">
|
||||
<n-input v-model:value="currentItem.invoiceAmount" :placeholder="amountPlaceholder" clearable />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
@@ -538,18 +538,18 @@
|
||||
</n-form>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- ===== 新增单据号弹窗 ===== -->
|
||||
<n-modal v-model:show="showOrderDialog" preset="card" title="新增单据号" style="width:420px" :mask-closable="false">
|
||||
<n-input v-model:value="orderInputValue" placeholder="请输入业务单据号" clearable @keyup.enter="confirmAddOrderNo" />
|
||||
<template #footer>
|
||||
<div style="display:flex;justify-content:flex-end;gap:8px">
|
||||
<n-button @click="showOrderDialog = false">取消</n-button>
|
||||
<n-button type="primary" @click="confirmAddOrderNo">确定</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
<!-- ===== 新增单据号弹窗 ===== -->
|
||||
<n-modal v-model:show="showOrderDialog" preset="card" title="新增单据号" style="width:420px" :mask-closable="false">
|
||||
<n-input v-model:value="orderInputValue" placeholder="请输入业务单据号" clearable @keyup.enter="confirmAddOrderNo" />
|
||||
<template #footer>
|
||||
<div style="display:flex;justify-content:flex-end;gap:8px">
|
||||
<n-button @click="showOrderDialog = false">取消</n-button>
|
||||
<n-button type="primary" @click="confirmAddOrderNo">确定</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -557,7 +557,6 @@ import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { Plus, RefreshCw } from 'lucide-vue-next'
|
||||
import {
|
||||
NButton,
|
||||
NCard,
|
||||
NDatePicker,
|
||||
NForm,
|
||||
NFormItem,
|
||||
@@ -1111,19 +1110,24 @@ watch(() => form.specialInvoiceKind, (newVal, oldVal) => {
|
||||
if (!newVal || newVal === oldVal) return
|
||||
|
||||
if (newVal === '02') {
|
||||
dialog.warning({
|
||||
title: '提示',
|
||||
content: '农产品收购发票的开票种类只能是数电普票(82),是否将开票种类修改为"数电普票(82)"?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
form.invoiceIssueKindCode = '82'
|
||||
showSwapSellerBuyerDialog()
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
form.specialInvoiceKind = oldVal
|
||||
}
|
||||
})
|
||||
if (form.invoiceIssueKindCode === '82') {
|
||||
// 已经是数电普票,直接弹交换信息弹窗
|
||||
showSwapSellerBuyerDialog()
|
||||
} else {
|
||||
dialog.warning({
|
||||
title: '提示',
|
||||
content: '农产品收购发票的开票种类只能是数电普票(82),是否将开票种类修改为"数电普票(82)"?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
form.invoiceIssueKindCode = '82'
|
||||
showSwapSellerBuyerDialog()
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
form.specialInvoiceKind = oldVal
|
||||
}
|
||||
})
|
||||
}
|
||||
} else if (newVal === '12') {
|
||||
showSwapSellerBuyerDialog()
|
||||
}
|
||||
@@ -1237,7 +1241,7 @@ watch(() => form.itemList, (items) => {
|
||||
flex-shrink: 0;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
padding: 12px 24px;
|
||||
padding: 6px 24px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
|
||||
|
||||
Reference in New Issue
Block a user