feat: 积分商城-对账-发票-优化2

This commit is contained in:
2026-09-08 13:40:12 +08:00
parent 9159f8912d
commit 7b877255fb
21 changed files with 707 additions and 351 deletions
@@ -25,6 +25,8 @@ public interface SysFileService extends BaseService<Sys_file> {
*/
String uploadReturnUrl(String engine, TempFile file);
String uploadReturnUrl(String engine, String fileName, byte[] bytes);
/**
* 分页
*/
@@ -69,6 +69,11 @@ public class SysFileServiceImpl extends BaseServiceImpl<Sys_file> implements Sys
return this.storageFile(engine, file, false);
}
@Override
public String uploadReturnUrl(String engine, String fileName, byte[] bytes) {
return this.storageFile(engine, fileName, bytes, false);
}
@Override
public Pagination page(Sys_file file) {
return null;
@@ -37,13 +37,34 @@ public class PointsMallInvoiceController {
@At
@SaCheckPermission("points.mall.invoice")
public Result infoPage(PointsMallInvoiceInfoPageParam param) {
return Result.success(pointsMallInvoiceService.infoPage(param));
try {
return Result.success(pointsMallInvoiceService.infoPage(param));
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
@At
@SaCheckPermission("points.mall.invoice")
public Result infoDetail(@Param("id") String id) {
return Result.success(pointsMallInvoiceService.infoDetail(id));
try {
return Result.success(pointsMallInvoiceService.infoDetail(id));
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
@At
@org.nutz.mvc.annotation.POST
@SLog(tag = "积分商城发票管理", msg = "重新获取发票")
@SaCheckPermission("points.mall.invoice")
public Result retryInvoice(@Param("mainId") String mainId) {
try {
pointsMallInvoiceService.retryInvoice(mainId);
return Result.success();
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
@At
@@ -16,6 +16,9 @@ import java.util.Date;
@Comment("积分商城发票信息")
public class PointsMallInvoiceInfo extends BaseModel implements Serializable {
// 详情实时读取供应商 PDF 生成结果:1 成功,2 失败,不以本地文件地址推断状态。
private Integer status;
@Name
@Column
@Comment("ID")
@@ -3,6 +3,7 @@ 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.models.PointsMallInvoiceInfo;
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;
@@ -19,7 +20,9 @@ public interface PointsMallInvoiceService extends BaseService<PointsMallInvoiceM
Pagination<Record> infoPage(PointsMallInvoiceInfoPageParam param);
Record infoDetail(String id);
PointsMallInvoiceInfo infoDetail(String id);
void retryInvoice(String mainId);
Pagination<Record> orderPage(PointsMallInvoiceOrderPageParam param);
@@ -13,11 +13,16 @@ import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceMainPagePara
import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceOrderPageParam;
import com.budwk.app.zhgh.pointsmall.invoice.service.PointsMallInvoiceService;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallInvoiceApplyDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.InvoiceInfoDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.QueryInvoiceInfoDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.GetInvoiceInfoDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallOutboundResult;
import com.budwk.app.zhgh.pointsmall.mallbridge.service.MallInvoiceApplyOutboundService;
import com.budwk.app.zhgh.pointsmall.mallbridge.service.MallInvoiceInfoQueryService;
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.trans.Atom;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
@@ -25,8 +30,13 @@ import org.nutz.dao.entity.Record;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.trans.Trans;
import java.math.BigDecimal;
import com.budwk.app.zhgh.pointsmall.invoice.models.PointsMallInvoiceInfo;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderEventDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallInboundResult;
import com.budwk.app.zhgh.pointsmall.mallbridge.service.MallInboundEventService;
import java.util.List;
import java.util.stream.Collectors;
@@ -35,6 +45,10 @@ public class PointsMallInvoiceServiceImpl extends BaseServiceImpl<PointsMallInvo
@Inject
private MallInvoiceApplyOutboundService mallInvoiceApplyOutboundService;
@Inject
private MallInboundEventService mallInboundEventService;
@Inject
private MallInvoiceInfoQueryService mallInvoiceInfoQueryService;
public PointsMallInvoiceServiceImpl(Dao dao) {
super(dao);
@@ -64,6 +78,23 @@ public class PointsMallInvoiceServiceImpl extends BaseServiceImpl<PointsMallInvo
@Override
public Pagination<Record> infoPage(PointsMallInvoiceInfoPageParam param) {
if (StrUtil.isNotBlank(param.getMainId()) && dao().count(PointsMallInvoiceInfo.class,
Cnd.where("mainId", "=", param.getMainId()).and("delFlag", "=", false)) == 0) {
PointsMallInvoiceMain main = fetch(param.getMainId());
PointsMallOrderSub order = main == null ? null : dao().fetch(PointsMallOrderSub.class,
Cnd.where("invoiceCode", "=", main.getSettlementId()).and("delFlag", "=", false));
if (order != null) {
MallOrderEventDTO event = new MallOrderEventDTO();
event.setSupplierId(order.getSupplierId());
event.setOrderId(main.getSettlementId());
MallInboundResult result = mallInboundEventService.invoiceInfo(event);
if (result.getResultCode() != 0) {
dao().update(PointsMallInvoiceMain.class, Chain.make("bSuccess", 1).add("failMsg", result.getMessage()),
Cnd.where("id", "=", main.getId()));
throw new IllegalArgumentException(result.getMessage());
}
}
}
Cnd cnd = Cnd.where("delFlag", "=", false);
if (StrUtil.isNotBlank(param.getMainId())) {
cnd.and("mainId", "=", param.getMainId());
@@ -87,13 +118,61 @@ public class PointsMallInvoiceServiceImpl extends BaseServiceImpl<PointsMallInvo
}
@Override
public Record infoDetail(String id) {
public PointsMallInvoiceInfo 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();
PointsMallInvoiceInfo info = dao().fetch(PointsMallInvoiceInfo.class, Cnd.where("id", "=", id).and("delFlag", "=", false));
if (info == null) {
return null;
}
MallOrderEventDTO event = invoiceEvent(info.getMainId());
QueryInvoiceInfoDTO query = new QueryInvoiceInfoDTO();
query.setSupplierId(event.getSupplierId());
query.setSettlementId(event.getOrderId());
GetInvoiceInfoDTO result = mallInvoiceInfoQueryService.queryInvoicesInfo(query);
if (result == null || !event.getOrderId().equals(result.getSettlementId()) || result.getInvoiceInfos() == null) {
throw new IllegalArgumentException("供应商未返回对应结算单的发票详情");
}
InvoiceInfoDTO remote = result.getInvoiceInfos().stream()
.filter(item -> info.getInvoiceId().equals(item.getInvoiceId())).findFirst()
.orElseThrow(() -> new IllegalArgumentException("供应商未返回对应发票详情"));
info.setStatus(remote.getStatus());
info.setRemark(remote.getRemark());
if (!Integer.valueOf(1).equals(remote.getStatus())) {
info.setUrl("");
info.setImageEncode("");
}
return info;
}
private MallOrderEventDTO invoiceEvent(String mainId) {
PointsMallInvoiceMain main = StrUtil.isBlank(mainId) ? null : dao().fetch(PointsMallInvoiceMain.class,
Cnd.where("id", "=", mainId).and("delFlag", "=", false));
if (main == null) {
throw new IllegalArgumentException("开票申请记录不存在");
}
PointsMallOrderSub order = dao().fetch(PointsMallOrderSub.class,
Cnd.where("invoiceCode", "=", main.getSettlementId()).and("delFlag", "=", false));
if (order == null) {
throw new IllegalArgumentException("未找到结算单对应的供应商订单");
}
MallOrderEventDTO event = new MallOrderEventDTO();
event.setSupplierId(order.getSupplierId());
event.setOrderId(main.getSettlementId());
return event;
}
@Override
public void retryInvoice(String mainId) {
MallInboundResult result = mallInboundEventService.invoiceInfo(invoiceEvent(mainId), true);
if (result.getResultCode() != 0) {
throw new IllegalArgumentException(result.getMessage());
}
PointsMallInvoiceMain main = fetch(mainId);
if (!Integer.valueOf(0).equals(main.getBSuccess())) {
throw new IllegalArgumentException(StrUtil.blankToDefault(main.getFailMsg(), "供应商发票生成失败"));
}
}
@Override
@@ -132,8 +211,8 @@ public class PointsMallInvoiceServiceImpl extends BaseServiceImpl<PointsMallInvo
String settlementId = System.currentTimeMillis() + RandomUtil.randomNumbers(6);
PointsMallInvoiceMain invoice = new PointsMallInvoiceMain();
invoice.setSettlementId(settlementId);
invoice.setBSuccess(0);
invoice.setSucOrderIds(String.join(",", orderIds));
invoice.setBSuccess(null);
invoice.setSucOrderIds("");
invoice.setFailOrderIds("");
invoice.setFailMsg("");
insert(invoice);
@@ -157,18 +236,44 @@ public class PointsMallInvoiceServiceImpl extends BaseServiceImpl<PointsMallInvo
dto.setRegisteredAddress(param.getRegisteredAddress());
dto.setBankName(param.getBankName());
MallOutboundResult result = mallInvoiceApplyOutboundService.applyInvoice(dto);
dao().update(PointsMallOrderSub.class, Chain.make("invoiceStatus", 2).add("invoiceCode", settlementId),
Cnd.where("supplierId", "=", param.getSupplierId()).and("orderId", "in", orderIds));
MallOutboundResult result;
boolean requestFailed = false;
try {
result = mallInvoiceApplyOutboundService.applyInvoice(dto);
} catch (Exception e) {
requestFailed = true;
result = MallOutboundResult.fail(502, e.getMessage());
}
if (result == null || result.getResultCode() != 0) {
PointsMallInvoiceMain current = fetch(invoice.getId());
if (current != null && Integer.valueOf(0).equals(current.getBSuccess())) {
return "开票已完成,结算单号:" + settlementId;
}
if (!requestFailed && result != null && result.getResultCode() != 502) {
Trans.exec((Atom) () -> {
dao().update(PointsMallInvoiceInfo.class, Chain.make("delFlag", true),
Cnd.where("mainId", "=", invoice.getId()));
dao().update(PointsMallInvoiceMain.class, Chain.make("delFlag", true),
Cnd.where("id", "=", invoice.getId()));
dao().update(PointsMallOrderSub.class, Chain.make("invoiceStatus", 0).add("invoiceCode", ""),
Cnd.where("supplierId", "=", param.getSupplierId()).and("orderId", "in", orderIds)
.and("invoiceCode", "=", settlementId).and("invoiceStatus", "in", new Integer[]{0, 2}));
});
throw new IllegalArgumentException(result.getMessage());
}
invoice.setBSuccess(1);
invoice.setSucOrderIds("");
invoice.setFailOrderIds(String.join(",", orderIds));
invoice.setFailMsg(result == null ? "供应商开票申请无响应" : result.getMessage());
updateIgnoreNull(invoice);
dao().update(PointsMallOrderSub.class, Chain.make("invoiceStatus", 0),
Cnd.where("supplierId", "=", param.getSupplierId()).and("orderId", "in", orderIds)
.and("invoiceCode", "=", settlementId).and("invoiceStatus", "=", 2));
throw new IllegalArgumentException(invoice.getFailMsg());
}
dao().update(PointsMallOrderSub.class, Chain.make("invoiceStatus", 2).add("invoiceCode", settlementId),
Cnd.where("supplierId", "=", param.getSupplierId()).and("orderId", "in", orderIds));
return "开票申请已生成,结算单号:" + settlementId;
}
@@ -189,11 +294,7 @@ public class PointsMallInvoiceServiceImpl extends BaseServiceImpl<PointsMallInvo
}
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() + "%");
}
cnd.and(field(alias, "orderId"), "in", orderIds);
}
if (StrUtil.isNotBlank(param.getStartDate())) {
cnd.and(field(alias, "orderCompleteTime"), ">=", DateUtil.beginOfDay(DateUtil.parse(param.getStartDate())));
@@ -7,6 +7,8 @@ import java.time.LocalDate;
@Data
public class InvoiceInfoDTO {
private Integer status;
private String remark;
private String invoiceId;
private String invoiceCode;
private LocalDate invoiceDate;
@@ -4,6 +4,7 @@ import lombok.Data;
@Data
public class QueryInvoiceInfoDTO {
private Boolean retry;
private String supplierId;
private String token;
private String settlementId;
@@ -94,7 +94,7 @@ public class MallSplitSubOrderEventHandler implements MallOrderEventHandler {
// 调用外部服务获取子订单信息
MallOutboundResult result = mallOrderQueryOutboundService.orderQuery(queryDTO);
if (result == null || result.getResultCode() != 0) {
throw new RuntimeException(result == null ? "获取订单详情返回为空" : result.getMessage());
return MallInboundResult.fail(result == null ? 502 : result.getResultCode(), result == null ? "获取订单详情返回为空" : result.getMessage());
}
orderInfoList = result.getOrderInfo();
} catch (Exception e) {
@@ -47,6 +47,11 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Base64;
import java.util.stream.Collectors;
import com.budwk.app.sys.services.SysFileService;
import com.budwk.app.sys.enums.SysFileEngineTypeEnum;
import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallPictureOutboundResult;
/**
* 商城桥接订单事件入站服务。
@@ -62,6 +67,8 @@ public class MallInboundEventService extends AbstractMallBridgeSupport {
@Inject
private MallInvoiceInfoQueryService mallInvoiceInfoQueryService;
@Inject
private SysFileService sysFileService;
@Inject
private MallCancelPendingOrderEventHandler mallCancelPendingOrderEventHandler;
@Inject
private MallSplitSubOrderEventHandler mallSplitSubOrderEventHandler;
@@ -424,6 +431,10 @@ public class MallInboundEventService extends AbstractMallBridgeSupport {
}
public MallInboundResult invoiceInfo(MallOrderEventDTO dto) {
return invoiceInfo(dto, false);
}
public MallInboundResult invoiceInfo(MallOrderEventDTO dto, boolean retry) {
String settlementId = dto.getOrderId();
if (StrUtil.hasBlank(dto.getSupplierId(), settlementId)) {
return MallInboundResult.fail(400, "供应商和结算单号不能为空");
@@ -431,6 +442,7 @@ public class MallInboundEventService extends AbstractMallBridgeSupport {
QueryInvoiceInfoDTO query = new QueryInvoiceInfoDTO();
query.setSupplierId(dto.getSupplierId());
query.setSettlementId(settlementId);
query.setRetry(retry);
GetInvoiceInfoDTO result;
try {
result = mallInvoiceInfoQueryService.queryInvoicesInfo(query);
@@ -444,14 +456,56 @@ public class MallInboundEventService extends AbstractMallBridgeSupport {
if (main == null) {
return MallInboundResult.fail(404, "开票申请记录不存在");
}
if (result == null || !settlementId.equals(result.getSettlementId())) {
return MallInboundResult.fail(502, "供应商返回的发票结算单不匹配");
}
Integer success = result.getBSuccess() == null ? 0 : result.getBSuccess();
if (success == 0) {
if (result.getInvoiceInfos() == null || result.getInvoiceInfos().isEmpty() || StrUtil.isBlank(result.getSucOrderIds())) {
return MallInboundResult.fail(502, "供应商开票结果缺少发票明细或成功订单");
}
try {
List<String> fileIds = result.getInvoiceInfos().stream().map(InvoiceInfoDTO::getInvoiceId).collect(Collectors.toList());
if (fileIds.stream().anyMatch(StrUtil::isBlank)) {
throw new IllegalArgumentException("供应商发票文件标识为空");
}
MallPictureOutboundResult files = mallInvoiceInfoQueryService.queryInvoiceFiles(dto.getSupplierId(), settlementId, fileIds);
for (InvoiceInfoDTO item : result.getInvoiceInfos()) {
if (item.getStatus() != null && item.getStatus() != 1) {
throw new IllegalArgumentException("供应商发票尚未生成成功");
}
String base64 = files.getImageEncodeMap() == null ? null : files.getImageEncodeMap().get(item.getInvoiceId());
if (StrUtil.isBlank(base64)) {
throw new IllegalArgumentException("未获取到发票文件Base64" + item.getInvoiceId());
}
if (base64.startsWith("data:")) {
base64 = base64.substring(base64.indexOf(',') + 1);
}
byte[] bytes = Base64.getDecoder().decode(base64.replaceAll("\\s", ""));
if (bytes.length == 0) {
throw new IllegalArgumentException("发票文件内容为空");
}
String fileType = StrUtil.blankToDefault(files.getFileType(), StrUtil.blankToDefault(item.getFileType(), "PDF"));
if (!fileType.matches("(?i)pdf|jpg|jpeg|png")) {
throw new IllegalArgumentException("不支持的发票文件类型:" + fileType);
}
String localUrl = sysFileService.uploadReturnUrl(SysFileEngineTypeEnum.MINIO.getValue(),
"invoice-" + java.util.UUID.randomUUID() + "." + fileType.toLowerCase(java.util.Locale.ROOT), bytes);
item.setUrl(localUrl);
item.setImageEncode(localUrl);
item.setFileType(fileType);
}
} catch (Exception e) {
log.error("发票文件同步失败,settlementId={}", settlementId, e);
return MallInboundResult.fail(502, "发票文件同步失败:" + e.getMessage());
}
}
Trans.exec((Atom) () -> {
main.setBSuccess(success);
main.setSucOrderIds(result.getSucOrderIds());
main.setFailOrderIds(result.getFailOrderIds());
main.setFailMsg(result.getFailMsg());
dao.updateIgnoreNull(main);
if (success == 0 && result.getInvoiceInfos() != null) {
if (result.getInvoiceInfos() != null) {
for (InvoiceInfoDTO item : result.getInvoiceInfos()) {
PointsMallInvoiceInfo info = dao.fetch(PointsMallInvoiceInfo.class,
Cnd.where("mainId", "=", main.getId()).and("invoiceId", "=", item.getInvoiceId()).and("delFlag", "=", false));
@@ -472,6 +526,11 @@ public class MallInboundEventService extends AbstractMallBridgeSupport {
info.setFileType(item.getFileType());
info.setCheckCode(item.getCheckCode());
info.setInvoiceAddress(item.getInvoiceAddress());
info.setRemark(item.getRemark());
if (success != 0) {
info.setUrl("");
info.setImageEncode("");
}
if (StrUtil.isBlank(info.getId())) {
dao.insert(info);
} else {
@@ -490,6 +549,8 @@ public class MallInboundEventService extends AbstractMallBridgeSupport {
dao.update(PointsMallOrderSub.class, Chain.make("invoiceStatus", 0), Cnd.where("supplierId", "=", dto.getSupplierId())
.and("orderId", "in", failOrderIds).and("delFlag", "=", false));
}
dao.updateIgnoreNull(main);
});
log.info("供应商发票信息更新完成,supplierId={}settlementId={}", dto.getSupplierId(), settlementId);
return MallInboundResult.success();
}
@@ -3,6 +3,7 @@ package com.budwk.app.zhgh.pointsmall.mallbridge.service;
import cn.hutool.http.HttpUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.zhgh.pointsmall.mallbridge.util.MallBridgeResponseUtil;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallInvoiceApplyDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallOutboundResult;
import com.budwk.app.zhgh.pointsmall.mallbridge.service.support.AbstractMallBridgeSupport;
@@ -79,6 +80,6 @@ public class MallInvoiceApplyOutboundService extends AbstractMallBridgeSupport {
if (StrUtil.isBlank(response)) {
return MallOutboundResult.fail(502, "供应商开票申请无响应");
}
return JSONUtil.toBean(response, MallOutboundResult.class);
return JSONUtil.toBean(MallBridgeResponseUtil.parse(response), MallOutboundResult.class);
}
}
@@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.budwk.app.zhgh.pointsmall.mallbridge.util.MallBridgeResponseUtil;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.GetInvoiceInfoDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.QueryInvoiceInfoDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.service.support.AbstractMallBridgeSupport;
@@ -17,6 +18,9 @@ import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.HashMap;
import java.util.List;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.PictureQueryDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallPictureOutboundResult;
/**
* 商城桥接发票信息查询服务。
@@ -32,6 +36,40 @@ public class MallInvoiceInfoQueryService extends AbstractMallBridgeSupport {
super(dao);
}
public MallPictureOutboundResult queryInvoiceFiles(String supplierId, String settlementId, List<String> fileIds) {
PointsMallSupplier supplier = dao.fetch(PointsMallSupplier.class,
Cnd.where("supplierId", "=", supplierId).and("delFlag", "=", false));
if (supplier == null || StrUtil.isBlank(supplier.getSupplierApiUrl())) {
throw new IllegalArgumentException("供应商或供应商接口地址未配置");
}
String apiUrl = conf.get("spdb.bab7.url", "");
if (StrUtil.isBlank(apiUrl)) {
throw new IllegalArgumentException("未配置 BAB7 服务地址");
}
PictureQueryDTO dto = new PictureQueryDTO();
long timestamp = System.currentTimeMillis();
dto.setSupplierId(supplierId);
dto.setToken(MallBridgeCryptoUtil.generateSign(supplierId, supplier.getClientId(), timestamp));
dto.setTimestamp(timestamp);
dto.setQueryType("2");
dto.setIdNumber(settlementId);
dto.setFileIdList(fileIds);
HashMap<String, Object> body = new HashMap<>();
body.put("Address4", supplier.getSupplierApiUrl() + "/supplierOpenApi/pointOrder/queryFileBase64Info");
body.put("BussDealMd", "P");
body.put("SroNo", supplier.getSroNo());
body.put("RsrvFld1", supplier.getClientId());
body.put("AplParmObjct", MallBridgeCryptoUtil.sm4CbcEncrypt(JSONUtil.toJsonStr(dto),
conf.get("sm4cbc.keyHex", ""), conf.get("sm4cbc.ivHex", "")));
String response = HttpUtil.createPost(apiUrl).contentType("application/json;charset=UTF-8")
.body(JSONUtil.toJsonStr(body)).execute().body();
JSONObject payload = MallBridgeResponseUtil.parse(response);
if (payload.getInt("resultCode") != 0) {
throw new IllegalArgumentException(StrUtil.blankToDefault(payload.getStr("message"), "获取发票文件失败"));
}
return JSONUtil.toBean(payload, MallPictureOutboundResult.class);
}
/**
* 查询指定结算单的发票信息。
*/
@@ -82,7 +120,7 @@ public class MallInvoiceInfoQueryService extends AbstractMallBridgeSupport {
if (StrUtil.isBlank(response)) {
throw new IllegalArgumentException("没有查询到发票信息");
}
JSONObject responseJson = JSONUtil.parseObj(response);
JSONObject responseJson = MallBridgeResponseUtil.parse(response);
if (responseJson.getInt("resultCode", -1) != 0) {
throw new IllegalArgumentException(StrUtil.blankToDefault(responseJson.getStr("message"), "获取发票信息失败"));
}
@@ -2,6 +2,7 @@ package com.budwk.app.zhgh.pointsmall.mallbridge.service;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderQueryDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallOutboundResult;
@@ -85,6 +86,31 @@ public class MallOrderQueryOutboundService {
.execute()
.body();
log.info("9.2.2 获取订单详情调用结束,返回报文:{}", response);
return JSONUtil.toBean(response, MallOutboundResult.class);
JSONObject payload = JSONUtil.parseObj(response);
if (!payload.containsKey("resultCode")) {
JSONObject data = payload.getJSONObject("data");
JSONObject body = data == null ? null : data.getJSONObject("Body");
if (body == null) {
return MallOutboundResult.fail(502, "获取订单详情返回报文格式异常");
}
JSONObject header = body.getJSONObject("RspSvcHeader");
if (header == null || !"000000000000".equals(header.getStr("ReturnCode"))) {
return MallOutboundResult.fail(502, header == null ? "BAB7返回服务头缺失" :
StrUtil.blankToDefault(header.getStr("ReturnMsg"), "BAB7获取订单详情失败"));
}
JSONObject serviceBody = body.getJSONObject("RspSvcBody");
if (serviceBody == null || !"200".equals(serviceBody.getStr("ReturnStCd"))) {
return MallOutboundResult.fail(502, "BAB7调用供应商订单详情接口失败");
}
String content = serviceBody.getStr("ReturnCntnt");
if (StrUtil.isBlank(content)) {
return MallOutboundResult.fail(502, "供应商订单详情返回内容为空");
}
payload = JSONUtil.parseObj(content);
}
if (payload.getInt("resultCode") == null) {
return MallOutboundResult.fail(502, "供应商订单详情返回状态缺失");
}
return JSONUtil.toBean(payload, MallOutboundResult.class);
}
}
@@ -69,26 +69,8 @@ public class MallReconciliationInfoQueryService {
.body(JSONUtil.toJsonStr(bodyMap))
.execute()
.body();
if (response == null) {
return Collections.emptyList();
}
JSONObject returnResult = JSONUtil.parseObj(response);
log.info("通过对账接口获取对账订单数据,响应结果:{}", returnResult);
if ("-1".equals(returnResult.getStr("resultCode"))) {
throw new IllegalArgumentException("查询对账信息异常:" + returnResult);
}
MallOutboundPageResult mallOutboundPageResult = JSONUtil.toBean(returnResult, MallOutboundPageResult.class);
if (mallOutboundPageResult == null) {
return Collections.emptyList();
}
// 错误码为0表示查询成功,不等于0表示查询失败
Integer resultCode = mallOutboundPageResult.getResultCode();
if (resultCode != 0) {
log.info("通过对账接口获取对账订单数据,查询失败:{}", dto);
}
log.info("通过对账接口获取对账订单数据,响应结果:{}", response);
MallOutboundPageResult mallOutboundPageResult = parseReconciliationResponse(response);
if (CollUtil.isEmpty(mallOutboundPageResult.getOrderInfos())) {
return Collections.emptyList();
}
@@ -109,11 +91,10 @@ public class MallReconciliationInfoQueryService {
.body(JSONUtil.toJsonStr(bodyMap))
.execute()
.body();
mallOutboundPageResult = JSONUtil.toBean(response, MallOutboundPageResult.class);
mallOutboundPageResult = parseReconciliationResponse(response);
log.info("9.2.4 对账接口-分页循环拉取,返回报文:{}", mallOutboundPageResult);
// 错误码为0表示查询成功,不等于0表示查询失败
if (mallOutboundPageResult.getResultCode() != 0) {
log.info("通过对账接口获取对账订单数据,查询失败:{}", dto);
if (CollUtil.isEmpty(mallOutboundPageResult.getOrderInfos())) {
throw new IllegalArgumentException("供应商对账分页数据为空,页码:" + dto.getPage());
}
orderInfoList.addAll(mallOutboundPageResult.getOrderInfos());
page++;
@@ -123,6 +104,49 @@ public class MallReconciliationInfoQueryService {
return orderInfoList;
}
/**
* 解析 BAB7 包装或供应商直接返回的对账报文,并校验查询结果。
*/
private MallOutboundPageResult parseReconciliationResponse(String response) {
if (StrUtil.isBlank(response)) {
throw new IllegalArgumentException("获取对账信息返回报文为空");
}
JSONObject payload = JSONUtil.parseObj(response);
if (!payload.containsKey("resultCode")) {
JSONObject data = payload.getJSONObject("data");
JSONObject body = data == null ? null : data.getJSONObject("Body");
if (body == null) {
throw new IllegalArgumentException("获取对账信息返回报文格式异常");
}
JSONObject header = body.getJSONObject("RspSvcHeader");
if (header == null || !"000000000000".equals(header.getStr("ReturnCode"))) {
throw new IllegalArgumentException(header == null ? "BAB7返回服务头缺失" :
StrUtil.blankToDefault(header.getStr("ReturnMsg"), "BAB7获取对账信息失败"));
}
JSONObject serviceBody = body.getJSONObject("RspSvcBody");
if (serviceBody == null || !"200".equals(serviceBody.getStr("ReturnStCd"))) {
throw new IllegalArgumentException("BAB7调用供应商对账接口失败");
}
String content = serviceBody.getStr("ReturnCntnt");
if (StrUtil.isBlank(content)) {
throw new IllegalArgumentException("供应商对账接口返回内容为空");
}
payload = JSONUtil.parseObj(content);
}
Integer resultCode = payload.getInt("resultCode");
if (resultCode == null) {
throw new IllegalArgumentException("供应商对账接口返回状态缺失");
}
if (resultCode != 0) {
throw new IllegalArgumentException(StrUtil.blankToDefault(payload.getStr("message"), "供应商对账查询失败"));
}
MallOutboundPageResult result = JSONUtil.toBean(payload, MallOutboundPageResult.class);
if (CollUtil.isNotEmpty(result.getOrderInfos()) && (result.getTotalPage() == null || result.getTotalPage() < 1)) {
throw new IllegalArgumentException("供应商对账接口返回总页数无效");
}
return result;
}
/**
* 组装请求报文
*
@@ -34,29 +34,77 @@ import java.util.stream.Collectors;
* <p>只放通用转换和常量,具体接口业务放在各自服务中。</p>
*/
public abstract class AbstractMallBridgeSupport {
/**
* 访问令牌有效期,单位为秒(1 小时)。
*/
protected static final long ACCESS_EXPIRE_SECONDS = 3600;
/**
* 刷新令牌有效期,单位为秒(7 天)。
*/
protected static final long REFRESH_EXPIRE_SECONDS = 86400L * 7;
/**
* 订单状态:待支付。
*/
protected static final int ORDER_PENDING_PAY = 0;
/**
* 订单状态:已支付。
*/
protected static final int ORDER_FINISH_PAY = 1;
/**
* 订单状态:处理中。
*/
protected static final int ORDER_IN_PROGRESS = 2;
/**
* 订单状态:已取消。
*/
protected static final int ORDER_CANCELLED = 3;
/**
* 订单状态:已收货。
*/
protected static final int ORDER_RECEIVED = 4;
/**
* 订单状态:已完成。
*/
protected static final int ORDER_COMPLETED = 5;
/**
* 订单状态:退换货。
*/
protected static final int ORDER_EXCHANGE = 6;
/**
* 订单状态:已退款。
*/
protected static final int ORDER_REFUNDED = 7;
/**
* 订单状态:已作废。
*/
protected static final int ORDER_ABOLISH = 8;
/**
* 供桥接服务查询和保存用户、订单、商品等数据的 DAO。
*/
protected final Dao dao;
/**
* 初始化桥接公共支撑类,复用业务服务传入的 DAO。
*/
protected AbstractMallBridgeSupport(Dao dao) {
this.dao = dao;
}
/**
* 检查订单事件及供应商、订单、主订单、用户标识是否齐全;不校验对应数据是否存在。
*/
protected boolean hasRequiredOrderEventFields(MallOrderEventDTO dto) {
return dto != null && !StrUtil.hasBlank(dto.getSupplierId(), dto.getOrderId(), dto.getMainOrderId(), dto.getUserId());
}
/**
* 根据对外用户虚拟 ID 查询未禁用、未删除的系统用户。
*
* @param virtualId 供应商接口传入的用户虚拟 ID,不是系统用户主键
* @return 符合条件的用户;虚拟 ID 为空或用户不存在时返回 null
*/
protected Sys_user fetchUser(String virtualId) {
if (StrUtil.isBlank(virtualId)) {
return null;
@@ -65,6 +113,16 @@ public abstract class AbstractMallBridgeSupport {
.and("disabled", "=", false).and("delFlag", "=", false));
}
/**
* 将退换货事件和详情转换为退换货记录,并从原子单补充商品图片及文件信息。
*
* @param dto 订单事件,其中 userId 为用户虚拟 ID,保存时转换为系统用户主键
* @param orderInfo 供应商返回的退换货单详情
* @param sub 原子单,提供商品扩展信息
* @param orderStatus 待保存的退换货订单状态
* @return 尚未入库的退换货记录
* @throws IllegalArgumentException 虚拟 ID 对应的有效用户不存在时抛出
*/
protected PointsMallOrderExchange toExchange(MallOrderEventDTO dto, OrderInfoDTO orderInfo, PointsMallOrderSub sub, Integer orderStatus) {
Sys_user user = fetchUser(dto.getUserId());
if (user == null) {
@@ -94,6 +152,9 @@ public abstract class AbstractMallBridgeSupport {
return exchange;
}
/**
* 按 SKU 将原子单的图片及文件信息覆盖到传入商品列表中;原子单重复 SKU 取首项,传入列表为空时返回空列表。
*/
protected List<OrderProInfoDTO> mergeProductInfo(List<OrderProInfoDTO> products, String subExtJson) {
if (products == null || products.isEmpty()) {
return Collections.emptyList();
@@ -115,6 +176,15 @@ public abstract class AbstractMallBridgeSupport {
return products;
}
/**
* 将供应商订单详情转换为主订单,金额为空时按零处理,现金支付标记初始化为 0。
*
* @param supplierId 供应商标识
* @param userId 已由虚拟 ID 查得的系统用户主键
* @param orderInfo 供应商返回的订单详情
* @param state 主订单状态
* @return 尚未入库的主订单
*/
protected PointsMallOrderMain toMain(String supplierId, String userId, OrderInfoDTO orderInfo, Integer state) {
PointsMallOrderMain main = new PointsMallOrderMain();
main.setOrderId(orderInfo.getOrderId());
@@ -142,6 +212,9 @@ public abstract class AbstractMallBridgeSupport {
return main;
}
/**
* 从主订单复制信息生成子订单;子订单号为空时使用主订单号,更新时间取当前时间,对账和发票状态初始化为 0。
*/
protected PointsMallOrderSub toSub(PointsMallOrderMain main, String subOrderId) {
PointsMallOrderSub sub = new PointsMallOrderSub();
sub.setOrderId(StrUtil.blankToDefault(subOrderId, main.getOrderId()));
@@ -171,6 +244,9 @@ public abstract class AbstractMallBridgeSupport {
return sub;
}
/**
* 将商品逐条保存到指定子订单,仅保存图片列表中的首张图片;商品列表为 null 时不处理。
*/
protected void insertProducts(String orderSubId, List<OrderProInfoDTO> products) {
if (products == null) {
return;
@@ -188,6 +264,9 @@ public abstract class AbstractMallBridgeSupport {
}
}
/**
* 将查询记录转换为对外订单详情,统一日期和金额格式,并读取订单商品信息。
*/
protected OrderInfoDTO toOrderInfo(Record record) {
OrderInfoDTO dto = new OrderInfoDTO();
dto.setOrderId(record.getString("orderId"));
@@ -215,6 +294,9 @@ public abstract class AbstractMallBridgeSupport {
return dto;
}
/**
* 优先读取订单下未删除的子单商品;无商品记录时从 extJson 解析,扩展信息为空或为 null 字符串时返回空列表。
*/
protected List<OrderProInfoDTO> readProducts(String orderId, String extJson) {
List<PointsMallOrderSubProduct> products = dao.query(PointsMallOrderSubProduct.class, Cnd.where("orderSubId", "=", orderId).and("delFlag", "=", false));
if (!products.isEmpty()) {
@@ -226,6 +308,9 @@ public abstract class AbstractMallBridgeSupport {
return JSONUtil.toList(extJson, OrderProInfoDTO.class);
}
/**
* 将子单商品转换为对外商品信息,单张图片包装为列表,并以 SKU 和图片地址填充文件标识及路径。
*/
protected OrderProInfoDTO toProductDTO(PointsMallOrderSubProduct product) {
OrderProInfoDTO dto = new OrderProInfoDTO();
dto.setSku(product.getSku());
@@ -239,6 +324,9 @@ public abstract class AbstractMallBridgeSupport {
return dto;
}
/**
* 将发票记录转换为对外发票信息,并将开票时间转换为本地日期。
*/
protected InvoiceInfoDTO toInvoiceInfoDTO(PointsMallInvoiceInfo info) {
InvoiceInfoDTO dto = new InvoiceInfoDTO();
dto.setInvoiceId(info.getInvoiceId());
@@ -257,6 +345,9 @@ public abstract class AbstractMallBridgeSupport {
return dto;
}
/**
* 解析日期字符串,将日期时间分隔符 T 替换为空格;空白值返回 null,非法日期由日期工具抛出异常。
*/
protected Date parseDate(String value) {
if (StrUtil.isBlank(value)) {
return null;
@@ -264,6 +355,9 @@ public abstract class AbstractMallBridgeSupport {
return DateUtil.parse(value.replace("T", " "));
}
/**
* 将日期值统一格式化为 yyyy-MM-dd HH:mm:ssnull 值返回 null。
*/
protected String formatDate(Object value) {
if (value == null) {
return null;
@@ -271,10 +365,16 @@ public abstract class AbstractMallBridgeSupport {
return DateUtil.format(Convert.toDate(value), "yyyy-MM-dd HH:mm:ss");
}
/**
* 使用系统默认时区将时间转换为日期;null 值返回 null。
*/
protected LocalDate toLocalDate(Date date) {
return date == null ? null : date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}
/**
* 拆分逗号分隔的标识,去除首尾空白、空项和重复项,保留首次出现的顺序;空白值返回空列表。
*/
protected List<String> splitIds(String value) {
if (StrUtil.isBlank(value)) {
return Collections.emptyList();
@@ -286,18 +386,30 @@ public abstract class AbstractMallBridgeSupport {
.collect(Collectors.toList());
}
/**
* 金额为 null 时返回零,否则保留原值。
*/
protected BigDecimal nvl(BigDecimal value) {
return value == null ? BigDecimal.ZERO : value;
}
/**
* 金额为 null 时返回指定默认值,否则保留原值。
*/
protected BigDecimal nvl(BigDecimal value, BigDecimal defaultValue) {
return value == null ? defaultValue : value;
}
/**
* 将对象转换为金额,无法转换或值为 null 时使用零作为默认值。
*/
protected BigDecimal decimal(Object value) {
return Convert.toBigDecimal(value, BigDecimal.ZERO);
}
/**
* 将字符串转换为整数;空白值或无法转换的值返回 null。
*/
protected Integer parseInteger(String value) {
if (StrUtil.isBlank(value)) {
return null;
@@ -0,0 +1,37 @@
package com.budwk.app.zhgh.pointsmall.mallbridge.util;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
/** 提取供应商业务响应,兼容 BAB7 包装报文。 */
public class MallBridgeResponseUtil {
public static JSONObject parse(String response) {
if (StrUtil.isBlank(response)) {
throw new IllegalArgumentException("供应商接口返回为空");
}
JSONObject payload = JSONUtil.parseObj(response);
if (!payload.containsKey("resultCode")) {
JSONObject data = payload.getJSONObject("data");
JSONObject body = data == null ? null : data.getJSONObject("Body");
JSONObject header = body == null ? null : body.getJSONObject("RspSvcHeader");
if (header == null || !"000000000000".equals(header.getStr("ReturnCode"))) {
throw new IllegalArgumentException(header == null ? "BAB7返回报文格式异常" :
StrUtil.blankToDefault(header.getStr("ReturnMsg"), "BAB7调用失败"));
}
JSONObject serviceBody = body.getJSONObject("RspSvcBody");
if (serviceBody == null || !"200".equals(serviceBody.getStr("ReturnStCd"))) {
throw new IllegalArgumentException("BAB7调用供应商接口失败");
}
String content = serviceBody.getStr("ReturnCntnt");
if (StrUtil.isBlank(content)) {
throw new IllegalArgumentException("供应商业务响应为空");
}
payload = JSONUtil.parseObj(content);
}
if (payload.getInt("resultCode") == null) {
throw new IllegalArgumentException("供应商业务响应缺少结果码");
}
return payload;
}
}
@@ -8,6 +8,10 @@ public class PointsMallReconciliationParam {
private String supplierId;
private Integer reconciliationType;
private String orderId;
private String queryOrderId;
private String userId;
private Integer reconciliationStatus;
private Integer invoiceStatus;
private String startDate;
private String endDate;
}
@@ -91,32 +91,28 @@ public class PointsMallReconciliationServiceImpl extends BaseServiceImpl<PointsM
*/
@Override
public Result executeReconciliation(PointsMallReconciliationParam reconciliationBO) {
Integer reconciliationType = reconciliationBO.getReconciliationType();
List<String> orderIdList = null;
// 校验参数
if (reconciliationType == 1) {
if (StrUtil.isBlank(reconciliationBO.getStartDate())) {
return Result.error("请选择开始日期");
}
} else if (reconciliationType == 2) { // 历史异常订单对账
if (StrUtil.isBlank(reconciliationBO.getOrderId())) {
return Result.error("订单号不能为空");
}
orderIdList = StrUtil.splitTrim(reconciliationBO.getOrderId(), ",");
} else {
return Result.error("对账类型不正确");
try {
checkReconciliationParam(reconciliationBO);
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
Integer reconciliationType = reconciliationBO.getReconciliationType();
// 从浦发积分商城订单表查询对账订单
List<PointsMallOrderSub> orderInfoList = dao().query(PointsMallOrderSub.class, reconciliationCnd(reconciliationBO, ""));
if (CollUtil.isEmpty(orderInfoList)) {
return Result.error("浦发积分商城没有查询到对账订单数据");
}
Set<String> orderIdList = orderInfoList.stream().map(PointsMallOrderSub::getOrderId).collect(Collectors.toSet());
if (StrUtil.isNotBlank(reconciliationBO.getOrderId())
&& !orderIdList.containsAll(StrUtil.splitTrim(reconciliationBO.getOrderId(), ","))) {
return Result.error("部分勾选订单已不符合对账条件,请重新查询后选择");
}
QueryReconciliationInfoDTO queryReconciliationInfoDTO = new QueryReconciliationInfoDTO();
queryReconciliationInfoDTO.setSupplierId(reconciliationBO.getSupplierId());
queryReconciliationInfoDTO.setReconciliationType(reconciliationType);
queryReconciliationInfoDTO.setOrderId(orderIdList == null ? reconciliationBO.getOrderId() : String.join(",", orderIdList));
queryReconciliationInfoDTO.setOrderId(String.join(",", orderIdList));
queryReconciliationInfoDTO.setStartDate(reconciliationBO.getStartDate());
queryReconciliationInfoDTO.setEndDate(reconciliationBO.getEndDate());
queryReconciliationInfoDTO.setPage(1);
@@ -126,6 +122,7 @@ public class PointsMallReconciliationServiceImpl extends BaseServiceImpl<PointsM
if (CollUtil.isEmpty(result)) {
return Result.error("供应商侧没有拉取到对账订单数据");
}
result = result.stream().filter(o -> orderIdList.contains(o.getOrderId())).collect(Collectors.toList());
String supplierId = reconciliationBO.getSupplierId();
result.forEach(o -> o.setSupplierId(supplierId));
// 根据供应商ID + 订单ID进行分组
@@ -291,22 +288,23 @@ public class PointsMallReconciliationServiceImpl extends BaseServiceImpl<PointsM
}
private Cnd reconciliationCnd(PointsMallReconciliationParam param, String alias) {
Cnd cnd = Cnd.where(field(alias, "delFlag"), "=", false);
cnd.and(field(alias, "supplierId"), "=", param.getSupplierId());
PointsMallReconciliationOrderPageParam query = new PointsMallReconciliationOrderPageParam();
query.setSupplierId(param.getSupplierId());
query.setOrderId(param.getQueryOrderId());
query.setUserId(param.getUserId());
query.setReconciliationStatus(param.getReconciliationStatus());
query.setInvoiceStatus(param.getInvoiceStatus());
query.setStartDate(param.getStartDate());
query.setEndDate(param.getEndDate());
Cnd cnd = orderCnd(query, alias);
if (param.getReconciliationType() != null && param.getReconciliationType() == 2) {
cnd.and(field(alias, "reconciliationStatus"), "=", 2);
} else {
cnd.and(field(alias, "reconciliationStatus"), "=", 0);
}
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() + "%");
}
cnd.and(field(alias, "orderId"), "in", StrUtil.splitTrim(param.getOrderId(), ","));
}
addDateRange(cnd, alias, param.getStartDate(), param.getEndDate());
return cnd;
}
@@ -336,8 +334,8 @@ public class PointsMallReconciliationServiceImpl extends BaseServiceImpl<PointsM
if (StrUtil.isBlank(param.getStartDate()) || StrUtil.isBlank(param.getEndDate())) {
throw new IllegalArgumentException("对账日期不能为空");
}
if (param.getReconciliationType() == 2 && StrUtil.isBlank(param.getOrderId())) {
throw new IllegalArgumentException("订单号不能为空");
if (param.getReconciliationType() != 1 && param.getReconciliationType() != 2) {
throw new IllegalArgumentException("对账类型不正确");
}
}
@@ -14,19 +14,11 @@ layout("/layouts/platform.html"){
<el-option label="失败" :value="1"></el-option>
</el-select>
</search-item>
<search-item label="成功订单">
<el-input clearable placeholder="请输入订单ID" v-model="pageForm.sucOrderIds"></el-input>
</search-item>
<search-item label="失败订单">
<el-input clearable placeholder="请输入订单ID" v-model="pageForm.failOrderIds"></el-input>
</search-item>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool label="发票管理">
<el-button @click="openApplyDialog" size="mini" type="success">开票申请</el-button>
<el-button @click="openOrderDialog" size="mini" type="primary">可开票订单</el-button>
</table-tool>
<el-table :data="tableData" border class="vi-table" size="small" style="width: 100%" v-loading="tableLoading">
<el-table-column :index="indexMethod" align="center" label="序号" type="index" width="70"></el-table-column>
@@ -36,13 +28,11 @@ layout("/layouts/platform.html"){
<el-tag :type="invoiceResultType(row.bSuccess)" size="mini">{{ invoiceResultName(row.bSuccess) }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="成功订单" min-width="220" prop="sucOrderIds" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="失败订单" min-width="220" prop="failOrderIds" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="失败原因" min-width="220" prop="failMsg" show-overflow-tooltip></el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="220">
<el-table-column align="center" fixed="right" label="操作" width="340">
<template slot-scope="{row}">
<el-button @click="openInfoDialog(row)" size="mini" type="primary">发票明细</el-button>
<el-button @click="openOrderDialog(row.sucOrderIds)" size="mini">成功订单</el-button>
<el-button @click="openOrderDialog(row.sucOrderIds)" size="mini" type="primary">开票成功列表</el-button>
<el-button @click="openInfoDialog(row)" size="mini">查看</el-button>
<el-button v-if="Number(row.bSuccess) === 1" @click="retryInvoice(row.id)" :loading="retryLoading" size="mini" type="warning">重新获取发票</el-button>
</template>
</el-table-column>
</el-table>
@@ -58,114 +48,18 @@ layout("/layouts/platform.html"){
></el-pagination>
</el-card>
<el-dialog :close-on-click-modal="false" title="开票申请" :visible.sync="applyDialogVisible" top="5vh" width="720px">
<el-form :model="applyForm" label-width="120px" size="small">
<el-form-item label="供应商" required>
<el-select clearable filterable placeholder="请选择供应商" style="width: 100%" v-model="applyForm.supplierId">
<el-option :key="item.supplierId" :label="item.supplierName" :value="item.supplierId" v-for="item in supplierList"></el-option>
</el-select>
</el-form-item>
<el-form-item label="订单完成日期" required>
<el-date-picker
end-placeholder="结束日期"
range-separator="至"
start-placeholder="开始日期"
style="width: 100%"
type="daterange"
value-format="yyyy-MM-dd"
v-model="applyDateRange"
></el-date-picker>
</el-form-item>
<el-form-item label="订单ID" required>
<el-input placeholder="多个订单ID用英文逗号分隔" type="textarea" v-model="applyForm.orderIds"></el-input>
</el-form-item>
<el-form-item label="开票日期" required>
<el-date-picker placeholder="请选择开票日期" style="width: 100%" type="date" value-format="yyyy-MM-dd" v-model="applyForm.invoiceDate"></el-date-picker>
</el-form-item>
<el-form-item label="发票抬头">
<el-input clearable placeholder="请输入发票抬头" v-model="applyForm.invoiceTitle"></el-input>
</el-form-item>
<el-form-item label="纳税人识别号" required>
<el-input clearable placeholder="请输入纳税人识别号" v-model="applyForm.taxIdNumber"></el-input>
</el-form-item>
<el-form-item label="发票地址" required>
<el-input clearable placeholder="请输入发票地址" v-model="applyForm.invoiceAddress"></el-input>
</el-form-item>
<el-form-item label="联系电话" required>
<el-input clearable placeholder="请输入联系电话" v-model="applyForm.invoiceContact"></el-input>
</el-form-item>
<el-form-item label="发票类型">
<el-select clearable placeholder="请选择发票类型" style="width: 100%" v-model="applyForm.invoiceType">
<el-option label="全电专票" :value="9"></el-option>
<el-option label="全电普票" :value="10"></el-option>
</el-select>
</el-form-item>
<el-form-item label="开票内容">
<el-input clearable placeholder="请输入开票内容" v-model="applyForm.invoiceContent"></el-input>
</el-form-item>
<el-form-item label="开户银行">
<el-input clearable placeholder="请输入开户银行" v-model="applyForm.bankName"></el-input>
</el-form-item>
<el-form-item label="银行账号">
<el-input clearable placeholder="请输入银行账号" v-model="applyForm.bankAccount"></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input placeholder="请输入备注" type="textarea" v-model="applyForm.remark"></el-input>
</el-form-item>
<el-form-item label="订单积分合计">
<span>{{ orderTotalPoints }}</span>
<el-button @click="loadOrderTotalPoints" size="mini" style="margin-left: 12px">计算</el-button>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="applyDialogVisible = false">取消</el-button>
<el-button @click="submitApply" :loading="applyLoading" type="primary">提交申请</el-button>
</span>
</el-dialog>
<el-dialog :close-on-click-modal="false" title="可开票订单" :visible.sync="orderDialogVisible" top="5vh" width="90%">
<el-form :inline="true" size="small" @submit.native.prevent>
<el-form-item label="供应商">
<el-select clearable filterable placeholder="供应商" style="width: 180px" v-model="orderForm.supplierId">
<el-option :key="item.supplierId" :label="item.supplierName" :value="item.supplierId" v-for="item in supplierList"></el-option>
</el-select>
</el-form-item>
<el-form-item label="订单ID">
<el-input clearable placeholder="多个订单ID用英文逗号分隔" v-model="orderForm.orderId"></el-input>
</el-form-item>
<el-form-item label="完成日期">
<el-date-picker
end-placeholder="结束日期"
range-separator="至"
start-placeholder="开始日期"
style="width: 260px"
type="daterange"
value-format="yyyy-MM-dd"
v-model="orderDateRange"
></el-date-picker>
</el-form-item>
<el-form-item label="开票状态">
<el-select clearable placeholder="开票状态" style="width: 130px" v-model="orderForm.invoiceStatus">
<el-option label="未开票" :value="0"></el-option>
<el-option label="已开票" :value="1"></el-option>
<el-option label="开票中" :value="2"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="orderSearch" type="primary">查询</el-button>
<el-button @click="orderReset">重置</el-button>
<el-button @click="useOrdersForApply" type="success">用于开票</el-button>
</el-form-item>
</el-form>
<el-table :data="orderList" @selection-change="orderSelectionChange" border size="small" style="width: 100%" v-loading="orderLoading">
<el-table-column align="center" type="selection" width="50"></el-table-column>
<el-dialog :close-on-click-modal="false" title="订单明细" :visible.sync="orderDialogVisible" top="5vh" width="90%">
<el-table :data="orderList" border size="small" style="width: 100%" v-loading="orderLoading">
<el-table-column :index="orderIndexMethod" align="center" label="序号" type="index" width="70"></el-table-column>
<el-table-column align="center" label="订单ID" min-width="170" prop="orderId" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="主订单号" min-width="170" prop="mainOrderId" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="供应商" min-width="150" prop="supplierName" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="用户ID" min-width="120" prop="userId" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="完成时间" min-width="160" prop="orderCompleteTime"></el-table-column>
<el-table-column align="center" label="订单总金额" min-width="110" prop="totalPrice"></el-table-column>
<el-table-column align="center" label="积分金额" min-width="110" prop="pointsPrice"></el-table-column>
<el-table-column align="center" label="实际消耗积分" min-width="110">
<template slot-scope="{row}">{{ (Number(row.pointsPrice || 0) - Number(row.refund || 0)).toFixed(2) }}</template>
</el-table-column>
<el-table-column align="center" label="退款积分" min-width="110" prop="refund"></el-table-column>
<el-table-column align="center" label="开票状态" min-width="100">
<template slot-scope="{row}">
@@ -184,37 +78,30 @@ layout("/layouts/platform.html"){
style="margin-top: 16px; text-align: right"
></el-pagination>
<span slot="footer">
<span style="margin-right: 20px">总消耗积分:{{ orderTotalPoints }}</span>
<el-button @click="orderDialogVisible = false">关闭</el-button>
</span>
</el-dialog>
<el-dialog :close-on-click-modal="false" title="发票明细" :visible.sync="infoDialogVisible" top="5vh" width="90%">
<el-form :inline="true" size="small" @submit.native.prevent>
<el-form-item label="发票号码">
<el-input clearable placeholder="发票号码" v-model="infoForm.invoiceId"></el-input>
</el-form-item>
<el-form-item label="发票代码">
<el-input clearable placeholder="发票代码" v-model="infoForm.invoiceCode"></el-input>
</el-form-item>
<el-form-item label="发票日期">
<el-date-picker clearable placeholder="发票日期" type="date" value-format="yyyy-MM-dd" v-model="infoForm.invoiceDate"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button @click="infoSearch" type="primary">查询</el-button>
</el-form-item>
</el-form>
<el-table :data="infoList" border size="small" style="width: 100%" v-loading="infoLoading">
<el-table-column :index="infoIndexMethod" align="center" label="序号" type="index" width="70"></el-table-column>
<el-table-column align="center" label="发票号码" min-width="150" prop="invoiceId" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="发票代码" min-width="130" prop="invoiceCode" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="发票日期" min-width="120" prop="invoiceDate"></el-table-column>
<el-table-column align="center" label="发票类型" min-width="110">
<template slot-scope="{row}">{{ invoiceTypeName(row.invoiceType) }}</template>
<el-dialog :close-on-click-modal="false" title="发票明细列表" :visible.sync="infoDialogVisible" top="5vh" width="90%">
<el-table :data="infoList" stripe size="small" style="width: 100%" v-loading="infoLoading">
<el-table-column label="发票主ID" min-width="160" prop="mainId" show-overflow-tooltip></el-table-column>
<el-table-column label="发票号码" min-width="170" prop="invoiceId" show-overflow-tooltip></el-table-column>
<el-table-column label="发票代码" min-width="160" prop="invoiceCode" show-overflow-tooltip></el-table-column>
<el-table-column label="发票日期" min-width="140" prop="invoiceDate"></el-table-column>
<el-table-column label="发票裸价" min-width="130" prop="invoiceNakeAmount"></el-table-column>
<el-table-column label="发票税率" min-width="110" prop="invoiceTaxRate"></el-table-column>
<el-table-column label="发票税额" min-width="130" prop="invoiceTaxAmount"></el-table-column>
<el-table-column label="价税合计" min-width="130" prop="invoiceAmount"></el-table-column>
<el-table-column label="发票类型" min-width="180">
<template slot-scope="{row}"><el-tag size="mini">{{ Number(row.invoiceType) === 9 ? '全电发票(增值税专用发票)' : Number(row.invoiceType) === 10 ? '全电发票(增值税普通发票)' : invoiceTypeName(row.invoiceType) }}</el-tag></template>
</el-table-column>
<el-table-column align="center" label="电子发票" width="110">
<template slot-scope="{row}"><el-button @click="previewInvoice(row)" size="mini" type="text">预览</el-button></template>
</el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="100">
<template slot-scope="{row}"><el-button @click="openInvoiceDetail(row)" size="mini" type="text">详情</el-button></template>
</el-table-column>
<el-table-column align="center" label="价税合计" min-width="110" prop="invoiceAmount"></el-table-column>
<el-table-column align="center" label="税额" min-width="100" prop="invoiceTaxAmount"></el-table-column>
<el-table-column align="center" label="纳税人识别号" min-width="160" prop="taxIdNumber" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="电子发票地址" min-width="220" prop="url" show-overflow-tooltip></el-table-column>
</el-table>
<el-pagination
:current-page="infoForm.pageNumber"
@@ -225,9 +112,46 @@ layout("/layouts/platform.html"){
style="margin-top: 16px; text-align: right"
></el-pagination>
<span slot="footer">
<el-button @click="retryInvoice(infoForm.mainId)" :loading="retryLoading" type="primary">重新获取发票</el-button>
<el-button @click="infoDialogVisible = false">关闭</el-button>
</span>
</el-dialog>
<el-dialog :close-on-click-modal="false" title="发票详情" :visible.sync="detailDialogVisible" append-to-body top="5vh" width="70%">
<el-card v-loading="detailLoading" shadow="never">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="发票主ID">{{ detailData.mainId || '-' }}</el-descriptions-item>
<el-descriptions-item label="发票号码">{{ detailData.invoiceId || '-' }}</el-descriptions-item>
<el-descriptions-item label="发票代码">{{ detailData.invoiceCode || '-' }}</el-descriptions-item>
<el-descriptions-item label="发票日期">{{ detailData.invoiceDate || '-' }}</el-descriptions-item>
<el-descriptions-item label="发票类型">
<el-tag size="mini" :type="Number(detailData.invoiceType) === 9 ? 'warning' : 'primary'">{{ Number(detailData.invoiceType) === 9 ? '全电发票(增值税专用发票)' : Number(detailData.invoiceType) === 10 ? '全电发票(增值税普通发票)' : invoiceTypeName(detailData.invoiceType) }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="发票状态"><el-tag size="mini" :type="Number(detailData.status) === 1 ? 'success' : Number(detailData.status) === 2 ? 'danger' : 'info'">{{ Number(detailData.status) === 1 ? '成功' : Number(detailData.status) === 2 ? '失败' : '未知' }}</el-tag></el-descriptions-item>
<el-descriptions-item label="发票裸价"><span style="color: #409eff">¥{{ Number(detailData.invoiceNakeAmount || 0).toFixed(2) }}</span></el-descriptions-item>
<el-descriptions-item label="发票税率">{{ detailData.invoiceTaxRate == null ? '-' : (Number(detailData.invoiceTaxRate) * 100).toFixed(2) + '%' }}</el-descriptions-item>
<el-descriptions-item label="发票税额"><span style="color: #409eff">¥{{ Number(detailData.invoiceTaxAmount || 0).toFixed(2) }}</span></el-descriptions-item>
<el-descriptions-item label="价税合计" :span="3"><strong style="color: #f56c6c; font-size: 18px">¥{{ Number(detailData.invoiceAmount || 0).toFixed(2) }}</strong></el-descriptions-item>
<el-descriptions-item label="备注" :span="2" v-if="detailData.remark">{{ detailData.remark }}</el-descriptions-item>
<el-descriptions-item label="电子发票">
<template v-if="detailData.url || detailData.imageEncode">
<el-button @click="previewInvoice(detailData)" size="mini" type="text" icon="el-icon-view">预览PDF</el-button>
<el-link :href="detailData.url || detailData.imageEncode" :download="(detailData.invoiceId || '发票') + '.' + (detailData.fileType || 'pdf').toLowerCase()" type="success" :underline="false" icon="el-icon-download" style="margin-left: 16px">下载PDF</el-link>
</template>
<span v-else>暂无地址</span>
</el-descriptions-item>
</el-descriptions>
</el-card>
<span slot="footer">
<el-button v-if="detailData.id && (Number(detailData.status) === 2 || !detailData.url)" @click="retryInvoice(detailData.mainId)" :loading="retryLoading" type="primary">重新获取发票</el-button>
<el-button @click="detailDialogVisible = false">关闭</el-button>
</span>
</el-dialog>
<el-dialog :close-on-click-modal="false" title="PDF预览" :visible.sync="pdfDialogVisible" append-to-body width="80%" @closed="closePdfPreview">
<div v-loading="pdfLoading" style="height: 70vh">
<iframe v-if="pdfUrl" :src="pdfUrl" title="发票PDF预览" style="width: 100%; height: 100%; border: 0"></iframe>
</div>
<span slot="footer"><el-button @click="pdfDialogVisible = false">关闭</el-button></span>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
@@ -236,7 +160,6 @@ layout("/layouts/platform.html"){
mixins: [initTableMixins],
data() {
return {
supplierList: [],
tableData: [],
tableLoading: false,
pageForm: {
@@ -244,48 +167,28 @@ layout("/layouts/platform.html"){
pageSize: 10,
totalCount: 0,
settlementId: null,
bSuccess: null,
sucOrderIds: null,
failOrderIds: null,
failMsg: null
bSuccess: null
},
applyDialogVisible: false,
applyLoading: false,
applyDateRange: [],
orderTotalPoints: 0,
applyForm: this.emptyApplyForm(),
orderDialogVisible: false,
orderLoading: false,
orderDateRange: [],
orderList: [],
orderSelections: [],
orderForm: this.emptyOrderForm(),
infoDialogVisible: false,
infoLoading: false,
infoList: [],
infoForm: this.emptyInfoForm()
infoForm: this.emptyInfoForm(),
detailDialogVisible: false,
detailLoading: false,
retryLoading: false,
pdfDialogVisible: false,
pdfLoading: false,
pdfUrl: null,
detailData: {}
}
},
methods: {
emptyApplyForm() {
return {
supplierId: null,
orderIds: null,
startDate: null,
endDate: null,
invoiceDate: null,
invoiceTitle: null,
taxIdNumber: null,
invoiceAddress: null,
invoiceContact: null,
invoiceType: 10,
invoiceContent: null,
remark: null,
bankAccount: null,
registeredAddress: null,
bankName: null
}
},
emptyOrderForm() {
return {
pageNumber: 1,
@@ -295,7 +198,7 @@ layout("/layouts/platform.html"){
orderId: null,
startDate: null,
endDate: null,
invoiceStatus: 0
invoiceStatus: null
}
},
emptyInfoForm() {
@@ -343,79 +246,21 @@ layout("/layouts/platform.html"){
this.pageForm.pageNumber = 1
this.pageData()
},
async loadSuppliers() {
const resp = await this.$axios.post(loc() + "/supplierList")
if (resp.code === 0) {
this.supplierList = resp.data || []
}
},
openApplyDialog() {
this.applyForm = this.emptyApplyForm()
this.applyDateRange = []
this.orderTotalPoints = 0
this.applyDialogVisible = true
},
syncApplyDateRange() {
this.applyForm.startDate = this.applyDateRange && this.applyDateRange.length ? this.applyDateRange[0] : null
this.applyForm.endDate = this.applyDateRange && this.applyDateRange.length ? this.applyDateRange[1] : null
},
async loadOrderTotalPoints() {
this.syncApplyDateRange()
if (!this.applyForm.supplierId || !this.applyForm.orderIds) {
this.notifyWarning("请先选择供应商并填写订单ID")
return
}
const resp = await this.$axios.post(loc() + "/orderTotalPoints", {
supplierId: this.applyForm.supplierId,
orderId: this.applyForm.orderIds,
startDate: this.applyForm.startDate,
endDate: this.applyForm.endDate,
invoiceStatus: 0
})
if (resp.code === 0) {
this.orderTotalPoints = resp.data || 0
} else {
this.notifyWarning(resp.msg)
}
},
async submitApply() {
this.syncApplyDateRange()
this.applyLoading = true
const resp = await this.$axios.post(loc() + "/applyInvoice", this.applyForm)
this.applyLoading = false
if (resp.code === 0) {
this.notifySuccess(resp.data || "开票申请成功")
this.applyDialogVisible = false
this.pageData()
} else {
this.notifyWarning(resp.msg)
}
},
openOrderDialog(orderIds) {
this.orderDialogVisible = true
this.orderForm = this.emptyOrderForm()
this.orderDateRange = []
if (typeof orderIds === "string" && orderIds) {
this.orderForm.orderId = orderIds
this.orderForm.invoiceStatus = null
this.orderList = []
this.orderTotalPoints = 0
if (typeof orderIds !== "string" || !orderIds.trim()) {
return
}
this.orderForm.orderId = orderIds
this.orderPageData()
},
syncOrderDateRange() {
this.orderForm.startDate = this.orderDateRange && this.orderDateRange.length ? this.orderDateRange[0] : null
this.orderForm.endDate = this.orderDateRange && this.orderDateRange.length ? this.orderDateRange[1] : null
},
orderSearch() {
this.orderForm.pageNumber = 1
this.orderPageData()
},
orderReset() {
this.orderForm = this.emptyOrderForm()
this.orderDateRange = []
this.orderPageData()
this.loadOrderTotalPoints()
},
async orderPageData() {
this.syncOrderDateRange()
if (!this.orderForm.orderId) return
this.orderLoading = true
const resp = await this.$axios.post(loc() + "/orderPage", this.orderForm)
this.orderLoading = false
@@ -435,27 +280,77 @@ layout("/layouts/platform.html"){
this.orderForm.pageNumber = 1
this.orderPageData()
},
orderSelectionChange(rows) {
this.orderSelections = rows || []
async loadOrderTotalPoints() {
const resp = await this.$axios.post(loc() + "/orderTotalPoints", this.orderForm)
if (resp.code === 0) this.orderTotalPoints = resp.data || 0
else this.notifyWarning(resp.msg)
},
useOrdersForApply() {
if (!this.orderSelections.length) {
this.notifyWarning("请先选择订单")
async openInvoiceDetail(row) {
this.detailData = {}
this.detailDialogVisible = true
this.detailLoading = true
try {
const resp = await this.$axios.post(loc() + "/infoDetail", { id: row.id })
if (resp.code === 0 && resp.data) this.detailData = resp.data
else this.notifyWarning(resp.msg || "发票详情不存在")
} finally {
this.detailLoading = false
}
},
async retryInvoice(mainId) {
if (!mainId || this.retryLoading) return
this.retryLoading = true
try {
const resp = await this.$axios.post(loc() + "/retryInvoice", { mainId })
if (resp.code !== 0) this.notifyWarning(resp.msg || "重新获取发票失败")
else this.$message.success("发票已重新获取")
await this.pageData()
if (this.infoDialogVisible && this.infoForm.mainId === mainId) await this.infoPageData()
if (this.detailDialogVisible && this.detailData.mainId === mainId) {
await this.openInvoiceDetail({ id: this.detailData.id })
}
} catch (e) {
this.notifyWarning(e.message || "重新获取发票失败")
} finally {
this.retryLoading = false
}
},
async previewInvoice(row) {
const path = row.url || row.imageEncode
if (!path) {
this.notifyWarning("暂无可预览的发票文件")
return
}
const first = this.orderSelections[0]
this.applyForm = this.emptyApplyForm()
this.applyForm.supplierId = first.supplierId
this.applyForm.orderIds = this.orderSelections.map((item) => item.orderId).join(",")
this.applyForm.invoiceDate = new Date().toISOString().slice(0, 10)
this.applyDateRange = [this.orderForm.startDate, this.orderForm.endDate].filter(Boolean)
this.orderDialogVisible = false
this.applyDialogVisible = true
this.loadOrderTotalPoints()
if ((row.fileType || "pdf").toLowerCase() !== "pdf") {
this.$commonUtil.previewFile({ suffix: row.fileType.toLowerCase(), downloadPath: path, name: row.invoiceId || "发票" })
return
}
if (this.pdfLoading) return
this.closePdfPreview()
this.pdfDialogVisible = true
this.pdfLoading = true
try {
const response = await fetch(path, { credentials: "same-origin" })
if (!response.ok) throw new Error("获取发票文件失败")
const bytes = await response.arrayBuffer()
const signature = String.fromCharCode.apply(null, new Uint8Array(bytes, 0, Math.min(5, bytes.byteLength)))
if (signature !== "%PDF-") throw new Error("返回内容不是有效的PDF文件")
if (this.pdfDialogVisible) this.pdfUrl = URL.createObjectURL(new Blob([bytes], { type: "application/pdf" }))
} catch (e) {
this.notifyWarning(e.message || "发票预览失败")
this.pdfDialogVisible = false
} finally {
this.pdfLoading = false
}
},
closePdfPreview() {
if (this.pdfUrl) URL.revokeObjectURL(this.pdfUrl)
this.pdfUrl = null
},
openInfoDialog(row) {
this.infoForm = this.emptyInfoForm()
this.infoForm.mainId = row.id
this.infoList = []
this.infoDialogVisible = true
this.infoPageData()
},
@@ -470,6 +365,7 @@ layout("/layouts/platform.html"){
if (resp.code === 0) {
this.infoList = resp.data.list || []
this.infoForm.totalCount = resp.data.totalCount || 0
this.pageData()
} else {
this.notifyWarning(resp.msg)
}
@@ -479,10 +375,10 @@ layout("/layouts/platform.html"){
this.infoPageData()
},
invoiceResultName(value) {
return Number(value) === 0 ? "成功" : "失败"
return value == null ? "开票中" : Number(value) === 0 ? "成功" : "失败"
},
invoiceResultType(value) {
return Number(value) === 0 ? "success" : "danger"
return value == null ? "warning" : Number(value) === 0 ? "success" : "danger"
},
invoiceStatusName(value) {
const map = { 0: "未开票", 1: "已开票", 2: "开票中" }
@@ -498,8 +394,11 @@ layout("/layouts/platform.html"){
}
},
created() {
this.loadSuppliers()
this.pageData()
},
beforeDestroy() {
this.pdfDialogVisible = false
this.closePdfPreview()
}
})
</script>
@@ -117,6 +117,7 @@ layout("/layouts/platform.html"){
</el-card>
<el-dialog :close-on-click-modal="false" title="确认对账" :visible.sync="reconcileDialogVisible" width="480px">
<el-alert :closable="false" show-icon style="margin-bottom: 16px" :title="reconcileSelectionCount ? '本次仅对已勾选的 ' + reconcileSelectionCount + ' 笔订单执行对账。' : '您尚未勾选订单,本次将对当前查询条件下所有符合对账条件的订单执行对账(包含其他分页)。请确认对账范围。'" :type="reconcileSelectionCount ? 'info' : 'warning'"></el-alert>
<el-descriptions :column="1" border size="small">
<el-descriptions-item label="本次对账金额">{{ reconcilePointsTotal }}</el-descriptions-item>
</el-descriptions>
@@ -244,6 +245,8 @@ layout("/layouts/platform.html"){
tableData: [],
tableLoading: false,
selections: [],
lastQuery: null,
reconcileSelectionCount: 0,
hasSearched: false,
pageForm: {
pageNumber: 1,
@@ -272,12 +275,12 @@ layout("/layouts/platform.html"){
applyDialogVisible: false,
applyLoading: false,
applyForm: {
invoiceDate: null,
invoiceTitle: null,
taxIdNumber: null,
invoiceAddress: null,
invoiceContact: null,
invoiceType: null
invoiceDate: [new Date().getFullYear(), String(new Date().getMonth() + 1).padStart(2, "0"), String(new Date().getDate()).padStart(2, "0")].join("-"),
invoiceTitle: "测试工会(开发测试)",
taxIdNumber: "91320100MA00000000",
invoiceAddress: "江苏省南京市测试路1号(开发测试)",
invoiceContact: "13800000000",
invoiceType: 10
},
applyRules: {
invoiceDate: [{ required: true, message: "请选择开票日期", trigger: "change" }],
@@ -374,9 +377,13 @@ layout("/layouts/platform.html"){
},
async pageData() {
this.tableLoading = true
const resp = await this.$axios.post(loc() + "/pageData", this.pageForm)
const query = Object.assign({}, this.pageForm)
const resp = await this.$axios.post(loc() + "/pageData", query)
this.tableLoading = false
if (resp.code === 0) {
this.lastQuery = query
this.selections = []
if (this.$refs.tableRef) this.$refs.tableRef.clearSelection()
this.tableData = resp.data.list || []
this.pageForm.totalCount = resp.data.totalCount || 0
} else {
@@ -399,16 +406,26 @@ layout("/layouts/platform.html"){
}
},
async openReconcileDialog() {
if (this.tableLoading || !this.lastQuery) {
this.notifyWarning("请先查询订单列表")
return
}
const query = this.lastQuery
this.reconcileSelectionCount = this.selections.length
this.reconcileForm = {
supplierId: this.pageForm.supplierId,
reconciliationType: this.pageForm.reconciliationType,
orderId: this.pageForm.orderId,
startDate: this.pageForm.startDate,
endDate: this.pageForm.endDate
supplierId: query.supplierId,
reconciliationType: query.reconciliationType,
orderId: this.selections.map(item => item.orderId).join(",") || null,
queryOrderId: query.orderId,
userId: query.userId,
reconciliationStatus: query.reconciliationStatus,
invoiceStatus: query.invoiceStatus,
startDate: query.startDate,
endDate: query.endDate
}
this.reconcilePointsTotal = 0
if (!this.reconciliationParamsComplete()) {
this.notifyWarning(this.reconcileForm.reconciliationType === 2 ? "请选择供应商、对账类型对账日期并填写订单ID" : "请选择供应商、对账类型和对账日期")
this.notifyWarning("请选择供应商、对账类型对账日期,并查询订单列表")
return
}
if (await this.loadPointsTotal()) {
@@ -422,7 +439,7 @@ layout("/layouts/platform.html"){
if (!this.reconcileForm.startDate || !this.reconcileForm.endDate) {
return false
}
return this.reconcileForm.reconciliationType !== 2 || !!this.reconcileForm.orderId
return true
},
async loadPointsTotal() {
if (!this.reconciliationParamsComplete()) {
@@ -443,7 +460,7 @@ layout("/layouts/platform.html"){
},
async executeReconciliation() {
if (!this.reconciliationParamsComplete()) {
this.notifyWarning(this.reconcileForm.reconciliationType === 2 ? "请先选择供应商并填写订单ID" : "请先选择供应商、对账类型和对账日期")
this.notifyWarning("请先选择供应商、对账类型和对账日期,并查询订单列表")
return
}
this.executeLoading = true
@@ -469,12 +486,12 @@ layout("/layouts/platform.html"){
return
}
this.applyForm = {
invoiceDate: null,
invoiceTitle: null,
taxIdNumber: null,
invoiceAddress: null,
invoiceContact: null,
invoiceType: null
invoiceDate: [new Date().getFullYear(), String(new Date().getMonth() + 1).padStart(2, "0"), String(new Date().getDate()).padStart(2, "0")].join("-"),
invoiceTitle: "测试工会(开发测试)",
taxIdNumber: "91320100MA00000000",
invoiceAddress: "江苏省南京市测试路1号(开发测试)",
invoiceContact: "13800000000",
invoiceType: 10
}
this.applyDialogVisible = true
},
@@ -509,7 +526,7 @@ layout("/layouts/platform.html"){
openRecordDialog() {
this.recordDialogVisible = true
this.recordForm.supplierId = this.pageForm.supplierId
this.recordForm.orderId = this.pageForm.orderId
this.recordForm.orderId = null
this.recordPageData()
},
recordSearch() {
@@ -257,6 +257,7 @@ layout("/layouts/platform_h5.html"){
return item ? item.class : "gray"
},
getOrderStatusItem(value) {
if (Number(value) === 1) return { code: 1, name: "已支付", class: "blue" }
const list = this.normalTabList.concat(this.specialTabList)
return list.find((tab) => Number(tab.code) === Number(value))
},