开票历史模块
This commit is contained in:
@@ -1,7 +1,10 @@
|
|||||||
package com.bbit.ticket.bootstrap
|
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.InvoiceItemTable
|
||||||
import com.bbit.ticket.database.piaotong.InvoiceOrderTable
|
|
||||||
import com.bbit.ticket.database.system.SysApiAccessLogTable
|
import com.bbit.ticket.database.system.SysApiAccessLogTable
|
||||||
import com.bbit.ticket.database.system.SysDictItemTable
|
import com.bbit.ticket.database.system.SysDictItemTable
|
||||||
import com.bbit.ticket.database.system.SysDictTypeTable
|
import com.bbit.ticket.database.system.SysDictTypeTable
|
||||||
@@ -33,7 +36,10 @@ object DatabaseInitializer {
|
|||||||
SysOperationLogTable,
|
SysOperationLogTable,
|
||||||
SysApiAccessLogTable,
|
SysApiAccessLogTable,
|
||||||
InvoiceItemTable,
|
InvoiceItemTable,
|
||||||
InvoiceOrderTable,
|
HistoryInvoiceBasicTable,
|
||||||
|
HistoryInvoiceGoodsTable,
|
||||||
|
HistoryInvoiceVoucherTable,
|
||||||
|
HistoryInvoiceOrderTable,
|
||||||
)
|
)
|
||||||
// 先通过 Exposed 生成迁移 SQL,再逐条执行,避免启动时静默跳过缺失表或字段。
|
// 先通过 Exposed 生成迁移 SQL,再逐条执行,避免启动时静默跳过缺失表或字段。
|
||||||
transaction {
|
transaction {
|
||||||
|
|||||||
@@ -2,31 +2,43 @@
|
|||||||
|
|
||||||
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.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.InvoiceItemTable
|
||||||
import com.bbit.ticket.database.piaotong.InvoiceOrderTable
|
|
||||||
import com.bbit.ticket.database.system.SysUserTable
|
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.TaxRegisterInfo
|
||||||
import com.bbit.ticket.entity.request.UpdateDigitalAccountRequest
|
import com.bbit.ticket.entity.request.UpdateDigitalAccountRequest
|
||||||
import com.bbit.ticket.entity.request.UpdateEnterpriseInfoRequest
|
import com.bbit.ticket.entity.request.UpdateEnterpriseInfoRequest
|
||||||
import com.bbit.ticket.entity.request.UpdatePresetDataRequest
|
import com.bbit.ticket.entity.request.UpdatePresetDataRequest
|
||||||
import com.bbit.ticket.entity.response.DigitalAccountResponse
|
import com.bbit.ticket.entity.response.DigitalAccountResponse
|
||||||
import com.bbit.ticket.entity.response.EnterpriseInfoResponse
|
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 com.bbit.ticket.entity.response.PresetDataResponse
|
||||||
import kotlinx.serialization.encodeToString
|
import com.bbit.ticket.utils.formatDateTime
|
||||||
import kotlinx.serialization.json.Json
|
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.core.eq
|
||||||
import org.jetbrains.exposed.v1.jdbc.insert
|
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.selectAll
|
||||||
import org.jetbrains.exposed.v1.jdbc.update
|
import org.jetbrains.exposed.v1.jdbc.update
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.text.DecimalFormat
|
import java.time.LocalDateTime
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
import kotlin.uuid.ExperimentalUuidApi
|
import kotlin.uuid.ExperimentalUuidApi
|
||||||
import kotlin.uuid.Uuid
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
|
private fun pageOffset(page: Int, pageSize: Int): Long = ((page - 1) * pageSize).toLong()
|
||||||
|
|
||||||
object EnterpriseTaxDao {
|
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 now = OffsetDateTime.now()
|
||||||
val row = InvoiceOrderTable.insert {
|
|
||||||
it[InvoiceOrderTable.userId] = userId
|
// 1. 插入 HistoryInvoiceBasicTable(基本信息历史快照)
|
||||||
it[InvoiceOrderTable.invoiceReqSerialNo] = req.invoiceReqSerialNo
|
val basicRow = HistoryInvoiceBasicTable.insert {
|
||||||
it[InvoiceOrderTable.taxpayerNum] = req.taxpayerNum
|
it[HistoryInvoiceBasicTable.userId] = userId
|
||||||
it[InvoiceOrderTable.invoiceKindCode] = req.invoiceIssueKindCode
|
it[HistoryInvoiceBasicTable.invoiceReqSerialNo] = req.invoiceReqSerialNo
|
||||||
it[InvoiceOrderTable.buyerName] = req.buyerName
|
it[HistoryInvoiceBasicTable.taxpayerNum] = req.taxpayerNum
|
||||||
it[InvoiceOrderTable.buyerTaxpayerNum] = req.buyerTaxpayerNum
|
it[HistoryInvoiceBasicTable.invoiceKindCode] = req.invoiceIssueKindCode
|
||||||
it[InvoiceOrderTable.buyerAddress] = req.buyerAddress
|
it[HistoryInvoiceBasicTable.invoiceType] = "BLUE"
|
||||||
it[InvoiceOrderTable.buyerTel] = req.buyerTel
|
it[HistoryInvoiceBasicTable.buyerName] = req.buyerName
|
||||||
it[InvoiceOrderTable.buyerBankName] = req.buyerBankName
|
it[HistoryInvoiceBasicTable.buyerTaxpayerNum] = req.buyerTaxpayerNum
|
||||||
it[InvoiceOrderTable.buyerBankAccount] = req.buyerBankAccount
|
it[HistoryInvoiceBasicTable.buyerAddress] = req.buyerAddress
|
||||||
it[InvoiceOrderTable.remark] = req.remark
|
it[HistoryInvoiceBasicTable.buyerTel] = req.buyerTel
|
||||||
it[InvoiceOrderTable.definedData] = req.definedData
|
it[HistoryInvoiceBasicTable.buyerBankName] = req.buyerBankName
|
||||||
it[InvoiceOrderTable.tradeNo] = req.tradeNo
|
it[HistoryInvoiceBasicTable.buyerBankAccount] = req.buyerBankAccount
|
||||||
it[InvoiceOrderTable.taxAmount] = BigDecimal.ZERO
|
it[HistoryInvoiceBasicTable.remark] = req.remark
|
||||||
it[InvoiceOrderTable.amount] = BigDecimal.ZERO
|
it[HistoryInvoiceBasicTable.definedData] = req.definedData
|
||||||
it[InvoiceOrderTable.totalAmount] = BigDecimal.ZERO
|
it[HistoryInvoiceBasicTable.tradeNo] = req.tradeNo
|
||||||
it[InvoiceOrderTable.requestJson] = Json.encodeToString(req)
|
it[HistoryInvoiceBasicTable.amount] = req.itemList.sumOf { it.invoiceAmount.toBigDecimal() }
|
||||||
it[InvoiceOrderTable.status] = "PENDING"
|
it[HistoryInvoiceBasicTable.taxAmount] = req.itemList.sumOf { it.taxRateAmount?.toBigDecimalOrNull() ?: BigDecimal.ZERO }
|
||||||
it[InvoiceOrderTable.createdAt] = now
|
it[HistoryInvoiceBasicTable.totalAmount] = req.itemList.sumOf { item ->
|
||||||
it[InvoiceOrderTable.createdBy] = userId
|
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
|
var lineNo = 1
|
||||||
for (item in req.itemList) {
|
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 {
|
InvoiceItemTable.insert {
|
||||||
it[InvoiceItemTable.invoiceId] = invoiceId
|
it[InvoiceItemTable.invoiceId] = historyBasicId
|
||||||
it[InvoiceItemTable.lineNo] = lineNo++
|
it[InvoiceItemTable.lineNo] = lineNo
|
||||||
it[InvoiceItemTable.goodsName] = item.goodsName
|
it[InvoiceItemTable.goodsName] = item.goodsName
|
||||||
it[InvoiceItemTable.taxClassificationCode] = item.taxClassificationCode
|
it[InvoiceItemTable.taxClassificationCode] = item.taxClassificationCode
|
||||||
it[InvoiceItemTable.specificationModel] = item.specificationModel
|
it[InvoiceItemTable.specificationModel] = item.specificationModel
|
||||||
@@ -176,6 +271,167 @@ object EnterpriseTaxDao {
|
|||||||
it[InvoiceItemTable.deductionAmount] = item.deductionAmount?.toBigDecimalOrNull()
|
it[InvoiceItemTable.deductionAmount] = item.deductionAmount?.toBigDecimalOrNull()
|
||||||
it[InvoiceItemTable.createdAt] = now
|
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.ExperimentalUuidApi
|
||||||
import kotlin.uuid.Uuid
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发票基本信息表(历史)
|
||||||
|
* 存储发票开具时的基本信息快照
|
||||||
|
*/
|
||||||
@OptIn(ExperimentalUuidApi::class)
|
@OptIn(ExperimentalUuidApi::class)
|
||||||
object InvoiceOrderTable : Table("invoice_order") {
|
object HistoryInvoiceBasicTable : Table("history_invoice_basic") {
|
||||||
|
|
||||||
val id = uuid("id").clientDefault { Uuid.Companion.random() }
|
val id = uuid("id").clientDefault { Uuid.Companion.random() }
|
||||||
|
|
||||||
@@ -30,29 +34,23 @@ object InvoiceOrderTable : Table("invoice_order") {
|
|||||||
* 发票种类
|
* 发票种类
|
||||||
* 81:电子发票(增值税专用发票)
|
* 81:电子发票(增值税专用发票)
|
||||||
* 82:电子发票(普通发票)
|
* 82:电子发票(普通发票)
|
||||||
* 87:数电纸质发票(机动车销售统一发票)
|
|
||||||
* 10:增值税电子普通发票
|
|
||||||
* 08:增值税电子专用发票
|
|
||||||
* 04:增值税普通发票
|
|
||||||
* 01:增值税专用发票
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
val invoiceKindCode = varchar("invoice_kind_code", 8)
|
val invoiceKindCode = varchar("invoice_kind_code", 8)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发票类型
|
||||||
|
* BLUE:蓝票
|
||||||
|
* RED:红票
|
||||||
|
*/
|
||||||
|
val invoiceType = varchar("invoice_type", 8).default("BLUE")
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// 购买方信息(历史快照)
|
// 购买方信息
|
||||||
// =========================
|
// =========================
|
||||||
|
|
||||||
/**
|
|
||||||
* 购买方名称
|
|
||||||
*/
|
|
||||||
val buyerName = varchar("buyer_name", 200)
|
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()
|
val buyerAddress = varchar("buyer_address", 255).nullable()
|
||||||
|
|
||||||
@@ -85,82 +83,36 @@ object InvoiceOrderTable : Table("invoice_order") {
|
|||||||
// 开票结果
|
// 开票结果
|
||||||
// =========================
|
// =========================
|
||||||
|
|
||||||
/**
|
|
||||||
* 发票号码
|
|
||||||
*/
|
|
||||||
val invoiceNo = varchar("invoice_no", 64).nullable()
|
val invoiceNo = varchar("invoice_no", 64).nullable()
|
||||||
|
|
||||||
/**
|
|
||||||
* 发票代码
|
|
||||||
*/
|
|
||||||
val invoiceCode = varchar("invoice_code", 64).nullable()
|
val invoiceCode = varchar("invoice_code", 64).nullable()
|
||||||
|
|
||||||
/**
|
|
||||||
* 数电票号码
|
|
||||||
*/
|
|
||||||
val electronicInvoiceNo = varchar("electronic_invoice_no", 64).nullable()
|
val electronicInvoiceNo = varchar("electronic_invoice_no", 64).nullable()
|
||||||
|
|
||||||
/**
|
|
||||||
* 开票时间
|
|
||||||
*/
|
|
||||||
val issuedAt = timestampWithTimeZone("issued_at").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 errorMessage = text("error_message").nullable()
|
||||||
|
|
||||||
/**
|
|
||||||
* 原始请求报文
|
|
||||||
*/
|
|
||||||
val requestJson = text("request_json").nullable()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 原始响应报文
|
|
||||||
*/
|
|
||||||
val responseJson = text("response_json").nullable()
|
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// 文件
|
// 文件
|
||||||
// =========================
|
// =========================
|
||||||
|
|
||||||
/**
|
|
||||||
* PDF地址
|
|
||||||
*/
|
|
||||||
val pdfUrl = text("pdf_url").nullable()
|
val pdfUrl = text("pdf_url").nullable()
|
||||||
|
|
||||||
/**
|
|
||||||
* OFD地址
|
|
||||||
*/
|
|
||||||
val ofdUrl = text("ofd_url").nullable()
|
val ofdUrl = text("ofd_url").nullable()
|
||||||
|
|
||||||
/**
|
|
||||||
* XML地址
|
|
||||||
*/
|
|
||||||
val xmlUrl = text("xml_url").nullable()
|
val xmlUrl = text("xml_url").nullable()
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// 业务字段
|
// 业务字段
|
||||||
// =========================
|
// =========================
|
||||||
|
|
||||||
/**
|
|
||||||
* 订单号
|
|
||||||
*/
|
|
||||||
val tradeNo = varchar("trade_no", 128).nullable()
|
val tradeNo = varchar("trade_no", 128).nullable()
|
||||||
|
|
||||||
/**
|
|
||||||
* 备注
|
|
||||||
*/
|
|
||||||
val remark = text("remark").nullable()
|
val remark = text("remark").nullable()
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义透传数据
|
|
||||||
*/
|
|
||||||
val definedData = text("defined_data").nullable()
|
val definedData = text("defined_data").nullable()
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
@@ -178,4 +130,4 @@ object InvoiceOrderTable : Table("invoice_order") {
|
|||||||
val deletedAt = timestampWithTimeZone("deleted_at").nullable()
|
val deletedAt = timestampWithTimeZone("deleted_at").nullable()
|
||||||
|
|
||||||
override val primaryKey = PrimaryKey(id)
|
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
|
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.core.Table
|
||||||
import org.jetbrains.exposed.v1.javatime.timestampWithTimeZone
|
import org.jetbrains.exposed.v1.javatime.timestampWithTimeZone
|
||||||
import kotlin.uuid.ExperimentalUuidApi
|
import kotlin.uuid.ExperimentalUuidApi
|
||||||
@@ -13,7 +14,7 @@ object InvoiceItemTable : Table("invoice_item") {
|
|||||||
/**
|
/**
|
||||||
* 发票ID
|
* 发票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
|
package com.bbit.ticket.entity.request
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数电发票开票请求
|
* 数电发票开票请求
|
||||||
*/
|
*/
|
||||||
@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
|
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.PTException
|
||||||
import com.bbit.ticket.entity.common.fail
|
import com.bbit.ticket.entity.common.fail
|
||||||
import com.bbit.ticket.entity.common.ok
|
import com.bbit.ticket.entity.common.ok
|
||||||
import com.bbit.ticket.entity.common.BizException
|
import com.bbit.ticket.entity.request.AskInvoiceRequest
|
||||||
import com.bbit.ticket.entity.common.ErrorCode
|
import com.bbit.ticket.entity.request.QueryInvoiceRequest
|
||||||
import com.bbit.ticket.entity.request.InvoiceRequest
|
|
||||||
import com.bbit.ticket.entity.request.TaxBureauAuthReq
|
import com.bbit.ticket.entity.request.TaxBureauAuthReq
|
||||||
import com.bbit.ticket.entity.request.TaxRegisterInfo
|
import com.bbit.ticket.entity.request.TaxRegisterInfo
|
||||||
import com.bbit.ticket.entity.request.TaxRegisterUserRequest
|
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.PTAuthService
|
||||||
import com.bbit.ticket.service.piaotong.PTConfigService
|
import com.bbit.ticket.service.piaotong.PTConfigService
|
||||||
import com.bbit.ticket.utils.requireCurrentUser
|
import com.bbit.ticket.utils.requireCurrentUser
|
||||||
import io.ktor.http.HttpStatusCode
|
|
||||||
import io.ktor.server.auth.authenticate
|
import io.ktor.server.auth.authenticate
|
||||||
import io.ktor.server.request.receive
|
import io.ktor.server.request.receive
|
||||||
import io.ktor.server.response.respond
|
import io.ktor.server.response.respond
|
||||||
@@ -168,7 +165,7 @@ fun Route.registerPTTestRoutes() {
|
|||||||
post("/invoiceBlue") {
|
post("/invoiceBlue") {
|
||||||
try {
|
try {
|
||||||
val currentUser = call.requireCurrentUser()
|
val currentUser = call.requireCurrentUser()
|
||||||
val req = call.receive<InvoiceRequest>()
|
val req = call.receive<AskInvoiceRequest>()
|
||||||
val response = PTAuthService.invoiceBlue(req, currentUser.id)
|
val response = PTAuthService.invoiceBlue(req, currentUser.id)
|
||||||
call.respond(ok(response))
|
call.respond(ok(response))
|
||||||
} catch (e: PTException) {
|
} 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 {
|
get {
|
||||||
call.requirePermission("system:dict:view")
|
call.requirePermission("system:dict:view")
|
||||||
val page = call.queryInt("page", 1)
|
val page = call.queryInt("page", 1)
|
||||||
|
|||||||
@@ -3,18 +3,25 @@
|
|||||||
package com.bbit.ticket.service.piaotong
|
package com.bbit.ticket.service.piaotong
|
||||||
|
|
||||||
import com.bbit.ticket.dao.piaotong.EnterpriseTaxDao
|
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.TaxBureauAuthReq
|
||||||
import com.bbit.ticket.entity.request.TaxRegister
|
import com.bbit.ticket.entity.request.TaxRegister
|
||||||
import com.bbit.ticket.entity.request.TaxRegisterInfo
|
import com.bbit.ticket.entity.request.TaxRegisterInfo
|
||||||
import com.bbit.ticket.entity.request.TaxRegisterUserRequest
|
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.EnterpriseTaxInfo
|
||||||
import com.bbit.ticket.entity.response.EtaxRegisterResponse
|
import com.bbit.ticket.entity.response.EtaxRegisterResponse
|
||||||
import com.bbit.ticket.entity.response.InvoiceCreateResponse
|
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.entity.response.TaxBureauAccountAuthContent
|
||||||
import com.bbit.ticket.plugins.dbQuery
|
import com.bbit.ticket.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.PTClient
|
||||||
|
import io.ktor.server.util.url
|
||||||
import kotlin.uuid.Uuid
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
object PTAuthService {
|
object PTAuthService {
|
||||||
@@ -62,28 +69,73 @@ object PTAuthService {
|
|||||||
body = req
|
body = req
|
||||||
)
|
)
|
||||||
dbQuery { EnterpriseTaxDao.updateEnterpriseInfo(userId, req) }
|
dbQuery { EnterpriseTaxDao.updateEnterpriseInfo(userId, req) }
|
||||||
return "操作成功,企业状态为审核中(待审核)"
|
return "操作成功"
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 蓝票接口调用
|
* 蓝票接口调用
|
||||||
*/
|
*/
|
||||||
suspend fun invoiceBlue(req: InvoiceRequest, userId: Uuid): String {
|
suspend fun invoiceBlue(req: AskInvoiceRequest, userId: Uuid): String {
|
||||||
PTClient.ptPost<InvoiceRequest, InvoiceCreateResponse>(
|
PTClient.ptPost<AskInvoiceRequest, InvoiceCreateResponse>(
|
||||||
url = "invoiceBlue.pt",
|
url = "invoiceBlue.pt",
|
||||||
body = req
|
body = req
|
||||||
)
|
)
|
||||||
dbQuery { EnterpriseTaxDao.addInvoice(userId, req) }
|
dbQuery { EnterpriseTaxDao.addInvoice(userId, req) }
|
||||||
return "操作成功,企业状态为审核中(待审核)"
|
return "操作成功"
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 红票接口调用
|
* 红票接口调用
|
||||||
*/
|
*/
|
||||||
suspend fun invoiceRed(req: InvoiceRequest, userId: Uuid): String {
|
suspend fun invoiceRed(req: AskInvoiceRequest, userId: Uuid): String {
|
||||||
PTClient.ptPost<InvoiceRequest, InvoiceCreateResponse>(
|
PTClient.ptPost<AskInvoiceRequest, InvoiceCreateResponse>(
|
||||||
url = "invoiceBlue.pt",
|
url = "invoiceBlue.pt",
|
||||||
body = req
|
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.bootstrap.Global
|
||||||
import com.bbit.ticket.entity.common.PTException
|
import com.bbit.ticket.entity.common.PTException
|
||||||
import com.bbit.ticket.entity.response.PTResponse
|
import com.bbit.ticket.entity.response.PTResponse
|
||||||
|
import com.bbit.ticket.plugins.myJson
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.engine.cio.*
|
import io.ktor.client.engine.cio.*
|
||||||
import io.ktor.client.plugins.contentnegotiation.*
|
import io.ktor.client.plugins.contentnegotiation.*
|
||||||
@@ -24,14 +25,7 @@ object PTClient {
|
|||||||
val client = HttpClient(CIO) {
|
val client = HttpClient(CIO) {
|
||||||
|
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json(
|
json(myJson)
|
||||||
Json {
|
|
||||||
explicitNulls = false
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
prettyPrint = true
|
|
||||||
isLenient = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,8 +42,8 @@ object PTClient {
|
|||||||
|
|
||||||
url {
|
url {
|
||||||
// ⚠️ 把 Req 转成 JSON 再拆成 query(统一协议口径)
|
// ⚠️ 把 Req 转成 JSON 再拆成 query(统一协议口径)
|
||||||
val json = Json.encodeToString(queryParams)
|
val json = myJson.encodeToString(queryParams)
|
||||||
val element = Json.parseToJsonElement(json).jsonObject
|
val element = myJson.parseToJsonElement(json).jsonObject
|
||||||
|
|
||||||
element.forEach { (k, v) ->
|
element.forEach { (k, v) ->
|
||||||
parameters.append(k, v.toString().trim('"'))
|
parameters.append(k, v.toString().trim('"'))
|
||||||
@@ -63,7 +57,7 @@ object PTClient {
|
|||||||
|
|
||||||
val decrypted = disposeResponse(response)
|
val decrypted = disposeResponse(response)
|
||||||
|
|
||||||
val result = Json.decodeFromString<PTResponse<JsonElement>>(decrypted)
|
val result = myJson.decodeFromString<PTResponse<JsonElement>>(decrypted)
|
||||||
|
|
||||||
if (result.code != "0000") {
|
if (result.code != "0000") {
|
||||||
throw PTException(
|
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(
|
suspend inline fun <reified Req, reified Resp> ptPost(
|
||||||
@@ -85,11 +79,11 @@ object PTClient {
|
|||||||
val response = client.post(Global.baseUrl + url) {
|
val response = client.post(Global.baseUrl + url) {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
headers.forEach { (k, v) -> header(k, v) }
|
headers.forEach { (k, v) -> header(k, v) }
|
||||||
setBody(buildRequestData(Json.encodeToString(body)))
|
setBody(buildRequestData(myJson.encodeToString(body)))
|
||||||
}.bodyAsText()
|
}.bodyAsText()
|
||||||
|
|
||||||
val decrypted = disposeResponse(response)
|
val decrypted = disposeResponse(response)
|
||||||
val result = Json.decodeFromString<PTResponse<JsonElement>>(decrypted)
|
val result = myJson.decodeFromString<PTResponse<JsonElement>>(decrypted)
|
||||||
if (result.code != "0000") {
|
if (result.code != "0000") {
|
||||||
throw PTException(
|
throw PTException(
|
||||||
code = result.code,
|
code = result.code,
|
||||||
@@ -99,7 +93,7 @@ object PTClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
println("res = $result.content")
|
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["timestamp"] = sdf.format(Date())
|
||||||
map["serialNo"] = ptDate()
|
map["serialNo"] = ptDate()
|
||||||
map["sign"] = RSAUtil.sign(RSAUtil.getSignatureContent(map), Global.ptPrivateKey) ?: ""
|
map["sign"] = RSAUtil.sign(RSAUtil.getSignatureContent(map), Global.ptPrivateKey) ?: ""
|
||||||
return Json.encodeToString(map)
|
return myJson.encodeToString(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun disposeResponse(
|
fun disposeResponse(
|
||||||
jsonStr: String,
|
jsonStr: String,
|
||||||
): String {
|
): String {
|
||||||
|
|
||||||
val json = Json.parseToJsonElement(jsonStr).jsonObject
|
val json = myJson.parseToJsonElement(jsonStr).jsonObject
|
||||||
// 1. 转 Map(用于签名验证)
|
// 1. 转 Map(用于签名验证)
|
||||||
val mutableMap = json
|
val mutableMap = json
|
||||||
.toMutableMap()
|
.toMutableMap()
|
||||||
@@ -161,7 +155,7 @@ object PTClient {
|
|||||||
?: "{}"
|
?: "{}"
|
||||||
|
|
||||||
val contentElement = runCatching {
|
val contentElement = runCatching {
|
||||||
Json.parseToJsonElement(plainContent)
|
myJson.parseToJsonElement(plainContent)
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
JsonObject(emptyMap())
|
JsonObject(emptyMap())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -335,3 +335,193 @@ export interface InvoiceRequest {
|
|||||||
export function invoiceIssueApi(payload: InvoiceRequest): Promise<string> {
|
export function invoiceIssueApi(payload: InvoiceRequest): Promise<string> {
|
||||||
return http.post('/pt/invoiceBlue', payload)
|
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>
|
<template>
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<div class="placeholder">
|
<div class="page-header">
|
||||||
<h2>开票历史</h2>
|
<h2 class="page-title">开票历史</h2>
|
||||||
<p>功能开发中,敬请期待</p>
|
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</template>
|
</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>
|
<style scoped>
|
||||||
.page {
|
.page {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
background: #f7f8fa;
|
background: #f7f8fa;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
justify-content: center;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder {
|
.page-header {
|
||||||
text-align: center;
|
flex-shrink: 0;
|
||||||
color: #bbb;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder h2 {
|
.page-title {
|
||||||
margin: 0 0 8px;
|
margin: 0;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #999;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder p {
|
.search-bar {
|
||||||
margin: 0;
|
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-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding-left: 8px;
|
||||||
|
border-left: 3px solid #409eff;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -448,13 +448,13 @@
|
|||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
<n-gi>
|
<n-gi>
|
||||||
<n-form-item label="金额 *" path="invoiceAmount">
|
<n-form-item label="金额是否含税 *" path="includeTaxFlag">
|
||||||
<n-input v-model:value="currentItem.invoiceAmount" :placeholder="amountPlaceholder" clearable />
|
<n-select v-model:value="currentItem.includeTaxFlag" :options="includeTaxFlagOptions" placeholder="选择含税标示" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
<n-gi>
|
<n-gi>
|
||||||
<n-form-item label="含税标示 *" path="includeTaxFlag">
|
<n-form-item label="金额 *" path="invoiceAmount">
|
||||||
<n-select v-model:value="currentItem.includeTaxFlag" :options="includeTaxFlagOptions" placeholder="选择含税标示" />
|
<n-input v-model:value="currentItem.invoiceAmount" :placeholder="amountPlaceholder" clearable />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
<n-gi>
|
<n-gi>
|
||||||
@@ -538,18 +538,18 @@
|
|||||||
</n-form>
|
</n-form>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ===== 新增单据号弹窗 ===== -->
|
<!-- ===== 新增单据号弹窗 ===== -->
|
||||||
<n-modal v-model:show="showOrderDialog" preset="card" title="新增单据号" style="width:420px" :mask-closable="false">
|
<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" />
|
<n-input v-model:value="orderInputValue" placeholder="请输入业务单据号" clearable @keyup.enter="confirmAddOrderNo" />
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div style="display:flex;justify-content:flex-end;gap:8px">
|
<div style="display:flex;justify-content:flex-end;gap:8px">
|
||||||
<n-button @click="showOrderDialog = false">取消</n-button>
|
<n-button @click="showOrderDialog = false">取消</n-button>
|
||||||
<n-button type="primary" @click="confirmAddOrderNo">确定</n-button>
|
<n-button type="primary" @click="confirmAddOrderNo">确定</n-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -557,7 +557,6 @@ import { computed, onMounted, reactive, ref, watch } from 'vue'
|
|||||||
import { Plus, RefreshCw } from 'lucide-vue-next'
|
import { Plus, RefreshCw } from 'lucide-vue-next'
|
||||||
import {
|
import {
|
||||||
NButton,
|
NButton,
|
||||||
NCard,
|
|
||||||
NDatePicker,
|
NDatePicker,
|
||||||
NForm,
|
NForm,
|
||||||
NFormItem,
|
NFormItem,
|
||||||
@@ -1111,19 +1110,24 @@ watch(() => form.specialInvoiceKind, (newVal, oldVal) => {
|
|||||||
if (!newVal || newVal === oldVal) return
|
if (!newVal || newVal === oldVal) return
|
||||||
|
|
||||||
if (newVal === '02') {
|
if (newVal === '02') {
|
||||||
dialog.warning({
|
if (form.invoiceIssueKindCode === '82') {
|
||||||
title: '提示',
|
// 已经是数电普票,直接弹交换信息弹窗
|
||||||
content: '农产品收购发票的开票种类只能是数电普票(82),是否将开票种类修改为"数电普票(82)"?',
|
showSwapSellerBuyerDialog()
|
||||||
positiveText: '确定',
|
} else {
|
||||||
negativeText: '取消',
|
dialog.warning({
|
||||||
onPositiveClick: () => {
|
title: '提示',
|
||||||
form.invoiceIssueKindCode = '82'
|
content: '农产品收购发票的开票种类只能是数电普票(82),是否将开票种类修改为"数电普票(82)"?',
|
||||||
showSwapSellerBuyerDialog()
|
positiveText: '确定',
|
||||||
},
|
negativeText: '取消',
|
||||||
onNegativeClick: () => {
|
onPositiveClick: () => {
|
||||||
form.specialInvoiceKind = oldVal
|
form.invoiceIssueKindCode = '82'
|
||||||
}
|
showSwapSellerBuyerDialog()
|
||||||
})
|
},
|
||||||
|
onNegativeClick: () => {
|
||||||
|
form.specialInvoiceKind = oldVal
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
} else if (newVal === '12') {
|
} else if (newVal === '12') {
|
||||||
showSwapSellerBuyerDialog()
|
showSwapSellerBuyerDialog()
|
||||||
}
|
}
|
||||||
@@ -1237,7 +1241,7 @@ watch(() => form.itemList, (items) => {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-bottom: 1px solid #e8e8e8;
|
border-bottom: 1px solid #e8e8e8;
|
||||||
padding: 12px 24px;
|
padding: 6px 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
|
||||||
|
|||||||
Reference in New Issue
Block a user