feat: 发票管理

This commit is contained in:
2026-07-27 11:34:31 +08:00
parent 2d8a31a3cc
commit 5bbfba353d
10 changed files with 1081 additions and 0 deletions
@@ -0,0 +1,77 @@
package com.budwk.app.zhgh.pointsmall.invoice.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceApplyParam;
import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceInfoPageParam;
import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceMainPageParam;
import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceOrderPageParam;
import com.budwk.app.zhgh.pointsmall.invoice.service.PointsMallInvoiceService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
@IocBean
@Ok("json:full")
@At("/platform/zhgh/points-mall/invoice")
public class PointsMallInvoiceController {
@Inject
private PointsMallInvoiceService pointsMallInvoiceService;
@At("")
@Ok("beetl:/platform/zhgh/points-mall/invoice/index.html")
@SaCheckPermission("points.mall.invoice")
public void index() {
}
@At
@SaCheckPermission("points.mall.invoice")
public Result pageData(PointsMallInvoiceMainPageParam param) {
return Result.success(pointsMallInvoiceService.mainPage(param));
}
@At
@SaCheckPermission("points.mall.invoice")
public Result infoPage(PointsMallInvoiceInfoPageParam param) {
return Result.success(pointsMallInvoiceService.infoPage(param));
}
@At
@SaCheckPermission("points.mall.invoice")
public Result infoDetail(@Param("id") String id) {
return Result.success(pointsMallInvoiceService.infoDetail(id));
}
@At
@SaCheckPermission("points.mall.invoice")
public Result orderPage(PointsMallInvoiceOrderPageParam param) {
return Result.success(pointsMallInvoiceService.orderPage(param));
}
@At
@SaCheckPermission("points.mall.invoice")
public Result orderTotalPoints(PointsMallInvoiceOrderPageParam param) {
return Result.success(pointsMallInvoiceService.orderTotalPoints(param));
}
@At
@SaCheckPermission("points.mall.invoice")
public Result supplierList() {
return Result.success(pointsMallInvoiceService.listSuppliers());
}
@At
@SLog(tag = "积分商城发票管理", msg = "发起开票申请")
@SaCheckPermission("points.mall.invoice")
public Result applyInvoice(PointsMallInvoiceApplyParam param) {
try {
return Result.success(pointsMallInvoiceService.applyInvoice(param));
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
}
@@ -0,0 +1,128 @@
package com.budwk.app.zhgh.pointsmall.invoice.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("points_mall_invoice_info")
@Comment("积分商城发票信息")
public class PointsMallInvoiceInfo extends BaseModel implements Serializable {
@Name
@Column
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("发票主表ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String mainId;
@Column
@Comment("发票号码")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String invoiceId;
@Column
@Comment("发票代码")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String invoiceCode;
@Column
@Comment("发票日期")
private Date invoiceDate;
@Column
@Comment("发票裸价")
@ColDefine(type = ColType.FLOAT, width = 18, precision = 2)
private BigDecimal invoiceNakeAmount;
@Column
@Comment("发票税率")
@ColDefine(type = ColType.FLOAT, width = 10, precision = 4)
private BigDecimal invoiceTaxRate;
@Column
@Comment("发票税额")
@ColDefine(type = ColType.FLOAT, width = 18, precision = 2)
private BigDecimal invoiceTaxAmount;
@Column
@Comment("价税合计")
@ColDefine(type = ColType.FLOAT, width = 18, precision = 2)
private BigDecimal invoiceAmount;
@Column
@Comment("发票类型")
private Integer invoiceType;
@Column
@Comment("电子发票地址")
@ColDefine(type = ColType.VARCHAR, width = 1000)
private String url;
@Column
@Comment("发票Base64")
@ColDefine(type = ColType.TEXT)
private String imageEncode;
@Column
@Comment("文件类型")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String fileType;
@Column
@Comment("校验码")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String checkCode;
@Column
@Comment("发票地址")
@ColDefine(type = ColType.VARCHAR, width = 500)
private String invoiceAddress;
@Column
@Comment("开户银行")
@ColDefine(type = ColType.VARCHAR, width = 200)
private String invoiceBank;
@Column
@Comment("银行账号")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String invoiceAccount;
@Column
@Comment("联系电话")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String invoiceContact;
@Column
@Comment("开票内容")
@ColDefine(type = ColType.VARCHAR, width = 500)
private String invoiceContent;
@Column
@Comment("纳税人识别号")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String taxIdNumber;
@Column
@Comment("备注")
@ColDefine(type = ColType.VARCHAR, width = 1000)
private String remark;
@Column
@Comment("开票人")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String biller;
}
@@ -0,0 +1,47 @@
package com.budwk.app.zhgh.pointsmall.invoice.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("points_mall_invoice_main")
@Comment("积分商城发票主表")
public class PointsMallInvoiceMain extends BaseModel implements Serializable {
@Name
@Column
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("结算单号")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String settlementId;
@Column
@Comment("开票结果")
private Integer bSuccess;
@Column
@Comment("可开票订单列表")
@ColDefine(type = ColType.TEXT)
private String sucOrderIds;
@Column
@Comment("无法开票订单列表")
@ColDefine(type = ColType.TEXT)
private String failOrderIds;
@Column
@Comment("开票失败原因")
@ColDefine(type = ColType.VARCHAR, width = 1000)
private String failMsg;
}
@@ -0,0 +1,23 @@
package com.budwk.app.zhgh.pointsmall.invoice.param;
import lombok.Data;
@Data
public class PointsMallInvoiceApplyParam {
private String supplierId;
private String orderIds;
private String startDate;
private String endDate;
private String invoiceDate;
private String invoiceTitle;
private String invoiceCode;
private String invoiceAddress;
private String invoiceContact;
private Integer invoiceType;
private String invoiceContent;
private String remark;
private String bankAccount;
private String registeredAddress;
private String bankName;
}
@@ -0,0 +1,16 @@
package com.budwk.app.zhgh.pointsmall.invoice.param;
import com.budwk.app.base.param.PageForm;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class PointsMallInvoiceInfoPageParam extends PageForm {
private String mainId;
private String invoiceId;
private String invoiceCode;
private String invoiceDate;
private Integer invoiceType;
}
@@ -0,0 +1,16 @@
package com.budwk.app.zhgh.pointsmall.invoice.param;
import com.budwk.app.base.param.PageForm;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class PointsMallInvoiceMainPageParam extends PageForm {
private String settlementId;
private Integer bSuccess;
private String sucOrderIds;
private String failOrderIds;
private String failMsg;
}
@@ -0,0 +1,16 @@
package com.budwk.app.zhgh.pointsmall.invoice.param;
import com.budwk.app.base.param.PageForm;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class PointsMallInvoiceOrderPageParam extends PageForm {
private String supplierId;
private String orderId;
private String startDate;
private String endDate;
private Integer invoiceStatus;
}
@@ -0,0 +1,31 @@
package com.budwk.app.zhgh.pointsmall.invoice.service;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.pointsmall.invoice.models.PointsMallInvoiceMain;
import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceApplyParam;
import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceInfoPageParam;
import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceMainPageParam;
import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceOrderPageParam;
import com.budwk.app.zhgh.pointsmall.supplier.models.PointsMallSupplier;
import org.nutz.dao.entity.Record;
import java.math.BigDecimal;
import java.util.List;
public interface PointsMallInvoiceService extends BaseService<PointsMallInvoiceMain> {
Pagination<PointsMallInvoiceMain> mainPage(PointsMallInvoiceMainPageParam param);
Pagination<Record> infoPage(PointsMallInvoiceInfoPageParam param);
Record infoDetail(String id);
Pagination<Record> orderPage(PointsMallInvoiceOrderPageParam param);
BigDecimal orderTotalPoints(PointsMallInvoiceOrderPageParam param);
String applyInvoice(PointsMallInvoiceApplyParam param);
List<PointsMallSupplier> listSuppliers();
}
@@ -0,0 +1,218 @@
package com.budwk.app.zhgh.pointsmall.invoice.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.pointsmall.invoice.models.PointsMallInvoiceInfo;
import com.budwk.app.zhgh.pointsmall.invoice.models.PointsMallInvoiceMain;
import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceApplyParam;
import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceInfoPageParam;
import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceMainPageParam;
import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceOrderPageParam;
import com.budwk.app.zhgh.pointsmall.invoice.service.PointsMallInvoiceService;
import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderSub;
import com.budwk.app.zhgh.pointsmall.supplier.models.PointsMallSupplier;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.entity.Record;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.IocBean;
import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;
@IocBean(args = {"refer:dao"})
public class PointsMallInvoiceServiceImpl extends BaseServiceImpl<PointsMallInvoiceMain> implements PointsMallInvoiceService {
public PointsMallInvoiceServiceImpl(Dao dao) {
super(dao);
}
@Override
public Pagination<PointsMallInvoiceMain> mainPage(PointsMallInvoiceMainPageParam param) {
Cnd cnd = Cnd.where("delFlag", "=", false);
if (StrUtil.isNotBlank(param.getSettlementId())) {
cnd.and("settlementId", "like", "%" + param.getSettlementId() + "%");
}
if (param.getBSuccess() != null) {
cnd.and("bSuccess", "=", param.getBSuccess());
}
if (StrUtil.isNotBlank(param.getSucOrderIds())) {
cnd.and("sucOrderIds", "like", "%" + param.getSucOrderIds() + "%");
}
if (StrUtil.isNotBlank(param.getFailOrderIds())) {
cnd.and("failOrderIds", "like", "%" + param.getFailOrderIds() + "%");
}
if (StrUtil.isNotBlank(param.getFailMsg())) {
cnd.and("failMsg", "like", "%" + param.getFailMsg() + "%");
}
cnd.desc("createdAt");
return listPage(param.getPageNumber(), param.getPageSize(), PointsMallInvoiceMain.class, cnd);
}
@Override
public Pagination<Record> infoPage(PointsMallInvoiceInfoPageParam param) {
Cnd cnd = Cnd.where("delFlag", "=", false);
if (StrUtil.isNotBlank(param.getMainId())) {
cnd.and("mainId", "=", param.getMainId());
}
if (StrUtil.isNotBlank(param.getInvoiceId())) {
cnd.and("invoiceId", "like", "%" + param.getInvoiceId() + "%");
}
if (StrUtil.isNotBlank(param.getInvoiceCode())) {
cnd.and("invoiceCode", "like", "%" + param.getInvoiceCode() + "%");
}
if (StrUtil.isNotBlank(param.getInvoiceDate())) {
cnd.and("invoiceDate", "=", DateUtil.parse(param.getInvoiceDate()));
}
if (param.getInvoiceType() != null) {
cnd.and("invoiceType", "=", param.getInvoiceType());
}
cnd.desc("createdAt");
Sql sql = Sqls.create("select * from points_mall_invoice_info $condition");
sql.setCondition(cnd);
return listPageMap(param.getPageNumber(), param.getPageSize(), sql);
}
@Override
public Record infoDetail(String id) {
if (StrUtil.isBlank(id)) {
return null;
}
Sql sql = Sqls.create("select * from points_mall_invoice_info where id=@id and delFlag=0");
sql.params().set("id", id);
return (Record) dao().execute(sql.setCallback(Sqls.callback.record())).getResult();
}
@Override
public Pagination<Record> orderPage(PointsMallInvoiceOrderPageParam param) {
Cnd cnd = invoiceOrderCnd(param, "o");
cnd.desc("o.orderCompleteTime").desc("o.orderCreateTime");
Sql sql = Sqls.create("select o.*, s.supplierName from points_mall_order_sub o left join points_mall_supplier s on o.supplierId = s.supplierId and s.delFlag = 0 $condition");
sql.setCondition(cnd);
return listPageMap(param.getPageNumber(), param.getPageSize(), sql);
}
@Override
public BigDecimal orderTotalPoints(PointsMallInvoiceOrderPageParam param) {
Sql sql = Sqls.create("select ifnull(sum(ifnull(pointsPrice,0) - ifnull(refund,0)), 0) as totalPoints from points_mall_order_sub $condition");
sql.setCondition(invoiceOrderCnd(param, ""));
Record record = (Record) dao().execute(sql.setCallback(Sqls.callback.record())).getResult();
return record == null || record.get("totalPoints") == null ? BigDecimal.ZERO : new BigDecimal(record.get("totalPoints").toString());
}
@Override
public String applyInvoice(PointsMallInvoiceApplyParam param) {
checkApplyParam(param);
PointsMallInvoiceOrderPageParam query = new PointsMallInvoiceOrderPageParam();
query.setSupplierId(param.getSupplierId());
query.setOrderId(param.getOrderIds());
query.setStartDate(param.getStartDate());
query.setEndDate(param.getEndDate());
query.setInvoiceStatus(0);
List<PointsMallOrderSub> orders = dao().query(PointsMallOrderSub.class, invoiceOrderCnd(query, ""));
if (CollUtil.isEmpty(orders)) {
throw new IllegalArgumentException("无符合条件的订单数据");
}
List<String> orderIds = orders.stream().map(PointsMallOrderSub::getOrderId).collect(Collectors.toList());
String settlementId = System.currentTimeMillis() + RandomUtil.randomNumbers(6);
PointsMallInvoiceMain invoice = new PointsMallInvoiceMain();
invoice.setSettlementId(settlementId);
invoice.setBSuccess(0);
invoice.setSucOrderIds(String.join(",", orderIds));
invoice.setFailOrderIds("");
invoice.setFailMsg("");
insert(invoice);
PointsMallInvoiceInfo info = new PointsMallInvoiceInfo();
info.setMainId(invoice.getId());
info.setInvoiceId(settlementId);
info.setInvoiceCode(param.getInvoiceCode());
info.setInvoiceDate(DateUtil.parse(param.getInvoiceDate()));
info.setInvoiceType(param.getInvoiceType());
info.setInvoiceAmount(orderTotalPoints(query));
info.setInvoiceNakeAmount(info.getInvoiceAmount());
info.setInvoiceAddress(param.getInvoiceAddress());
info.setInvoiceContact(param.getInvoiceContact());
info.setInvoiceContent(param.getInvoiceContent());
info.setTaxIdNumber(param.getInvoiceCode());
info.setRemark(param.getRemark());
info.setInvoiceBank(param.getBankName());
info.setInvoiceAccount(param.getBankAccount());
dao().insert(info);
dao().update(PointsMallOrderSub.class, Chain.make("invoiceStatus", 2), Cnd.where("supplierId", "=", param.getSupplierId()).and("orderId", "in", orderIds));
return "开票申请已生成,结算单号:" + settlementId;
}
@Override
public List<PointsMallSupplier> listSuppliers() {
return dao().query(PointsMallSupplier.class, Cnd.where("delFlag", "=", false).asc("sortCode"));
}
private Cnd invoiceOrderCnd(PointsMallInvoiceOrderPageParam param, String alias) {
Cnd cnd = Cnd.where(field(alias, "delFlag"), "=", false)
.and(field(alias, "orderState"), "=", 5)
.and(field(alias, "reconciliationStatus"), "=", 1);
if (StrUtil.isNotBlank(param.getSupplierId())) {
cnd.and(field(alias, "supplierId"), "=", param.getSupplierId());
}
if (param.getInvoiceStatus() != null) {
cnd.and(field(alias, "invoiceStatus"), "=", param.getInvoiceStatus());
}
if (StrUtil.isNotBlank(param.getOrderId())) {
List<String> orderIds = StrUtil.splitTrim(param.getOrderId(), ",");
if (orderIds.size() > 1) {
cnd.and(field(alias, "orderId"), "in", orderIds);
} else {
cnd.and(field(alias, "orderId"), "like", "%" + param.getOrderId() + "%");
}
}
if (StrUtil.isNotBlank(param.getStartDate())) {
cnd.and(field(alias, "orderCompleteTime"), ">=", DateUtil.beginOfDay(DateUtil.parse(param.getStartDate())));
}
if (StrUtil.isNotBlank(param.getEndDate())) {
cnd.and(field(alias, "orderCompleteTime"), "<=", DateUtil.endOfDay(DateUtil.parse(param.getEndDate())));
}
return cnd;
}
private String field(String alias, String field) {
return StrUtil.isBlank(alias) ? field : alias + "." + field;
}
private void checkApplyParam(PointsMallInvoiceApplyParam param) {
if (param == null) {
throw new IllegalArgumentException("开票参数不能为空");
}
if (StrUtil.isBlank(param.getSupplierId())) {
throw new IllegalArgumentException("供应商不能为空");
}
if (StrUtil.isBlank(param.getOrderIds())) {
throw new IllegalArgumentException("订单不能为空");
}
if (StrUtil.isBlank(param.getStartDate()) || StrUtil.isBlank(param.getEndDate())) {
throw new IllegalArgumentException("订单完成日期不能为空");
}
if (StrUtil.isBlank(param.getInvoiceDate())) {
throw new IllegalArgumentException("开票日期不能为空");
}
if (StrUtil.isBlank(param.getInvoiceCode())) {
throw new IllegalArgumentException("纳税人识别号不能为空");
}
if (StrUtil.isBlank(param.getInvoiceAddress())) {
throw new IllegalArgumentException("发票地址不能为空");
}
if (StrUtil.isBlank(param.getInvoiceContact())) {
throw new IllegalArgumentException("联系电话不能为空");
}
}
}