应收账单功能实现
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
package com.ruoyi.web.controller.ar;
|
||||
|
||||
import java.util.List;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.ar.domain.ArBill;
|
||||
import com.ruoyi.ar.domain.ArBankAccount;
|
||||
import com.ruoyi.ar.domain.ArBillExport;
|
||||
import com.ruoyi.ar.domain.ArBillLog;
|
||||
import com.ruoyi.ar.domain.vo.ArBillActionRequest;
|
||||
import com.ruoyi.ar.domain.vo.ArBillGenerateQuery;
|
||||
import com.ruoyi.ar.domain.vo.ArBillGenerateRequest;
|
||||
import com.ruoyi.ar.service.IArBillService;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.uuid.IdUtils;
|
||||
import com.ruoyi.price.domain.PrcOrderBill;
|
||||
|
||||
/** 应收账单管理。 */
|
||||
@RestController
|
||||
@RequestMapping("/ar/bill")
|
||||
@Validated
|
||||
public class ArBillController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private IArBillService billService;
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('ar:bill:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(ArBill query)
|
||||
{
|
||||
startPage();
|
||||
List<ArBill> list = billService.selectArBillList(query);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('ar:bill:query')")
|
||||
@GetMapping("/{arBillId}")
|
||||
public AjaxResult getInfo(@PathVariable Long arBillId)
|
||||
{
|
||||
return success(billService.selectArBillById(arBillId));
|
||||
}
|
||||
|
||||
/** 查询指定币种的公司默认收款账户,账号返回脱敏值。 */
|
||||
@PreAuthorize("@ss.hasPermi('ar:bill:generate')")
|
||||
@GetMapping("/default-bank-account")
|
||||
public AjaxResult defaultBankAccount(@RequestParam(required = false, defaultValue = "CNY") String currency)
|
||||
{
|
||||
ArBankAccount account = billService.getDefaultBankAccount(currency);
|
||||
return success(account);
|
||||
}
|
||||
|
||||
/** 查询生成账单前的订单计费结果。 */
|
||||
@PreAuthorize("@ss.hasPermi('ar:bill:generate')")
|
||||
@GetMapping("/generate/preview")
|
||||
public TableDataInfo preview(ArBillGenerateQuery query)
|
||||
{
|
||||
List<PrcOrderBill> list = billService.previewBill(query);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@Log(title = "应收账单", businessType = BusinessType.INSERT)
|
||||
@PreAuthorize("@ss.hasPermi('ar:bill:generate')")
|
||||
@PostMapping("/generate")
|
||||
public AjaxResult generate(@Valid @RequestBody ArBillGenerateRequest request,
|
||||
@RequestHeader(value = "X-Request-Id", required = false) String headerRequestId)
|
||||
{
|
||||
String requestId = requestId(request.getRequestId(), headerRequestId);
|
||||
ArBill bill = billService.generateBill(request, getUsername(), requestId);
|
||||
return success(bill).put("request_id", requestId);
|
||||
}
|
||||
|
||||
@Log(title = "应收账单", businessType = BusinessType.UPDATE)
|
||||
@PreAuthorize("@ss.hasPermi('ar:bill:confirm')")
|
||||
@PutMapping("/{arBillId}/confirm")
|
||||
public AjaxResult confirm(@PathVariable Long arBillId, @RequestBody(required = false) ArBillActionRequest request,
|
||||
@RequestHeader(value = "X-Request-Id", required = false) String headerRequestId)
|
||||
{
|
||||
String requestId = requestId(request == null ? null : request.getRequestId(), headerRequestId);
|
||||
return success(billService.confirmBill(arBillId, request, getUsername(), requestId)).put("request_id", requestId);
|
||||
}
|
||||
|
||||
@Log(title = "应收账单", businessType = BusinessType.UPDATE)
|
||||
@PreAuthorize("@ss.hasPermi('ar:bill:void')")
|
||||
@PutMapping("/{arBillId}/void")
|
||||
public AjaxResult voidBill(@PathVariable Long arBillId, @RequestBody ArBillActionRequest request,
|
||||
@RequestHeader(value = "X-Request-Id", required = false) String headerRequestId)
|
||||
{
|
||||
String requestId = requestId(request == null ? null : request.getRequestId(), headerRequestId);
|
||||
return success(billService.voidBill(arBillId, request, getUsername(), requestId)).put("request_id", requestId);
|
||||
}
|
||||
|
||||
@Log(title = "应收账单导出", businessType = BusinessType.EXPORT)
|
||||
@PreAuthorize("@ss.hasPermi('ar:bill:export')")
|
||||
@PostMapping("/{arBillId}/export")
|
||||
public AjaxResult export(@PathVariable Long arBillId,
|
||||
@RequestHeader(value = "X-Request-Id", required = false) String headerRequestId)
|
||||
{
|
||||
String requestId = requestId(null, headerRequestId);
|
||||
return success(billService.exportBill(arBillId, getUsername(), requestId)).put("request_id", requestId);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('ar:bill:query')")
|
||||
@GetMapping("/{arBillId}/exports")
|
||||
public AjaxResult exports(@PathVariable Long arBillId)
|
||||
{
|
||||
return success(billService.selectExports(arBillId));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('ar:bill:export')")
|
||||
@GetMapping("/export/{exportId}/download")
|
||||
public void download(@PathVariable Long exportId, HttpServletResponse response) throws Exception
|
||||
{
|
||||
billService.downloadExport(exportId, response);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('ar:log:list')")
|
||||
@GetMapping("/log/list")
|
||||
public TableDataInfo logs(ArBillLog query)
|
||||
{
|
||||
startPage();
|
||||
return getDataTable(billService.selectLogList(query));
|
||||
}
|
||||
|
||||
private String requestId(String bodyRequestId, String headerRequestId)
|
||||
{
|
||||
if (StringUtils.isNotEmpty(bodyRequestId)) return bodyRequestId;
|
||||
if (StringUtils.isNotEmpty(headerRequestId)) return headerRequestId;
|
||||
return IdUtils.fastSimpleUUID();
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package com.ruoyi.web.controller.ar;
|
||||
|
||||
import java.util.List;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.ar.domain.ArReceipt;
|
||||
import com.ruoyi.ar.domain.ArReceiptWriteoff;
|
||||
import com.ruoyi.ar.domain.vo.ArBillActionRequest;
|
||||
import com.ruoyi.ar.domain.vo.ArReceiptQuery;
|
||||
import com.ruoyi.ar.domain.vo.ArReceiptWriteoffRequest;
|
||||
import com.ruoyi.ar.service.IArReceiptService;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.uuid.IdUtils;
|
||||
|
||||
/** 应收收款与核销管理。 */
|
||||
@RestController
|
||||
@RequestMapping("/ar/receipt")
|
||||
@Validated
|
||||
public class ArReceiptController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private IArReceiptService receiptService;
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('ar:receipt:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(ArReceiptQuery query)
|
||||
{
|
||||
startPage();
|
||||
return getDataTable(receiptService.selectReceiptList(query));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('ar:receipt:list')")
|
||||
@GetMapping("/{receiptId}/writeoffs")
|
||||
public AjaxResult writeoffs(@PathVariable Long receiptId)
|
||||
{
|
||||
return success(receiptService.selectWriteoffs(receiptId));
|
||||
}
|
||||
|
||||
@Log(title = "应收收款", businessType = BusinessType.INSERT)
|
||||
@PreAuthorize("@ss.hasPermi('ar:receipt:add')")
|
||||
@PostMapping
|
||||
public AjaxResult add(@Valid @RequestBody ArReceipt receipt,
|
||||
@RequestHeader(value = "X-Request-Id", required = false) String headerRequestId)
|
||||
{
|
||||
String requestId = requestId(headerRequestId);
|
||||
return success(receiptService.registerReceipt(receipt, getUsername(), requestId)).put("request_id", requestId);
|
||||
}
|
||||
|
||||
@Log(title = "应收收款", businessType = BusinessType.UPDATE)
|
||||
@PreAuthorize("@ss.hasPermi('ar:receipt:cancel')")
|
||||
@PutMapping("/{receiptId}/cancel")
|
||||
public AjaxResult cancelReceipt(@PathVariable Long receiptId, @RequestBody(required = false) ArBillActionRequest request,
|
||||
@RequestHeader(value = "X-Request-Id", required = false) String headerRequestId)
|
||||
{
|
||||
String requestId = requestId(request == null ? null : request.getRequestId(), headerRequestId);
|
||||
receiptService.cancelReceipt(receiptId, request == null ? null : request.getRemark(), getUsername(), requestId);
|
||||
return success().put("request_id", requestId);
|
||||
}
|
||||
|
||||
@Log(title = "应收收款核销", businessType = BusinessType.UPDATE)
|
||||
@PreAuthorize("@ss.hasPermi('ar:receipt:writeoff')")
|
||||
@PostMapping("/writeoff")
|
||||
public AjaxResult writeoff(@Valid @RequestBody ArReceiptWriteoffRequest request,
|
||||
@RequestHeader(value = "X-Request-Id", required = false) String headerRequestId)
|
||||
{
|
||||
String requestId = StringUtils.isNotEmpty(request.getRequestId()) ? request.getRequestId() : requestId(headerRequestId);
|
||||
List<ArReceiptWriteoff> rows = receiptService.writeoff(request, getUsername(), requestId);
|
||||
return success(rows).put("request_id", requestId);
|
||||
}
|
||||
|
||||
@Log(title = "应收收款核销", businessType = BusinessType.UPDATE)
|
||||
@PreAuthorize("@ss.hasPermi('ar:receipt:cancel')")
|
||||
@PutMapping("/writeoff/{writeoffId}/cancel")
|
||||
public AjaxResult cancel(@PathVariable Long writeoffId, @RequestBody(required = false) ArBillActionRequest request,
|
||||
@RequestHeader(value = "X-Request-Id", required = false) String headerRequestId)
|
||||
{
|
||||
String requestId = requestId(request == null ? null : request.getRequestId(), headerRequestId);
|
||||
receiptService.cancelWriteoff(writeoffId, request == null ? null : request.getRemark(), getUsername(), requestId);
|
||||
return success().put("request_id", requestId);
|
||||
}
|
||||
|
||||
private String requestId(String headerRequestId)
|
||||
{
|
||||
return StringUtils.isNotEmpty(headerRequestId) ? headerRequestId : IdUtils.fastSimpleUUID();
|
||||
}
|
||||
|
||||
private String requestId(String bodyRequestId, String headerRequestId)
|
||||
{
|
||||
return StringUtils.isNotEmpty(bodyRequestId) ? bodyRequestId : requestId(headerRequestId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.ruoyi.ar.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/** 公司收款账户配置。 */
|
||||
@Data
|
||||
public class ArBankAccount implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long bankAccountId;
|
||||
private String accountName;
|
||||
private String accountNo;
|
||||
private String bankName;
|
||||
private String currency;
|
||||
private String isDefault;
|
||||
private String status;
|
||||
private String remark;
|
||||
private String createBy;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
private String updateBy;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.ruoyi.ar.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/** 正式应收账单。 */
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ArBill extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long arBillId;
|
||||
private String billNo;
|
||||
private String billName;
|
||||
private Long customerId;
|
||||
private String customerCode;
|
||||
private String customerName;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date billPeriodStart;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date billPeriodEnd;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date billDate;
|
||||
private String currency;
|
||||
private Integer orderCount;
|
||||
private BigDecimal totalAmount;
|
||||
private BigDecimal writtenOffAmount;
|
||||
private BigDecimal unwrittenAmount;
|
||||
private String status;
|
||||
private Integer billVersion;
|
||||
private String bankAccountName;
|
||||
private String bankAccountNo;
|
||||
private String bankName;
|
||||
private String remark;
|
||||
private String confirmedBy;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date confirmedTime;
|
||||
|
||||
private List<ArBillDetail> details;
|
||||
private List<ArBillExport> exports;
|
||||
private List<ArBillLog> logs;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.ruoyi.ar.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/** 账单订单快照。 */
|
||||
@Data
|
||||
public class ArBillDetail implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long detailId;
|
||||
private Long arBillId;
|
||||
private String prcBillId;
|
||||
private Integer detailNo;
|
||||
private String orderNo;
|
||||
private String trackingNo;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date receiveTime;
|
||||
private String productCode;
|
||||
private String productName;
|
||||
private String goodsDescription;
|
||||
private String countryCode;
|
||||
private String countryName;
|
||||
private String destPostcode;
|
||||
private BigDecimal actualWeight;
|
||||
private BigDecimal volumeWeight;
|
||||
private BigDecimal chargeWeight;
|
||||
private BigDecimal pkgLength;
|
||||
private BigDecimal pkgWidth;
|
||||
private BigDecimal pkgHeight;
|
||||
private String currency;
|
||||
private BigDecimal totalAmount;
|
||||
private String remark;
|
||||
private String createBy;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
private List<ArBillDetailFee> fees;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.ruoyi.ar.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/** 账单费用快照。 */
|
||||
@Data
|
||||
public class ArBillDetailFee implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long detailFeeId;
|
||||
private Long detailId;
|
||||
private String prcFeeId;
|
||||
private Integer feeNo;
|
||||
private String feeType;
|
||||
private String feeName;
|
||||
private Long scId;
|
||||
private BigDecimal quantity;
|
||||
private String unit;
|
||||
private BigDecimal unitPrice;
|
||||
private BigDecimal amount;
|
||||
private String currency;
|
||||
private String calculationFormula;
|
||||
private String roundMode;
|
||||
private String remark;
|
||||
private String createBy;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ruoyi.ar.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/** 账单导出文件元数据。 */
|
||||
@Data
|
||||
public class ArBillExport implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long exportId;
|
||||
private Long arBillId;
|
||||
private String fileName;
|
||||
private String fileType;
|
||||
private String templateCode;
|
||||
private String templateVersion;
|
||||
private String storageKey;
|
||||
private Long fileSize;
|
||||
private String fileHash;
|
||||
private Integer rowCount;
|
||||
private String exportStatus;
|
||||
private String exportBy;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date exportTime;
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.ruoyi.ar.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/** 应收模块追加式审计日志。 */
|
||||
@Data
|
||||
public class ArBillLog implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long logId;
|
||||
private Long arBillId;
|
||||
private String objectType;
|
||||
private String objectId;
|
||||
/** 对象业务标识,例如账单号、收款单号或核销单号。 */
|
||||
private String objectDisplay;
|
||||
private String operationType;
|
||||
private String operationDesc;
|
||||
private String fromStatus;
|
||||
private String toStatus;
|
||||
private String beforeData;
|
||||
private String afterData;
|
||||
private String operatorId;
|
||||
private String operatorName;
|
||||
private String operationSource;
|
||||
private String requestId;
|
||||
private String traceId;
|
||||
private String result;
|
||||
private String errorMessage;
|
||||
private String clientIp;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.ruoyi.ar.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/** 应收真实收款。 */
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ArReceipt extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long receiptId;
|
||||
private String receiptNo;
|
||||
private Long customerId;
|
||||
/** 客户名称,仅用于列表展示。 */
|
||||
private String customerName;
|
||||
/** 客户编码,仅用于列表展示。 */
|
||||
private String customerCode;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date receiptDate;
|
||||
private String currency;
|
||||
private BigDecimal receiptAmount;
|
||||
private BigDecimal unallocatedAmount;
|
||||
private String paymentMethod;
|
||||
private String bankReference;
|
||||
private String status;
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.ruoyi.ar.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/** 收款与账单的核销关系。 */
|
||||
@Data
|
||||
public class ArReceiptWriteoff implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long writeoffId;
|
||||
private Long receiptId;
|
||||
private Long arBillId;
|
||||
/** 关联账单号,仅用于核销记录展示。 */
|
||||
private String billNo;
|
||||
/** 关联收款单号,仅用于核销记录展示。 */
|
||||
private String receiptNo;
|
||||
private String writeoffNo;
|
||||
private BigDecimal writeoffAmount;
|
||||
private String currency;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date writeoffDate;
|
||||
private String status;
|
||||
private String remark;
|
||||
private String createBy;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
private String cancelBy;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date cancelTime;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.ruoyi.ar.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/** 账单状态动作参数。 */
|
||||
@Data
|
||||
public class ArBillActionRequest
|
||||
{
|
||||
private String remark;
|
||||
private String requestId;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ruoyi.ar.domain.vo;
|
||||
|
||||
import java.util.Date;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/** 生成应收账单前的订单计费结果查询条件。 */
|
||||
@Data
|
||||
public class ArBillGenerateQuery
|
||||
{
|
||||
private Long customerId;
|
||||
|
||||
private String customerCode;
|
||||
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private Date billPeriodEnd;
|
||||
|
||||
private String currency;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ruoyi.ar.domain.vo;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/** 从订单计费结果生成正式账单的参数。 */
|
||||
@Data
|
||||
public class ArBillGenerateRequest
|
||||
{
|
||||
@NotNull(message = "客户不能为空")
|
||||
private Long customerId;
|
||||
@NotBlank(message = "账单名称不能为空")
|
||||
private String billName;
|
||||
private String customerCode;
|
||||
private String customerName;
|
||||
@NotNull(message = "账单截止日不能为空")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date billPeriodEnd;
|
||||
@NotEmpty(message = "请至少选择一条订单")
|
||||
private List<String> sourceBillIds;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date billDate;
|
||||
private String currency = "CNY";
|
||||
private String bankAccountName;
|
||||
private String bankAccountNo;
|
||||
private String bankName;
|
||||
private String remark;
|
||||
private String requestId;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ruoyi.ar.domain.vo;
|
||||
|
||||
import java.util.Date;
|
||||
import com.ruoyi.ar.domain.ArReceipt;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/** 收款查询参数。 */
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ArReceiptQuery extends ArReceipt
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private Date receiptDateStart;
|
||||
private Date receiptDateEnd;
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.ruoyi.ar.domain.vo;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/** 批量收款核销参数。 */
|
||||
@Data
|
||||
public class ArReceiptWriteoffRequest
|
||||
{
|
||||
@NotNull(message = "收款ID不能为空")
|
||||
private Long receiptId;
|
||||
@NotEmpty(message = "至少选择一张账单")
|
||||
@Valid
|
||||
private List<Allocation> allocations;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date writeoffDate;
|
||||
private String currency;
|
||||
private String remark;
|
||||
private String requestId;
|
||||
|
||||
@Data
|
||||
public static class Allocation
|
||||
{
|
||||
@NotNull(message = "账单ID不能为空")
|
||||
private Long arBillId;
|
||||
@NotNull(message = "核销金额不能为空")
|
||||
private BigDecimal amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.ruoyi.ar.enums;
|
||||
|
||||
/** 应收账单状态。数据库保存值使用 code,避免业务代码散落状态字符串。 */
|
||||
public enum ArBillStatus
|
||||
{
|
||||
DRAFT("DRAFT", "草稿"),
|
||||
CONFIRMED("CONFIRMED", "已确认"),
|
||||
PARTIAL_WRITTEN_OFF("PARTIAL_WRITTEN_OFF", "部分核销"),
|
||||
WRITTEN_OFF("WRITTEN_OFF", "已核销"),
|
||||
VOID("VOID", "已作废");
|
||||
|
||||
private final String code;
|
||||
private final String label;
|
||||
|
||||
ArBillStatus(String code, String label)
|
||||
{
|
||||
this.code = code;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return label;
|
||||
}
|
||||
|
||||
public boolean canConfirm()
|
||||
{
|
||||
return this == DRAFT;
|
||||
}
|
||||
|
||||
public boolean canVoid()
|
||||
{
|
||||
return this == DRAFT || this == CONFIRMED;
|
||||
}
|
||||
|
||||
public boolean canWriteOff()
|
||||
{
|
||||
return this == CONFIRMED || this == PARTIAL_WRITTEN_OFF;
|
||||
}
|
||||
|
||||
public static ArBillStatus of(String code)
|
||||
{
|
||||
for (ArBillStatus status : values())
|
||||
{
|
||||
if (status.code.equalsIgnoreCase(code))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean is(String code, ArBillStatus status)
|
||||
{
|
||||
return status != null && status.code.equalsIgnoreCase(code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ruoyi.ar.enums;
|
||||
|
||||
/** 应收账单导出任务状态。 */
|
||||
public enum ArExportStatus
|
||||
{
|
||||
PROCESSING("PROCESSING"),
|
||||
SUCCESS("SUCCESS"),
|
||||
FAILED("FAILED");
|
||||
|
||||
private final String code;
|
||||
|
||||
ArExportStatus(String code)
|
||||
{
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ruoyi.ar.enums;
|
||||
|
||||
/** 应收操作日志结果。 */
|
||||
public enum ArLogResult
|
||||
{
|
||||
SUCCESS("SUCCESS"),
|
||||
FAILED("FAILED");
|
||||
|
||||
private final String code;
|
||||
|
||||
ArLogResult(String code)
|
||||
{
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ruoyi.ar.enums;
|
||||
|
||||
/** 计费订单的应收账单占用状态。 */
|
||||
public enum ArOrderBillStatus
|
||||
{
|
||||
UNBILLED("UNBILLED"),
|
||||
BILLED("BILLED");
|
||||
|
||||
private final String code;
|
||||
|
||||
ArOrderBillStatus(String code)
|
||||
{
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.ruoyi.ar.enums;
|
||||
|
||||
/** 应收收款状态。 */
|
||||
public enum ArReceiptStatus
|
||||
{
|
||||
REGISTERED("REGISTERED"),
|
||||
PARTIAL_WRITTEN_OFF("PARTIAL_WRITTEN_OFF"),
|
||||
WRITTEN_OFF("WRITTEN_OFF"),
|
||||
CANCELLED("CANCELLED");
|
||||
|
||||
private final String code;
|
||||
|
||||
ArReceiptStatus(String code)
|
||||
{
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ruoyi.ar.enums;
|
||||
|
||||
/** 收款核销状态。 */
|
||||
public enum ArWriteoffStatus
|
||||
{
|
||||
EFFECTIVE("EFFECTIVE"),
|
||||
CANCELLED("CANCELLED");
|
||||
|
||||
private final String code;
|
||||
|
||||
ArWriteoffStatus(String code)
|
||||
{
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.ruoyi.ar.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.ruoyi.ar.domain.ArBankAccount;
|
||||
|
||||
public interface ArBankAccountMapper
|
||||
{
|
||||
ArBankAccount selectDefaultByCurrency(@Param("currency") String currency);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.ruoyi.ar.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.ar.domain.ArBillDetailFee;
|
||||
|
||||
public interface ArBillDetailFeeMapper
|
||||
{
|
||||
List<ArBillDetailFee> selectByDetailId(Long detailId);
|
||||
|
||||
List<ArBillDetailFee> selectByBillId(Long arBillId);
|
||||
|
||||
int insert(ArBillDetailFee fee);
|
||||
|
||||
int insertBatch(List<ArBillDetailFee> fees);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ruoyi.ar.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.ar.domain.ArBillDetail;
|
||||
|
||||
public interface ArBillDetailMapper
|
||||
{
|
||||
List<ArBillDetail> selectByBillId(Long arBillId);
|
||||
|
||||
/** 查询账单明细及费用明细,避免详情页逐层查询。 */
|
||||
List<ArBillDetail> selectByBillIdWithFees(Long arBillId);
|
||||
|
||||
int insert(ArBillDetail detail);
|
||||
|
||||
int insertBatch(List<ArBillDetail> details);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.ruoyi.ar.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.ar.domain.ArBillExport;
|
||||
|
||||
public interface ArBillExportMapper
|
||||
{
|
||||
List<ArBillExport> selectByBillId(Long arBillId);
|
||||
|
||||
ArBillExport selectById(Long exportId);
|
||||
|
||||
ArBillExport selectLatestSuccessByBillId(Long arBillId);
|
||||
|
||||
int insert(ArBillExport export);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.ruoyi.ar.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.ruoyi.ar.domain.ArBillLog;
|
||||
|
||||
public interface ArBillLogMapper
|
||||
{
|
||||
List<ArBillLog> selectList(ArBillLog query);
|
||||
|
||||
ArBillLog selectSuccessByRequestId(@Param("requestId") String requestId,
|
||||
@Param("objectType") String objectType);
|
||||
|
||||
int insert(ArBillLog log);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.ruoyi.ar.mapper;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.ruoyi.ar.domain.ArBill;
|
||||
|
||||
public interface ArBillMapper
|
||||
{
|
||||
List<ArBill> selectArBillList(ArBill query);
|
||||
|
||||
ArBill selectArBillById(Long arBillId);
|
||||
|
||||
ArBill selectArBillByBillNo(String billNo);
|
||||
|
||||
Integer selectMaxBillVersion(@Param("customerId") Long customerId,
|
||||
@Param("cutoffDate") Date cutoffDate, @Param("currency") String currency);
|
||||
|
||||
ArBill selectArBillByIdForUpdate(Long arBillId);
|
||||
|
||||
int insertArBill(ArBill bill);
|
||||
|
||||
int updateArBillStatus(@Param("arBillId") Long arBillId, @Param("status") String status,
|
||||
@Param("remark") String remark, @Param("operator") String operator,
|
||||
@Param("fromStatus") String fromStatus);
|
||||
|
||||
int updateArBillAmounts(@Param("arBillId") Long arBillId,
|
||||
@Param("writtenOffAmount") BigDecimal writtenOffAmount,
|
||||
@Param("unwrittenAmount") BigDecimal unwrittenAmount,
|
||||
@Param("status") String status, @Param("operator") String operator,
|
||||
@Param("fromStatus") String fromStatus);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.ruoyi.ar.mapper;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.ruoyi.ar.domain.ArReceipt;
|
||||
import com.ruoyi.ar.domain.vo.ArReceiptQuery;
|
||||
|
||||
public interface ArReceiptMapper
|
||||
{
|
||||
List<ArReceipt> selectArReceiptList(ArReceiptQuery query);
|
||||
|
||||
ArReceipt selectArReceiptById(Long receiptId);
|
||||
|
||||
ArReceipt selectArReceiptByIdForUpdate(Long receiptId);
|
||||
|
||||
ArReceipt selectArReceiptByNo(String receiptNo);
|
||||
|
||||
ArReceipt selectArReceiptByReference(@Param("paymentMethod") String paymentMethod,
|
||||
@Param("bankReference") String bankReference);
|
||||
|
||||
int insertArReceipt(ArReceipt receipt);
|
||||
|
||||
int updateBalanceAndStatus(@Param("receiptId") Long receiptId,
|
||||
@Param("unallocatedAmount") BigDecimal unallocatedAmount,
|
||||
@Param("status") String status, @Param("operator") String operator);
|
||||
|
||||
int updateCancelled(@Param("receiptId") Long receiptId, @Param("operator") String operator,
|
||||
@Param("remark") String remark);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.ruoyi.ar.mapper;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.ruoyi.ar.domain.ArReceiptWriteoff;
|
||||
|
||||
public interface ArReceiptWriteoffMapper
|
||||
{
|
||||
List<ArReceiptWriteoff> selectByBillId(Long arBillId);
|
||||
|
||||
List<ArReceiptWriteoff> selectByReceiptId(Long receiptId);
|
||||
|
||||
ArReceiptWriteoff selectById(Long writeoffId);
|
||||
|
||||
ArReceiptWriteoff selectByIdForUpdate(Long writeoffId);
|
||||
|
||||
int insert(ArReceiptWriteoff writeoff);
|
||||
|
||||
int updateCancelled(@Param("writeoffId") Long writeoffId, @Param("cancelBy") String cancelBy,
|
||||
@Param("remark") String remark);
|
||||
|
||||
BigDecimal sumEffectiveByBillId(Long arBillId);
|
||||
|
||||
BigDecimal sumEffectiveByReceiptId(Long receiptId);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.ruoyi.ar.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.ar.domain.ArBillLog;
|
||||
|
||||
public interface IArBillLogService
|
||||
{
|
||||
List<ArBillLog> selectLogList(ArBillLog query);
|
||||
|
||||
void record(ArBillLog log);
|
||||
|
||||
void recordFailure(Long arBillId, String objectType, String objectId, String operationType,
|
||||
String fromStatus, String message, String operator, String requestId);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.ruoyi.ar.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.ar.domain.ArBill;
|
||||
import com.ruoyi.ar.domain.ArBankAccount;
|
||||
import com.ruoyi.ar.domain.ArBillExport;
|
||||
import com.ruoyi.ar.domain.ArBillLog;
|
||||
import com.ruoyi.ar.domain.vo.ArBillGenerateRequest;
|
||||
import com.ruoyi.ar.domain.vo.ArBillGenerateQuery;
|
||||
import com.ruoyi.ar.domain.vo.ArBillActionRequest;
|
||||
import com.ruoyi.price.domain.PrcOrderBill;
|
||||
|
||||
public interface IArBillService
|
||||
{
|
||||
List<ArBill> selectArBillList(ArBill query);
|
||||
|
||||
ArBill selectArBillById(Long arBillId);
|
||||
|
||||
ArBankAccount getDefaultBankAccount(String currency);
|
||||
|
||||
List<PrcOrderBill> previewBill(ArBillGenerateQuery query);
|
||||
|
||||
ArBill generateBill(ArBillGenerateRequest request, String operator, String requestId);
|
||||
|
||||
ArBill confirmBill(Long arBillId, ArBillActionRequest request, String operator, String requestId);
|
||||
|
||||
ArBill voidBill(Long arBillId, ArBillActionRequest request, String operator, String requestId);
|
||||
|
||||
ArBillExport exportBill(Long arBillId, String operator, String requestId);
|
||||
|
||||
List<ArBillExport> selectExports(Long arBillId);
|
||||
|
||||
void downloadExport(Long exportId, jakarta.servlet.http.HttpServletResponse response) throws Exception;
|
||||
|
||||
List<ArBillLog> selectLogList(ArBillLog query);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.ruoyi.ar.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.ar.domain.ArReceipt;
|
||||
import com.ruoyi.ar.domain.ArReceiptWriteoff;
|
||||
import com.ruoyi.ar.domain.vo.ArReceiptQuery;
|
||||
import com.ruoyi.ar.domain.vo.ArReceiptWriteoffRequest;
|
||||
|
||||
public interface IArReceiptService
|
||||
{
|
||||
List<ArReceipt> selectReceiptList(ArReceiptQuery query);
|
||||
|
||||
List<ArReceiptWriteoff> selectWriteoffs(Long receiptId);
|
||||
|
||||
ArReceipt registerReceipt(ArReceipt receipt, String operator, String requestId);
|
||||
|
||||
void cancelReceipt(Long receiptId, String remark, String operator, String requestId);
|
||||
|
||||
List<ArReceiptWriteoff> writeoff(ArReceiptWriteoffRequest request, String operator, String requestId);
|
||||
|
||||
void cancelWriteoff(Long writeoffId, String remark, String operator, String requestId);
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package com.ruoyi.ar.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.ruoyi.ar.domain.ArBillLog;
|
||||
import com.ruoyi.ar.enums.ArLogResult;
|
||||
import com.ruoyi.ar.mapper.ArBillLogMapper;
|
||||
import com.ruoyi.ar.service.IArBillLogService;
|
||||
import com.ruoyi.common.utils.uuid.IdUtils;
|
||||
|
||||
@Service
|
||||
public class ArBillLogServiceImpl implements IArBillLogService
|
||||
{
|
||||
@Autowired
|
||||
private ArBillLogMapper logMapper;
|
||||
|
||||
@Override
|
||||
public List<ArBillLog> selectLogList(ArBillLog query)
|
||||
{
|
||||
return logMapper.selectList(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void record(ArBillLog log)
|
||||
{
|
||||
logMapper.insert(log);
|
||||
}
|
||||
|
||||
/** 失败日志必须独立提交,不能随业务异常一起回滚。 */
|
||||
@Override
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public void recordFailure(Long arBillId, String objectType, String objectId, String operationType,
|
||||
String fromStatus, String message, String operator, String requestId)
|
||||
{
|
||||
ArBillLog log = new ArBillLog();
|
||||
log.setArBillId(arBillId);
|
||||
log.setObjectType(objectType);
|
||||
log.setObjectId(objectId);
|
||||
log.setOperationType(operationType);
|
||||
log.setOperationDesc(message);
|
||||
log.setFromStatus(fromStatus);
|
||||
log.setOperatorId(operator);
|
||||
log.setOperatorName(operator);
|
||||
log.setOperationSource("WEB");
|
||||
log.setRequestId(requestId == null ? IdUtils.fastSimpleUUID() : requestId);
|
||||
log.setResult(ArLogResult.FAILED.getCode());
|
||||
log.setErrorMessage(message);
|
||||
logMapper.insert(log);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,902 @@
|
||||
package com.ruoyi.ar.service.impl;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.file.Files;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.poi.ss.usermodel.BorderStyle;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.ss.usermodel.Font;
|
||||
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.VerticalAlignment;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.ruoyi.ar.domain.ArBill;
|
||||
import com.ruoyi.ar.domain.ArBankAccount;
|
||||
import com.ruoyi.ar.domain.ArBillDetail;
|
||||
import com.ruoyi.ar.domain.ArBillDetailFee;
|
||||
import com.ruoyi.ar.domain.ArBillExport;
|
||||
import com.ruoyi.ar.domain.ArBillLog;
|
||||
import com.ruoyi.ar.enums.ArBillStatus;
|
||||
import com.ruoyi.ar.enums.ArExportStatus;
|
||||
import com.ruoyi.ar.enums.ArLogResult;
|
||||
import com.ruoyi.ar.enums.ArOrderBillStatus;
|
||||
import com.ruoyi.ar.mapper.ArBillDetailFeeMapper;
|
||||
import com.ruoyi.ar.mapper.ArBillDetailMapper;
|
||||
import com.ruoyi.ar.mapper.ArBillExportMapper;
|
||||
import com.ruoyi.ar.mapper.ArBillLogMapper;
|
||||
import com.ruoyi.ar.mapper.ArBillMapper;
|
||||
import com.ruoyi.ar.mapper.ArBankAccountMapper;
|
||||
import com.ruoyi.ar.service.IArBillLogService;
|
||||
import com.ruoyi.ar.service.IArBillService;
|
||||
import com.ruoyi.ar.domain.vo.ArBillActionRequest;
|
||||
import com.ruoyi.ar.domain.vo.ArBillGenerateQuery;
|
||||
import com.ruoyi.ar.domain.vo.ArBillGenerateRequest;
|
||||
import com.ruoyi.common.config.RuoYiConfig;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.file.FileUtils;
|
||||
import com.ruoyi.common.utils.file.FileUploadUtils;
|
||||
import com.ruoyi.common.utils.uuid.IdUtils;
|
||||
import com.ruoyi.price.domain.PrcIncomeBillFee;
|
||||
import com.ruoyi.price.domain.PrcOrderBill;
|
||||
import com.ruoyi.price.mapper.PrcIncomeBillFeeMapper;
|
||||
import com.ruoyi.price.mapper.PrcOrderBillMapper;
|
||||
|
||||
@Service
|
||||
public class ArBillServiceImpl implements IArBillService
|
||||
{
|
||||
private static final String EXPORT_TEMPLATE_RESOURCE = "templates/ar/ar_bill_template.xlsx";
|
||||
private static final int TEMPLATE_DATA_START_ROW = 4;
|
||||
private static final int TEMPLATE_FOOTER_START_ROW = 5;
|
||||
private static final int TEMPLATE_COLUMN_COUNT = 20;
|
||||
|
||||
@Autowired private ArBillMapper billMapper;
|
||||
@Autowired private ArBankAccountMapper bankAccountMapper;
|
||||
@Autowired private ArBillDetailMapper detailMapper;
|
||||
@Autowired private ArBillDetailFeeMapper detailFeeMapper;
|
||||
@Autowired private ArBillExportMapper exportMapper;
|
||||
@Autowired private ArBillLogMapper logMapper;
|
||||
@Autowired private IArBillLogService logService;
|
||||
@Autowired private PrcOrderBillMapper sourceBillMapper;
|
||||
@Autowired private PrcIncomeBillFeeMapper sourceFeeMapper;
|
||||
|
||||
@Override
|
||||
public List<ArBill> selectArBillList(ArBill query)
|
||||
{
|
||||
List<ArBill> bills = billMapper.selectArBillList(query);
|
||||
for (ArBill bill : bills)
|
||||
{
|
||||
bill.setBankAccountNo(maskAccount(bill.getBankAccountNo()));
|
||||
}
|
||||
return bills;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArBill selectArBillById(Long arBillId)
|
||||
{
|
||||
ArBill bill = billMapper.selectArBillById(arBillId);
|
||||
if (bill == null)
|
||||
{
|
||||
throw new ServiceException("应收账单不存在");
|
||||
}
|
||||
fillDetails(bill);
|
||||
bill.setBankAccountNo(maskAccount(bill.getBankAccountNo()));
|
||||
bill.setExports(exportMapper.selectByBillId(arBillId));
|
||||
return bill;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArBankAccount getDefaultBankAccount(String currency)
|
||||
{
|
||||
ArBankAccount account = bankAccountMapper.selectDefaultByCurrency(
|
||||
StringUtils.isEmpty(currency) ? "CNY" : currency);
|
||||
if (account != null)
|
||||
{
|
||||
account.setAccountNo(maskAccount(account.getAccountNo()));
|
||||
}
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PrcOrderBill> previewBill(ArBillGenerateQuery query)
|
||||
{
|
||||
validatePreviewQuery(query);
|
||||
return sourceBillMapper.selectPrcOrderBillByCustomerPeriod(query.getCustomerId(),
|
||||
query.getCustomerCode(), query.getBillPeriodEnd(), query.getCurrency(), ArOrderBillStatus.UNBILLED.getCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ArBill generateBill(ArBillGenerateRequest request, String operator, String requestId)
|
||||
{
|
||||
validatePeriod(request);
|
||||
ArBillLog previous = logMapper.selectSuccessByRequestId(requestId, "BILL");
|
||||
if (previous != null && isNumber(previous.getObjectId()))
|
||||
{
|
||||
return selectArBillById(Long.valueOf(previous.getObjectId()));
|
||||
}
|
||||
|
||||
List<String> requestedSourceBillIds = normalizeSourceBillIds(request.getSourceBillIds());
|
||||
if (requestedSourceBillIds.isEmpty())
|
||||
{
|
||||
throw failure(null, "BILL", "", "GENERATE", null, "请至少选择一条有效订单", operator, requestId);
|
||||
}
|
||||
String requestedCurrency = StringUtils.isEmpty(request.getCurrency()) ? "CNY" : request.getCurrency();
|
||||
List<PrcOrderBill> sources = sourceBillMapper.selectPrcOrderBillByCustomerCutoffAndIds(
|
||||
request.getCustomerId(), request.getCustomerCode(), request.getBillPeriodEnd(), requestedCurrency,
|
||||
requestedSourceBillIds, ArOrderBillStatus.UNBILLED.getCode());
|
||||
if (sources == null || sources.isEmpty() || sources.size() != requestedSourceBillIds.size())
|
||||
{
|
||||
throw failure(null, "BILL", "", "GENERATE", null,
|
||||
"部分所选订单已出账、已变更或不符合当前筛选条件,请重新查询后生成", operator, requestId);
|
||||
}
|
||||
|
||||
String currency = requestedCurrency;
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (PrcOrderBill source : sources)
|
||||
{
|
||||
if (source.getCurrency() != null && !currency.equalsIgnoreCase(source.getCurrency()))
|
||||
{
|
||||
throw failure(null, "BILL", "", "GENERATE", null, "截止日期内存在多个币种,不能合并为一张账单", operator, requestId);
|
||||
}
|
||||
total = total.add(nvl(source.getIncomeTotal()));
|
||||
}
|
||||
|
||||
ArBankAccount bankAccount = bankAccountMapper.selectDefaultByCurrency(currency);
|
||||
if (bankAccount == null)
|
||||
{
|
||||
throw failure(null, "BILL", "", "GENERATE", null,
|
||||
"未配置 " + currency + " 币种的公司默认收款账户,请先完成收款账户配置", operator, requestId);
|
||||
}
|
||||
|
||||
ArBill bill = new ArBill();
|
||||
bill.setBillNo("AR" + new SimpleDateFormat("yyyyMM").format(new Date())
|
||||
+ IdUtils.fastSimpleUUID().substring(0, 8).toUpperCase());
|
||||
bill.setBillName(request.getBillName().trim());
|
||||
bill.setCustomerId(request.getCustomerId());
|
||||
bill.setCustomerCode(StringUtils.isEmpty(request.getCustomerCode()) ? sources.get(0).getCustomerCode() : request.getCustomerCode());
|
||||
bill.setCustomerName(StringUtils.isEmpty(request.getCustomerName()) ? sources.get(0).getCustomerName() : request.getCustomerName());
|
||||
bill.setBillPeriodStart(earliestBizDate(sources));
|
||||
bill.setBillPeriodEnd(request.getBillPeriodEnd());
|
||||
bill.setBillDate(request.getBillDate() == null ? new Date() : request.getBillDate());
|
||||
bill.setCurrency(currency);
|
||||
bill.setOrderCount(sources.size());
|
||||
bill.setTotalAmount(total);
|
||||
bill.setWrittenOffAmount(BigDecimal.ZERO);
|
||||
bill.setUnwrittenAmount(total);
|
||||
bill.setStatus(ArBillStatus.DRAFT.getCode());
|
||||
bill.setBankAccountName(bankAccount.getAccountName());
|
||||
bill.setBankAccountNo(bankAccount.getAccountNo());
|
||||
bill.setBankName(bankAccount.getBankName());
|
||||
bill.setRemark(request.getRemark());
|
||||
bill.setCreateBy(operator);
|
||||
bill.setUpdateBy(operator);
|
||||
|
||||
List<String> sourceBillIds = new ArrayList<>();
|
||||
for (PrcOrderBill source : sources)
|
||||
{
|
||||
sourceBillIds.add(source.getBillId());
|
||||
}
|
||||
int claimedCount = sourceBillMapper.claimArBillOrders(sourceBillIds, operator,
|
||||
ArOrderBillStatus.UNBILLED.getCode(), ArOrderBillStatus.BILLED.getCode());
|
||||
if (claimedCount != sourceBillIds.size())
|
||||
{
|
||||
throw failure(null, "BILL", "", "GENERATE", null,
|
||||
"部分计费订单已被其他账单占用,请重新查询后生成", operator, requestId);
|
||||
}
|
||||
|
||||
// 订单占用成功后再锁定版本号,避免并发生成时先查到同一个版本。
|
||||
Integer maxVersion = billMapper.selectMaxBillVersion(request.getCustomerId(), request.getBillPeriodEnd(), currency);
|
||||
bill.setBillVersion((maxVersion == null ? 0 : maxVersion) + 1);
|
||||
|
||||
billMapper.insertArBill(bill);
|
||||
|
||||
List<ArBillDetail> details = new ArrayList<>();
|
||||
int detailNo = 1;
|
||||
for (PrcOrderBill source : sources)
|
||||
{
|
||||
details.add(toDetail(bill, source, detailNo++, operator));
|
||||
}
|
||||
detailMapper.insertBatch(details);
|
||||
|
||||
// Batch insert does not need to expose generated IDs to the caller. Re-read the
|
||||
// persisted details once, then attach source fees in one batch query.
|
||||
Map<String, ArBillDetail> detailsBySourceBill = new HashMap<>();
|
||||
for (ArBillDetail detail : detailMapper.selectByBillId(bill.getArBillId()))
|
||||
{
|
||||
detailsBySourceBill.put(detail.getPrcBillId(), detail);
|
||||
}
|
||||
List<ArBillDetailFee> detailFees = new ArrayList<>();
|
||||
List<PrcIncomeBillFee> sourceFees = sourceBillIds.isEmpty()
|
||||
? new ArrayList<PrcIncomeBillFee>() : sourceFeeMapper.selectByBillIds(sourceBillIds);
|
||||
Map<String, Integer> feeNumbers = new HashMap<>();
|
||||
for (PrcIncomeBillFee sourceFee : sourceFees)
|
||||
{
|
||||
ArBillDetail detail = detailsBySourceBill.get(sourceFee.getBillId());
|
||||
if (detail == null)
|
||||
{
|
||||
throw new ServiceException("来源费用找不到对应的账单明细:" + sourceFee.getBillId());
|
||||
}
|
||||
int feeNo = feeNumbers.merge(sourceFee.getBillId(), 1, Integer::sum);
|
||||
detailFees.add(toDetailFee(detail, sourceFee, feeNo, operator));
|
||||
}
|
||||
if (!detailFees.isEmpty())
|
||||
{
|
||||
detailFeeMapper.insertBatch(detailFees);
|
||||
}
|
||||
recordSuccess(bill, "CREATE", "生成正式账单", null, ArBillStatus.DRAFT.getCode(), requestId, operator);
|
||||
bill.setBankAccountNo(maskAccount(bill.getBankAccountNo()));
|
||||
return bill;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ArBill confirmBill(Long arBillId, ArBillActionRequest request, String operator, String requestId)
|
||||
{
|
||||
ArBill bill = billMapper.selectArBillByIdForUpdate(arBillId);
|
||||
if (bill == null)
|
||||
{
|
||||
throw failure(arBillId, "BILL", String.valueOf(arBillId), "CONFIRM", null, "应收账单不存在", operator, requestId);
|
||||
}
|
||||
ArBillStatus currentStatus = ArBillStatus.of(bill.getStatus());
|
||||
if (currentStatus == null || !currentStatus.canConfirm())
|
||||
{
|
||||
throw failure(arBillId, "BILL", String.valueOf(arBillId), "CONFIRM", bill.getStatus(),
|
||||
"只有草稿账单可以确认,当前状态为 " + bill.getStatus(), operator, requestId);
|
||||
}
|
||||
if (!validateDetails(arBillId, bill.getTotalAmount()))
|
||||
{
|
||||
throw failure(arBillId, "BILL", String.valueOf(arBillId), "CONFIRM", bill.getStatus(),
|
||||
"账单费用明细汇总与订单或账单总额不一致", operator, requestId);
|
||||
}
|
||||
if (billMapper.updateArBillStatus(arBillId, ArBillStatus.CONFIRMED.getCode(),
|
||||
StringUtils.isEmpty(request == null ? null : request.getRemark()) ? bill.getRemark() : request.getRemark(),
|
||||
operator, ArBillStatus.DRAFT.getCode()) != 1)
|
||||
{
|
||||
throw failure(arBillId, "BILL", String.valueOf(arBillId), "CONFIRM", bill.getStatus(),
|
||||
"账单状态已发生变化,请刷新后重试", operator, requestId);
|
||||
}
|
||||
recordSuccess(bill, "CONFIRM", "确认应收账单", ArBillStatus.DRAFT.getCode(), ArBillStatus.CONFIRMED.getCode(), requestId, operator);
|
||||
return selectArBillById(arBillId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ArBill voidBill(Long arBillId, ArBillActionRequest request, String operator, String requestId)
|
||||
{
|
||||
String remark = request == null ? null : request.getRemark();
|
||||
if (StringUtils.isEmpty(remark))
|
||||
{
|
||||
throw failure(arBillId, "BILL", String.valueOf(arBillId), "VOID", null, "作废必须填写原因", operator, requestId);
|
||||
}
|
||||
ArBill bill = billMapper.selectArBillByIdForUpdate(arBillId);
|
||||
if (bill == null)
|
||||
{
|
||||
throw failure(arBillId, "BILL", String.valueOf(arBillId), "VOID", null, "应收账单不存在", operator, requestId);
|
||||
}
|
||||
ArBillStatus currentStatus = ArBillStatus.of(bill.getStatus());
|
||||
if (currentStatus == null || !currentStatus.canVoid())
|
||||
{
|
||||
throw failure(arBillId, "BILL", String.valueOf(arBillId), "VOID", bill.getStatus(),
|
||||
"当前状态不允许作废", operator, requestId);
|
||||
}
|
||||
List<String> sourceBillIds = new ArrayList<>();
|
||||
for (ArBillDetail detail : detailMapper.selectByBillId(arBillId))
|
||||
{
|
||||
if (!StringUtils.isEmpty(detail.getPrcBillId()))
|
||||
{
|
||||
sourceBillIds.add(detail.getPrcBillId());
|
||||
}
|
||||
}
|
||||
if (billMapper.updateArBillStatus(arBillId, ArBillStatus.VOID.getCode(), remark, operator, currentStatus.getCode()) != 1)
|
||||
{
|
||||
throw failure(arBillId, "BILL", String.valueOf(arBillId), "VOID", bill.getStatus(),
|
||||
"账单状态已发生变化,请刷新后重试", operator, requestId);
|
||||
}
|
||||
if (!sourceBillIds.isEmpty())
|
||||
{
|
||||
int releasedCount = sourceBillMapper.releaseArBillOrders(sourceBillIds, operator,
|
||||
ArOrderBillStatus.BILLED.getCode(), ArOrderBillStatus.UNBILLED.getCode());
|
||||
if (releasedCount != sourceBillIds.size())
|
||||
{
|
||||
throw failure(arBillId, "BILL", String.valueOf(arBillId), "VOID", bill.getStatus(),
|
||||
"部分来源订单状态异常,账单未作废,请刷新后重试", operator, requestId);
|
||||
}
|
||||
}
|
||||
recordSuccess(bill, "VOID", "作废应收账单:" + remark, bill.getStatus(), ArBillStatus.VOID.getCode(), requestId, operator);
|
||||
return selectArBillById(arBillId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArBillExport exportBill(Long arBillId, String operator, String requestId)
|
||||
{
|
||||
ArBillLog previous = logMapper.selectSuccessByRequestId(requestId, "EXPORT");
|
||||
if (previous != null && isNumber(previous.getObjectId()))
|
||||
{
|
||||
ArBillExport previousExport = exportMapper.selectById(Long.valueOf(previous.getObjectId()));
|
||||
if (previousExport != null)
|
||||
{
|
||||
return previousExport;
|
||||
}
|
||||
}
|
||||
ArBill bill = billMapper.selectArBillById(arBillId);
|
||||
if (bill == null)
|
||||
{
|
||||
throw failure(arBillId, "EXPORT", String.valueOf(arBillId), "EXPORT", null, "应收账单不存在", operator, requestId);
|
||||
}
|
||||
if (ArBillStatus.is(bill.getStatus(), ArBillStatus.DRAFT) || ArBillStatus.is(bill.getStatus(), ArBillStatus.VOID))
|
||||
{
|
||||
throw failure(arBillId, "EXPORT", String.valueOf(arBillId), "EXPORT", bill.getStatus(),
|
||||
"草稿或作废账单不可导出", operator, requestId);
|
||||
}
|
||||
fillDetails(bill);
|
||||
String fileName = bill.getBillNo() + "_" + safeFilePart(bill.getCustomerName()) + "_"
|
||||
+ new SimpleDateFormat("yyyyMM").format(bill.getBillPeriodEnd()) + ".xlsx";
|
||||
ArBillExport export = new ArBillExport();
|
||||
export.setArBillId(arBillId);
|
||||
export.setFileName(fileName);
|
||||
export.setFileType("XLSX");
|
||||
export.setTemplateCode("AR_CUSTOMER_BILL");
|
||||
export.setTemplateVersion("1.0");
|
||||
export.setExportBy(operator);
|
||||
export.setRowCount(bill.getOrderCount());
|
||||
try
|
||||
{
|
||||
byte[] data = buildWorkbook(bill);
|
||||
String relative = "ar/" + DateUtils.datePath() + "/" + IdUtils.fastSimpleUUID() + ".xlsx";
|
||||
String downloadDir = RuoYiConfig.getProfile() + File.separator + "download";
|
||||
File file = FileUploadUtils.getAbsoluteFile(downloadDir, relative);
|
||||
Files.write(file.toPath(), data);
|
||||
export.setStorageKey(FileUploadUtils.getPathFileName(downloadDir, relative));
|
||||
export.setFileSize((long) data.length);
|
||||
export.setFileHash(sha256(data));
|
||||
export.setExportStatus(ArExportStatus.SUCCESS.getCode());
|
||||
exportMapper.insert(export);
|
||||
recordSuccess(bill, "EXPORT", "导出账单 XLSX", bill.getStatus(), bill.getStatus(), requestId, operator, export.getExportId());
|
||||
return export;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
export.setStorageKey("");
|
||||
export.setExportStatus(ArExportStatus.FAILED.getCode());
|
||||
export.setRemark(e.getMessage());
|
||||
exportMapper.insert(export);
|
||||
logService.recordFailure(arBillId, "EXPORT", String.valueOf(arBillId), "EXPORT", bill.getStatus(),
|
||||
e.getMessage(), operator, requestId);
|
||||
throw new ServiceException("账单导出失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArBillExport> selectExports(Long arBillId)
|
||||
{
|
||||
return exportMapper.selectByBillId(arBillId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void downloadExport(Long exportId, HttpServletResponse response) throws Exception
|
||||
{
|
||||
ArBillExport export = exportMapper.selectById(exportId);
|
||||
if (export == null || !ArExportStatus.SUCCESS.getCode().equals(export.getExportStatus()) || StringUtils.isEmpty(export.getStorageKey()))
|
||||
{
|
||||
throw new ServiceException("导出文件不存在或尚未生成成功");
|
||||
}
|
||||
String resource = export.getStorageKey();
|
||||
if (!FileUtils.checkAllowDownload(resource))
|
||||
{
|
||||
throw new ServiceException("导出文件路径非法");
|
||||
}
|
||||
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||
FileUtils.setAttachmentResponseHeader(response, export.getFileName());
|
||||
FileUtils.writeBytes(RuoYiConfig.getProfile() + FileUtils.stripPrefix(resource), response.getOutputStream());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArBillLog> selectLogList(ArBillLog query)
|
||||
{
|
||||
return logService.selectLogList(query);
|
||||
}
|
||||
|
||||
private void fillDetails(ArBill bill)
|
||||
{
|
||||
List<ArBillDetail> details = detailMapper.selectByBillIdWithFees(bill.getArBillId());
|
||||
for (ArBillDetail detail : details)
|
||||
{
|
||||
if (detail.getFees() == null)
|
||||
{
|
||||
detail.setFees(new ArrayList<>());
|
||||
}
|
||||
}
|
||||
bill.setDetails(details);
|
||||
}
|
||||
|
||||
private boolean validateDetails(Long arBillId, BigDecimal billTotal)
|
||||
{
|
||||
List<ArBillDetail> details = detailMapper.selectByBillIdWithFees(arBillId);
|
||||
BigDecimal detailTotal = BigDecimal.ZERO;
|
||||
for (ArBillDetail detail : details)
|
||||
{
|
||||
BigDecimal feeTotal = BigDecimal.ZERO;
|
||||
List<ArBillDetailFee> fees = detail.getFees();
|
||||
if (fees == null)
|
||||
{
|
||||
fees = new ArrayList<ArBillDetailFee>();
|
||||
}
|
||||
for (ArBillDetailFee fee : fees)
|
||||
{
|
||||
feeTotal = feeTotal.add(nvl(fee.getAmount()));
|
||||
}
|
||||
if (feeTotal.compareTo(nvl(detail.getTotalAmount())) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
detailTotal = detailTotal.add(nvl(detail.getTotalAmount()));
|
||||
}
|
||||
return !details.isEmpty() && detailTotal.compareTo(nvl(billTotal)) == 0;
|
||||
}
|
||||
|
||||
private ArBillDetail toDetail(ArBill bill, PrcOrderBill source, int detailNo, String operator)
|
||||
{
|
||||
ArBillDetail detail = new ArBillDetail();
|
||||
detail.setArBillId(bill.getArBillId());
|
||||
detail.setPrcBillId(source.getBillId());
|
||||
detail.setDetailNo(detailNo);
|
||||
detail.setOrderNo(source.getOrderNo());
|
||||
detail.setTrackingNo(source.getTrackingNo());
|
||||
detail.setReceiveTime(source.getOrderTime());
|
||||
detail.setProductCode(source.getProductCode());
|
||||
detail.setProductName(source.getProductName());
|
||||
detail.setGoodsDescription(source.getOrderRemark());
|
||||
detail.setCountryCode(source.getCountryCode());
|
||||
detail.setCountryName(source.getCountryName());
|
||||
detail.setDestPostcode(source.getDestPostcode());
|
||||
detail.setActualWeight(source.getActualWeight());
|
||||
detail.setVolumeWeight(source.getVolumeWeight());
|
||||
detail.setChargeWeight(source.getChargeWeight() == null ? source.getWeight() : source.getChargeWeight());
|
||||
detail.setPkgLength(source.getPkgLength());
|
||||
detail.setPkgWidth(source.getPkgWidth());
|
||||
detail.setPkgHeight(source.getPkgHeight());
|
||||
detail.setCurrency(source.getCurrency() == null ? bill.getCurrency() : source.getCurrency());
|
||||
detail.setTotalAmount(nvl(source.getIncomeTotal()));
|
||||
detail.setRemark(source.getOrderRemark());
|
||||
detail.setCreateBy(operator);
|
||||
return detail;
|
||||
}
|
||||
|
||||
private ArBillDetailFee toDetailFee(ArBillDetail detail, PrcIncomeBillFee source, int feeNo, String operator)
|
||||
{
|
||||
ArBillDetailFee fee = new ArBillDetailFee();
|
||||
fee.setDetailId(detail.getDetailId());
|
||||
fee.setPrcFeeId(source.getFeeId());
|
||||
fee.setFeeNo(feeNo);
|
||||
fee.setFeeType(source.getFeeType());
|
||||
fee.setFeeName(source.getFeeName());
|
||||
fee.setScId(source.getScId());
|
||||
fee.setAmount(nvl(source.getAmount()));
|
||||
fee.setCurrency(source.getCurrency() == null ? detail.getCurrency() : source.getCurrency());
|
||||
fee.setRoundMode(source.getRoundMode());
|
||||
fee.setRemark(source.getRemark());
|
||||
fee.setCreateBy(operator);
|
||||
return fee;
|
||||
}
|
||||
|
||||
private void recordSuccess(ArBill bill, String operationType, String desc, String fromStatus,
|
||||
String toStatus, String requestId, String operator)
|
||||
{
|
||||
recordSuccess(bill, operationType, desc, fromStatus, toStatus, requestId, operator, null);
|
||||
}
|
||||
|
||||
private void recordSuccess(ArBill bill, String operationType, String desc, String fromStatus,
|
||||
String toStatus, String requestId, String operator, Long objectId)
|
||||
{
|
||||
ArBillLog log = new ArBillLog();
|
||||
log.setArBillId(bill.getArBillId());
|
||||
log.setObjectType("BILL".equals(operationType) ? "BILL" : operationType.equals("EXPORT") ? "EXPORT" : "BILL");
|
||||
log.setObjectId(objectId == null ? String.valueOf(bill.getArBillId()) : String.valueOf(objectId));
|
||||
log.setOperationType(operationType);
|
||||
log.setOperationDesc(desc);
|
||||
log.setFromStatus(fromStatus);
|
||||
log.setToStatus(toStatus);
|
||||
log.setBeforeData(safeStatusJson(fromStatus));
|
||||
log.setAfterData(safeStatusJson(toStatus));
|
||||
log.setOperatorId(operator);
|
||||
log.setOperatorName(operator);
|
||||
log.setOperationSource("WEB");
|
||||
log.setRequestId(requestId);
|
||||
log.setResult(ArLogResult.SUCCESS.getCode());
|
||||
logService.record(log);
|
||||
}
|
||||
|
||||
private ServiceException failure(Long arBillId, String objectType, String objectId, String operationType,
|
||||
String fromStatus, String message, String operator, String requestId)
|
||||
{
|
||||
logService.recordFailure(arBillId, objectType, objectId, operationType, fromStatus, message, operator, requestId);
|
||||
return new ServiceException(message);
|
||||
}
|
||||
|
||||
private byte[] buildWorkbook(ArBill bill) throws IOException
|
||||
{
|
||||
ClassPathResource resource = new ClassPathResource(EXPORT_TEMPLATE_RESOURCE);
|
||||
if (!resource.exists())
|
||||
{
|
||||
throw new IOException("应收账单导出模板不存在: " + EXPORT_TEMPLATE_RESOURCE);
|
||||
}
|
||||
try (InputStream input = resource.getInputStream(); XSSFWorkbook workbook = new XSSFWorkbook(input))
|
||||
{
|
||||
Sheet sheet = workbook.getSheet("8.31");
|
||||
if (sheet == null)
|
||||
{
|
||||
sheet = workbook.getSheetAt(0);
|
||||
}
|
||||
|
||||
Row templateDataRow = sheet.getRow(TEMPLATE_DATA_START_ROW);
|
||||
List<ArBillDetail> details = bill.getDetails() == null ? new ArrayList<ArBillDetail>() : bill.getDetails();
|
||||
CellStyle bodyStyle = createTemplateBodyStyle(workbook);
|
||||
int footerStartRow = TEMPLATE_FOOTER_START_ROW + Math.max(details.size() - 1, 0);
|
||||
int footerShift = footerStartRow - TEMPLATE_FOOTER_START_ROW;
|
||||
if (footerShift != 0)
|
||||
{
|
||||
sheet.shiftRows(TEMPLATE_FOOTER_START_ROW, sheet.getLastRowNum(), footerShift);
|
||||
}
|
||||
|
||||
set(sheet, 0, 0, StringUtils.isEmpty(bill.getBankAccountName()) ? "威狮国际供应链(深圳)有限公司" : bill.getBankAccountName());
|
||||
set(sheet, 2, 2, bill.getCustomerName());
|
||||
set(sheet, 2, 15, exportPeriod(bill));
|
||||
|
||||
for (int i = 0; i < details.size(); i++)
|
||||
{
|
||||
Row row = sheet.getRow(TEMPLATE_DATA_START_ROW + i);
|
||||
if (row == null)
|
||||
{
|
||||
row = sheet.createRow(TEMPLATE_DATA_START_ROW + i);
|
||||
copyRowStyle(templateDataRow, row);
|
||||
}
|
||||
writeTemplateDetail(row, details.get(i), bodyStyle);
|
||||
}
|
||||
|
||||
int bankRow = footerStartRow;
|
||||
set(sheet, bankRow, 1, "银行信息");
|
||||
set(sheet, bankRow + 1, 1, "开户名");
|
||||
set(sheet, bankRow + 1, 2, bill.getBankAccountName());
|
||||
set(sheet, bankRow + 2, 1, "账号");
|
||||
set(sheet, bankRow + 2, 2, exportBankAccountNo(bill.getBankAccountNo()));
|
||||
set(sheet, bankRow + 3, 1, "开户行");
|
||||
set(sheet, bankRow + 3, 2, bill.getBankName());
|
||||
|
||||
set(sheet, bankRow, 15, "本期应收金额");
|
||||
set(sheet, bankRow, 18, bill.getTotalAmount());
|
||||
set(sheet, bankRow + 1, 15, "本期已核销金额");
|
||||
set(sheet, bankRow + 1, 18, bill.getWrittenOffAmount());
|
||||
set(sheet, bankRow + 2, 15, "本期未核销金额");
|
||||
set(sheet, bankRow + 2, 18, bill.getUnwrittenAmount());
|
||||
return toBytes(workbook);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] toBytes(XSSFWorkbook workbook) throws IOException
|
||||
{
|
||||
java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
|
||||
workbook.write(out);
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private void set(Row row, int index, Object value)
|
||||
{
|
||||
Cell cell = row.getCell(index, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
|
||||
cell.setCellValue("");
|
||||
if (value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (value instanceof Number)
|
||||
{
|
||||
cell.setCellValue(((Number) value).doubleValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.setCellValue(String.valueOf(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void set(Sheet sheet, int rowIndex, int columnIndex, Object value)
|
||||
{
|
||||
Row row = sheet.getRow(rowIndex);
|
||||
if (row == null)
|
||||
{
|
||||
row = sheet.createRow(rowIndex);
|
||||
}
|
||||
set(row, columnIndex, value);
|
||||
}
|
||||
|
||||
private void copyRowStyle(Row source, Row target)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
target.setHeight(source.getHeight());
|
||||
for (int index = 0; index < TEMPLATE_COLUMN_COUNT; index++)
|
||||
{
|
||||
Cell sourceCell = source.getCell(index);
|
||||
if (sourceCell != null)
|
||||
{
|
||||
target.getCell(index, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).setCellStyle(sourceCell.getCellStyle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CellStyle createTemplateBodyStyle(XSSFWorkbook workbook)
|
||||
{
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
Font font = workbook.createFont();
|
||||
font.setFontName("微软雅黑");
|
||||
font.setFontHeightInPoints((short) 10);
|
||||
style.setFont(font);
|
||||
style.setAlignment(HorizontalAlignment.CENTER);
|
||||
style.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
style.setWrapText(true);
|
||||
style.setBorderBottom(BorderStyle.THIN);
|
||||
style.setBorderTop(BorderStyle.THIN);
|
||||
style.setBorderLeft(BorderStyle.THIN);
|
||||
style.setBorderRight(BorderStyle.THIN);
|
||||
return style;
|
||||
}
|
||||
|
||||
private void writeTemplateDetail(Row row, ArBillDetail detail, CellStyle bodyStyle)
|
||||
{
|
||||
row.setHeightInPoints(24);
|
||||
Object[] values = {
|
||||
detail.getDetailNo(),
|
||||
detail.getReceiveTime() == null ? "" : new SimpleDateFormat("yyyy-MM-dd").format(detail.getReceiveTime()),
|
||||
detail.getOrderNo(), detail.getTrackingNo(),
|
||||
StringUtils.isEmpty(detail.getProductName()) ? detail.getProductCode() : detail.getProductName(),
|
||||
StringUtils.isEmpty(detail.getCountryName()) ? detail.getCountryCode() : detail.getCountryName(),
|
||||
detail.getDestPostcode(), decimal(detail.getActualWeight(), 3), decimal(detail.getPkgLength(), 2),
|
||||
decimal(detail.getPkgHeight(), 2), decimal(detail.getPkgWidth(), 2), decimal(detail.getVolumeWeight(), 3),
|
||||
decimal(detail.getChargeWeight(), 3), exportUnitPrice(detail), exportProcessingFee(detail),
|
||||
exportFormula(detail), exportFeeTypes(detail), detail.getCurrency(), detail.getTotalAmount(), exportRemark(detail)
|
||||
};
|
||||
for (int index = 0; index < values.length; index++)
|
||||
{
|
||||
set(row, index, values[index]);
|
||||
row.getCell(index, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).setCellStyle(bodyStyle);
|
||||
}
|
||||
}
|
||||
|
||||
private String exportRemark(ArBillDetail detail)
|
||||
{
|
||||
return StringUtils.isEmpty(detail.getRemark()) ? detail.getGoodsDescription() : detail.getRemark();
|
||||
}
|
||||
|
||||
private String exportPeriod(ArBill bill)
|
||||
{
|
||||
if (bill.getBillPeriodEnd() == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return "截至 " + new SimpleDateFormat("yyyy-MM-dd").format(bill.getBillPeriodEnd());
|
||||
}
|
||||
|
||||
private BigDecimal exportUnitPrice(ArBillDetail detail)
|
||||
{
|
||||
BigDecimal direct = firstFeeUnitPrice(detail.getFees());
|
||||
if (direct != null)
|
||||
{
|
||||
return direct;
|
||||
}
|
||||
BigDecimal freight = feeAmount(detail, "BASIC_FREIGHT");
|
||||
if (freight == null || detail.getChargeWeight() == null || detail.getChargeWeight().compareTo(BigDecimal.ZERO) <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return freight.divide(detail.getChargeWeight(), 4, java.math.RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
private BigDecimal firstFeeUnitPrice(List<ArBillDetailFee> fees)
|
||||
{
|
||||
if (fees == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
for (ArBillDetailFee fee : fees)
|
||||
{
|
||||
if (fee.getUnitPrice() != null)
|
||||
{
|
||||
return fee.getUnitPrice();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private BigDecimal exportProcessingFee(ArBillDetail detail)
|
||||
{
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
boolean found = false;
|
||||
if (detail.getFees() != null)
|
||||
{
|
||||
for (ArBillDetailFee fee : detail.getFees())
|
||||
{
|
||||
String name = fee.getFeeName() == null ? "" : fee.getFeeName();
|
||||
if ("REGISTRATION_FEE".equalsIgnoreCase(fee.getFeeType()) || name.contains("处理费"))
|
||||
{
|
||||
total = total.add(nvl(fee.getAmount()));
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return found ? total : null;
|
||||
}
|
||||
|
||||
private String exportFormula(ArBillDetail detail)
|
||||
{
|
||||
String formula = joinFeeText(detail.getFees(), true);
|
||||
if (!StringUtils.isEmpty(formula))
|
||||
{
|
||||
return formula;
|
||||
}
|
||||
boolean freight = feeAmount(detail, "BASIC_FREIGHT") != null;
|
||||
boolean processing = exportProcessingFee(detail) != null;
|
||||
boolean surcharge = hasFeeType(detail.getFees(), "SURCHARGE");
|
||||
if (!freight)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return "结算重量*公斤价" + (processing ? "+处理费" : "") + (surcharge ? "+附加费" : "");
|
||||
}
|
||||
|
||||
private String exportFeeTypes(ArBillDetail detail)
|
||||
{
|
||||
return joinFeeText(detail.getFees(), false);
|
||||
}
|
||||
|
||||
private String joinFeeText(List<ArBillDetailFee> fees, boolean formula)
|
||||
{
|
||||
if (fees == null || fees.isEmpty())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (ArBillDetailFee fee : fees)
|
||||
{
|
||||
String value = formula ? fee.getCalculationFormula() : (StringUtils.isEmpty(fee.getFeeName()) ? fee.getFeeType() : fee.getFeeName());
|
||||
if (StringUtils.isEmpty(value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (result.length() > 0)
|
||||
{
|
||||
result.append("、");
|
||||
}
|
||||
result.append(value);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private BigDecimal feeAmount(ArBillDetail detail, String feeType)
|
||||
{
|
||||
if (detail.getFees() == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
boolean found = false;
|
||||
for (ArBillDetailFee fee : detail.getFees())
|
||||
{
|
||||
if (feeType.equalsIgnoreCase(fee.getFeeType()))
|
||||
{
|
||||
total = total.add(nvl(fee.getAmount()));
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
return found ? total : null;
|
||||
}
|
||||
|
||||
private boolean hasFeeType(List<ArBillDetailFee> fees, String feeType)
|
||||
{
|
||||
if (fees == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (ArBillDetailFee fee : fees)
|
||||
{
|
||||
if (feeType.equalsIgnoreCase(fee.getFeeType()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String exportBankAccountNo(String value)
|
||||
{
|
||||
if (StringUtils.isEmpty(value))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
String normalized = value.replaceAll("\\s+", "");
|
||||
StringBuilder result = new StringBuilder(normalized.length() + normalized.length() / 4);
|
||||
for (int index = 0; index < normalized.length(); index++)
|
||||
{
|
||||
if (index > 0 && index % 4 == 0)
|
||||
{
|
||||
result.append(' ');
|
||||
}
|
||||
result.append(normalized.charAt(index));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private BigDecimal decimal(BigDecimal value, int scale) { return value == null ? null : value.setScale(scale, java.math.RoundingMode.HALF_UP); }
|
||||
private BigDecimal nvl(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; }
|
||||
private String safeFilePart(String value) { return StringUtils.isEmpty(value) ? "客户" : value.replaceAll("[^a-zA-Z0-9\\u4e00-\\u9fa5_-]", "_"); }
|
||||
private String maskAccount(String value) { if (StringUtils.isEmpty(value)) return ""; return value.length() <= 4 ? "****" : "****" + value.substring(value.length() - 4); }
|
||||
private String sha256(byte[] data) throws NoSuchAlgorithmException { byte[] digest = MessageDigest.getInstance("SHA-256").digest(data); StringBuilder sb = new StringBuilder(); for (byte b : digest) sb.append(String.format("%02x", b)); return sb.toString(); }
|
||||
private boolean isNumber(String value) { if (StringUtils.isEmpty(value)) return false; try { Long.valueOf(value); return true; } catch (NumberFormatException e) { return false; } }
|
||||
private String safeStatusJson(String status) { Map<String, String> data = new HashMap<>(); data.put("status", status); return JSON.toJSONString(data); }
|
||||
private void validatePeriod(ArBillGenerateRequest request)
|
||||
{
|
||||
if (request == null || request.getCustomerId() == null || StringUtils.isEmpty(request.getBillName())
|
||||
|| request.getBillName().trim().isEmpty()
|
||||
|| request.getBillPeriodEnd() == null || request.getSourceBillIds() == null || request.getSourceBillIds().isEmpty())
|
||||
{
|
||||
throw new ServiceException("客户、账单名称、账单截止日和订单选择不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePreviewQuery(ArBillGenerateQuery query)
|
||||
{
|
||||
if (query == null || query.getCustomerId() == null || query.getBillPeriodEnd() == null)
|
||||
{
|
||||
throw new ServiceException("客户和账单截止日不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
private Date earliestBizDate(List<PrcOrderBill> sources)
|
||||
{
|
||||
Date earliest = null;
|
||||
for (PrcOrderBill source : sources)
|
||||
{
|
||||
if (source.getBizDate() != null && (earliest == null || source.getBizDate().before(earliest)))
|
||||
{
|
||||
earliest = source.getBizDate();
|
||||
}
|
||||
}
|
||||
return earliest == null ? new Date() : earliest;
|
||||
}
|
||||
|
||||
private List<String> normalizeSourceBillIds(List<String> sourceBillIds)
|
||||
{
|
||||
LinkedHashSet<String> uniqueIds = new LinkedHashSet<>();
|
||||
if (sourceBillIds != null)
|
||||
{
|
||||
for (String billId : sourceBillIds)
|
||||
{
|
||||
if (!StringUtils.isEmpty(billId) && billId.trim().length() <= 36)
|
||||
{
|
||||
uniqueIds.add(billId.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(uniqueIds);
|
||||
}
|
||||
}
|
||||
+275
@@ -0,0 +1,275 @@
|
||||
package com.ruoyi.ar.service.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.ruoyi.ar.domain.ArBill;
|
||||
import com.ruoyi.ar.domain.ArBillLog;
|
||||
import com.ruoyi.ar.domain.ArReceipt;
|
||||
import com.ruoyi.ar.domain.ArReceiptWriteoff;
|
||||
import com.ruoyi.ar.domain.vo.ArReceiptQuery;
|
||||
import com.ruoyi.ar.domain.vo.ArReceiptWriteoffRequest;
|
||||
import com.ruoyi.ar.enums.ArBillStatus;
|
||||
import com.ruoyi.ar.enums.ArLogResult;
|
||||
import com.ruoyi.ar.enums.ArReceiptStatus;
|
||||
import com.ruoyi.ar.enums.ArWriteoffStatus;
|
||||
import com.ruoyi.ar.mapper.ArBillMapper;
|
||||
import com.ruoyi.ar.mapper.ArBillLogMapper;
|
||||
import com.ruoyi.ar.mapper.ArReceiptMapper;
|
||||
import com.ruoyi.ar.mapper.ArReceiptWriteoffMapper;
|
||||
import com.ruoyi.ar.service.IArBillLogService;
|
||||
import com.ruoyi.ar.service.IArReceiptService;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.uuid.IdUtils;
|
||||
|
||||
@Service
|
||||
public class ArReceiptServiceImpl implements IArReceiptService
|
||||
{
|
||||
@Autowired private ArReceiptMapper receiptMapper;
|
||||
@Autowired private ArReceiptWriteoffMapper writeoffMapper;
|
||||
@Autowired private ArBillMapper billMapper;
|
||||
@Autowired private ArBillLogMapper logMapper;
|
||||
@Autowired private IArBillLogService logService;
|
||||
|
||||
@Override
|
||||
public List<ArReceipt> selectReceiptList(ArReceiptQuery query)
|
||||
{
|
||||
return receiptMapper.selectArReceiptList(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArReceiptWriteoff> selectWriteoffs(Long receiptId)
|
||||
{
|
||||
return writeoffMapper.selectByReceiptId(receiptId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ArReceipt registerReceipt(ArReceipt receipt, String operator, String requestId)
|
||||
{
|
||||
if (receipt == null || receipt.getCustomerId() == null || receipt.getReceiptDate() == null
|
||||
|| receipt.getReceiptAmount() == null || receipt.getReceiptAmount().compareTo(BigDecimal.ZERO) <= 0)
|
||||
{
|
||||
throw failure(null, "RECEIPT", "REGISTER", "收款客户、日期和正数金额不能为空", operator, requestId);
|
||||
}
|
||||
if (StringUtils.isEmpty(receipt.getReceiptNo()))
|
||||
{
|
||||
receipt.setReceiptNo("RC" + DateUtils.dateTimeNow("yyyyMMddHHmmss") + IdUtils.fastSimpleUUID().substring(0, 6).toUpperCase());
|
||||
}
|
||||
if (StringUtils.isEmpty(receipt.getCurrency())) receipt.setCurrency("CNY");
|
||||
if (StringUtils.isEmpty(receipt.getPaymentMethod())) receipt.setPaymentMethod("BANK");
|
||||
ArBillLog previous = logMapper.selectSuccessByRequestId(requestId, "RECEIPT");
|
||||
if (previous != null && isNumber(previous.getObjectId()))
|
||||
{
|
||||
ArReceipt existed = receiptMapper.selectArReceiptById(Long.valueOf(previous.getObjectId()));
|
||||
if (existed != null) return existed;
|
||||
}
|
||||
if (receiptMapper.selectArReceiptByNo(receipt.getReceiptNo()) != null)
|
||||
{
|
||||
throw failure(null, "RECEIPT", receipt.getReceiptNo(), "收款单号已存在", operator, requestId);
|
||||
}
|
||||
if (!StringUtils.isEmpty(receipt.getBankReference())
|
||||
&& receiptMapper.selectArReceiptByReference(receipt.getPaymentMethod(), receipt.getBankReference()) != null)
|
||||
{
|
||||
throw failure(null, "RECEIPT", receipt.getReceiptNo(), "银行流水号已存在", operator, requestId);
|
||||
}
|
||||
receipt.setUnallocatedAmount(receipt.getReceiptAmount());
|
||||
receipt.setStatus(ArReceiptStatus.REGISTERED.getCode());
|
||||
receipt.setCreateBy(operator);
|
||||
receipt.setUpdateBy(operator);
|
||||
try
|
||||
{
|
||||
receiptMapper.insertArReceipt(receipt);
|
||||
}
|
||||
catch (DuplicateKeyException e)
|
||||
{
|
||||
ArReceipt existed = receiptMapper.selectArReceiptByNo(receipt.getReceiptNo());
|
||||
if (existed != null) return existed;
|
||||
throw e;
|
||||
}
|
||||
ArBillLog log = new ArBillLog();
|
||||
log.setObjectType("RECEIPT"); log.setObjectId(String.valueOf(receipt.getReceiptId()));
|
||||
log.setOperationType("RECEIPT"); log.setOperationDesc("登记应收收款");
|
||||
log.setOperatorId(operator); log.setOperatorName(operator); log.setOperationSource("WEB");
|
||||
log.setRequestId(requestId); log.setResult(ArLogResult.SUCCESS.getCode()); logService.record(log);
|
||||
return receipt;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void cancelReceipt(Long receiptId, String remark, String operator, String requestId)
|
||||
{
|
||||
ArBillLog previous = logMapper.selectSuccessByRequestId(requestId, "RECEIPT");
|
||||
if (previous != null && "CANCEL_RECEIPT".equals(previous.getOperationType())) return;
|
||||
ArReceipt receipt = receiptMapper.selectArReceiptByIdForUpdate(receiptId);
|
||||
if (receipt == null) throw failure(null, "RECEIPT", String.valueOf(receiptId), "收款不存在", operator, requestId);
|
||||
if (!ArReceiptStatus.REGISTERED.getCode().equals(receipt.getStatus())
|
||||
|| nvl(receipt.getUnallocatedAmount()).compareTo(nvl(receipt.getReceiptAmount())) != 0)
|
||||
{
|
||||
throw failure(null, "RECEIPT", String.valueOf(receiptId), "收款已发生核销,不能直接取消", operator, requestId);
|
||||
}
|
||||
receiptMapper.updateCancelled(receiptId, operator, StringUtils.isEmpty(remark) ? "取消收款" : remark);
|
||||
ArBillLog log = new ArBillLog();
|
||||
log.setObjectType("RECEIPT"); log.setObjectId(String.valueOf(receiptId)); log.setOperationType("CANCEL_RECEIPT");
|
||||
log.setOperationDesc(StringUtils.isEmpty(remark) ? "取消收款" : remark); log.setFromStatus(ArReceiptStatus.REGISTERED.getCode());
|
||||
log.setToStatus(ArReceiptStatus.CANCELLED.getCode()); log.setOperatorId(operator); log.setOperatorName(operator);
|
||||
log.setOperationSource("WEB"); log.setRequestId(requestId); log.setResult(ArLogResult.SUCCESS.getCode()); logService.record(log);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<ArReceiptWriteoff> writeoff(ArReceiptWriteoffRequest request, String operator, String requestId)
|
||||
{
|
||||
if (request == null || request.getReceiptId() == null || request.getAllocations() == null || request.getAllocations().isEmpty())
|
||||
{
|
||||
throw failure(null, "WRITEOFF", "", "核销收款和账单不能为空", operator, requestId);
|
||||
}
|
||||
ArBillLog previous = logMapper.selectSuccessByRequestId(requestId, "WRITEOFF");
|
||||
if (previous != null && isNumber(previous.getObjectId()))
|
||||
{
|
||||
ArReceiptWriteoff existed = writeoffMapper.selectById(Long.valueOf(previous.getObjectId()));
|
||||
if (existed != null) return writeoffMapper.selectByReceiptId(existed.getReceiptId());
|
||||
}
|
||||
ArReceipt receipt = receiptMapper.selectArReceiptByIdForUpdate(request.getReceiptId());
|
||||
if (receipt == null) throw failure(null, "WRITEOFF", String.valueOf(request.getReceiptId()), "收款不存在", operator, requestId);
|
||||
if (ArReceiptStatus.CANCELLED.getCode().equals(receipt.getStatus()))
|
||||
{
|
||||
throw failure(null, "WRITEOFF", String.valueOf(request.getReceiptId()), "收款已取消,不允许核销", operator, requestId);
|
||||
}
|
||||
String currency = StringUtils.isEmpty(request.getCurrency()) ? receipt.getCurrency() : request.getCurrency();
|
||||
if (!currency.equalsIgnoreCase(receipt.getCurrency()))
|
||||
{
|
||||
throw failure(null, "WRITEOFF", String.valueOf(request.getReceiptId()), "收款与核销币种不一致", operator, requestId);
|
||||
}
|
||||
BigDecimal allocationTotal = BigDecimal.ZERO;
|
||||
for (ArReceiptWriteoffRequest.Allocation allocation : request.getAllocations())
|
||||
{
|
||||
if (allocation.getAmount() == null || allocation.getAmount().compareTo(BigDecimal.ZERO) <= 0)
|
||||
{
|
||||
throw failure(null, "WRITEOFF", String.valueOf(request.getReceiptId()), "核销金额必须大于零", operator, requestId);
|
||||
}
|
||||
allocationTotal = allocationTotal.add(allocation.getAmount());
|
||||
}
|
||||
if (allocationTotal.compareTo(nvl(receipt.getUnallocatedAmount())) > 0)
|
||||
{
|
||||
throw failure(null, "WRITEOFF", String.valueOf(request.getReceiptId()), "核销金额超过收款未分配余额", operator, requestId);
|
||||
}
|
||||
|
||||
List<ArReceiptWriteoff> result = new ArrayList<>();
|
||||
BigDecimal remainingReceipt = nvl(receipt.getUnallocatedAmount());
|
||||
Date writeoffDate = request.getWriteoffDate() == null ? new Date() : request.getWriteoffDate();
|
||||
for (ArReceiptWriteoffRequest.Allocation allocation : request.getAllocations())
|
||||
{
|
||||
ArBill bill = billMapper.selectArBillByIdForUpdate(allocation.getArBillId());
|
||||
if (bill == null) throw failure(null, "WRITEOFF", String.valueOf(allocation.getArBillId()), "账单不存在", operator, requestId);
|
||||
ArBillStatus billStatus = ArBillStatus.of(bill.getStatus());
|
||||
if (billStatus == ArBillStatus.VOID) throw failure(bill.getArBillId(), "WRITEOFF", String.valueOf(bill.getArBillId()), "账单已作废,不允许核销", operator, requestId);
|
||||
if (billStatus == null || !billStatus.canWriteOff())
|
||||
{
|
||||
throw failure(bill.getArBillId(), "WRITEOFF", String.valueOf(bill.getArBillId()), "账单尚未确认,不允许核销", operator, requestId);
|
||||
}
|
||||
if (!currency.equalsIgnoreCase(bill.getCurrency())) throw failure(bill.getArBillId(), "WRITEOFF", String.valueOf(bill.getArBillId()), "收款与账单币种不一致", operator, requestId);
|
||||
BigDecimal amount = allocation.getAmount();
|
||||
if (amount.compareTo(nvl(bill.getUnwrittenAmount())) > 0)
|
||||
{
|
||||
throw failure(bill.getArBillId(), "WRITEOFF", String.valueOf(bill.getArBillId()), "核销金额超过账单未核销余额", operator, requestId);
|
||||
}
|
||||
ArReceiptWriteoff writeoff = new ArReceiptWriteoff();
|
||||
writeoff.setReceiptId(receipt.getReceiptId()); writeoff.setArBillId(bill.getArBillId());
|
||||
writeoff.setWriteoffNo("WO" + DateUtils.dateTimeNow("yyyyMMddHHmmss") + IdUtils.fastSimpleUUID().substring(0, 6).toUpperCase());
|
||||
writeoff.setWriteoffAmount(amount); writeoff.setCurrency(currency); writeoff.setWriteoffDate(writeoffDate);
|
||||
writeoff.setStatus(ArWriteoffStatus.EFFECTIVE.getCode()); writeoff.setRemark(request.getRemark()); writeoff.setCreateBy(operator);
|
||||
writeoffMapper.insert(writeoff);
|
||||
remainingReceipt = remainingReceipt.subtract(amount);
|
||||
BigDecimal written = nvl(bill.getWrittenOffAmount()).add(amount);
|
||||
BigDecimal unwritten = nvl(bill.getUnwrittenAmount()).subtract(amount);
|
||||
String nextBillStatus = billStatus(written, nvl(bill.getTotalAmount()), bill.getStatus());
|
||||
if (billMapper.updateArBillAmounts(bill.getArBillId(), written, unwritten,
|
||||
nextBillStatus, operator, bill.getStatus()) != 1)
|
||||
{
|
||||
throw failure(bill.getArBillId(), "WRITEOFF", String.valueOf(bill.getArBillId()), "账单状态已发生变化,请刷新后重试", operator, requestId);
|
||||
}
|
||||
writeoffLog(writeoff, bill, operator, requestId);
|
||||
result.add(writeoff);
|
||||
}
|
||||
receiptMapper.updateBalanceAndStatus(receipt.getReceiptId(), remainingReceipt,
|
||||
remainingReceipt.compareTo(BigDecimal.ZERO) == 0 ? ArReceiptStatus.WRITTEN_OFF.getCode() : ArReceiptStatus.PARTIAL_WRITTEN_OFF.getCode(), operator);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void cancelWriteoff(Long writeoffId, String remark, String operator, String requestId)
|
||||
{
|
||||
ArBillLog previous = logMapper.selectSuccessByRequestId(requestId, "WRITEOFF");
|
||||
if (previous != null && "CANCEL_WRITEOFF".equals(previous.getOperationType())) return;
|
||||
ArReceiptWriteoff writeoff = writeoffMapper.selectByIdForUpdate(writeoffId);
|
||||
if (writeoff == null) throw failure(null, "WRITEOFF", String.valueOf(writeoffId), "核销记录不存在", operator, requestId);
|
||||
if (!ArWriteoffStatus.EFFECTIVE.getCode().equals(writeoff.getStatus())) throw failure(writeoff.getArBillId(), "WRITEOFF", String.valueOf(writeoffId), "只有有效核销可以取消", operator, requestId);
|
||||
ArReceipt receipt = receiptMapper.selectArReceiptByIdForUpdate(writeoff.getReceiptId());
|
||||
ArBill bill = billMapper.selectArBillByIdForUpdate(writeoff.getArBillId());
|
||||
if (receipt == null || bill == null) throw failure(writeoff.getArBillId(), "WRITEOFF", String.valueOf(writeoffId), "核销关联的收款或账单不存在", operator, requestId);
|
||||
writeoffMapper.updateCancelled(writeoffId, operator, StringUtils.isEmpty(remark) ? "取消核销" : remark);
|
||||
BigDecimal receiptUnallocated = nvl(receipt.getUnallocatedAmount()).add(writeoff.getWriteoffAmount());
|
||||
receiptMapper.updateBalanceAndStatus(receipt.getReceiptId(), receiptUnallocated,
|
||||
receiptUnallocated.compareTo(nvl(receipt.getReceiptAmount())) == 0 ? ArReceiptStatus.REGISTERED.getCode() : ArReceiptStatus.PARTIAL_WRITTEN_OFF.getCode(), operator);
|
||||
BigDecimal written = nvl(bill.getWrittenOffAmount()).subtract(writeoff.getWriteoffAmount());
|
||||
if (written.compareTo(BigDecimal.ZERO) < 0) written = BigDecimal.ZERO;
|
||||
BigDecimal unwritten = nvl(bill.getTotalAmount()).subtract(written);
|
||||
String nextBillStatus = billStatus(written, nvl(bill.getTotalAmount()), bill.getStatus());
|
||||
if (billMapper.updateArBillAmounts(bill.getArBillId(), written, unwritten,
|
||||
nextBillStatus, operator, bill.getStatus()) != 1)
|
||||
{
|
||||
throw failure(bill.getArBillId(), "WRITEOFF", String.valueOf(bill.getArBillId()), "账单状态已发生变化,请刷新后重试", operator, requestId);
|
||||
}
|
||||
ArBillLog log = new ArBillLog();
|
||||
log.setArBillId(bill.getArBillId()); log.setObjectType("WRITEOFF"); log.setObjectId(String.valueOf(writeoffId));
|
||||
log.setOperationType("CANCEL_WRITEOFF"); log.setOperationDesc(StringUtils.isEmpty(remark) ? "取消核销" : remark);
|
||||
log.setFromStatus(ArWriteoffStatus.EFFECTIVE.getCode()); log.setToStatus(ArWriteoffStatus.CANCELLED.getCode()); log.setOperatorId(operator); log.setOperatorName(operator);
|
||||
log.setOperationSource("WEB"); log.setRequestId(requestId); log.setResult(ArLogResult.SUCCESS.getCode()); logService.record(log);
|
||||
}
|
||||
|
||||
private void writeoffLog(ArReceiptWriteoff writeoff, ArBill bill, String operator, String requestId)
|
||||
{
|
||||
ArBillLog log = new ArBillLog();
|
||||
log.setArBillId(bill.getArBillId()); log.setObjectType("WRITEOFF"); log.setObjectId(String.valueOf(writeoff.getWriteoffId()));
|
||||
log.setOperationType("WRITEOFF"); log.setOperationDesc("收款核销到账单"); log.setFromStatus(bill.getStatus());
|
||||
log.setToStatus(billStatus(nvl(bill.getWrittenOffAmount()).add(writeoff.getWriteoffAmount()), nvl(bill.getTotalAmount()), bill.getStatus()));
|
||||
log.setOperatorId(operator); log.setOperatorName(operator); log.setOperationSource("WEB"); log.setRequestId(requestId); log.setResult(ArLogResult.SUCCESS.getCode());
|
||||
logService.record(log);
|
||||
}
|
||||
|
||||
private String billStatus(BigDecimal written, BigDecimal total, String current)
|
||||
{
|
||||
if (written.compareTo(BigDecimal.ZERO) == 0)
|
||||
{
|
||||
return ArBillStatus.DRAFT.getCode().equals(current)
|
||||
? ArBillStatus.DRAFT.getCode() : ArBillStatus.CONFIRMED.getCode();
|
||||
}
|
||||
return written.compareTo(total) >= 0
|
||||
? ArBillStatus.WRITTEN_OFF.getCode() : ArBillStatus.PARTIAL_WRITTEN_OFF.getCode();
|
||||
}
|
||||
|
||||
private ServiceException failure(Long billId, String objectType, String message, String operator, String requestId)
|
||||
{
|
||||
logService.recordFailure(billId, objectType, objectType, "RECEIPT", null, message, operator, requestId);
|
||||
return new ServiceException(message);
|
||||
}
|
||||
|
||||
private ServiceException failure(Long billId, String objectType, String objectId, String message, String operator, String requestId)
|
||||
{
|
||||
logService.recordFailure(billId, objectType, objectId,
|
||||
"RECEIPT".equals(objectType) ? "RECEIPT" : "WRITEOFF", null, message, operator, requestId);
|
||||
return new ServiceException(message);
|
||||
}
|
||||
|
||||
private BigDecimal nvl(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; }
|
||||
private boolean isNumber(String value) { if (StringUtils.isEmpty(value)) return false; try { Long.valueOf(value); return true; } catch (NumberFormatException e) { return false; } }
|
||||
}
|
||||
@@ -71,6 +71,8 @@ public class PrcOrderBill extends BaseEntity
|
||||
/** payable total */
|
||||
private BigDecimal payTotal;
|
||||
private String currency;
|
||||
/** 应收账单占用状态:UNBILLED/BILLED。 */
|
||||
private String arBillStatus;
|
||||
private String roundMode;
|
||||
|
||||
private List<PrcIncomeBillFee> incomeFees;
|
||||
|
||||
@@ -9,6 +9,8 @@ public interface PrcIncomeBillFeeMapper
|
||||
{
|
||||
public List<PrcIncomeBillFee> selectByBillId(String billId);
|
||||
|
||||
public List<PrcIncomeBillFee> selectByBillIds(@Param("billIds") List<String> billIds);
|
||||
|
||||
public int insertPrcIncomeBillFee(PrcIncomeBillFee fee);
|
||||
|
||||
public int insertBatch(@Param("list") List<PrcIncomeBillFee> fees);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.ruoyi.price.mapper;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.ruoyi.price.domain.PrcOrderBill;
|
||||
@@ -14,6 +15,25 @@ public interface PrcOrderBillMapper
|
||||
|
||||
public List<PrcOrderBill> selectPrcOrderBillList(PrcOrderBill bill);
|
||||
|
||||
/** 应收账单生成读取指定客户截止日期前的未出账订单计费快照。 */
|
||||
public List<PrcOrderBill> selectPrcOrderBillByCustomerPeriod(@Param("customerId") Long customerId,
|
||||
@Param("customerCode") String customerCode, @Param("cutoffDate") Date cutoffDate,
|
||||
@Param("currency") String currency, @Param("arBillStatus") String arBillStatus);
|
||||
|
||||
/** 按生成账单时用户勾选的订单读取,并再次校验账单筛选条件。 */
|
||||
public List<PrcOrderBill> selectPrcOrderBillByCustomerCutoffAndIds(@Param("customerId") Long customerId,
|
||||
@Param("customerCode") String customerCode, @Param("cutoffDate") Date cutoffDate,
|
||||
@Param("currency") String currency, @Param("billIds") List<String> billIds,
|
||||
@Param("arBillStatus") String arBillStatus);
|
||||
|
||||
/** 原子占用订单,防止同一订单被并发生成到多张应收账单。 */
|
||||
public int claimArBillOrders(@Param("billIds") List<String> billIds, @Param("operator") String operator,
|
||||
@Param("fromStatus") String fromStatus, @Param("toStatus") String toStatus);
|
||||
|
||||
/** 作废账单后释放订单,允许后续重新生成新版本账单。 */
|
||||
public int releaseArBillOrders(@Param("billIds") List<String> billIds, @Param("operator") String operator,
|
||||
@Param("fromStatus") String fromStatus, @Param("toStatus") String toStatus);
|
||||
|
||||
public int insertPrcOrderBill(PrcOrderBill bill);
|
||||
|
||||
public int updatePrcOrderBill(PrcOrderBill bill);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.ar.mapper.ArBankAccountMapper">
|
||||
<resultMap id="ArBankAccountResult" type="ArBankAccount">
|
||||
<id property="bankAccountId" column="bank_account_id"/>
|
||||
<result property="accountName" column="account_name"/>
|
||||
<result property="accountNo" column="account_no"/>
|
||||
<result property="bankName" column="bank_name"/>
|
||||
<result property="currency" column="currency"/>
|
||||
<result property="isDefault" column="is_default"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateBy" column="update_by"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectDefaultByCurrency" resultMap="ArBankAccountResult">
|
||||
select bank_account_id, account_name, account_no, bank_name, currency,
|
||||
is_default, status, remark, create_by, create_time, update_by, update_time
|
||||
from ar_bank_account
|
||||
where currency = #{currency}
|
||||
and status = '0'
|
||||
order by is_default desc, bank_account_id
|
||||
limit 1
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.ar.mapper.ArBillDetailFeeMapper">
|
||||
<resultMap id="ArBillDetailFeeResult" type="ArBillDetailFee">
|
||||
<id property="detailFeeId" column="detail_fee_id"/>
|
||||
<result property="detailId" column="detail_id"/>
|
||||
<result property="prcFeeId" column="prc_fee_id"/>
|
||||
<result property="feeNo" column="fee_no"/>
|
||||
<result property="feeType" column="fee_type"/>
|
||||
<result property="feeName" column="fee_name"/>
|
||||
<result property="scId" column="sc_id"/>
|
||||
<result property="quantity" column="quantity"/>
|
||||
<result property="unit" column="unit"/>
|
||||
<result property="unitPrice" column="unit_price"/>
|
||||
<result property="amount" column="amount"/>
|
||||
<result property="currency" column="currency"/>
|
||||
<result property="calculationFormula" column="calculation_formula"/>
|
||||
<result property="roundMode" column="round_mode"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectByDetailId" parameterType="long" resultMap="ArBillDetailFeeResult">
|
||||
select detail_fee_id, detail_id, prc_fee_id, fee_no, fee_type, fee_name, sc_id, quantity,
|
||||
unit, unit_price, amount, currency, calculation_formula, round_mode, remark, create_by, create_time
|
||||
from ar_bill_detail_fee where detail_id = #{detailId} order by fee_no, detail_fee_id
|
||||
</select>
|
||||
|
||||
<select id="selectByBillId" parameterType="long" resultMap="ArBillDetailFeeResult">
|
||||
select detail_fee_id, detail_id, prc_fee_id, fee_no, fee_type, fee_name, sc_id, quantity,
|
||||
unit, unit_price, amount, currency, calculation_formula, round_mode, remark, create_by, create_time
|
||||
from ar_bill_detail_fee
|
||||
where detail_id in (select detail_id from ar_bill_detail where ar_bill_id = #{arBillId})
|
||||
order by detail_id, fee_no, detail_fee_id
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterType="ArBillDetailFee" useGeneratedKeys="true" keyProperty="detailFeeId">
|
||||
insert into ar_bill_detail_fee (
|
||||
detail_id, prc_fee_id, fee_no, fee_type, fee_name, sc_id, quantity, unit, unit_price,
|
||||
amount, currency, calculation_formula, round_mode, remark, create_by, create_time
|
||||
) values (
|
||||
#{detailId}, #{prcFeeId}, #{feeNo}, #{feeType}, #{feeName}, #{scId}, #{quantity}, #{unit},
|
||||
#{unitPrice}, #{amount}, #{currency}, #{calculationFormula}, #{roundMode}, #{remark},
|
||||
#{createBy}, sysdate()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<insert id="insertBatch" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="detailFeeId">
|
||||
insert into ar_bill_detail_fee (
|
||||
detail_id, prc_fee_id, fee_no, fee_type, fee_name, sc_id, quantity, unit, unit_price,
|
||||
amount, currency, calculation_formula, round_mode, remark, create_by, create_time
|
||||
) values
|
||||
<foreach collection="list" item="item" separator=",">
|
||||
(#{item.detailId}, #{item.prcFeeId}, #{item.feeNo}, #{item.feeType}, #{item.feeName}, #{item.scId},
|
||||
#{item.quantity}, #{item.unit}, #{item.unitPrice}, #{item.amount}, #{item.currency},
|
||||
#{item.calculationFormula}, #{item.roundMode}, #{item.remark}, #{item.createBy}, sysdate())
|
||||
</foreach>
|
||||
</insert>
|
||||
</mapper>
|
||||
@@ -0,0 +1,109 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.ar.mapper.ArBillDetailMapper">
|
||||
<resultMap id="ArBillDetailResult" type="ArBillDetail">
|
||||
<id property="detailId" column="detail_id"/>
|
||||
<result property="arBillId" column="ar_bill_id"/>
|
||||
<result property="prcBillId" column="prc_bill_id"/>
|
||||
<result property="detailNo" column="detail_no"/>
|
||||
<result property="orderNo" column="order_no"/>
|
||||
<result property="trackingNo" column="tracking_no"/>
|
||||
<result property="receiveTime" column="receive_time"/>
|
||||
<result property="productCode" column="product_code"/>
|
||||
<result property="productName" column="product_name"/>
|
||||
<result property="goodsDescription" column="goods_description"/>
|
||||
<result property="countryCode" column="country_code"/>
|
||||
<result property="countryName" column="country_name"/>
|
||||
<result property="destPostcode" column="dest_postcode"/>
|
||||
<result property="actualWeight" column="actual_weight"/>
|
||||
<result property="volumeWeight" column="volume_weight"/>
|
||||
<result property="chargeWeight" column="charge_weight"/>
|
||||
<result property="pkgLength" column="pkg_length"/>
|
||||
<result property="pkgWidth" column="pkg_width"/>
|
||||
<result property="pkgHeight" column="pkg_height"/>
|
||||
<result property="currency" column="currency"/>
|
||||
<result property="totalAmount" column="total_amount"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectByBillId" parameterType="long" resultMap="ArBillDetailResult">
|
||||
select detail_id, ar_bill_id, prc_bill_id, detail_no, order_no, tracking_no, receive_time,
|
||||
product_code, product_name, goods_description, country_code, country_name, dest_postcode,
|
||||
actual_weight, volume_weight, charge_weight, pkg_length, pkg_width, pkg_height,
|
||||
currency, total_amount, remark, create_by, create_time
|
||||
from ar_bill_detail where ar_bill_id = #{arBillId} order by detail_no
|
||||
</select>
|
||||
|
||||
<resultMap id="ArBillDetailWithFeesResult" type="ArBillDetail" extends="ArBillDetailResult">
|
||||
<collection property="fees" ofType="ArBillDetailFee">
|
||||
<id property="detailFeeId" column="fee_detail_id"/>
|
||||
<result property="detailId" column="fee_parent_detail_id"/>
|
||||
<result property="prcFeeId" column="fee_prc_fee_id"/>
|
||||
<result property="feeNo" column="fee_no"/>
|
||||
<result property="feeType" column="fee_type"/>
|
||||
<result property="feeName" column="fee_name"/>
|
||||
<result property="scId" column="fee_sc_id"/>
|
||||
<result property="quantity" column="fee_quantity"/>
|
||||
<result property="unit" column="fee_unit"/>
|
||||
<result property="unitPrice" column="fee_unit_price"/>
|
||||
<result property="amount" column="fee_amount"/>
|
||||
<result property="currency" column="fee_currency"/>
|
||||
<result property="calculationFormula" column="fee_calculation_formula"/>
|
||||
<result property="roundMode" column="fee_round_mode"/>
|
||||
<result property="remark" column="fee_remark"/>
|
||||
<result property="createBy" column="fee_create_by"/>
|
||||
<result property="createTime" column="fee_create_time"/>
|
||||
</collection>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectByBillIdWithFees" parameterType="long" resultMap="ArBillDetailWithFeesResult">
|
||||
select d.detail_id, d.ar_bill_id, d.prc_bill_id, d.detail_no, d.order_no, d.tracking_no, d.receive_time,
|
||||
d.product_code, d.product_name, d.goods_description, d.country_code, d.country_name, d.dest_postcode,
|
||||
d.actual_weight, d.volume_weight, d.charge_weight, d.pkg_length, d.pkg_width, d.pkg_height,
|
||||
d.currency, d.total_amount, d.remark, d.create_by, d.create_time,
|
||||
f.detail_fee_id as fee_detail_id, f.detail_id as fee_parent_detail_id, f.prc_fee_id as fee_prc_fee_id,
|
||||
f.fee_no, f.fee_type, f.fee_name, f.sc_id as fee_sc_id, f.quantity as fee_quantity,
|
||||
f.unit as fee_unit, f.unit_price as fee_unit_price, f.amount as fee_amount,
|
||||
f.currency as fee_currency, f.calculation_formula as fee_calculation_formula,
|
||||
f.round_mode as fee_round_mode, f.remark as fee_remark, f.create_by as fee_create_by,
|
||||
f.create_time as fee_create_time
|
||||
from ar_bill_detail d
|
||||
left join ar_bill_detail_fee f on f.detail_id = d.detail_id
|
||||
where d.ar_bill_id = #{arBillId}
|
||||
order by d.detail_no, d.detail_id, f.fee_no, f.detail_fee_id
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterType="ArBillDetail" useGeneratedKeys="true" keyProperty="detailId">
|
||||
insert into ar_bill_detail (
|
||||
ar_bill_id, prc_bill_id, detail_no, order_no, tracking_no, receive_time, product_code,
|
||||
product_name, goods_description, country_code, country_name, dest_postcode, actual_weight,
|
||||
volume_weight, charge_weight, pkg_length, pkg_width, pkg_height, currency, total_amount,
|
||||
remark, create_by, create_time
|
||||
) values (
|
||||
#{arBillId}, #{prcBillId}, #{detailNo}, #{orderNo}, #{trackingNo}, #{receiveTime},
|
||||
#{productCode}, #{productName}, #{goodsDescription}, #{countryCode}, #{countryName},
|
||||
#{destPostcode}, #{actualWeight}, #{volumeWeight}, #{chargeWeight}, #{pkgLength}, #{pkgWidth},
|
||||
#{pkgHeight}, #{currency}, #{totalAmount}, #{remark}, #{createBy}, sysdate()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<insert id="insertBatch" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="detailId">
|
||||
insert into ar_bill_detail (
|
||||
ar_bill_id, prc_bill_id, detail_no, order_no, tracking_no, receive_time, product_code,
|
||||
product_name, goods_description, country_code, country_name, dest_postcode, actual_weight,
|
||||
volume_weight, charge_weight, pkg_length, pkg_width, pkg_height, currency, total_amount,
|
||||
remark, create_by, create_time
|
||||
) values
|
||||
<foreach collection="list" item="item" separator=",">
|
||||
(#{item.arBillId}, #{item.prcBillId}, #{item.detailNo}, #{item.orderNo}, #{item.trackingNo},
|
||||
#{item.receiveTime}, #{item.productCode}, #{item.productName}, #{item.goodsDescription},
|
||||
#{item.countryCode}, #{item.countryName}, #{item.destPostcode}, #{item.actualWeight},
|
||||
#{item.volumeWeight}, #{item.chargeWeight}, #{item.pkgLength}, #{item.pkgWidth}, #{item.pkgHeight},
|
||||
#{item.currency}, #{item.totalAmount}, #{item.remark}, #{item.createBy}, sysdate())
|
||||
</foreach>
|
||||
</insert>
|
||||
</mapper>
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.ar.mapper.ArBillExportMapper">
|
||||
<resultMap id="ArBillExportResult" type="ArBillExport">
|
||||
<id property="exportId" column="export_id"/>
|
||||
<result property="arBillId" column="ar_bill_id"/>
|
||||
<result property="fileName" column="file_name"/>
|
||||
<result property="fileType" column="file_type"/>
|
||||
<result property="templateCode" column="template_code"/>
|
||||
<result property="templateVersion" column="template_version"/>
|
||||
<result property="storageKey" column="storage_key"/>
|
||||
<result property="fileSize" column="file_size"/>
|
||||
<result property="fileHash" column="file_hash"/>
|
||||
<result property="rowCount" column="row_count"/>
|
||||
<result property="exportStatus" column="export_status"/>
|
||||
<result property="exportBy" column="export_by"/>
|
||||
<result property="exportTime" column="export_time"/>
|
||||
<result property="remark" column="remark"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectExportColumns">
|
||||
select export_id, ar_bill_id, file_name, file_type, template_code, template_version,
|
||||
storage_key, file_size, file_hash, row_count, export_status, export_by, export_time, remark
|
||||
from ar_bill_export
|
||||
</sql>
|
||||
|
||||
<select id="selectByBillId" parameterType="long" resultMap="ArBillExportResult">
|
||||
<include refid="selectExportColumns"/> where ar_bill_id = #{arBillId} order by export_time desc
|
||||
</select>
|
||||
|
||||
<select id="selectById" parameterType="long" resultMap="ArBillExportResult">
|
||||
<include refid="selectExportColumns"/> where export_id = #{exportId}
|
||||
</select>
|
||||
|
||||
<select id="selectLatestSuccessByBillId" parameterType="long" resultMap="ArBillExportResult">
|
||||
<include refid="selectExportColumns"/>
|
||||
where ar_bill_id = #{arBillId} and export_status = 'SUCCESS'
|
||||
order by export_time desc limit 1
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterType="ArBillExport" useGeneratedKeys="true" keyProperty="exportId">
|
||||
insert into ar_bill_export (
|
||||
ar_bill_id, file_name, file_type, template_code, template_version, storage_key,
|
||||
file_size, file_hash, row_count, export_status, export_by, export_time, remark
|
||||
) values (
|
||||
#{arBillId}, #{fileName}, #{fileType}, #{templateCode}, #{templateVersion}, #{storageKey},
|
||||
#{fileSize}, #{fileHash}, #{rowCount}, #{exportStatus}, #{exportBy}, sysdate(), #{remark}
|
||||
)
|
||||
</insert>
|
||||
</mapper>
|
||||
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.ar.mapper.ArBillLogMapper">
|
||||
<resultMap id="ArBillLogResult" type="ArBillLog">
|
||||
<id property="logId" column="log_id"/>
|
||||
<result property="arBillId" column="ar_bill_id"/>
|
||||
<result property="objectType" column="object_type"/>
|
||||
<result property="objectId" column="object_id"/>
|
||||
<result property="objectDisplay" column="object_display"/>
|
||||
<result property="operationType" column="operation_type"/>
|
||||
<result property="operationDesc" column="operation_desc"/>
|
||||
<result property="fromStatus" column="from_status"/>
|
||||
<result property="toStatus" column="to_status"/>
|
||||
<result property="beforeData" column="before_data"/>
|
||||
<result property="afterData" column="after_data"/>
|
||||
<result property="operatorId" column="operator_id"/>
|
||||
<result property="operatorName" column="operator_name"/>
|
||||
<result property="operationSource" column="operation_source"/>
|
||||
<result property="requestId" column="request_id"/>
|
||||
<result property="traceId" column="trace_id"/>
|
||||
<result property="result" column="result"/>
|
||||
<result property="errorMessage" column="error_message"/>
|
||||
<result property="clientIp" column="client_ip"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectList" parameterType="ArBillLog" resultMap="ArBillLogResult">
|
||||
select l.log_id, l.ar_bill_id, l.object_type, l.object_id, l.operation_type, l.operation_desc,
|
||||
case l.object_type
|
||||
when 'BILL' then b.bill_no
|
||||
when 'EXPORT' then coalesce(e.file_name, b.bill_no)
|
||||
when 'RECEIPT' then r.receipt_no
|
||||
when 'WRITEOFF' then w.writeoff_no
|
||||
else null
|
||||
end as object_display,
|
||||
l.from_status, l.to_status, l.before_data, l.after_data, l.operator_id, l.operator_name,
|
||||
l.operation_source, l.request_id, l.trace_id, l.result, l.error_message, l.client_ip, l.create_time
|
||||
from ar_bill_log l
|
||||
left join ar_bill b on b.ar_bill_id = l.ar_bill_id
|
||||
left join ar_bill_export e on e.export_id = case when l.object_type = 'EXPORT' then cast(l.object_id as unsigned) end
|
||||
left join ar_receipt r on r.receipt_id = case when l.object_type = 'RECEIPT' then cast(l.object_id as unsigned) end
|
||||
left join ar_receipt_writeoff w on w.writeoff_id = case when l.object_type = 'WRITEOFF' then cast(l.object_id as unsigned) end
|
||||
<where>
|
||||
<if test="arBillId != null">and l.ar_bill_id = #{arBillId}</if>
|
||||
<if test="objectType != null and objectType != ''">and l.object_type = #{objectType}</if>
|
||||
<if test="objectId != null and objectId != ''">and l.object_id = #{objectId}</if>
|
||||
<if test="operationType != null and operationType != ''">and l.operation_type = #{operationType}</if>
|
||||
<if test="requestId != null and requestId != ''">and l.request_id = #{requestId}</if>
|
||||
<if test="result != null and result != ''">and l.result = #{result}</if>
|
||||
</where>
|
||||
order by l.create_time desc, l.log_id desc
|
||||
</select>
|
||||
|
||||
<select id="selectSuccessByRequestId" resultMap="ArBillLogResult">
|
||||
select log_id, ar_bill_id, object_type, object_id, operation_type, operation_desc,
|
||||
from_status, to_status, before_data, after_data, operator_id, operator_name,
|
||||
operation_source, request_id, trace_id, result, error_message, client_ip, create_time
|
||||
from ar_bill_log
|
||||
where request_id = #{requestId} and object_type = #{objectType} and result = 'SUCCESS'
|
||||
order by log_id limit 1
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterType="ArBillLog" useGeneratedKeys="true" keyProperty="logId">
|
||||
insert into ar_bill_log (
|
||||
ar_bill_id, object_type, object_id, operation_type, operation_desc, from_status, to_status,
|
||||
before_data, after_data, operator_id, operator_name, operation_source, request_id,
|
||||
trace_id, result, error_message, client_ip, create_time
|
||||
) values (
|
||||
#{arBillId}, #{objectType}, #{objectId}, #{operationType}, #{operationDesc}, #{fromStatus}, #{toStatus},
|
||||
#{beforeData}, #{afterData}, #{operatorId}, #{operatorName}, #{operationSource}, #{requestId},
|
||||
#{traceId}, #{result}, #{errorMessage}, #{clientIp}, sysdate()
|
||||
)
|
||||
</insert>
|
||||
</mapper>
|
||||
@@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.ar.mapper.ArBillMapper">
|
||||
<resultMap id="ArBillResult" type="ArBill">
|
||||
<id property="arBillId" column="ar_bill_id"/>
|
||||
<result property="billNo" column="bill_no"/>
|
||||
<result property="billName" column="bill_name"/>
|
||||
<result property="customerId" column="customer_id"/>
|
||||
<result property="customerCode" column="customer_code"/>
|
||||
<result property="customerName" column="customer_name"/>
|
||||
<result property="billPeriodStart" column="bill_period_start"/>
|
||||
<result property="billPeriodEnd" column="bill_period_end"/>
|
||||
<result property="billDate" column="bill_date"/>
|
||||
<result property="currency" column="currency"/>
|
||||
<result property="orderCount" column="order_count"/>
|
||||
<result property="totalAmount" column="total_amount"/>
|
||||
<result property="writtenOffAmount" column="written_off_amount"/>
|
||||
<result property="unwrittenAmount" column="unwritten_amount"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="billVersion" column="bill_version"/>
|
||||
<result property="bankAccountName" column="bank_account_name"/>
|
||||
<result property="bankAccountNo" column="bank_account_no"/>
|
||||
<result property="bankName" column="bank_name"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="confirmedBy" column="confirmed_by"/>
|
||||
<result property="confirmedTime" column="confirmed_time"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateBy" column="update_by"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectArBillColumns">
|
||||
select ar_bill_id, bill_no, bill_name, customer_id, customer_code, customer_name,
|
||||
bill_period_start, bill_period_end, bill_date, currency, order_count,
|
||||
total_amount, written_off_amount, unwritten_amount, status, bill_version,
|
||||
bank_account_name, bank_account_no, bank_name, remark, confirmed_by,
|
||||
confirmed_time, create_by, create_time, update_by, update_time
|
||||
from ar_bill
|
||||
</sql>
|
||||
|
||||
<select id="selectArBillList" parameterType="ArBill" resultMap="ArBillResult">
|
||||
<include refid="selectArBillColumns"/>
|
||||
<where>
|
||||
<if test="billNo != null and billNo != ''">and bill_no like concat('%', #{billNo}, '%')</if>
|
||||
<if test="billName != null and billName != ''">and bill_name like concat('%', #{billName}, '%')</if>
|
||||
<if test="customerCode != null and customerCode != ''">and customer_code like concat('%', #{customerCode}, '%')</if>
|
||||
<if test="customerName != null and customerName != ''">and customer_name like concat('%', #{customerName}, '%')</if>
|
||||
<if test="customerId != null">and customer_id = #{customerId}</if>
|
||||
<if test="status != null and status != ''">and status = #{status}</if>
|
||||
<if test="currency != null and currency != ''">and currency = #{currency}</if>
|
||||
<if test="billPeriodStart != null">and bill_period_end >= #{billPeriodStart}</if>
|
||||
<if test="billPeriodEnd != null">and bill_period_start <= #{billPeriodEnd}</if>
|
||||
</where>
|
||||
order by create_time desc, ar_bill_id desc
|
||||
</select>
|
||||
|
||||
<select id="selectArBillById" parameterType="long" resultMap="ArBillResult">
|
||||
<include refid="selectArBillColumns"/> where ar_bill_id = #{arBillId}
|
||||
</select>
|
||||
|
||||
<select id="selectArBillByBillNo" parameterType="string" resultMap="ArBillResult">
|
||||
<include refid="selectArBillColumns"/> where bill_no = #{billNo}
|
||||
</select>
|
||||
|
||||
<select id="selectMaxBillVersion" resultType="int">
|
||||
select bill_version
|
||||
from ar_bill
|
||||
where customer_id = #{customerId}
|
||||
and bill_period_end = #{cutoffDate}
|
||||
and currency = #{currency}
|
||||
order by bill_version desc
|
||||
limit 1
|
||||
for update
|
||||
</select>
|
||||
|
||||
<select id="selectArBillByIdForUpdate" parameterType="long" resultMap="ArBillResult">
|
||||
<include refid="selectArBillColumns"/> where ar_bill_id = #{arBillId} for update
|
||||
</select>
|
||||
|
||||
<insert id="insertArBill" parameterType="ArBill" useGeneratedKeys="true" keyProperty="arBillId">
|
||||
insert into ar_bill (
|
||||
bill_no, bill_name, customer_id, customer_code, customer_name, bill_period_start, bill_period_end,
|
||||
bill_date, currency, order_count, total_amount, written_off_amount, unwritten_amount,
|
||||
status, bill_version, bank_account_name, bank_account_no, bank_name, remark,
|
||||
confirmed_by, confirmed_time, create_by, create_time, update_by, update_time
|
||||
) values (
|
||||
#{billNo}, #{billName}, #{customerId}, #{customerCode}, #{customerName}, #{billPeriodStart}, #{billPeriodEnd},
|
||||
#{billDate}, #{currency}, #{orderCount}, #{totalAmount}, #{writtenOffAmount}, #{unwrittenAmount},
|
||||
#{status}, #{billVersion}, #{bankAccountName}, #{bankAccountNo}, #{bankName}, #{remark},
|
||||
#{confirmedBy}, #{confirmedTime}, #{createBy}, sysdate(), #{updateBy}, sysdate()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateArBillStatus">
|
||||
update ar_bill
|
||||
set status = #{status}, remark = #{remark}, update_by = #{operator}, update_time = sysdate(),
|
||||
confirmed_by = case when #{status} = 'CONFIRMED' then #{operator} else confirmed_by end,
|
||||
confirmed_time = case when #{status} = 'CONFIRMED' then sysdate() else confirmed_time end
|
||||
where ar_bill_id = #{arBillId}
|
||||
and status = #{fromStatus}
|
||||
</update>
|
||||
|
||||
<update id="updateArBillAmounts">
|
||||
update ar_bill
|
||||
set written_off_amount = #{writtenOffAmount}, unwritten_amount = #{unwrittenAmount},
|
||||
status = #{status}, update_by = #{operator}, update_time = sysdate()
|
||||
where ar_bill_id = #{arBillId}
|
||||
and status = #{fromStatus}
|
||||
</update>
|
||||
</mapper>
|
||||
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.ar.mapper.ArReceiptMapper">
|
||||
<resultMap id="ArReceiptResult" type="ArReceipt">
|
||||
<id property="receiptId" column="receipt_id"/>
|
||||
<result property="receiptNo" column="receipt_no"/>
|
||||
<result property="customerId" column="customer_id"/>
|
||||
<result property="customerName" column="customer_name"/>
|
||||
<result property="customerCode" column="customer_code"/>
|
||||
<result property="receiptDate" column="receipt_date"/>
|
||||
<result property="currency" column="currency"/>
|
||||
<result property="receiptAmount" column="receipt_amount"/>
|
||||
<result property="unallocatedAmount" column="unallocated_amount"/>
|
||||
<result property="paymentMethod" column="payment_method"/>
|
||||
<result property="bankReference" column="bank_reference"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateBy" column="update_by"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectReceiptColumns">
|
||||
select r.receipt_id, r.receipt_no, r.customer_id, c.customer_name, c.customer_code,
|
||||
r.receipt_date, r.currency, r.receipt_amount, r.unallocated_amount,
|
||||
r.payment_method, r.bank_reference, r.status, r.remark,
|
||||
r.create_by, r.create_time, r.update_by, r.update_time
|
||||
from ar_receipt r
|
||||
left join bms_customer c on c.customer_id = r.customer_id and c.del_flag = '0'
|
||||
</sql>
|
||||
|
||||
<!-- 加锁更新只读取收款主表,避免锁定展示关联表。 -->
|
||||
<sql id="selectReceiptLockColumns">
|
||||
select receipt_id, receipt_no, customer_id, receipt_date, currency, receipt_amount,
|
||||
unallocated_amount, payment_method, bank_reference, status, remark,
|
||||
create_by, create_time, update_by, update_time
|
||||
from ar_receipt
|
||||
</sql>
|
||||
|
||||
<select id="selectArReceiptList" parameterType="com.ruoyi.ar.domain.vo.ArReceiptQuery" resultMap="ArReceiptResult">
|
||||
<include refid="selectReceiptColumns"/>
|
||||
<where>
|
||||
<if test="receiptNo != null and receiptNo != ''">and r.receipt_no like concat('%', #{receiptNo}, '%')</if>
|
||||
<if test="customerId != null">and r.customer_id = #{customerId}</if>
|
||||
<if test="currency != null and currency != ''">and r.currency = #{currency}</if>
|
||||
<if test="paymentMethod != null and paymentMethod != ''">and r.payment_method = #{paymentMethod}</if>
|
||||
<if test="status != null and status != ''">and r.status = #{status}</if>
|
||||
<if test="receiptDateStart != null">and r.receipt_date >= #{receiptDateStart}</if>
|
||||
<if test="receiptDateEnd != null">and r.receipt_date <= #{receiptDateEnd}</if>
|
||||
</where>
|
||||
order by r.receipt_date desc, r.receipt_id desc
|
||||
</select>
|
||||
|
||||
<select id="selectArReceiptById" parameterType="long" resultMap="ArReceiptResult">
|
||||
<include refid="selectReceiptColumns"/> where r.receipt_id = #{receiptId}
|
||||
</select>
|
||||
|
||||
<select id="selectArReceiptByIdForUpdate" parameterType="long" resultMap="ArReceiptResult">
|
||||
<include refid="selectReceiptLockColumns"/> where receipt_id = #{receiptId} for update
|
||||
</select>
|
||||
|
||||
<select id="selectArReceiptByNo" parameterType="string" resultMap="ArReceiptResult">
|
||||
<include refid="selectReceiptColumns"/> where r.receipt_no = #{receiptNo}
|
||||
</select>
|
||||
|
||||
<select id="selectArReceiptByReference" resultMap="ArReceiptResult">
|
||||
<include refid="selectReceiptColumns"/>
|
||||
where r.payment_method = #{paymentMethod} and r.bank_reference = #{bankReference}
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<insert id="insertArReceipt" parameterType="ArReceipt" useGeneratedKeys="true" keyProperty="receiptId">
|
||||
insert into ar_receipt (
|
||||
receipt_no, customer_id, receipt_date, currency, receipt_amount, unallocated_amount,
|
||||
payment_method, bank_reference, status, remark, create_by, create_time, update_by, update_time
|
||||
) values (
|
||||
#{receiptNo}, #{customerId}, #{receiptDate}, #{currency}, #{receiptAmount}, #{unallocatedAmount},
|
||||
#{paymentMethod}, #{bankReference}, #{status}, #{remark}, #{createBy}, sysdate(), #{updateBy}, sysdate()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateBalanceAndStatus">
|
||||
update ar_receipt
|
||||
set unallocated_amount = #{unallocatedAmount}, status = #{status},
|
||||
update_by = #{operator}, update_time = sysdate()
|
||||
where receipt_id = #{receiptId}
|
||||
</update>
|
||||
|
||||
<update id="updateCancelled">
|
||||
update ar_receipt set status = 'CANCELLED', remark = #{remark},
|
||||
update_by = #{operator}, update_time = sysdate()
|
||||
where receipt_id = #{receiptId}
|
||||
</update>
|
||||
</mapper>
|
||||
@@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.ar.mapper.ArReceiptWriteoffMapper">
|
||||
<resultMap id="ArReceiptWriteoffResult" type="ArReceiptWriteoff">
|
||||
<id property="writeoffId" column="writeoff_id"/>
|
||||
<result property="receiptId" column="receipt_id"/>
|
||||
<result property="arBillId" column="ar_bill_id"/>
|
||||
<result property="billNo" column="bill_no"/>
|
||||
<result property="receiptNo" column="receipt_no"/>
|
||||
<result property="writeoffNo" column="writeoff_no"/>
|
||||
<result property="writeoffAmount" column="writeoff_amount"/>
|
||||
<result property="currency" column="currency"/>
|
||||
<result property="writeoffDate" column="writeoff_date"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="cancelBy" column="cancel_by"/>
|
||||
<result property="cancelTime" column="cancel_time"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectWriteoffColumns">
|
||||
select w.writeoff_id, w.receipt_id, w.ar_bill_id, b.bill_no, r.receipt_no,
|
||||
w.writeoff_no, w.writeoff_amount, w.currency, w.writeoff_date, w.status,
|
||||
w.remark, w.create_by, w.create_time, w.cancel_by, w.cancel_time
|
||||
from ar_receipt_writeoff w
|
||||
left join ar_bill b on b.ar_bill_id = w.ar_bill_id
|
||||
left join ar_receipt r on r.receipt_id = w.receipt_id
|
||||
</sql>
|
||||
|
||||
<!-- 加锁更新只读取核销关系主表,展示字段不参与锁定。 -->
|
||||
<sql id="selectWriteoffLockColumns">
|
||||
select writeoff_id, receipt_id, ar_bill_id, writeoff_no, writeoff_amount, currency,
|
||||
writeoff_date, status, remark, create_by, create_time, cancel_by, cancel_time
|
||||
from ar_receipt_writeoff
|
||||
</sql>
|
||||
|
||||
<select id="selectByBillId" parameterType="long" resultMap="ArReceiptWriteoffResult">
|
||||
<include refid="selectWriteoffColumns"/> where w.ar_bill_id = #{arBillId} order by w.create_time desc
|
||||
</select>
|
||||
|
||||
<select id="selectByReceiptId" parameterType="long" resultMap="ArReceiptWriteoffResult">
|
||||
<include refid="selectWriteoffColumns"/> where w.receipt_id = #{receiptId} order by w.create_time desc
|
||||
</select>
|
||||
|
||||
<select id="selectById" parameterType="long" resultMap="ArReceiptWriteoffResult">
|
||||
<include refid="selectWriteoffColumns"/> where w.writeoff_id = #{writeoffId}
|
||||
</select>
|
||||
|
||||
<select id="selectByIdForUpdate" parameterType="long" resultMap="ArReceiptWriteoffResult">
|
||||
<include refid="selectWriteoffLockColumns"/> where writeoff_id = #{writeoffId} for update
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterType="ArReceiptWriteoff" useGeneratedKeys="true" keyProperty="writeoffId">
|
||||
insert into ar_receipt_writeoff (
|
||||
receipt_id, ar_bill_id, writeoff_no, writeoff_amount, currency, writeoff_date,
|
||||
status, remark, create_by, create_time
|
||||
) values (
|
||||
#{receiptId}, #{arBillId}, #{writeoffNo}, #{writeoffAmount}, #{currency}, #{writeoffDate},
|
||||
#{status}, #{remark}, #{createBy}, sysdate()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateCancelled">
|
||||
update ar_receipt_writeoff
|
||||
set status = 'CANCELLED', remark = #{remark}, cancel_by = #{cancelBy}, cancel_time = sysdate()
|
||||
where writeoff_id = #{writeoffId} and status = 'EFFECTIVE'
|
||||
</update>
|
||||
|
||||
<select id="sumEffectiveByBillId" parameterType="long" resultType="java.math.BigDecimal">
|
||||
select coalesce(sum(writeoff_amount), 0) from ar_receipt_writeoff
|
||||
where ar_bill_id = #{arBillId} and status = 'EFFECTIVE'
|
||||
</select>
|
||||
|
||||
<select id="sumEffectiveByReceiptId" parameterType="long" resultType="java.math.BigDecimal">
|
||||
select coalesce(sum(writeoff_amount), 0) from ar_receipt_writeoff
|
||||
where receipt_id = #{receiptId} and status = 'EFFECTIVE'
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -24,6 +24,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
order by field(fee_type, 'BASIC_FREIGHT', 'REGISTRATION_FEE', 'SURCHARGE'), fee_id
|
||||
</select>
|
||||
|
||||
<select id="selectByBillIds" resultMap="PrcIncomeBillFeeResult">
|
||||
select fee_id, bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark, create_time
|
||||
from prc_income_bill_fee
|
||||
where bill_id in
|
||||
<foreach collection="billIds" item="billId" open="(" separator="," close=")">
|
||||
#{billId}
|
||||
</foreach>
|
||||
order by bill_id, field(fee_type, 'BASIC_FREIGHT', 'REGISTRATION_FEE', 'SURCHARGE'), fee_id
|
||||
</select>
|
||||
|
||||
<insert id="insertPrcIncomeBillFee" parameterType="PrcIncomeBillFee">
|
||||
insert into prc_income_bill_fee (
|
||||
fee_id, bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark, create_time
|
||||
|
||||
@@ -28,7 +28,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="productCode" column="product_code" />
|
||||
<result property="productName" column="product_name" />
|
||||
<result property="countryCode" column="country_code" />
|
||||
<result property="countryName" column="country_name" />
|
||||
<result property="countryName" column="country_name" />
|
||||
<result property="weight" column="weight" />
|
||||
<result property="bizDate" column="biz_date" />
|
||||
<result property="tmsId" column="tms_id" />
|
||||
@@ -55,6 +55,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="incomeTotal" column="income_total" />
|
||||
<result property="payTotal" column="pay_total" />
|
||||
<result property="currency" column="currency" />
|
||||
<result property="arBillStatus" column="ar_bill_status" />
|
||||
<result property="roundMode" column="round_mode" />
|
||||
<result property="createBy" column="created_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
@@ -71,7 +72,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
set_type, bind_priority, customer_priceset_id, product_country_id,
|
||||
weight_min, weight_max, weight_label, effective_date, charge_weight,
|
||||
freight_rate, base_freight_rate, discount_percent, registration_fee,
|
||||
freight_amount, surcharge_amount, income_total, pay_total, currency, round_mode,
|
||||
freight_amount, surcharge_amount, income_total, pay_total, currency, ar_bill_status, round_mode,
|
||||
created_by, create_time, updated_by, update_time
|
||||
from prc_order_bill
|
||||
</sql>
|
||||
@@ -123,6 +124,39 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
order by create_time desc, bill_id desc
|
||||
</select>
|
||||
|
||||
<!-- The repository's rename_bill_tables.sql maps the requirement's prc_bill to prc_order_bill. -->
|
||||
<select id="selectPrcOrderBillByCustomerPeriod" resultMap="PrcOrderBillResult">
|
||||
<include refid="selectPrcOrderBillVo"/>
|
||||
where customer_id = #{customerId}
|
||||
<if test="customerCode != null and customerCode != ''">
|
||||
and customer_code like concat('%', #{customerCode}, '%')
|
||||
</if>
|
||||
and biz_date <= #{cutoffDate}
|
||||
and ar_bill_status = #{arBillStatus}
|
||||
<if test="currency != null and currency != ''">
|
||||
and currency = #{currency}
|
||||
</if>
|
||||
order by biz_date, order_no, bill_id
|
||||
</select>
|
||||
|
||||
<select id="selectPrcOrderBillByCustomerCutoffAndIds" resultMap="PrcOrderBillResult">
|
||||
<include refid="selectPrcOrderBillVo"/>
|
||||
where customer_id = #{customerId}
|
||||
and biz_date <= #{cutoffDate}
|
||||
and ar_bill_status = #{arBillStatus}
|
||||
and bill_id in
|
||||
<foreach collection="billIds" item="billId" open="(" separator="," close=")">
|
||||
#{billId}
|
||||
</foreach>
|
||||
<if test="customerCode != null and customerCode != ''">
|
||||
and customer_code like concat('%', #{customerCode}, '%')
|
||||
</if>
|
||||
<if test="currency != null and currency != ''">
|
||||
and currency = #{currency}
|
||||
</if>
|
||||
order by biz_date, order_no, bill_id
|
||||
</select>
|
||||
|
||||
<insert id="insertPrcOrderBill" parameterType="PrcOrderBill">
|
||||
insert into prc_order_bill (
|
||||
bill_id, bill_key, order_no, tracking_no, customer_id, customer_code, customer_name,
|
||||
@@ -172,7 +206,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
product_code = #{productCode},
|
||||
product_name = #{productName},
|
||||
country_code = #{countryCode},
|
||||
country_name = #{countryName},
|
||||
country_name = #{countryName},
|
||||
|
||||
|
||||
weight = #{weight},
|
||||
biz_date = #{bizDate},
|
||||
tms_id = #{tmsId},
|
||||
@@ -212,4 +248,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
update_time = sysdate()
|
||||
where bill_id = #{billId}
|
||||
</update>
|
||||
|
||||
<update id="claimArBillOrders">
|
||||
update prc_order_bill
|
||||
set ar_bill_status = #{toStatus}, updated_by = #{operator}, update_time = sysdate()
|
||||
where ar_bill_status = #{fromStatus}
|
||||
and bill_id in
|
||||
<foreach collection="billIds" item="billId" open="(" separator="," close=")">
|
||||
#{billId}
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
<update id="releaseArBillOrders">
|
||||
update prc_order_bill
|
||||
set ar_bill_status = #{toStatus}, updated_by = #{operator}, update_time = sysdate()
|
||||
where ar_bill_status = #{fromStatus}
|
||||
and bill_id in
|
||||
<foreach collection="billIds" item="billId" open="(" separator="," close=")">
|
||||
#{billId}
|
||||
</foreach>
|
||||
</update>
|
||||
</mapper>
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
-- 公司收款账户配置。账单生成时按币种自动选择默认账户,并将其快照保存到 ar_bill。
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ar_bank_account (
|
||||
bank_account_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '收款账户ID',
|
||||
account_name VARCHAR(100) NOT NULL COMMENT '开户名',
|
||||
account_no VARCHAR(128) NOT NULL COMMENT '银行账号,应用层脱敏展示',
|
||||
bank_name VARCHAR(100) NOT NULL COMMENT '开户行',
|
||||
currency VARCHAR(10) NOT NULL DEFAULT 'CNY' COMMENT '币种',
|
||||
is_default CHAR(1) NOT NULL DEFAULT '0' COMMENT '是否默认账户,1是0否',
|
||||
status CHAR(1) NOT NULL DEFAULT '0' COMMENT '状态,0正常1停用',
|
||||
remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
|
||||
create_by VARCHAR(64) NOT NULL DEFAULT 'admin' COMMENT '创建人',
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人',
|
||||
update_time DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (bank_account_id),
|
||||
UNIQUE KEY uk_ar_bank_account_no_currency (account_no, currency),
|
||||
KEY idx_ar_bank_account_currency (currency, status, is_default)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='公司收款账户配置';
|
||||
|
||||
-- 图片提供的公司人民币收款账户。账号去除空格保存,展示时按系统安全规则脱敏。
|
||||
INSERT INTO ar_bank_account (
|
||||
account_name, account_no, bank_name, currency, is_default, status, remark
|
||||
)
|
||||
SELECT
|
||||
N'威狮国际供应链(深圳)有限公司', '86011110001370504', N'宁波银行深圳后海支行',
|
||||
'CNY', '1', '0', N'公司人民币默认收款账户'
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM ar_bank_account
|
||||
WHERE account_no = '86011110001370504' AND currency = 'CNY'
|
||||
);
|
||||
@@ -0,0 +1,193 @@
|
||||
-- BMS 应收 V1.0
|
||||
--
|
||||
-- 需求文档中的来源表 prc_bill / prc_bill_fee 在本项目已经由
|
||||
-- sql/price/rename_bill_tables.sql 迁移为 prc_order_bill / prc_income_bill_fee;
|
||||
-- 本脚本只创建正式应收表,不改动来源表及其 VARCHAR(36) 主键。
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ar_bill (
|
||||
ar_bill_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '应收账单ID',
|
||||
bill_no VARCHAR(50) NOT NULL COMMENT '账单号',
|
||||
bill_name VARCHAR(100) NOT NULL COMMENT '账单名称',
|
||||
customer_id BIGINT(20) NOT NULL COMMENT '客户ID',
|
||||
customer_code VARCHAR(50) NOT NULL COMMENT '客户编码快照',
|
||||
customer_name VARCHAR(100) NOT NULL COMMENT '客户名称快照',
|
||||
bill_period_start DATE NOT NULL COMMENT '账期开始日',
|
||||
bill_period_end DATE NOT NULL COMMENT '账期结束日',
|
||||
bill_date DATE NOT NULL COMMENT '账单出具日期',
|
||||
currency VARCHAR(10) NOT NULL DEFAULT 'CNY' COMMENT '币种',
|
||||
order_count INT(11) NOT NULL DEFAULT 0 COMMENT '订单数量',
|
||||
total_amount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '账单应收总额',
|
||||
written_off_amount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '已核销金额缓存',
|
||||
unwritten_amount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '未核销金额缓存',
|
||||
status VARCHAR(30) NOT NULL COMMENT 'DRAFT/CONFIRMED/PARTIAL_WRITTEN_OFF/WRITTEN_OFF/VOID',
|
||||
bill_version INT(11) NOT NULL DEFAULT 1 COMMENT '账单版本',
|
||||
bank_account_name VARCHAR(100) DEFAULT NULL COMMENT '收款账户名称快照',
|
||||
bank_account_no VARCHAR(128) DEFAULT NULL COMMENT '收款账号快照,应用层脱敏展示',
|
||||
bank_name VARCHAR(100) DEFAULT NULL COMMENT '开户行快照',
|
||||
remark VARCHAR(500) DEFAULT NULL COMMENT '备注或作废原因',
|
||||
confirmed_by VARCHAR(64) DEFAULT NULL COMMENT '确认人',
|
||||
confirmed_time DATETIME DEFAULT NULL COMMENT '确认时间',
|
||||
create_by VARCHAR(64) NOT NULL COMMENT '创建人',
|
||||
create_time DATETIME NOT NULL COMMENT '创建时间',
|
||||
update_by VARCHAR(64) NOT NULL COMMENT '更新人',
|
||||
update_time DATETIME NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (ar_bill_id),
|
||||
UNIQUE KEY uk_ar_bill_no (bill_no),
|
||||
UNIQUE KEY uk_ar_bill_customer_cutoff_version (customer_id, currency, bill_period_end, bill_version),
|
||||
KEY idx_ar_bill_customer_cutoff (customer_id, currency, bill_period_end),
|
||||
KEY idx_ar_bill_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='正式应收账单主表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ar_bill_detail (
|
||||
detail_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '账单订单明细ID',
|
||||
ar_bill_id BIGINT(20) NOT NULL COMMENT '应收账单ID',
|
||||
prc_bill_id VARCHAR(36) NOT NULL COMMENT '来源订单计费ID,保持来源类型',
|
||||
detail_no INT(11) NOT NULL COMMENT '账单内顺序号',
|
||||
order_no VARCHAR(64) NOT NULL COMMENT '订单号快照',
|
||||
tracking_no VARCHAR(64) DEFAULT NULL COMMENT '运单号快照',
|
||||
receive_time DATETIME DEFAULT NULL COMMENT '收件或入库时间',
|
||||
product_code VARCHAR(50) NOT NULL COMMENT '产品代码快照',
|
||||
product_name VARCHAR(100) DEFAULT NULL COMMENT '产品名称快照',
|
||||
goods_description VARCHAR(500) DEFAULT NULL COMMENT '货物描述快照',
|
||||
country_code CHAR(2) DEFAULT NULL COMMENT '目的国家二字码',
|
||||
country_name VARCHAR(50) DEFAULT NULL COMMENT '目的国家名称',
|
||||
dest_postcode VARCHAR(20) DEFAULT NULL COMMENT '目的邮编',
|
||||
actual_weight DECIMAL(12,3) DEFAULT NULL COMMENT '实际重量KG',
|
||||
volume_weight DECIMAL(12,3) DEFAULT NULL COMMENT '材积重量KG',
|
||||
charge_weight DECIMAL(12,3) DEFAULT NULL COMMENT '计费重量KG',
|
||||
pkg_length DECIMAL(10,2) DEFAULT NULL COMMENT '长度CM',
|
||||
pkg_width DECIMAL(10,2) DEFAULT NULL COMMENT '宽度CM',
|
||||
pkg_height DECIMAL(10,2) DEFAULT NULL COMMENT '高度CM',
|
||||
currency VARCHAR(10) NOT NULL COMMENT '币种快照',
|
||||
total_amount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '订单费用合计',
|
||||
remark VARCHAR(500) DEFAULT NULL COMMENT '订单账单备注',
|
||||
create_by VARCHAR(64) NOT NULL COMMENT '创建人',
|
||||
create_time DATETIME NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (detail_id),
|
||||
UNIQUE KEY uk_ar_bill_detail_source (ar_bill_id, prc_bill_id),
|
||||
UNIQUE KEY uk_ar_bill_detail_no (ar_bill_id, detail_no),
|
||||
KEY idx_ar_bill_detail_source (prc_bill_id),
|
||||
CONSTRAINT fk_ar_bill_detail_bill FOREIGN KEY (ar_bill_id) REFERENCES ar_bill (ar_bill_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='应收账单订单快照';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ar_bill_detail_fee (
|
||||
detail_fee_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '账单费用明细ID',
|
||||
detail_id BIGINT(20) NOT NULL COMMENT '账单订单明细ID',
|
||||
prc_fee_id VARCHAR(36) NOT NULL COMMENT '来源费用ID,保持来源类型',
|
||||
fee_no INT(11) NOT NULL COMMENT '订单内费用顺序号',
|
||||
fee_type VARCHAR(30) NOT NULL COMMENT '费用类型快照',
|
||||
fee_name VARCHAR(100) DEFAULT NULL COMMENT '费用名称快照',
|
||||
sc_id BIGINT(20) DEFAULT NULL COMMENT '附加费配置ID快照',
|
||||
quantity DECIMAL(12,3) DEFAULT NULL COMMENT '计费数量',
|
||||
unit VARCHAR(20) DEFAULT NULL COMMENT '计费单位',
|
||||
unit_price DECIMAL(15,4) DEFAULT NULL COMMENT '费用单价',
|
||||
amount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '费用金额',
|
||||
currency VARCHAR(10) NOT NULL COMMENT '币种',
|
||||
calculation_formula VARCHAR(500) DEFAULT NULL COMMENT '计算公式或说明',
|
||||
round_mode VARCHAR(20) DEFAULT NULL COMMENT '取舍规则',
|
||||
remark VARCHAR(500) DEFAULT NULL COMMENT '费用备注',
|
||||
create_by VARCHAR(64) NOT NULL COMMENT '创建人',
|
||||
create_time DATETIME NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (detail_fee_id),
|
||||
UNIQUE KEY uk_ar_detail_prc_fee (detail_id, prc_fee_id),
|
||||
KEY idx_ar_detail_fee_type (fee_type),
|
||||
KEY idx_ar_detail_prc_fee (prc_fee_id),
|
||||
KEY idx_ar_detail_fee_order (detail_id, fee_no, detail_fee_id),
|
||||
CONSTRAINT fk_ar_detail_fee_detail FOREIGN KEY (detail_id) REFERENCES ar_bill_detail (detail_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='应收账单费用快照';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ar_receipt (
|
||||
receipt_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '收款ID',
|
||||
receipt_no VARCHAR(50) NOT NULL COMMENT '收款单号',
|
||||
customer_id BIGINT(20) NOT NULL COMMENT '付款客户ID',
|
||||
receipt_date DATE NOT NULL COMMENT '到账日期',
|
||||
currency VARCHAR(10) NOT NULL DEFAULT 'CNY' COMMENT '币种',
|
||||
receipt_amount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '到账总额',
|
||||
unallocated_amount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '未分配金额缓存',
|
||||
payment_method VARCHAR(30) NOT NULL COMMENT '到账方式',
|
||||
bank_reference VARCHAR(100) DEFAULT NULL COMMENT '银行流水号或第三方交易号',
|
||||
status VARCHAR(30) NOT NULL COMMENT 'REGISTERED/PARTIAL_WRITTEN_OFF/WRITTEN_OFF/CANCELLED',
|
||||
remark VARCHAR(500) DEFAULT NULL COMMENT '收款备注或取消原因',
|
||||
create_by VARCHAR(64) NOT NULL COMMENT '创建人',
|
||||
create_time DATETIME NOT NULL COMMENT '创建时间',
|
||||
update_by VARCHAR(64) NOT NULL COMMENT '更新人',
|
||||
update_time DATETIME NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (receipt_id),
|
||||
UNIQUE KEY uk_ar_receipt_no (receipt_no),
|
||||
UNIQUE KEY uk_ar_payment_reference (payment_method, bank_reference),
|
||||
KEY idx_ar_receipt_customer_date (customer_id, receipt_date),
|
||||
KEY idx_ar_receipt_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='应收收款表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ar_receipt_writeoff (
|
||||
writeoff_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '核销ID',
|
||||
receipt_id BIGINT(20) NOT NULL COMMENT '收款ID',
|
||||
ar_bill_id BIGINT(20) NOT NULL COMMENT '应收账单ID',
|
||||
writeoff_no VARCHAR(50) NOT NULL COMMENT '核销单号',
|
||||
writeoff_amount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '本次核销金额',
|
||||
currency VARCHAR(10) NOT NULL COMMENT '币种',
|
||||
writeoff_date DATE NOT NULL COMMENT '核销日期',
|
||||
status VARCHAR(30) NOT NULL COMMENT 'EFFECTIVE/CANCELLED',
|
||||
remark VARCHAR(500) DEFAULT NULL COMMENT '核销备注或取消原因',
|
||||
create_by VARCHAR(64) NOT NULL COMMENT '创建人',
|
||||
create_time DATETIME NOT NULL COMMENT '创建时间',
|
||||
cancel_by VARCHAR(64) DEFAULT NULL COMMENT '取消人',
|
||||
cancel_time DATETIME DEFAULT NULL COMMENT '取消时间',
|
||||
PRIMARY KEY (writeoff_id),
|
||||
UNIQUE KEY uk_ar_writeoff_no (writeoff_no),
|
||||
KEY idx_ar_writeoff_receipt (receipt_id, status),
|
||||
KEY idx_ar_writeoff_bill (ar_bill_id, status),
|
||||
CONSTRAINT fk_ar_writeoff_receipt FOREIGN KEY (receipt_id) REFERENCES ar_receipt (receipt_id),
|
||||
CONSTRAINT fk_ar_writeoff_bill FOREIGN KEY (ar_bill_id) REFERENCES ar_bill (ar_bill_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='应收收款核销表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ar_bill_export (
|
||||
export_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '导出ID',
|
||||
ar_bill_id BIGINT(20) NOT NULL COMMENT '应收账单ID',
|
||||
file_name VARCHAR(255) NOT NULL COMMENT '下载展示名称',
|
||||
file_type VARCHAR(20) NOT NULL COMMENT 'XLSX/PDF',
|
||||
template_code VARCHAR(50) NOT NULL COMMENT '导出模板标识',
|
||||
template_version VARCHAR(50) DEFAULT NULL COMMENT '模板版本',
|
||||
storage_key VARCHAR(500) NOT NULL COMMENT '对象存储或本地文件Key',
|
||||
file_size BIGINT(20) DEFAULT NULL COMMENT '文件字节数',
|
||||
file_hash VARCHAR(128) DEFAULT NULL COMMENT 'SHA-256校验值',
|
||||
row_count INT(11) DEFAULT NULL COMMENT '导出订单行数',
|
||||
export_status VARCHAR(30) NOT NULL COMMENT 'PROCESSING/SUCCESS/FAILED',
|
||||
export_by VARCHAR(64) NOT NULL COMMENT '导出人',
|
||||
export_time DATETIME NOT NULL COMMENT '导出时间',
|
||||
remark VARCHAR(500) DEFAULT NULL COMMENT '失败原因或备注',
|
||||
PRIMARY KEY (export_id),
|
||||
KEY idx_ar_bill_export_bill (ar_bill_id, export_time),
|
||||
KEY idx_ar_bill_export_status (export_status),
|
||||
CONSTRAINT fk_ar_bill_export_bill FOREIGN KEY (ar_bill_id) REFERENCES ar_bill (ar_bill_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='应收账单导出文件元数据';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ar_bill_log (
|
||||
log_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '日志ID',
|
||||
ar_bill_id BIGINT(20) DEFAULT NULL COMMENT '账单ID',
|
||||
object_type VARCHAR(40) NOT NULL COMMENT 'BILL/RECEIPT/WRITEOFF/EXPORT',
|
||||
object_id VARCHAR(64) NOT NULL COMMENT '通用对象ID',
|
||||
operation_type VARCHAR(40) NOT NULL COMMENT '操作类型',
|
||||
operation_desc VARCHAR(500) DEFAULT NULL COMMENT '操作说明',
|
||||
from_status VARCHAR(30) DEFAULT NULL COMMENT '变更前状态',
|
||||
to_status VARCHAR(30) DEFAULT NULL COMMENT '变更后状态',
|
||||
before_data TEXT COMMENT '变更前安全快照',
|
||||
after_data TEXT COMMENT '变更后安全快照',
|
||||
operator_id VARCHAR(64) NOT NULL COMMENT '操作人ID',
|
||||
operator_name VARCHAR(100) DEFAULT NULL COMMENT '操作人名称',
|
||||
operation_source VARCHAR(30) NOT NULL COMMENT 'WEB/API/JOB/IMPORT',
|
||||
request_id VARCHAR(64) DEFAULT NULL COMMENT '请求幂等标识',
|
||||
trace_id VARCHAR(64) DEFAULT NULL COMMENT '链路标识',
|
||||
result VARCHAR(20) NOT NULL COMMENT 'SUCCESS/FAILED',
|
||||
error_message VARCHAR(1000) DEFAULT NULL COMMENT '失败原因',
|
||||
client_ip VARCHAR(64) DEFAULT NULL COMMENT '来源IP',
|
||||
create_time DATETIME NOT NULL COMMENT '日志时间',
|
||||
PRIMARY KEY (log_id),
|
||||
KEY idx_ar_bill_log_bill (ar_bill_id, create_time),
|
||||
KEY idx_ar_bill_log_object (object_type, object_id, create_time),
|
||||
KEY idx_ar_bill_log_request (request_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='应收账单操作日志';
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
@@ -0,0 +1,142 @@
|
||||
-- 应收账单:订单占用状态 + 单日期截止日迁移
|
||||
-- 适用于已经执行过 ar/ar_bill.sql、price/bill.sql 的数据库。
|
||||
-- 请在业务低峰期执行;本脚本只做字段、索引和历史状态初始化,不删除账单明细。
|
||||
SET NAMES utf8mb4;
|
||||
SET @db_name := DATABASE();
|
||||
|
||||
-- 1. 给正式账单增加账单名称。历史账单使用账单号生成默认名称。
|
||||
SET @has_bill_name := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db_name
|
||||
AND TABLE_NAME = 'ar_bill'
|
||||
AND COLUMN_NAME = 'bill_name'
|
||||
);
|
||||
SET @sql := IF(@has_bill_name = 0,
|
||||
'ALTER TABLE ar_bill ADD COLUMN bill_name VARCHAR(100) NULL COMMENT ''账单名称'' AFTER bill_no',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
UPDATE ar_bill
|
||||
SET bill_name = CONCAT('应收账单-', bill_no)
|
||||
WHERE bill_name IS NULL OR TRIM(bill_name) = '';
|
||||
|
||||
SET @sql := 'ALTER TABLE ar_bill MODIFY COLUMN bill_name VARCHAR(100) NOT NULL COMMENT ''账单名称''';
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 2. 给来源计费订单增加应收账单占用状态。
|
||||
SET @has_column := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db_name
|
||||
AND TABLE_NAME = 'prc_order_bill'
|
||||
AND COLUMN_NAME = 'ar_bill_status'
|
||||
);
|
||||
SET @sql := IF(@has_column = 0,
|
||||
'ALTER TABLE prc_order_bill ADD COLUMN ar_bill_status VARCHAR(20) NOT NULL DEFAULT ''UNBILLED'' COMMENT ''应收账单占用状态:UNBILLED/BILLED'' AFTER currency',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @has_index := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = @db_name
|
||||
AND TABLE_NAME = 'prc_order_bill'
|
||||
AND INDEX_NAME = 'idx_bill_ar_status'
|
||||
);
|
||||
SET @sql := IF(@has_index = 0,
|
||||
'ALTER TABLE prc_order_bill ADD INDEX idx_bill_ar_status (customer_id, currency, ar_bill_status, biz_date)',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 3. 根据已有账单明细回填历史订单状态。作废账单不占用订单。
|
||||
UPDATE prc_order_bill o
|
||||
JOIN (
|
||||
SELECT DISTINCT d.prc_bill_id
|
||||
FROM ar_bill_detail d
|
||||
JOIN ar_bill b ON b.ar_bill_id = d.ar_bill_id
|
||||
WHERE b.status <> 'VOID'
|
||||
) billed ON billed.prc_bill_id = o.bill_id
|
||||
SET o.ar_bill_status = 'BILLED'
|
||||
WHERE COALESCE(o.ar_bill_status, 'UNBILLED') <> 'BILLED';
|
||||
|
||||
-- 4. 账单唯一性改为“客户 + 币种 + 截止日 + 版本”。
|
||||
-- 同一截止日的作废账单保留历史,新账单使用递增版本号。
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp_ar_bill_versions;
|
||||
CREATE TEMPORARY TABLE tmp_ar_bill_versions AS
|
||||
SELECT ar_bill_id,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY customer_id, currency, bill_period_end
|
||||
ORDER BY create_time, ar_bill_id
|
||||
) AS version_no
|
||||
FROM ar_bill;
|
||||
|
||||
UPDATE ar_bill a
|
||||
JOIN tmp_ar_bill_versions v ON v.ar_bill_id = a.ar_bill_id
|
||||
SET a.bill_version = v.version_no;
|
||||
DROP TEMPORARY TABLE tmp_ar_bill_versions;
|
||||
|
||||
SET @has_old_unique := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = @db_name
|
||||
AND TABLE_NAME = 'ar_bill'
|
||||
AND INDEX_NAME = 'uk_ar_bill_customer_period'
|
||||
);
|
||||
SET @sql := IF(@has_old_unique > 0,
|
||||
'ALTER TABLE ar_bill DROP INDEX uk_ar_bill_customer_period',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @has_new_unique := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = @db_name
|
||||
AND TABLE_NAME = 'ar_bill'
|
||||
AND INDEX_NAME = 'uk_ar_bill_customer_cutoff_version'
|
||||
);
|
||||
SET @sql := IF(@has_new_unique = 0,
|
||||
'ALTER TABLE ar_bill ADD UNIQUE KEY uk_ar_bill_customer_cutoff_version (customer_id, currency, bill_period_end, bill_version)',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @has_cutoff_index := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = @db_name
|
||||
AND TABLE_NAME = 'ar_bill'
|
||||
AND INDEX_NAME = 'idx_ar_bill_customer_cutoff'
|
||||
);
|
||||
SET @sql := IF(@has_cutoff_index = 0,
|
||||
'ALTER TABLE ar_bill ADD INDEX idx_ar_bill_customer_cutoff (customer_id, currency, bill_period_end)',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 5. 详情页按账单加载费用时使用的排序索引。可重复执行,不会重复创建。
|
||||
SET @has_detail_fee_index := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = @db_name
|
||||
AND TABLE_NAME = 'ar_bill_detail_fee'
|
||||
AND INDEX_NAME = 'idx_ar_detail_fee_order'
|
||||
);
|
||||
SET @sql := IF(@has_detail_fee_index = 0,
|
||||
'ALTER TABLE ar_bill_detail_fee ADD INDEX idx_ar_detail_fee_order (detail_id, fee_no, detail_fee_id)',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
@@ -0,0 +1,84 @@
|
||||
-- 应收模块运行期优化脚本(不回填历史数据)
|
||||
--
|
||||
-- 用于已经存在业务表的环境。脚本可重复执行:字段或索引已存在时自动跳过,
|
||||
-- 不会修改历史订单的 ar_bill_status,也不会重算历史账单版本。
|
||||
SET NAMES utf8mb4;
|
||||
SET @db_name := DATABASE();
|
||||
|
||||
-- 1. 账单唯一性调整。
|
||||
-- 旧版本 uk_ar_bill_customer_period 会禁止同一客户同一账期再次生成账单,
|
||||
-- 与 bill_version 版本化方案冲突,必须删除。
|
||||
SET @has_old_bill_unique := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = @db_name
|
||||
AND TABLE_NAME = 'ar_bill'
|
||||
AND INDEX_NAME = 'uk_ar_bill_customer_period'
|
||||
);
|
||||
SET @sql := IF(@has_old_bill_unique > 0,
|
||||
'ALTER TABLE ar_bill DROP INDEX uk_ar_bill_customer_period',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 新唯一键要求已有数据的 bill_version 已正确区分。
|
||||
-- 如果这里创建失败,请先执行 ar_bill_cutoff_status_migration.sql 回填历史版本。
|
||||
SET @has_bill_version_unique := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = @db_name
|
||||
AND TABLE_NAME = 'ar_bill'
|
||||
AND INDEX_NAME = 'uk_ar_bill_customer_cutoff_version'
|
||||
);
|
||||
SET @sql := IF(@has_bill_version_unique = 0,
|
||||
'ALTER TABLE ar_bill ADD UNIQUE KEY uk_ar_bill_customer_cutoff_version (customer_id, currency, bill_period_end, bill_version)',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 2. 计费订单应收账单占用状态。
|
||||
SET @has_ar_bill_status := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db_name
|
||||
AND TABLE_NAME = 'prc_order_bill'
|
||||
AND COLUMN_NAME = 'ar_bill_status'
|
||||
);
|
||||
SET @sql := IF(@has_ar_bill_status = 0,
|
||||
'ALTER TABLE prc_order_bill ADD COLUMN ar_bill_status VARCHAR(20) NOT NULL DEFAULT ''UNBILLED'' COMMENT ''应收账单占用状态:UNBILLED/BILLED'' AFTER currency',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 3. 订单预览、占用和释放使用的组合索引。
|
||||
SET @has_order_status_index := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = @db_name
|
||||
AND TABLE_NAME = 'prc_order_bill'
|
||||
AND INDEX_NAME = 'idx_bill_ar_status'
|
||||
);
|
||||
SET @sql := IF(@has_order_status_index = 0,
|
||||
'ALTER TABLE prc_order_bill ADD INDEX idx_bill_ar_status (customer_id, currency, ar_bill_status, biz_date)',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 4. 详情页批量关联费用时使用的排序索引。
|
||||
SET @has_detail_fee_index := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = @db_name
|
||||
AND TABLE_NAME = 'ar_bill_detail_fee'
|
||||
AND INDEX_NAME = 'idx_ar_detail_fee_order'
|
||||
);
|
||||
SET @sql := IF(@has_detail_fee_index = 0,
|
||||
'ALTER TABLE ar_bill_detail_fee ADD INDEX idx_ar_detail_fee_order (detail_id, fee_no, detail_fee_id)',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
@@ -0,0 +1,57 @@
|
||||
-- 应收账单菜单和权限
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
SELECT 2400, N'应收管理', 0, 10, 'ar', NULL, '', '', 1, 0, 'M', '0', '0', '', 'money', 'admin', sysdate(), N'应收账单、收款与核销'
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2400);
|
||||
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
SELECT 2401, N'应收账单', 2400, 1, 'bill', 'ar/bill/index', 'ArBill', 1, 0, 'C', '0', '0', 'ar:bill:list', 'list', 'admin', sysdate(), N'正式应收账单'
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2401);
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
SELECT 2402, N'账单查询', 2401, 1, '', NULL, 1, 0, 'F', '0', '0', 'ar:bill:query', '#', 'admin', sysdate()
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2402);
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
SELECT 2403, N'生成账单', 2401, 2, '', NULL, 1, 0, 'F', '0', '0', 'ar:bill:generate', '#', 'admin', sysdate()
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2403);
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
SELECT 2404, N'确认账单', 2401, 3, '', NULL, 1, 0, 'F', '0', '0', 'ar:bill:confirm', '#', 'admin', sysdate()
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2404);
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
SELECT 2405, N'作废账单', 2401, 4, '', NULL, 1, 0, 'F', '0', '0', 'ar:bill:void', '#', 'admin', sysdate()
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2405);
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
SELECT 2406, N'导出账单', 2401, 5, '', NULL, 1, 0, 'F', '0', '0', 'ar:bill:export', '#', 'admin', sysdate()
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2406);
|
||||
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
SELECT 2410, N'应收收款', 2400, 2, 'receipt', 'ar/receipt/index', 'ArReceipt', 1, 0, 'C', '0', '0', 'ar:receipt:list', 'money', 'admin', sysdate(), N'收款登记与核销'
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2410);
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
SELECT 2411, N'收款登记', 2410, 1, '', NULL, 1, 0, 'F', '0', '0', 'ar:receipt:add', '#', 'admin', sysdate()
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2411);
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
SELECT 2412, N'批量核销', 2410, 2, '', NULL, 1, 0, 'F', '0', '0', 'ar:receipt:writeoff', '#', 'admin', sysdate()
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2412);
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
SELECT 2413, N'取消核销', 2410, 3, '', NULL, 1, 0, 'F', '0', '0', 'ar:receipt:cancel', '#', 'admin', sysdate()
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2413);
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
SELECT 2414, N'取消收款', 2410, 4, '', NULL, 1, 0, 'F', '0', '0', 'ar:receipt:cancel', '#', 'admin', sysdate()
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2414);
|
||||
|
||||
INSERT INTO sys_menu (
|
||||
menu_id, menu_name, parent_id, order_num, path, component,
|
||||
is_frame, is_cache, menu_type, visible, status, perms, icon,
|
||||
create_by, create_time
|
||||
)
|
||||
SELECT
|
||||
2420, N'应收日志', 2400, 3, 'log', 'ar/bill/log',
|
||||
1, 0, 'C', '0', '0', 'ar:log:list', 'log',
|
||||
'admin', sysdate()
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2420);
|
||||
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT 1, m.menu_id FROM (SELECT 2400 AS menu_id UNION SELECT 2401 UNION SELECT 2402 UNION SELECT 2403 UNION SELECT 2404 UNION SELECT 2405 UNION SELECT 2406 UNION SELECT 2410 UNION SELECT 2411 UNION SELECT 2412 UNION SELECT 2413 UNION SELECT 2414 UNION SELECT 2420) m
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_role_menu rm WHERE rm.role_id = 1 AND rm.menu_id = m.menu_id);
|
||||
@@ -55,6 +55,7 @@ CREATE TABLE IF NOT EXISTS prc_order_bill (
|
||||
income_total DECIMAL(10,2) DEFAULT NULL COMMENT '应收总额',
|
||||
pay_total DECIMAL(10,2) DEFAULT NULL COMMENT '应付总额',
|
||||
currency VARCHAR(10) NOT NULL DEFAULT 'CNY' COMMENT '币种',
|
||||
ar_bill_status VARCHAR(20) NOT NULL DEFAULT 'UNBILLED' COMMENT '应收账单占用状态:UNBILLED/BILLED',
|
||||
round_mode VARCHAR(20) NOT NULL DEFAULT 'HALF_UP' COMMENT '取值方式',
|
||||
created_by VARCHAR(64) DEFAULT '' COMMENT '创建人',
|
||||
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
|
||||
@@ -64,6 +65,7 @@ CREATE TABLE IF NOT EXISTS prc_order_bill (
|
||||
UNIQUE KEY uk_bill_order (order_no),
|
||||
UNIQUE KEY uk_bill_key (bill_key),
|
||||
INDEX idx_bill_customer (customer_code, biz_date),
|
||||
INDEX idx_bill_ar_status (customer_id, currency, ar_bill_status, biz_date),
|
||||
INDEX idx_bill_product (product_code, country_code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单信息主表';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user