53 Commits

Author SHA1 Message Date
caojia d4f5bb8a4c Merge branch 'test' 2026-11-10 18:11:49 +08:00
caojia e71103af58 Merge branch 'test' 2026-11-10 18:04:49 +08:00
caojia 79cc524c03 Merge branch 'test'
# Conflicts:
#	bms-back/bms-admin/src/main/java/com/bms/web/controller/price/SrvBillAirController.java
#	bms-back/bms-system/src/main/java/com/bms/price/airbill/domain/SrvBillAirFee.java
#	bms-back/bms-system/src/main/java/com/bms/price/airbill/domain/SrvBillAirFeeLog.java
#	bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcOrderBillMapper.java
#	bms-back/bms-system/src/main/java/com/bms/price/mapper/SrvBillAirFeeMapper.java
#	bms-back/bms-system/src/main/java/com/bms/price/service/ISrvBillAirFeeService.java
#	bms-back/bms-system/src/main/java/com/bms/price/service/impl/SrvBillAirFeeServiceImpl.java
#	bms-back/bms-system/src/main/resources/mapper/price/SrvBillAirFeeMapper.xml
#	bms-back/sql/price/air_bill_menu.sql
#	bms-front/src/api/price/airBill.js
#	bms-front/src/views/price/billing/airBill.vue
#	bms-front/src/views/price/billing/components/AirBillFeeDialog.vue
#	bms-front/src/views/price/billing/components/BillDetail.vue
2026-11-10 17:46:55 +08:00
wuweihua efc7ecbd01 增加计费日志显示 2026-11-09 14:26:37 +08:00
wuweihua b8bbf36767 修改删除逻辑 2026-11-09 14:26:36 +08:00
wuweihua 92cb19fbea 合并套餐 2026-11-09 11:45:35 +08:00
wuweihua 4cb82ec107 修改去空格 2026-11-09 11:45:33 +08:00
liuminghui e0dc755ee3 新增提单费用和费用日志 2026-11-09 11:45:27 +08:00
wuweihua 6a02de8069 增加计费列表收货时间查询 2026-11-09 11:45:26 +08:00
wuweihua 974f12429a 回退代码去掉收货查询 2026-11-09 11:45:25 +08:00
wuweihua e06f07b3dd 去掉监控的价格日志 2026-11-09 11:45:24 +08:00
wuweihua 956fb08bea 增加价格变更日志 2026-11-09 11:45:23 +08:00
wuweihua 8d34f84cbb 增加版本号显示 2026-11-09 11:45:22 +08:00
wuweihua b9cdb6c3f9 增加查看计费历史记录 2026-11-09 11:45:20 +08:00
wuweihua f57af555ea 增加查看价格套餐历史版本的变更 2026-11-09 11:45:20 +08:00
caojia 220b01127b 调整模板 2026-11-09 11:29:34 +08:00
caojia aa61dc2d1c 🐛 修正账单预览导出重量精度 2026-11-09 11:29:34 +08:00
caojia 86ed86fc68 🐛 修正账单预览重量精度显示 2026-11-09 11:29:33 +08:00
caojia fba8b786c0 Merge branch 'test' 2026-11-05 17:21:01 +08:00
caojia a4bda4d636 Merge branch 'test' 2026-11-04 18:32:28 +08:00
caojia 00e36cdf3f Merge branch 'test' 2026-11-04 15:20:56 +08:00
caojia 8eb5dca4f0 Merge branch 'test' 2026-11-04 13:45:59 +08:00
caojia b671ebefae Merge branch 'test' 2026-11-04 13:40:30 +08:00
caojia fb5d6d46a8 Merge branch 'test' 2026-11-04 10:27:30 +08:00
wuweihua 039c0d4858 Merge branch 'test' of https://git.lionquexpress.com/wuweihua/BMS
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2026-11-03 18:16:08 +08:00
caojia 43299e5329 Merge branch 'test' 2026-11-03 17:39:19 +08:00
caojia 3fe34c471c Merge branch 'test' 2026-11-02 16:54:26 +08:00
caojia 134855fc31 Merge branch 'test' 2026-11-02 10:07:19 +08:00
caojia 01cee41301 Merge branch 'test' 2026-11-02 09:17:44 +08:00
caojia 1f0d757e29 🐛 按当天截止时间筛选账单订单 2026-10-30 15:55:12 +08:00
caojia 762cad2c0d ✨ 新增应收账单日期类型筛选 2026-10-30 15:23:20 +08:00
wuweihua 7ef3f40a4f Merge branch 'test' of https://git.lionquexpress.com/wuweihua/BMS
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2026-10-29 18:19:48 +08:00
caojia 58e41b669d Merge branch 'test' 2026-10-29 16:59:06 +08:00
caojia 9a48abbacd Merge branch 'test' 2026-10-28 22:25:05 +08:00
wuweihua f95edbdf0a Merge branch 'master' of D:\wuweihua\BMS with conflicts. 2026-10-28 15:57:58 +08:00
wuweihua b30dfdf675 Merge branch 'test' of https://git.lionquexpress.com/wuweihua/BMS
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2026-10-28 15:56:10 +08:00
wuweihua 9a393fe7dc Merge branch 'master' of D:\wuweihua\BMS with conflicts. 2026-10-28 15:32:21 +08:00
wuweihua a2468099c2 Merge branch 'test' of https://git.lionquexpress.com/wuweihua/BMS
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2026-10-28 15:30:47 +08:00
caojia cbed4dc2a3 Merge branch 'test' 2026-10-28 09:28:14 +08:00
caojia d66b96b2ff Merge branch 'test' 2026-10-27 18:25:18 +08:00
wuweihua cdfc90556d 隐藏修改计费重 2026-10-27 17:51:40 +08:00
wuweihua dfa90d6e33 Merge branch 'master' of D:\wuweihua\BMS with conflicts. 2026-10-27 16:25:11 +08:00
wuweihua 4a34075022 Merge branch 'test' of https://git.lionquexpress.com/wuweihua/BMS
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2026-10-27 16:24:36 +08:00
wuweihua 6a03300f2f Merge branch 'test' of https://git.lionquexpress.com/wuweihua/BMS
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2026-10-27 10:31:58 +08:00
liuminghui 7496f8cbc6 Merge branch 'test' 2026-10-27 10:21:57 +08:00
wuweihua 2f2305825a Merge branch 'test' of https://git.lionquexpress.com/wuweihua/BMS
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2026-10-27 01:10:31 +08:00
wuweihua b79cd6f995 Merge branch 'master' of D:\wuweihua\BMS with conflicts. 2026-10-27 00:17:37 +08:00
wuweihua 70a3c34c20 Merge branch 'test' of https://git.lionquexpress.com/wuweihua/BMS
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2026-10-27 00:16:40 +08:00
caojia 2b5633190e Merge branch 'test' 2026-10-26 18:01:23 +08:00
wuweihua 77dfb4291b Merge remote-tracking branch 'origin/test' 2026-10-26 17:13:44 +08:00
caojia d71b7f04fe Merge branch 'test' 2026-10-26 17:09:01 +08:00
caojia f8ae347846 Merge branch 'test' 2026-10-26 16:39:11 +08:00
wuweihua 94991a1ed3 Merge pull request '修改操作文档' (#1) from test into master
Reviewed-on: #1
2026-10-26 15:23:59 +08:00
462 changed files with 3846 additions and 31287 deletions
@@ -59,9 +59,9 @@ public class TmsPriceController extends BaseController
private IBillSearchStore billSearchStore;
/**
* 按客户 + 产品 + 国家 + 重量试算(国家配置了分区时需传 destPostcode)。
* 分区解析:destPostcode -> prc_zone_detail/prc_zone -> product_country.zone_code。
* 匹配顺序:客户套餐 FIXED > DISCOUNT > 产品默认价。
* Quote by customer + product + country + weight (+ destPostcode when country has zones).
* Zone resolve: destPostcode -> prc_zone_detail/prc_zone -> product_country.zone_code.
* Match: customer package FIXED > DISCOUNT > product default.
*/
@PostMapping("/quote")
public AjaxResult quote(
@@ -83,9 +83,9 @@ public class TmsPriceController extends BaseController
}
/**
* 同步订单并落库 prc_order_bill / prc_income_bill_fee。
* 价格匹配与试算一致(绑定套餐 + 按 bizDate 取生效版本,否则取默认价)。
* 相同 orderNo 执行 upsert。
* Sync order and persist prc_order_bill / prc_income_bill_fee.
* Same price match as quote (bound set + active version by bizDate, else default).
* Same orderNo upserts.
*/
@PostMapping("/bill")
public AjaxResult bill(
@@ -106,8 +106,8 @@ public class TmsPriceController extends BaseController
}
/**
* 仓库入库:创建计费任务(PENDING/REBILL/REFUND)并发送 MQ。
* 消费端按 transferNo/orderNo 查询 Oracle,然后插入 prc_order_bill。
* Warehouse inbound: create billing task (PENDING/REBILL/REFUND) and publish MQ.
* Consumer queries Oracle by transferNo/orderNo then inserts prc_order_bill.
* POST /api/tms/price/billing-task
*/
@PostMapping("/billing-task")
@@ -121,7 +121,7 @@ public class TmsPriceController extends BaseController
}
/**
* 查询计费任务回写状态。
* Query billing task writeback status.
* GET /api/tms/price/billing-task/{taskId}
*/
@GetMapping("/billing-task/{taskId}")
@@ -135,7 +135,7 @@ public class TmsPriceController extends BaseController
}
/**
* 同步完成后按 orderNo 查询订单账单。
* Query order bill by orderNo after sync.
*/
@GetMapping("/bill/by-order/{orderNo}")
public AjaxResult getBillByOrder(
@@ -1,4 +1,4 @@
package com.bms.web.controller.receivable;
package com.bms.web.controller.ar;
import java.util.List;
@@ -7,8 +7,8 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.bms.receivable.domain.ArBankAccount;
import com.bms.receivable.service.IArBillService;
import com.bms.ar.domain.ArBankAccount;
import com.bms.ar.service.IArBillService;
import com.bms.common.core.controller.BaseController;
import com.bms.common.core.domain.AjaxResult;
@@ -1,9 +1,8 @@
package com.bms.web.controller.receivable;
package com.bms.web.controller.ar;
import java.util.ArrayList;
import java.util.List;
import java.util.Date;
import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -24,20 +23,21 @@ 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.bms.receivable.domain.ArBill;
import com.bms.receivable.domain.ArBankAccount;
import com.bms.receivable.domain.ArBillDetailFee;
import com.bms.receivable.domain.ArBillGenerateTask;
import com.bms.receivable.domain.ArBillLog;
import com.bms.receivable.domain.vo.ArBillActionRequest;
import com.bms.receivable.domain.vo.ArBillGenerateQuery;
import com.bms.receivable.domain.vo.ArBillGenerateRequest;
import com.bms.receivable.domain.vo.ArBillListExportRow;
import com.bms.receivable.domain.vo.ArBillQuery;
import com.bms.receivable.service.IArBillService;
import com.bms.receivable.service.ArBillVoidTaskService;
import com.bms.receivable.security.ArBillScopeResolver;
import com.bms.receivable.task.ArBillGeneratePreviewExportRunner;
import com.bms.ar.domain.ArBill;
import com.bms.ar.domain.ArBankAccount;
import com.bms.ar.domain.ArBillDetailFee;
import com.bms.ar.domain.ArBillExport;
import com.bms.ar.domain.ArBillGenerateTask;
import com.bms.ar.domain.ArBillLog;
import com.bms.ar.domain.vo.ArBillActionRequest;
import com.bms.ar.domain.vo.ArBillGenerateQuery;
import com.bms.ar.domain.vo.ArBillGeneratePreviewExportRow;
import com.bms.ar.domain.vo.ArBillGenerateRequest;
import com.bms.ar.domain.vo.ArBillListExportRow;
import com.bms.ar.domain.vo.ArBillQuery;
import com.bms.ar.enums.ArBillDateType;
import com.bms.ar.service.IArBillService;
import com.bms.ar.security.ArBillScopeResolver;
import com.bms.common.annotation.Log;
import com.bms.common.core.controller.BaseController;
import com.bms.common.core.domain.AjaxResult;
@@ -62,15 +62,9 @@ public class ArBillController extends BaseController {
@Autowired
private IArBillService billService;
@Autowired
private ArBillVoidTaskService voidTaskService;
@Autowired
private ArBillScopeResolver billScope;
@Autowired
private ArBillGeneratePreviewExportRunner previewExportRunner;
/** 应收筛选专用客户选项,与账单使用同一角色规则。 */
@PreAuthorize("@ss.hasPermi('ar:bill:list')")
@GetMapping("/customer-options")
@@ -177,35 +171,30 @@ public class ArBillController extends BaseController {
AjaxResult result = success();
result.put("rows", pageData.getRows());
result.put("total", pageData.getTotal());
result.put("totalAmount", billService.sumPreviewBill(query));
return result;
}
/** 按需汇总生成预览范围内的应收金额。 */
@PreAuthorize("@ss.hasPermi('ar:bill:generate')")
@PostMapping("/generate/preview/amount")
public AjaxResult previewAmount(@RequestBody ArBillGenerateQuery query) {
return success(billService.sumPreviewBill(query));
}
/** 按生成预览使用的相同筛选条件提交异步导出任务。 */
/** 按生成预览使用的相同筛选条件导出全部匹配订单,不受分页参数影响。 */
@Log(title = "应收账单生成订单", businessType = BusinessType.EXPORT)
@PreAuthorize("@ss.hasPermi('ar:bill:generate')")
@PostMapping("/generate/preview/export")
public AjaxResult exportGeneratePreview(@RequestBody ArBillGenerateQuery query) {
return success(Map.of("taskId", previewExportRunner.submit(query, getUserId())));
}
@PreAuthorize("@ss.hasPermi('ar:bill:generate')")
@GetMapping("/generate/preview/export/task/{taskId}")
public AjaxResult generatePreviewExportTask(@PathVariable String taskId) {
return success(previewExportRunner.getTask(taskId, getUserId()));
}
@PreAuthorize("@ss.hasPermi('ar:bill:generate')")
@GetMapping("/generate/preview/export/download/{taskId}")
public void downloadGeneratePreviewExport(@PathVariable String taskId, HttpServletResponse response)
throws Exception {
previewExportRunner.download(taskId, getUserId(), response);
public void exportGeneratePreview(HttpServletResponse response, ArBillGenerateQuery query,
HttpServletRequest request) {
rejectMultiValueParameter(request, "customerId");
ArBillGenerateQuery exportQuery = query == null ? new ArBillGenerateQuery() : query;
List<PrcOrderBill> bills = billService.previewBill(exportQuery);
List<ArBillGeneratePreviewExportRow> rows = new ArrayList<>(bills.size());
for (PrcOrderBill bill : bills) {
rows.add(ArBillGeneratePreviewExportRow.from(bill));
}
ExcelUtil<ArBillGeneratePreviewExportRow> util = new ExcelUtil<>(ArBillGeneratePreviewExportRow.class);
if (ArBillDateType.PICKUP_DATE == ArBillDateType.of(exportQuery.getDateType())) {
util.hideColumn("bizDate");
} else {
util.hideColumn("pickupDate");
}
util.exportExcel(response, rows, "应收账单待生成订单");
}
/** 查询需要从全选结果中排除的订单;空条件不执行查询,避免返回全部订单。 */
@@ -301,13 +290,7 @@ public class ArBillController extends BaseController {
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(voidTaskService.submit(arBillId, request == null ? null : request.getRemark(), getUsername(), requestId)).put("request_id", requestId);
}
@PreAuthorize("@ss.hasPermi('ar:bill:list')")
@GetMapping("/void/task/{taskId}")
public AjaxResult voidTask(@PathVariable Long taskId) {
return success(voidTaskService.get(taskId));
return success(billService.voidBill(arBillId, request, getUsername(), requestId)).put("request_id", requestId);
}
/**
@@ -341,16 +324,9 @@ public class ArBillController extends BaseController {
}
/**
* 获取已成功生成导出文件的下载方式;OSS 文件返回短时签名链接,本地历史文件走原下载接口。
* 下载已成功生成的导出文件;文件内容由服务层从临时文件或配置存储读取。
*/
@PreAuthorize("@ss.hasPermi('ar:bill:download')")
@GetMapping("/export/{exportId}/download-url")
public AjaxResult exportDownloadInfo(@PathVariable Long exportId) {
return success(billService.getExportDownloadInfo(exportId));
}
/** 下载本地历史导出文件;OSS 文件由前端通过签名链接直接下载。 */
@PreAuthorize("@ss.hasPermi('ar:bill:download')")
@GetMapping("/export/{exportId}/download")
public void download(@PathVariable Long exportId, HttpServletResponse response) throws Exception {
billService.downloadExport(exportId, response);
@@ -1,4 +1,4 @@
package com.bms.web.controller.receivable;
package com.bms.web.controller.ar;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
@@ -12,8 +12,9 @@ import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.bms.receivable.domain.ArBalanceWriteoff;
import com.bms.receivable.service.IArBalanceWriteoffService;
import com.bms.ar.domain.ArBalanceWriteoff;
import com.bms.ar.domain.ArBill;
import com.bms.ar.service.IArBalanceWriteoffService;
import com.bms.common.annotation.Log;
import com.bms.common.core.controller.BaseController;
import com.bms.common.core.domain.AjaxResult;
@@ -188,7 +188,7 @@ public class CommonController
}
/**
* 通用资源下载:本地 profile 路径或 oss://bucket/key(AR 账单风格)。
* Common resource download: local profile path or oss://bucket/key (AR bill style).
*/
@GetMapping("/download/resource")
public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response)
@@ -207,7 +207,7 @@ public class CommonController
}
if (ossFile)
{
// 扩展名校验仍适用于对象 key 后缀。
// Extension check still applies to the object key suffix.
if (!FileUtils.checkAllowDownload(resource))
{
throw new Exception(StringUtils.format("resource ({}) not allowed", resource));
@@ -13,13 +13,13 @@ import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.bms.receivable.domain.ArBill;
import com.bms.receivable.domain.ArBalanceWriteoff;
import com.bms.receivable.domain.vo.ArBillListExportRow;
import com.bms.receivable.domain.vo.ArBillQuery;
import com.bms.receivable.domain.vo.ArBillSummary;
import com.bms.receivable.service.IArBalanceWriteoffService;
import com.bms.receivable.service.IArBillService;
import com.bms.ar.domain.ArBill;
import com.bms.ar.domain.ArBalanceWriteoff;
import com.bms.ar.domain.vo.ArBillListExportRow;
import com.bms.ar.domain.vo.ArBillQuery;
import com.bms.ar.domain.vo.ArBillSummary;
import com.bms.ar.service.IArBalanceWriteoffService;
import com.bms.ar.service.IArBillService;
import com.bms.common.annotation.Log;
import com.bms.common.core.controller.BaseController;
import com.bms.common.core.domain.AjaxResult;
@@ -12,7 +12,6 @@ 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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.bms.common.annotation.Log;
@@ -20,7 +19,7 @@ import com.bms.common.core.controller.BaseController;
import com.bms.common.core.domain.AjaxResult;
import com.bms.common.core.page.TableDataInfo;
import com.bms.common.enums.BusinessType;
import com.bms.common.enums.PaymentMesEnum;
import com.bms.common.exception.ServiceException;
import com.bms.price.domain.PayPostcodeGroup;
import com.bms.price.domain.PayPostcodeLine;
import com.bms.price.service.IPayPostcodeService;
@@ -40,90 +39,10 @@ public class PayPostcodeController extends BaseController
public TableDataInfo list(PayPostcodeGroup query)
{
startPage();
List<PayPostcodeGroup> list = payPostcodeService.selectCountryList(query);
List<PayPostcodeGroup> list = payPostcodeService.selectGroupList(query);
return getDataTable(list);
}
@PreAuthorize("@ss.hasPermi('data:payPostcode:query')")
@GetMapping("/zones")
public AjaxResult zones(PayPostcodeGroup query)
{
return success(payPostcodeService.selectZoneList(query));
}
@PreAuthorize("@ss.hasPermi('data:payPostcode:edit')")
@Log(title = "应付邮编配置", businessType = BusinessType.INSERT)
@PostMapping("/zones")
public AjaxResult addZone(@RequestBody PayPostcodeGroup zone)
{
return toAjax(payPostcodeService.addZone(zone, getUsername()));
}
@PreAuthorize("@ss.hasPermi('data:payPostcode:edit')")
@Log(title = "应付邮编配置", businessType = BusinessType.UPDATE)
@PutMapping("/zones")
public AjaxResult editZone(@RequestBody PayPostcodeGroup zone)
{
return toAjax(payPostcodeService.updateZone(zone));
}
@PreAuthorize("@ss.hasPermi('data:payPostcode:remove')")
@Log(title = "应付邮编配置", businessType = BusinessType.DELETE)
@DeleteMapping("/zones")
public AjaxResult removeZone(PayPostcodeGroup zone)
{
return toAjax(payPostcodeService.deleteZone(zone));
}
@PreAuthorize("@ss.hasPermi('data:payPostcode:edit')")
@Log(title = "应付邮编配置", businessType = BusinessType.UPDATE)
@PutMapping("/country")
public AjaxResult editCountry(@RequestBody PayPostcodeGroup country)
{
return toAjax(payPostcodeService.updateCountry(country));
}
@PreAuthorize("@ss.hasPermi('data:payPostcode:remove')")
@Log(title = "应付邮编配置", businessType = BusinessType.DELETE)
@DeleteMapping("/country")
public AjaxResult removeCountry(PayPostcodeGroup country)
{
return toAjax(payPostcodeService.deleteCountry(country));
}
@PreAuthorize("@ss.hasPermi('data:payPostcode:edit')")
@Log(title = "应付邮编配置", businessType = BusinessType.INSERT)
@PostMapping("/lines")
public AjaxResult addLine(@RequestBody PayPostcodeLine line)
{
return toAjax(payPostcodeService.addLine(line));
}
@PreAuthorize("@ss.hasPermi('data:payPostcode:edit')")
@Log(title = "应付邮编配置", businessType = BusinessType.UPDATE)
@PutMapping("/lines")
public AjaxResult editLine(@RequestBody PayPostcodeLine line)
{
return toAjax(payPostcodeService.updateLine(line));
}
@PreAuthorize("@ss.hasPermi('data:payPostcode:remove')")
@Log(title = "应付邮编配置", businessType = BusinessType.DELETE)
@DeleteMapping("/lines/{lineIds}")
public AjaxResult removeLines(@PathVariable Long[] lineIds)
{
return toAjax(payPostcodeService.deleteLines(lineIds));
}
/** 按国家去重区块,供附加费编辑分区下拉 */
@PreAuthorize("@ss.hasPermi('data:payPostcode:list')")
@GetMapping("/blockOptions")
public AjaxResult blockOptions(@RequestParam(required = false) String countryCode,
@RequestParam(required = false) String countryName)
{
return success(payPostcodeService.selectBlockTitlesByCountry(countryCode, countryName));
}
@PreAuthorize("@ss.hasPermi('data:payPostcode:query')")
@GetMapping("/{groupId}")
public AjaxResult getInfo(@PathVariable Long groupId)
@@ -155,12 +74,12 @@ public class PayPostcodeController extends BaseController
{
if (file == null || file.isEmpty())
{
throw PaymentMesEnum.BMS_PAY_PICK_XLS.exception();
throw new ServiceException("请选择 Excel 文件");
}
String name = file.getOriginalFilename() == null ? "" : file.getOriginalFilename().toLowerCase(Locale.ROOT);
if (!name.endsWith(".xlsx"))
{
throw PaymentMesEnum.BMS_PAY_XLSX_ONLY.exception();
throw new ServiceException("只支持 xlsx 文件");
}
return success(payPostcodeService.importExcel(file.getBytes(), file.getOriginalFilename(), getUsername()));
}
@@ -33,7 +33,7 @@ public class BillingTaskMonitorController extends BaseController
@Autowired
private IPrcBillingTaskService billingTaskService;
/** 默认列表 = FAILED 异常任务。 */
/** Default list = FAILED abnormal tasks. */
@PreAuthorize("@ss.hasPermi('monitor:billingTask:list')")
@GetMapping("/list")
public TableDataInfo list(PrcBillingTask query)
@@ -58,7 +58,7 @@ public class BillingTaskMonitorController extends BaseController
return success(billingTaskService.getById(taskId));
}
/** 手工重同步单个任务(先重置为 WAITING 再处理)。 */
/** Manual resync one task (reset WAITING then process). */
@PreAuthorize("@ss.hasPermi('monitor:billingTask:retry')")
@Log(title = "billing task retry", businessType = BusinessType.UPDATE)
@PostMapping("/retry/{taskId}")
@@ -67,7 +67,7 @@ public class BillingTaskMonitorController extends BaseController
return success(billingTaskService.retryTask(taskId));
}
/** 手工重同步选中的任务。 */
/** Manual resync selected tasks. */
@PreAuthorize("@ss.hasPermi('monitor:billingTask:retry')")
@Log(title = "billing task batch retry", businessType = BusinessType.UPDATE)
@PostMapping("/retry")
@@ -116,8 +116,8 @@ public class BillingTaskMonitorController extends BaseController
}
/**
* 按客户 + 计费时间异步重同步(ID 分页批处理,避免 OOM)。
* 返回 asyncTaskId,通过 {@code GET /retryTask/{asyncTaskId}} 轮询进度。
* Async resync by customer + bill_time (batched ID pages to avoid OOM).
* Returns asyncTaskId; poll {@code GET /retryTask/{asyncTaskId}}.
*/
@PreAuthorize("@ss.hasPermi('monitor:billingTask:retry')")
@Log(title = "billing task filter retry", businessType = BusinessType.UPDATE)
@@ -53,7 +53,7 @@ public class PriceLogController extends BaseController
return rsp;
}
/** 按 productCode + 写入时间分组,用于套餐版本下钻。 */
/** Group by productCode + write time for package-version drill-down. */
@PreAuthorize("@ss.hasPermi('monitor:priceLog:list')")
@GetMapping("/groups")
public TableDataInfo groups(
@@ -119,7 +119,7 @@ public class TabOrderMonitorController extends BaseController
rsp.setTotal(0L);
return rsp;
}
// 清空时间,避免共用解析器误复用按月时间窗口
// clear time so shared resolvers cannot accidentally reuse month window
query.setBeginTime(null);
query.setEndTime(null);
customerKeywordResolver.resolve(query);
@@ -224,7 +224,7 @@ public class TabOrderMonitorController extends BaseController
@PostMapping("/syncByFilter")
public AjaxResult syncByFilter(@RequestBody TabOrderMonitorQuery query)
{
// 仅覆盖上游字段(不触发计费)。筛选条件与列表查询一致。
// Upstream field overwrite only (no billing). Same filters as list query.
if (!hasListFilters(query))
{
return error("orderIdentifiers or (customer and pickup time) required");
@@ -257,7 +257,7 @@ public class TabOrderMonitorController extends BaseController
return success(task);
}
/** 选中行:用 Oracle 数据覆盖本地账单字段;不做报价/重算。 */
/** Selected rows: overwrite local bill fields from Oracle; does not quote/rebill. */
@PreAuthorize("@ss.hasPermi('monitor:orderSource:sync') or @ss.hasPermi('price:orderBill:rebill')")
@Log(title = "order source sync selected", businessType = BusinessType.UPDATE)
@PostMapping("/sync")
@@ -318,7 +318,7 @@ public class TabOrderMonitorController extends BaseController
{
continue;
}
// 优先使用转单号作为 bizId(与 BILL / 上游同步 ES 一致)。
// Prefer transferNo as bizId (same as BILL / upstream sync ES).
if (StringUtils.isNotEmpty(row.getTransferOrderId()))
{
return prefix + "_" + row.getTransferOrderId().trim();
@@ -1,126 +0,0 @@
package com.bms.web.controller.price;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.bms.common.annotation.Log;
import com.bms.common.core.controller.BaseController;
import com.bms.common.core.domain.AjaxResult;
import com.bms.common.enums.BusinessType;
import com.bms.common.utils.poi.ExcelUtil;
import com.bms.price.airbill.domain.SrvBillAirFeeImportRow;
import com.bms.price.airbill.domain.SrvBillAirRoleFeeCommand;
import com.bms.price.airbill.domain.SrvBillAirRoleFeeQuery;
import com.bms.price.domain.vo.SrvBillAirQuery;
import com.bms.price.service.ISrvBillAirFeeService;
import com.bms.price.service.ISrvBillAirService;
/**
* 空运、报关、清关三个角色往提单上添加费用。
*/
@RestController
@RequestMapping("/price/airFee")
public class AirFeeRoleController extends BaseController
{
private static final int MAX_BILLS = 50;
@Autowired
private ISrvBillAirFeeService srvBillAirFeeService;
@Autowired
private ISrvBillAirService srvBillAirService;
/** 按提单号查出可绑定的提单,支持一次多选。 */
@PreAuthorize("@ss.hasAnyPermi('price:airFee:air,price:airFee:declare,price:airFee:clear')")
@GetMapping("/bills")
public AjaxResult bills(@RequestParam(value = "billCodes", required = false) String billCodes)
{
List<String> codes = splitCodes(billCodes);
if (codes.isEmpty())
{
return success(List.of());
}
if (codes.size() > MAX_BILLS)
{
return error("单次最多选择" + MAX_BILLS + "个提单");
}
SrvBillAirQuery query = new SrvBillAirQuery();
query.setBillCodes(codes);
return success(srvBillAirService.selectSrvBillAirList(query));
}
/** 查询已绑定到这些提单、且属于当前入口的费用。 */
@PreAuthorize("@ss.hasAnyPermi('price:airFee:air,price:airFee:declare,price:airFee:clear')")
@GetMapping("/list")
public AjaxResult list(@RequestParam("scene") String scene, @RequestParam(value = "billCodes", required = false) String billCodes)
{
SrvBillAirRoleFeeQuery query = new SrvBillAirRoleFeeQuery();
query.setScene(scene);
query.setBillCodes(splitCodes(billCodes));
return success(srvBillAirFeeService.listRoleFees(query));
}
/** 保存当前入口的费用。空运写入 b 段,报关和清关写入 c 段。 */
@PreAuthorize("@ss.hasAnyPermi('price:airFee:air,price:airFee:declare,price:airFee:clear')")
@Log(title = "提单角色费用", businessType = BusinessType.UPDATE)
@PostMapping("/save")
public AjaxResult save(@RequestBody SrvBillAirRoleFeeCommand command)
{
return success(srvBillAirFeeService.saveRole(command));
}
/** 下载导入模板,模板中带一行示例。 */
@PreAuthorize("@ss.hasAnyPermi('price:airFee:air,price:airFee:declare,price:airFee:clear')")
@PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response)
{
SrvBillAirFeeImportRow example = new SrvBillAirFeeImportRow();
example.setFeeName("燃油附加费");
example.setPrice(new BigDecimal("1000"));
example.setCurrency("EUR");
example.setRemark("导入时需清空这行示例数据");
List<SrvBillAirFeeImportRow> rows = new ArrayList<>();
rows.add(example);
new ExcelUtil<>(SrvBillAirFeeImportRow.class).exportExcel(response, rows, "提单费用导入模板");
}
/** 解析导入文件,不直接落库。示例行会被跳过。 */
@PreAuthorize("@ss.hasAnyPermi('price:airFee:air,price:airFee:declare,price:airFee:clear')")
@Log(title = "提单角色费用导入", businessType = BusinessType.IMPORT)
@PostMapping("/import")
public AjaxResult importFee(MultipartFile file, @RequestParam("scene") String scene) throws Exception
{
if (file == null || file.isEmpty())
{
return error("请选择导入文件");
}
return success(srvBillAirFeeService.parseImport(scene, file.getInputStream()));
}
private static List<String> splitCodes(String billCodes)
{
LinkedHashSet<String> codes = new LinkedHashSet<>();
if (billCodes != null)
{
for (String part : billCodes.split("[,,\\s]+"))
{
if (part != null && !part.isBlank())
{
codes.add(part.trim());
}
}
}
return new ArrayList<>(codes);
}
}
@@ -96,7 +96,7 @@ public class BillingController extends BaseController
private IBillSearchStore billSearchStore;
/**
* 历史报价列表。
* Historical quote list.
*/
@PreAuthorize("@ss.hasPermi('price:billing:quote')")
@GetMapping("/quote/list")
@@ -108,7 +108,7 @@ public class BillingController extends BaseController
}
/**
* 客户已绑定生效套餐版本下的产品(试算产品下拉)。
* Products of the customer's bound effective package version (trial product dropdown).
*/
@PreAuthorize("@ss.hasPermi('price:billing:quote')")
@GetMapping("/quote/products")
@@ -124,7 +124,7 @@ public class BillingController extends BaseController
}
/**
* 历史报价详情,含费用明细行。
* Historical quote detail with fee lines.
*/
@PreAuthorize("@ss.hasPermi('price:billing:quote')")
@GetMapping("/quote/{quoteId}")
@@ -134,7 +134,7 @@ public class BillingController extends BaseController
}
/**
* 与 TMS 开放接口使用相同匹配逻辑,供 BMS 后台试算使用。
* Same matching logic as TMS open API, for BMS console trial.
*/
@PreAuthorize("@ss.hasPermi('price:billing:quote')")
@PostMapping("/quote")
@@ -147,29 +147,13 @@ public class BillingController extends BaseController
@PreAuthorize("@ss.hasPermi('price:billing:bill')")
@GetMapping("/bill/list")
public TableDataInfo billList(PrcOrderBill query)
{
return doBillList(query);
}
/**
* POST 计费列表:大批单号等筛选放请求体,避免 GET URL 过长触发 HTTP 431。
* pageNum/pageSize 仍走 URL 查询参数,供 PageHelper 读取。
*/
@PreAuthorize("@ss.hasPermi('price:billing:bill')")
@PostMapping("/bill/list")
public TableDataInfo billListPost(@RequestBody(required = false) PrcOrderBill query)
{
return doBillList(query);
}
private TableDataInfo doBillList(PrcOrderBill query)
{
if (query == null)
{
query = new PrcOrderBill();
}
// 计费列表只展示计费成功的订单。
query.setBillingFailStatuses(limitBillingListFailStatus());
// Billing list only shows success and the three soft misses.
query.setBillingFailStatuses(limitBillingListFailStatus(query.getBillingFailStatuses()));
query.setBillingFailStatus(null);
startPage();
List<PrcOrderBill> list = prcOrderBillService.selectBillList(query);
@@ -177,8 +161,8 @@ public class BillingController extends BaseController
}
/**
* 按当前筛选条件异步导出计费列表(状态限定为成功 success=0)。
* 需至少一个有效筛选条件。轮询任务完成后再下载。
* Async export billing list by current filters (status limited to 0-3).
* Requires at least one meaningful filter. Poll task then download.
*/
@PreAuthorize("@ss.hasPermi('price:billing:export')")
@Log(title = "计费列表", businessType = BusinessType.EXPORT)
@@ -189,12 +173,12 @@ public class BillingController extends BaseController
{
query = new PrcOrderBill();
}
// 筛选条件校验使用原始请求;默认值仅用于查询范围。
// Filter gate uses the request as-is; defaults applied only for query scope.
if (!OrderBillExportSupport.hasExportFilter(query))
{
return error(MessageUtils.message("price.bill.export.filterRequired"));
}
query.setBillingFailStatuses(limitBillingListFailStatus());
query.setBillingFailStatuses(limitBillingListFailStatus(query.getBillingFailStatuses()));
query.setBillingFailStatus(null);
return successTask(prcOrderBillService.submitOrderBillExport(
PrcOrderBillExportTask.TYPE_BILLING, query));
@@ -220,13 +204,32 @@ public class BillingController extends BaseController
prcOrderBillService.downloadOrderBillExport(taskId, response);
}
private static List<Integer> limitBillingListFailStatus()
private static List<Integer> limitBillingListFailStatus(List<Integer> requested)
{
// 计费列表仅包含成功数据(billing_fail_status = 0)。
return new ArrayList<Integer>(Arrays.asList(Integer.valueOf(0)));
// Soft-fail list: success + sales 1/2/3 + product-catalog 23/24/25
List<Integer> allowed = Arrays.asList(
Integer.valueOf(0), Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3),
Integer.valueOf(23), Integer.valueOf(24), Integer.valueOf(25));
if (requested == null || requested.isEmpty())
{
return new ArrayList<Integer>(allowed);
}
List<Integer> kept = new ArrayList<Integer>();
for (Integer status : requested)
{
if (status != null && allowed.contains(status) && !kept.contains(status))
{
kept.add(status);
}
}
if (kept.isEmpty())
{
kept.add(Integer.valueOf(-1));
}
return kept;
}
/** 全部 prc_order_bill 记录。计费列表仍限定为状态 0。 */
/** All prc_order_bill rows. Billing list stays limited to status 0-3. */
@PreAuthorize("@ss.hasPermi('price:orderBill:list')")
@GetMapping("/orderBill/list")
public TableDataInfo orderBillList(PrcOrderBill query)
@@ -237,30 +240,8 @@ public class BillingController extends BaseController
}
/**
* POST 列表:筛选(尤其是大批量单号)放请求体,避免 GET URL 过长触发 HTTP 431。
* pageNum/pageSize 仍走 URL 查询参数,供 TableSupport/PageHelper 读取。
*/
@PreAuthorize("@ss.hasPermi('price:orderBill:list')")
@PostMapping("/orderBill/list")
public TableDataInfo orderBillListPost(@RequestBody(required = false) PrcOrderBill query)
{
startPage();
List<PrcOrderBill> list = prcOrderBillService.selectBillList(query == null ? new PrcOrderBill() : query);
return getDataTable(list);
}
/** 订单列表弹窗:按 billId 查询计费失败原因(受数据权限控制)。 */
@PreAuthorize("@ss.hasPermi('price:orderBill:list')")
@GetMapping("/orderBill/{billId}/failReason")
public AjaxResult orderBillFailReason(@PathVariable("billId") Long billId)
{
return success(prcOrderBillService.getBillingFailReason(billId));
}
/**
* 按当前筛选条件异步导出订单列表。
* 需至少一个有效筛选条件(仅默认 orderStatuses 不够)。
* Async export order list by current filters.
* Requires at least one meaningful filter (default orderStatuses alone is not enough).
*/
@PreAuthorize("@ss.hasPermi('price:orderBill:export')")
@Log(title = "订单列表", businessType = BusinessType.EXPORT)
@@ -292,8 +273,8 @@ public class BillingController extends BaseController
}
/**
* 对比本地订单账单产品与客户绑定套餐的 SALES 产品
* (包含 product_code_mapping)。
* Compare local order-bill products vs customer bound package SALES products
* (includes product_code_mapping).
*/
@PreAuthorize("@ss.hasPermi('price:orderBill:list')")
@PostMapping("/orderBill/checkProducts")
@@ -303,7 +284,7 @@ public class BillingController extends BaseController
return success(data);
}
/** 在同一校验筛选条件下,下钻查看单个产品的订单账单。 */
/** Drill-down order bills for one product under the same check filters. */
@PreAuthorize("@ss.hasPermi('price:orderBill:list')")
@PostMapping("/orderBill/checkProducts/orders")
public AjaxResult listOrderBillsByProduct(@RequestBody PrcOrderBill query)
@@ -312,8 +293,8 @@ public class BillingController extends BaseController
}
/**
* 订单列表重算,仅基于本地 prc_order_bill(不查询 Oracle TAB_ORDER)。
* 轮询 {@code GET /orderBill/rebill/task/{taskId}}。
* Order-list rebill from local prc_order_bill only (no Oracle TAB_ORDER).
* Poll {@code GET /orderBill/rebill/task/{taskId}}.
*/
@PreAuthorize("@ss.hasPermi('price:orderBill:rebill')")
@Log(title = "Order bill local rebill", businessType = BusinessType.UPDATE)
@@ -344,7 +325,7 @@ public class BillingController extends BaseController
}
/**
* 远程搜索下拉的订单号选项(计费列表多选搜索)。
* Order no options for remote-search dropdown (bill list multi search).
*/
@PreAuthorize("@ss.hasPermi('price:billing:bill') or @ss.hasPermi('price:orderBill:list')")
@GetMapping("/bill/orderNos")
@@ -380,24 +361,24 @@ public class BillingController extends BaseController
@PreAuthorize("@ss.hasPermi('price:billing:bill') or @ss.hasPermi('price:orderBill:list')")
@GetMapping("/bill/{billId}")
public AjaxResult getBill(@PathVariable Long billId)
public AjaxResult getBill(@PathVariable String billId)
{
return success(prcOrderBillService.getBill(billId));
}
/**
* 该订单的 ES BILL 日志(计费 / 上游同步 / 失败快照)。
* bizId 优先取 transferNo,其次取 orderNo(与写入路径一致)。
* ES BILL logs for this order (billing / upstream sync / fail snapshots).
* bizId prefers transferNo then orderNo (same as write path).
*/
@PreAuthorize("@ss.hasPermi('price:billing:bill') or @ss.hasPermi('price:orderBill:list')")
@GetMapping("/bill/{billId}/history")
public TableDataInfo billHistory(@PathVariable("billId") Long billId)
public TableDataInfo billHistory(@PathVariable("billId") String billId)
{
if (billId == null)
if (StringUtils.isEmpty(billId))
{
return getDataTable(new ArrayList<BillSyncSnapshot>());
}
TmsPriceQuoteResult bill = prcOrderBillService.getBill(billId);
TmsPriceQuoteResult bill = prcOrderBillService.getBill(billId.trim());
if (bill == null)
{
TableDataInfo empty = new TableDataInfo();
@@ -412,7 +393,7 @@ public class BillingController extends BaseController
int pageSize = pageDomain.getPageSize() == null ? 10 : pageDomain.getPageSize().intValue();
String primary = EsBizLogSupport.billBizId(bill.getTransferNo(), bill.getOrderNo());
BillSearchPage page = billSearchStore.search(EsLogType.BILL, primary, null, pageNum, pageSize);
// 当 bizId 使用 transferNo 时,再按 orderNo 拉取一次,以便出现更早的以 orderNo 为 key 的记录。
// Also pull by orderNo when transferNo was used as bizId, so older orderNo-keyed rows appear.
if (StringUtils.isNotEmpty(bill.getTransferNo()) && StringUtils.isNotEmpty(bill.getOrderNo())
&& !bill.getTransferNo().trim().equalsIgnoreCase(bill.getOrderNo().trim())
&& pageNum == 1)
@@ -477,18 +458,18 @@ public class BillingController extends BaseController
}
/**
* 通过计费任务 billType=REFUND 退费单笔应收账单。
* Refund one receivable bill via billing task billType=REFUND.
*/
@PreAuthorize("@ss.hasPermi('price:billing:refund')")
@Log(title = "Billing refund", businessType = BusinessType.UPDATE)
@PostMapping("/bill/{billId}/refund")
public AjaxResult refundBill(@PathVariable("billId") Long billId)
public AjaxResult refundBill(@PathVariable("billId") String billId)
{
if (billId == null)
if (StringUtils.isEmpty(billId))
{
return error("billId required");
}
TmsPriceQuoteResult bill = prcOrderBillService.getBill(billId);
TmsPriceQuoteResult bill = prcOrderBillService.getBill(billId.trim());
if (bill == null || StringUtils.isEmpty(bill.getOrderNo()))
{
return error("bill not found");
@@ -514,12 +495,12 @@ public class BillingController extends BaseController
}
/**
* 批量更新订单信用欠款状态(ARREARS -> NORMAL),并校验可用额度。
* Batch update order credit arrears status (ARREARS -> NORMAL) with available-credit check.
*/
@PreAuthorize("@ss.hasPermi('price:billing:arrearsUpdate')")
@Log(title = "Billing credit arrears", businessType = BusinessType.UPDATE)
@PutMapping("/bill/creditArrears")
public AjaxResult updateCreditArrears(@RequestBody java.util.List<Long> billIds)
public AjaxResult updateCreditArrears(@RequestBody java.util.List<String> billIds)
{
if (billIds == null || billIds.isEmpty())
{
@@ -527,7 +508,7 @@ public class BillingController extends BaseController
}
int success = 0;
java.util.List<String> fails = new java.util.ArrayList<String>();
for (Long billId : billIds)
for (String billId : billIds)
{
try
{
@@ -574,7 +555,7 @@ public class BillingController extends BaseController
return success(prcOrderBillService.addIncomeFee(request));
}
/** 批量加费前预览匹配的账单(筛选:客户 / transferNo / 国家)。 */
/** Preview matched bills for batch add fee (filter: customer / transferNo / country). */
@PreAuthorize("@ss.hasPermi('price:billing:feeAdd')")
@PostMapping("/bill/fee/batch/preview")
public AjaxResult previewBatchAddIncomeFee(@RequestBody BatchAddIncomeBillFeeRequest request)
@@ -582,7 +563,7 @@ public class BillingController extends BaseController
return success(prcOrderBillService.previewBatchAddIncomeFee(request));
}
/** 提交异步批量加费;轮询 /bill/fee/batch/task/{taskId}。 */
/** Submit async batch add fee; poll /bill/fee/batch/task/{taskId}. */
@PreAuthorize("@ss.hasPermi('price:billing:feeAdd')")
@Log(title = "Billing batch income fee", businessType = BusinessType.INSERT)
@PostMapping("/bill/fee/batch")
@@ -606,7 +587,7 @@ public class BillingController extends BaseController
return success(task);
}
/** 仅异步重算 product_amount。轮询 /bill/recalcPrice/task/{taskId}。 */
/** Async recalc product_amount only. Poll /bill/recalcPrice/task/{taskId}. */
@PreAuthorize("@ss.hasPermi('price:billing:recalc')")
@Log(title = "Billing recalc product amount", businessType = BusinessType.UPDATE)
@PostMapping("/bill/recalcProduct")
@@ -620,7 +601,7 @@ public class BillingController extends BaseController
return successTask(prcOrderBillService.submitRecalcBillPrice(request));
}
/** 仅异步重算 redline_amount。轮询 /bill/recalcPrice/task/{taskId}。 */
/** Async recalc redline_amount only. Poll /bill/recalcPrice/task/{taskId}. */
@PreAuthorize("@ss.hasPermi('price:billing:recalc')")
@Log(title = "Billing recalc redline amount", businessType = BusinessType.UPDATE)
@PostMapping("/bill/recalcRedline")
@@ -664,7 +645,7 @@ public class BillingController extends BaseController
@PreAuthorize("@ss.hasPermi('price:billing:feeRemove')")
@Log(title = "Billing income fee", businessType = BusinessType.DELETE)
@DeleteMapping("/bill/fee/{feeId}")
public AjaxResult deleteIncomeFee(@PathVariable Long feeId)
public AjaxResult deleteIncomeFee(@PathVariable String feeId)
{
return success(prcOrderBillService.deleteIncomeFee(feeId));
}
@@ -677,7 +658,7 @@ public class BillingController extends BaseController
return success(result);
}
/** 更新 charge_weight(保留 weight),随后重新计费。 */
/** Update charge_weight (keep weight), then rebill. */
@PreAuthorize("@ss.hasPermi('price:billing:chargeWeightEdit')")
@Log(title = "Billing charge weight", businessType = BusinessType.UPDATE)
@PutMapping("/bill/chargeWeight")
@@ -687,7 +668,7 @@ public class BillingController extends BaseController
}
/**
* 下载导入模板。fillByFilter=true 时按列表筛选条件填充 orderNo/customer。
* Download import template. fillByFilter=true fills orderNo/customer from list filters.
*/
@PreAuthorize("@ss.hasPermi('price:billing:chargeWeightEdit')")
@PostMapping("/bill/chargeWeight/importTemplate")
@@ -709,8 +690,8 @@ public class BillingController extends BaseController
}
/**
* 异步导入 charge_weight 更新并重新计费。
* 返回 taskId;轮询 /bill/chargeWeight/import/task/{taskId}。
* Async import charge_weight updates and rebill.
* Returns taskId; poll /bill/chargeWeight/import/task/{taskId}.
*/
@PreAuthorize("@ss.hasPermi('price:billing:chargeWeightEdit')")
@Log(title = "Billing charge weight import", businessType = BusinessType.IMPORT)
@@ -741,7 +722,7 @@ public class BillingController extends BaseController
}
catch (Exception e)
{
// 导入任务已入队;附件保存失败不应阻塞后续重新计费
// Import task already queued; attachment failure should not block rebill
Map<String, Object> payload = new HashMap<String, Object>();
payload.put("taskId", taskId);
payload.put("attachmentWarn", e.getMessage());
@@ -762,7 +743,7 @@ public class BillingController extends BaseController
}
/**
* 查询收费重导入的 Excel 附件记录。
* List charge-weight import Excel attachment records.
*/
@PreAuthorize("@ss.hasPermi('price:billing:chargeWeightEdit')")
@GetMapping("/bill/chargeWeight/import/attachments")
@@ -37,7 +37,7 @@ import com.bms.price.service.IPrcPriceExcelFileService;
import com.bms.price.service.IPrcPriceSetService;
/**
* 独家报价(与 PACKAGE 共用 prc_price_set 表)
* Exclusive quote (shares prc_price_set with PACKAGE)
*
* @author bms
*/
@@ -82,7 +82,7 @@ public class ExclusiveQuoteController extends BaseController
set.setCreateBy(getUsername());
set.setUpdateBy(getUsername());
String taskId = priceSetService.submitSaveAsync(set);
// 不要向 success() 直接传字符串:该重载会把字符串放进 msg 而不是 data。
// Do not pass bare String to success(): overload puts it into msg, not data.
Map<String, Object> payload = new HashMap<String, Object>();
payload.put("taskId", taskId);
return success(payload);
@@ -111,7 +111,7 @@ public class ExclusiveQuoteController extends BaseController
return toAjax(priceSetService.updatePrcPriceSet(set));
}
/** 仅更新套餐名称与备注。 */
/** Update package name and note only. */
@PreAuthorize("@ss.hasPermi('price:exclusive:edit')")
@Log(title = "Exclusive quote basic", businessType = BusinessType.UPDATE)
@PutMapping("/basic")
@@ -200,7 +200,7 @@ public class ExclusiveQuoteController extends BaseController
return toAjax(priceSetService.deleteVersion(psvId, getUsername()));
}
/** 版本的 Excel 附件导入(prc_price_excel_file.psv_id)。 */
/** Import excel attachments for a version (prc_price_excel_file.psv_id). */
@PreAuthorize("@ss.hasPermi('price:exclusive:list')")
@GetMapping("/version/{psvId}/excelFiles")
public AjaxResult versionExcelFiles(@PathVariable Long psvId)
@@ -253,7 +253,7 @@ public class ExclusiveQuoteController extends BaseController
return getDataTable(list);
}
/** 单个产品的全部明细行(不分页),供改价抽屉使用。 */
/** All detail rows for one product (no page); used by edit-price drawer. */
@PreAuthorize("@ss.hasPermi('price:exclusive:list')")
@GetMapping("/detail/byProduct")
public AjaxResult detailByProduct(@RequestParam("psvId") Long psvId,
@@ -262,7 +262,7 @@ public class ExclusiveQuoteController extends BaseController
return success(priceSetService.selectDetailListByProduct(psvId, productCode));
}
/** 某个版本的全部明细行(不分页);用批量预取替代 N 次 byProduct 调用。 */
/** All detail rows for a version (no page); batch prefetch instead of N byProduct calls. */
@PreAuthorize("@ss.hasPermi('price:exclusive:list')")
@GetMapping("/detail/byVersion")
public AjaxResult detailByVersion(@RequestParam("psvId") Long psvId)
@@ -318,7 +318,7 @@ public class ExclusiveQuoteController extends BaseController
}
/**
* 预览拉取产品库(不写库),用于新建向导提交前的预览。
* Preview pull product library (no DB write). For create wizard before finish.
*/
@PreAuthorize("@ss.hasPermi('price:exclusive:edit') or @ss.hasPermi('price:exclusive:forceEdit') or @ss.hasPermi('price:exclusive:add')")
@PostMapping("/detail/previewPullFromLibrary")
@@ -328,8 +328,8 @@ public class ExclusiveQuoteController extends BaseController
}
/**
* 预览 Excel 导入(不写库)。可选 detailsJson,用于 updateOnly / 构建价格合并。
* 可选 countryCode = 单国家专线 Excel(与按产品国家导入一致)。
* Preview Excel import (no DB write). Optional detailsJson for updateOnly / build-price merge.
* Optional countryCode = single-country dedicated-line Excel (same as product country import).
*/
@PreAuthorize("@ss.hasPermi('price:exclusive:edit') or @ss.hasPermi('price:exclusive:forceEdit') or @ss.hasPermi('price:exclusive:add')")
@PostMapping("/detail/previewImportPrices")
@@ -359,7 +359,7 @@ public class ExclusiveQuoteController extends BaseController
}
/**
* 异步拉取产品库到版本明细。返回 taskId,通过 /save/task/{taskId} 轮询进度。
* Async pull product library into version details. Returns taskId; poll /save/task/{taskId}.
*/
@PreAuthorize("@ss.hasPermi('price:exclusive:edit') or @ss.hasPermi('price:exclusive:forceEdit')")
@Log(title = "Exclusive quote拉取产品", businessType = BusinessType.IMPORT)
@@ -376,7 +376,7 @@ public class ExclusiveQuoteController extends BaseController
}
/**
* 异步批量写入明细附加费快照。返回 taskId,通过 /save/task/{taskId} 轮询进度。
* Async batch write of detail surcharge snapshots. Returns taskId; poll /save/task/{taskId}.
*/
@PreAuthorize("@ss.hasPermi('price:exclusive:edit') or @ss.hasPermi('price:exclusive:forceEdit')")
@Log(title = "Exclusive quote附加费", businessType = BusinessType.UPDATE)
@@ -8,7 +8,6 @@ import java.util.Locale;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -25,19 +24,15 @@ import com.bms.common.core.domain.AjaxResult;
import com.bms.common.core.page.TableDataInfo;
import com.bms.common.enums.BusinessType;
import com.bms.common.exception.ServiceException;
import com.bms.price.domain.LblProduct;
import com.bms.price.domain.LblSurcharge;
import com.bms.price.domain.LblWeightBand;
import com.bms.price.domain.LblSaveCommand;
import com.bms.price.domain.PaySaveCommand;
import com.bms.price.domain.PrcCustomerPriceSet;
import com.bms.price.domain.PrcCustomerPriceSetBatch;
import com.bms.price.domain.PrcPriceSet;
import com.bms.price.service.ILblProductService;
import com.bms.price.service.IPayProductService;
import com.bms.price.service.IPrcPriceSetService;
/**
* 打单套餐:套餐壳 + 独立打单价卡(prc_label_*)。
* 打单套餐:套餐壳 + 应收价卡(pay_*)。
*/
@RestController
@RequestMapping("/price/label")
@@ -47,7 +42,7 @@ public class LabelPackageController extends BaseController
private IPrcPriceSetService priceSetService;
@Autowired
private ILblProductService lblProductService;
private IPayProductService payProductService;
@PreAuthorize("@ss.hasPermi('price:label:list')")
@GetMapping("/list")
@@ -131,37 +126,30 @@ public class LabelPackageController extends BaseController
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
}
// 打单独立预览:已存在按本套餐判断,不与应付公共模板互挡
return success(lblProductService.preview(file.getBytes(), name, dictValue, progressId, priceSetId));
return success(payProductService.previewLabel(file.getBytes(), name, dictValue, progressId, priceSetId));
}
@PreAuthorize("@ss.hasPermi('price:label:import')")
@GetMapping("/preview/progress")
public AjaxResult parseProgress(@RequestParam("progressId") String progressId)
{
return success(lblProductService.parseProgress(progressId));
return success(payProductService.parseProgress(progressId));
}
@PreAuthorize("@ss.hasPermi('price:label:import')")
@Log(title = "打单套餐导入", businessType = BusinessType.IMPORT)
@PostMapping("/importSave")
public AjaxResult importSave(@RequestBody LblSaveCommand command)
public AjaxResult importSave(@RequestBody PaySaveCommand command)
{
if (command == null)
{
throw new ServiceException("保存参数不能为空");
}
String taskId = lblProductService.submitSave(command);
String message = payProductService.saveLabel(command);
Map<String, Object> payload = new HashMap<String, Object>();
payload.put("taskId", taskId);
// data.taskId + 顶层 taskId,兼容前端不同取值方式
return success(payload).put("taskId", taskId);
}
@PreAuthorize("@ss.hasPermi('price:label:import')")
@GetMapping("/importSave/task/{taskId}")
public AjaxResult importSaveTask(@PathVariable String taskId)
{
return success(lblProductService.getSaveTask(taskId));
payload.put("message", message);
payload.put("priceSetId", command.getPriceSetId());
return success(payload);
}
@PreAuthorize("@ss.hasPermi('price:label:list')")
@@ -169,7 +157,7 @@ public class LabelPackageController extends BaseController
public AjaxResult versions(@PathVariable Long priceSetId)
{
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
return success(lblProductService.selectVersions(priceSetId));
return success(payProductService.selectLabelVersions(priceSetId));
}
@PreAuthorize("@ss.hasPermi('price:label:edit')")
@@ -180,7 +168,7 @@ public class LabelPackageController extends BaseController
{
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
Date effectiveTime = parseEffectiveTime(body == null ? null : body.get("effectiveTime"));
String message = lblProductService.activateVersion(priceSetId, versionNo, effectiveTime);
String message = payProductService.activateLabelVersion(priceSetId, versionNo, effectiveTime);
Map<String, Object> payload = new HashMap<String, Object>();
payload.put("message", message);
return success(payload);
@@ -192,7 +180,7 @@ public class LabelPackageController extends BaseController
public AjaxResult deleteVersion(@PathVariable Long priceSetId, @PathVariable Integer versionNo)
{
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
return toAjax(lblProductService.deleteVersion(priceSetId, versionNo));
return toAjax(payProductService.deleteLabelVersion(priceSetId, versionNo));
}
@PreAuthorize("@ss.hasPermi('price:label:list')")
@@ -203,70 +191,46 @@ public class LabelPackageController extends BaseController
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
if (versionNo != null)
{
return success(lblProductService.selectProductsByVersion(priceSetId, versionNo));
return success(payProductService.selectLabelProductsByVersion(priceSetId, versionNo));
}
return success(lblProductService.selectProducts(priceSetId));
return success(payProductService.selectLabelProducts(priceSetId));
}
@PreAuthorize("@ss.hasPermi('price:label:query')")
@GetMapping("/product/{productId}")
public AjaxResult productDetail(@PathVariable Long productId,
@RequestParam(value = "termId", required = false) Long termId,
@RequestParam(value = "versionId", required = false) Long versionId)
@RequestParam(value = "termId", required = false) Long termId)
{
Long vid = versionId != null ? versionId : termId;
if (vid != null)
if (termId != null)
{
return success(lblProductService.selectDetailByVersionId(vid));
return success(payProductService.selectPayProductDetailByTerm(termId));
}
throw new ServiceException("请传入 versionId");
return success(payProductService.selectPayProductDetail(productId));
}
/** 保存打单产品明细(重量段/附加费等),按当前 version 覆盖子表 */
@PreAuthorize("@ss.hasPermi('price:label:edit')")
@Log(title = "打单产品明细", businessType = BusinessType.UPDATE)
@PutMapping("/product")
public AjaxResult updateProduct(@RequestBody LblProduct product)
@PreAuthorize("@ss.hasPermi('price:label:remove')")
@Log(title = "打单套餐移除产品", businessType = BusinessType.DELETE)
@DeleteMapping("/{priceSetId}/products/{productIds}")
public AjaxResult removeProducts(@PathVariable Long priceSetId, @PathVariable Long[] productIds)
{
return toAjax(lblProductService.updateDetail(product));
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
return toAjax(payProductService.removeLabelProducts(priceSetId, productIds));
}
/** 仅保存当前重量段行 */
@PreAuthorize("@ss.hasPermi('price:label:edit')")
@Log(title = "打单重量段", businessType = BusinessType.UPDATE)
@PutMapping("/product/band")
public AjaxResult updateWeightBand(@RequestBody LblWeightBand band)
{
return toAjax(lblProductService.updateWeightBand(band));
}
/** 仅保存当前附加费行 */
@PreAuthorize("@ss.hasPermi('price:label:edit')")
@Log(title = "打单附加费", businessType = BusinessType.UPDATE)
@PutMapping("/product/surcharge")
public AjaxResult updateSurcharge(@RequestBody LblSurcharge surcharge)
{
return toAjax(lblProductService.updateSurcharge(surcharge));
}
/** 查询打单产品已绑定渠道(默认应收 AR) */
@PreAuthorize("@ss.hasPermi('price:label:query')")
@GetMapping("/product/{productId}/channels")
public AjaxResult productChannels(@PathVariable Long productId,
@RequestParam(value = "payType", required = false) String payType)
@GetMapping("/{priceSetId}/channels")
public AjaxResult listChannels(@PathVariable Long priceSetId)
{
String type = (payType == null || payType.trim().isEmpty()) ? "AR" : payType.trim();
return success(lblProductService.listProductChannelBinds(productId, type));
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
return success(payProductService.listLabelChannels(priceSetId));
}
/** 覆盖保存打单产品渠道绑定,写入 srv_channel_product_map */
@PreAuthorize("@ss.hasPermi('price:label:edit')")
@Log(title = "打单产品绑定渠道", businessType = BusinessType.UPDATE)
@PutMapping("/product/{productId}/channels")
public AjaxResult replaceProductChannels(@PathVariable Long productId, @RequestBody Map<String, Object> body)
@Log(title = "打单套餐绑定渠道", businessType = BusinessType.UPDATE)
@PutMapping("/{priceSetId}/channels")
public AjaxResult bindChannels(@PathVariable Long priceSetId, @RequestBody Map<String, Object> body)
{
String payType = body == null || body.get("payType") == null ? "AR" : String.valueOf(body.get("payType")).trim();
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
List<String> channelIds = new ArrayList<String>();
Object rawIds = body == null ? null : body.get("channelIds");
if (rawIds instanceof List<?>)
@@ -279,20 +243,29 @@ public class LabelPackageController extends BaseController
}
}
}
String msg = lblProductService.replaceProductChannelBinds(productId, payType, channelIds);
return AjaxResult.success(msg);
Long productId = null;
Object rawProductId = body == null ? null : body.get("productId");
if (rawProductId instanceof Number)
{
productId = ((Number) rawProductId).longValue();
}
else if (rawProductId != null && !String.valueOf(rawProductId).trim().isEmpty()
&& !"null".equalsIgnoreCase(String.valueOf(rawProductId).trim()))
{
productId = Long.valueOf(String.valueOf(rawProductId).trim());
}
return success(payProductService.bindLabelChannels(priceSetId, channelIds, productId));
}
@PreAuthorize("@ss.hasPermi('price:label:remove')")
@Log(title = "打单套餐移除产品", businessType = BusinessType.DELETE)
@DeleteMapping("/{priceSetId}/products/{productIds}")
public AjaxResult removeProducts(@PathVariable Long priceSetId, @PathVariable Long[] productIds)
@PreAuthorize("@ss.hasPermi('price:label:edit')")
@Log(title = "打单套餐解绑渠道", businessType = BusinessType.DELETE)
@DeleteMapping("/{priceSetId}/channels/{channelId}")
public AjaxResult unbindChannel(@PathVariable Long priceSetId, @PathVariable String channelId)
{
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
return toAjax(lblProductService.removeProducts(priceSetId, productIds));
return toAjax(payProductService.unbindLabelChannel(priceSetId, channelId));
}
@PreAuthorize("@ss.hasPermi('price:label:list')")
@GetMapping("/customer/list")
public TableDataInfo customerList(PrcCustomerPriceSet customer)
@@ -1,14 +1,8 @@
package com.bms.web.controller.price;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@@ -21,7 +15,6 @@ import com.bms.common.core.domain.AjaxResult;
import com.bms.common.core.page.TableDataInfo;
import com.bms.price.domain.PayBill;
import com.bms.price.domain.PayBillCommand;
import com.bms.price.domain.PayBillFile;
import com.bms.price.service.IPayBillService;
/**
@@ -126,176 +119,4 @@ public class PayBillController extends BaseController
payBillService.voidBill(command);
return success();
}
/**
* 草稿提交审核。
*
* @param billId 应付账单ID
*/
@PreAuthorize("@ss.hasAnyPermi('price:payBill:submit,price:payBill:confirm')")
@PutMapping("/submit/{billId}")
public AjaxResult submit(@PathVariable Long billId)
{
payBillService.submit(billId);
return success();
}
/**
* 审核通过。蓝字账单需要先有凭证。
*
* @param billId 应付账单ID
*/
@PreAuthorize("@ss.hasAnyPermi('price:payBill:approve,price:payBill:confirm')")
@PutMapping("/approve/{billId}")
public AjaxResult approve(@PathVariable Long billId)
{
payBillService.approve(billId);
return success();
}
/**
* 审核驳回。
*
* @param command 账单ID和驳回意见
*/
@PreAuthorize("@ss.hasAnyPermi('price:payBill:approve,price:payBill:confirm')")
@PutMapping("/reject")
public AjaxResult reject(@RequestBody PayBillCommand command)
{
payBillService.reject(command);
return success();
}
/**
* 草稿登记对方金额。
*
* @param command 账单和费用行
*/
@PreAuthorize("@ss.hasAnyPermi('price:payBill:add,price:payBill:submit')")
@PutMapping("/diff")
public AjaxResult diff(@RequestBody PayBillCommand command)
{
payBillService.saveSupplierAmounts(command);
return success();
}
/**
* 接受我方或接受对方金额。
*
* @param command 费用行和处理方式
*/
@PreAuthorize("@ss.hasAnyPermi('price:payBill:add,price:payBill:submit')")
@PutMapping("/diff/accept")
public AjaxResult accept(@RequestBody PayBillCommand command)
{
payBillService.acceptDiff(command);
return success();
}
/**
* 从草稿剔除一条费用。
*
* @param command 账单ID和行ID
*/
@PreAuthorize("@ss.hasAnyPermi('price:payBill:add,price:payBill:submit')")
@PutMapping("/line/remove")
public AjaxResult removeLine(@RequestBody PayBillCommand command)
{
payBillService.removeLine(command);
return success();
}
/**
* 上传应付凭证。
*
* @param file 凭证
*/
@PreAuthorize("@ss.hasAnyPermi('price:payVoucher:add,price:payBill:add')")
@PostMapping("/file")
public AjaxResult saveFile(@RequestBody PayBillFile file)
{
payBillService.saveFile(file);
return success();
}
/**
* 删除应付凭证。
*
* @param fileId 凭证ID
*/
@PreAuthorize("@ss.hasAnyPermi('price:payVoucher:remove,price:payBill:void')")
@DeleteMapping("/file/{fileId}")
public AjaxResult deleteFile(@PathVariable Long fileId)
{
payBillService.deleteFile(fileId);
return success();
}
/**
* 查询应付凭证。
*
* @param query 账单号、供应商、发票号、类型
*/
@PreAuthorize("@ss.hasAnyPermi('price:payVoucher:list,price:payBill:query')")
@GetMapping("/file/list")
public TableDataInfo fileList(PayBillFile query)
{
startPage();
return getDataTable(payBillService.listFiles(query));
}
/**
* 对未付款的已确认账单开红字。
*
* @param command 原账单和原因
*/
@PreAuthorize("@ss.hasAnyPermi('price:payBill:approve,price:payBill:confirm')")
@PostMapping("/red")
public AjaxResult red(@RequestBody PayBillCommand command)
{
return success(payBillService.redBill(command));
}
/**
* 草稿保存税率和汇率。
*
* @param command 账单、税率、汇率和汇率日期
*/
@PreAuthorize("@ss.hasAnyPermi('price:payBill:add,price:payBill:submit')")
@PutMapping("/finance")
public AjaxResult finance(@RequestBody PayBillCommand command)
{
payBillService.saveFinance(command);
return success();
}
/**
* 导出对账单。
*
* @param billId 应付账单ID
* @param response 文件流
*/
@PreAuthorize("@ss.hasPermi('price:payBill:query')")
@GetMapping("/statement/{billId}")
public void statement(@PathVariable Long billId, HttpServletResponse response) throws Exception
{
String name = URLEncoder.encode("应付对账单.xlsx", StandardCharsets.UTF_8).replace("+", "%20");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + name);
payBillService.exportStatement(billId, response.getOutputStream());
}
/**
* 导入对方金额。
*
* @param billId 应付账单ID
* @param file 导出的对账单
*/
@PreAuthorize("@ss.hasAnyPermi('price:payBill:add,price:payBill:submit')")
@PostMapping("/diff/import")
public AjaxResult importDiff(@RequestParam Long billId, @RequestParam("file") MultipartFile file)
{
payBillService.importSupplierAmounts(billId, file);
return success();
}
}
@@ -1,137 +0,0 @@
package com.bms.web.controller.price;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.bms.common.core.controller.BaseController;
import com.bms.common.core.domain.AjaxResult;
import com.bms.common.core.page.TableDataInfo;
import com.bms.price.domain.PayPayment;
import com.bms.price.domain.PayPaymentAlloc;
import com.bms.price.service.IPayPaymentService;
/**
* 账单管理下的应付付款。
*/
@RestController
@RequestMapping("/price/payPayment")
public class PayPaymentController extends BaseController
{
@Autowired
private IPayPaymentService payPaymentService;
/**
* 分页查询应付付款。
*
* @param query 付款单号、供应商、状态、币种
* @return 付款单列表
*/
@PreAuthorize("@ss.hasPermi('price:payPayment:list')")
@GetMapping("/list")
public TableDataInfo list(PayPayment query)
{
startPage();
List<PayPayment> list = payPaymentService.selectPayPaymentList(query);
return getDataTable(list);
}
/**
* 付款单详情,含核销明细。
*
* @param paymentId 付款单ID
* @return 付款单
*/
@PreAuthorize("@ss.hasPermi('price:payPayment:query')")
@GetMapping("/{paymentId}")
public AjaxResult detail(@PathVariable Long paymentId)
{
return success(payPaymentService.selectPayPaymentDetail(paymentId));
}
/**
* 查询这个供应商、这个币种下还能核销的账单。
*
* @param supplierId 供应商ID
* @param currency 币种
* @return 未付账单
*/
@PreAuthorize("@ss.hasPermi('price:payPayment:query')")
@GetMapping("/open")
public AjaxResult open(Long supplierId, String currency)
{
List<PayPaymentAlloc> list = payPaymentService.selectOpenBills(supplierId, currency);
return success(list);
}
/**
* 登记草稿付款。确认后才核销账单。
*
* @param payment 付款单和核销明细
* @return 付款单
*/
@PreAuthorize("@ss.hasPermi('price:payPayment:add')")
@PostMapping
public AjaxResult create(@RequestBody PayPayment payment)
{
return success(payPaymentService.create(payment));
}
/**
* 确认付款并核销。
*
* @param paymentId 付款单ID
*/
@PreAuthorize("@ss.hasPermi('price:payPayment:confirm')")
@PutMapping("/confirm/{paymentId}")
public AjaxResult confirm(@PathVariable Long paymentId)
{
payPaymentService.confirm(paymentId);
return success();
}
/**
* 作废草稿付款。已确认的付款不在这里作废。
*
* @param paymentId 付款单ID
*/
@PreAuthorize("@ss.hasPermi('price:payPayment:void')")
@PutMapping("/void/{paymentId}")
public AjaxResult voidPayment(@PathVariable Long paymentId)
{
payPaymentService.voidPayment(paymentId);
return success();
}
/**
* 反核销。账单未付金额恢复。
*
* @param paymentId 付款单ID
*/
@PreAuthorize("@ss.hasPermi('price:payPayment:confirm')")
@PutMapping("/reverse/{paymentId}")
public AjaxResult reverse(@PathVariable Long paymentId)
{
payPaymentService.reverse(paymentId);
return success();
}
/**
* 退款。冲回核销,付款单改为已退款。
*
* @param paymentId 付款单ID
*/
@PreAuthorize("@ss.hasPermi('price:payPayment:confirm')")
@PutMapping("/refund/{paymentId}")
public AjaxResult refund(@PathVariable Long paymentId)
{
payPaymentService.refund(paymentId);
return success();
}
}
@@ -22,7 +22,7 @@ import com.bms.common.core.controller.BaseController;
import com.bms.common.core.domain.AjaxResult;
import com.bms.common.core.page.TableDataInfo;
import com.bms.common.enums.BusinessType;
import com.bms.common.enums.PaymentMesEnum;
import com.bms.common.exception.ServiceException;
import com.bms.common.utils.file.FileUtils;
import com.bms.common.utils.poi.ExcelUtil;
import com.bms.price.domain.ChannelProductMap;
@@ -30,10 +30,8 @@ import com.bms.price.domain.PayProduct;
import com.bms.price.domain.PayProductTerm;
import com.bms.price.domain.PaySaveCommand;
import com.bms.price.pay.PayDetailExporter;
import com.bms.price.domain.PaySurchargeRuleSave;
import com.bms.price.service.IPayOrderCostService;
import com.bms.price.service.IPayProductService;
import com.bms.price.service.IPaySurchargeRuleService;
import jakarta.servlet.http.HttpServletResponse;
/**
@@ -49,9 +47,6 @@ public class PayProductController extends BaseController
@Autowired
private IPayOrderCostService payOrderCostService;
@Autowired
private IPaySurchargeRuleService paySurchargeRuleService;
@PreAuthorize("@ss.hasPermi('price:pay:list')")
@GetMapping("/list")
public TableDataInfo list(PayProduct query)
@@ -89,10 +84,9 @@ public class PayProductController extends BaseController
@PreAuthorize("@ss.hasAnyPermi('price:billing:bill,price:pay:edit')")
@Log(title = "应付账单", businessType = BusinessType.INSERT)
@PostMapping("/order/{billId}")
public AjaxResult generateOrderCost(@PathVariable Long billId)
public AjaxResult generateOrderCost(@PathVariable String billId)
{
payOrderCostService.generate(billId);
return success("操作成功");
return success(payOrderCostService.generate(billId));
}
/** 下载某一版导入时上传的源文件 */
@@ -107,31 +101,6 @@ public class PayProductController extends BaseController
payProductService.writeSource(productId, response.getOutputStream());
}
/** 产品上的附加费计费条件。不按价格版本拆开。 */
@PreAuthorize("@ss.hasAnyPermi('price:pay:query,price:pay:edit')")
@GetMapping("/surchargeRule")
public AjaxResult surchargeRule(@RequestParam Long productId, @RequestParam(required = false) Long termId)
{
return success(paySurchargeRuleService.list(productId, termId));
}
/** 这个产品上的附加费配置操作记录。 */
@PreAuthorize("@ss.hasAnyPermi('price:pay:query,price:pay:edit')")
@GetMapping("/surchargeRule/log")
public AjaxResult surchargeRuleLog(@RequestParam Long productId, @RequestParam(required = false) Long termId)
{
return success(paySurchargeRuleService.logs(productId, termId));
}
@PreAuthorize("@ss.hasPermi('price:pay:edit')")
@Log(title = "应付附加费计费条件", businessType = BusinessType.UPDATE)
@PostMapping("/surchargeRule")
public AjaxResult saveSurchargeRule(@RequestBody PaySurchargeRuleSave command)
{
paySurchargeRuleService.save(command);
return success();
}
@PreAuthorize("@ss.hasPermi('price:pay:query')")
@GetMapping("/{productId}")
public AjaxResult getInfo(@PathVariable Long productId)
@@ -291,16 +260,16 @@ public class PayProductController extends BaseController
{
if (file == null || file.isEmpty())
{
throw PaymentMesEnum.BMS_PAY_PICK_FILE.exception();
throw new ServiceException("请选择价卡文件");
}
String filename = file.getOriginalFilename();
if (filename == null || !filename.toLowerCase(Locale.ROOT).endsWith(".xlsx"))
{
throw PaymentMesEnum.BMS_PAY_XLSX.exception();
throw new ServiceException("请上传xlsx价卡");
}
if (file.getSize() > 30L * 1024 * 1024)
{
throw PaymentMesEnum.BMS_PAY_FILE_30M.exception();
throw new ServiceException("价卡文件不能超过30MB");
}
}
@@ -55,7 +55,7 @@ public class PriceSetController extends BaseController
@GetMapping("/list")
public TableDataInfo list(PrcPriceSet set)
{
// 普通套餐模块:该列表不混入专属报价。
// Ordinary package module: never mix exclusive quotes into this list.
set.setSetCategory(PrcPriceSet.CATEGORY_PACKAGE);
startPage();
List<PrcPriceSet> list = priceSetService.selectPrcPriceSetList(set);
@@ -83,7 +83,7 @@ public class PriceSetController extends BaseController
set.setCreateBy(getUsername());
set.setUpdateBy(getUsername());
String taskId = priceSetService.submitSaveAsync(set);
// 不要向 success() 直接传裸 String:重载会把它放入 msg 而不是 data。
// Do not pass bare String to success(): overload puts it into msg, not data.
Map<String, Object> payload = new HashMap<String, Object>();
payload.put("taskId", taskId);
return success(payload);
@@ -112,7 +112,7 @@ public class PriceSetController extends BaseController
return toAjax(priceSetService.updatePrcPriceSet(set));
}
/** 仅更新套餐名称和备注。 */
/** Update package name and note only. */
@PreAuthorize("@ss.hasPermi('price:set:edit')")
@Log(title = "价格套餐基本信息", businessType = BusinessType.UPDATE)
@PutMapping("/basic")
@@ -190,7 +190,7 @@ public class PriceSetController extends BaseController
return toAjax(priceSetService.deleteVersion(psvId, getUsername()));
}
/** 某个版本的导入 Excel 附件(prc_price_excel_file.psv_id)。 */
/** Import excel attachments for a version (prc_price_excel_file.psv_id). */
@PreAuthorize("@ss.hasPermi('price:set:list')")
@GetMapping("/version/{psvId}/excelFiles")
public AjaxResult versionExcelFiles(@PathVariable Long psvId)
@@ -240,7 +240,7 @@ public class PriceSetController extends BaseController
return getDataTable(list);
}
/** 某个产品的全部明细行(不分页);供改价抽屉使用。 */
/** All detail rows for one product (no page); used by edit-price drawer. */
@PreAuthorize("@ss.hasPermi('price:set:list')")
@GetMapping("/detail/byProduct")
public AjaxResult detailByProduct(@RequestParam("psvId") Long psvId,
@@ -249,7 +249,7 @@ public class PriceSetController extends BaseController
return success(priceSetService.selectDetailListByProduct(psvId, productCode));
}
/** 某个版本的全部明细行(不分页);批量预取,避免 N 次 byProduct 调用。 */
/** All detail rows for a version (no page); batch prefetch instead of N byProduct calls. */
@PreAuthorize("@ss.hasPermi('price:set:list')")
@GetMapping("/detail/byVersion")
public AjaxResult detailByVersion(@RequestParam("psvId") Long psvId)
@@ -304,7 +304,7 @@ public class PriceSetController extends BaseController
}
/**
* 预览从产品库拉取(不写库)。用于新建向导完成前的预览。
* Preview pull product library (no DB write). For create wizard before finish.
*/
@PreAuthorize("@ss.hasPermi('price:set:edit') or @ss.hasPermi('price:set:forceEdit') or @ss.hasPermi('price:set:add')")
@PostMapping("/detail/previewPullFromLibrary")
@@ -314,8 +314,8 @@ public class PriceSetController extends BaseController
}
/**
* 预览 Excel 导入(不写库)。可选 detailsJson 用于 updateOnly / 构建价格时的合并。
* 可选 countryCode = 单国家专线 Excel(与产品国家导入一致)。
* Preview Excel import (no DB write). Optional detailsJson for updateOnly / build-price merge.
* Optional countryCode = single-country dedicated-line Excel (same as product country import).
*/
@PreAuthorize("@ss.hasPermi('price:set:edit') or @ss.hasPermi('price:set:forceEdit') or @ss.hasPermi('price:set:add')")
@PostMapping("/detail/previewImportPrices")
@@ -345,7 +345,7 @@ public class PriceSetController extends BaseController
}
/**
* 异步从产品库拉取到版本明细。返回 taskId;轮询 /save/task/{taskId}。
* Async pull product library into version details. Returns taskId; poll /save/task/{taskId}.
*/
@PreAuthorize("@ss.hasPermi('price:set:edit') or @ss.hasPermi('price:set:forceEdit')")
@Log(title = "套餐拉取产品", businessType = BusinessType.IMPORT)
@@ -361,7 +361,7 @@ public class PriceSetController extends BaseController
}
/**
* 异步批量写入明细附加费快照。返回 taskId;轮询 /save/task/{taskId}。
* Async batch write of detail surcharge snapshots. Returns taskId; poll /save/task/{taskId}.
*/
@PreAuthorize("@ss.hasPermi('price:set:edit') or @ss.hasPermi('price:set:forceEdit')")
@Log(title = "套餐附加费", businessType = BusinessType.UPDATE)
@@ -63,8 +63,8 @@ public class ProductController extends BaseController
}
/**
* 产品下拉(非分页)。精简查询,不做列表页状态刷新。
* 供价格套餐 / 计费 / 客户绑定使用;需登录,无额外权限要求。
* Product dropdown (non-paged). Slim query, no list-page status refresh.
* Used by price-set / billing / customer bind; login required, no extra perm.
*/
@GetMapping("/optionselect")
public AjaxResult optionselect(BmsProduct product)
@@ -139,7 +139,7 @@ public class ProductController extends BaseController
return success();
}
/** 将新建页预览明细保存到草稿/待生效/生效版本。 */
/** Persist create-page preview details into draft/pending/effect version. */
@PreAuthorize("@ss.hasPermi('price:product:add') or @ss.hasPermi('price:product:edit')")
@Log(title = "product preview apply", businessType = BusinessType.UPDATE)
@PostMapping("/version/applyPreview")
@@ -148,7 +148,7 @@ public class ProductController extends BaseController
return success(productService.applyCatalogPreview(body));
}
/** 异步并行保存新建页预览。返回 taskId;轮询 /import/task/{taskId}。 */
/** Async parallel save of create-page preview. Returns taskId; poll /import/task/{taskId}. */
@PreAuthorize("@ss.hasPermi('price:product:add') or @ss.hasPermi('price:product:edit')")
@Log(title = "product preview apply", businessType = BusinessType.UPDATE)
@PostMapping("/version/applyPreviewAsync")
@@ -160,7 +160,7 @@ public class ProductController extends BaseController
return success(payload);
}
/** 将导入 Excel 附件保存到草稿/待生效/生效版本(prc_price_excel_file)。 */
/** Save import Excel attachment onto a draft/pending/effect version (prc_price_excel_file). */
@PreAuthorize("@ss.hasPermi('price:product:import')")
@Log(title = "product version excel", businessType = BusinessType.IMPORT)
@PostMapping("/version/{psvId}/excel")
@@ -176,7 +176,7 @@ public class ProductController extends BaseController
return success(row);
}
/** 查询某个版本的 Excel 附件列表。 */
/** List Excel attachments of a version. */
@PreAuthorize("@ss.hasPermi('price:product:query')")
@GetMapping("/version/{psvId}/excelFiles")
public AjaxResult listVersionExcelFiles(@PathVariable Long psvId)
@@ -201,8 +201,8 @@ public class ProductController extends BaseController
}
/**
* 异步导入 Weishi B2C 工作簿。返回 taskId;轮询 /import/task/{taskId}。
* effectiveDate 必填:写入 product / product_country;原有未失效记录在该日期失效。
* Async import Weishi B2C workbook. Returns taskId; poll /import/task/{taskId}.
* effectiveDate is required: written to product / product_country; previous open rows expire at this date.
*/
@Log(title = "产品列表", businessType = BusinessType.IMPORT)
@PreAuthorize("@ss.hasPermi('price:product:import')")
@@ -217,8 +217,8 @@ public class ProductController extends BaseController
}
/**
* 异步导入单国家成本表。返回 taskId;轮询 /import/task/{taskId}。
* effectiveDate 必填:写入 product / product_country;原有未失效记录在该日期失效。
* Async single-country cost-sheet import. Returns taskId; poll /import/task/{taskId}.
* effectiveDate is required: written to product / product_country; previous open rows expire at this date.
*/
@Log(title = "产品列表", businessType = BusinessType.IMPORT)
@PreAuthorize("@ss.hasPermi('price:product:import')")
@@ -233,7 +233,7 @@ public class ProductController extends BaseController
return success(payload);
}
/** 仅解析的异步预览;返回 taskId,轮询 /import/task/{taskId} 获取结果。 */
/** Async parse-only preview; returns taskId, poll /import/task/{taskId} for result. */
@PreAuthorize("@ss.hasPermi('price:product:import')")
@PostMapping("/previewImport")
public AjaxResult previewImport(
@@ -246,7 +246,7 @@ public class ProductController extends BaseController
return success(payload);
}
/** 单国家仅解析的异步预览;返回 taskId,轮询 /import/task/{taskId}。 */
/** Async single-country parse-only preview; returns taskId, poll /import/task/{taskId}. */
@PreAuthorize("@ss.hasPermi('price:product:import')")
@PostMapping("/previewImportCountry")
public AjaxResult previewImportCountry(
@@ -139,7 +139,7 @@ public class RedlineProductController extends BaseController
return success();
}
/** 将新建页预览明细保存到草稿/待生效/生效版本。 */
/** Persist create-page preview details into draft/pending/effect version. */
@PreAuthorize("@ss.hasPermi('price:redline:add') or @ss.hasPermi('price:redline:edit')")
@Log(title = "redline preview apply", businessType = BusinessType.UPDATE)
@PostMapping("/version/applyPreview")
@@ -148,7 +148,7 @@ public class RedlineProductController extends BaseController
return success(productService.applyCatalogPreview(body));
}
/** 异步并行保存新建页预览。返回 taskId;轮询 /import/task/{taskId}。 */
/** Async parallel save of create-page preview. Returns taskId; poll /import/task/{taskId}. */
@PreAuthorize("@ss.hasPermi('price:redline:add') or @ss.hasPermi('price:redline:edit')")
@Log(title = "redline preview apply", businessType = BusinessType.UPDATE)
@PostMapping("/version/applyPreviewAsync")
@@ -160,7 +160,7 @@ public class RedlineProductController extends BaseController
return success(payload);
}
/** 将导入 Excel 附件保存到草稿/待生效/生效版本(prc_price_excel_file)。 */
/** Save import Excel attachment onto a draft/pending/effect version (prc_price_excel_file). */
@PreAuthorize("@ss.hasPermi('price:redline:import')")
@Log(title = "redline version excel", businessType = BusinessType.IMPORT)
@PostMapping("/version/{psvId}/excel")
@@ -176,7 +176,7 @@ public class RedlineProductController extends BaseController
return success(row);
}
/** 查询某个版本的 Excel 附件列表。 */
/** List Excel attachments of a version. */
@PreAuthorize("@ss.hasPermi('price:redline:query')")
@GetMapping("/version/{psvId}/excelFiles")
public AjaxResult listVersionExcelFiles(@PathVariable Long psvId)
@@ -201,8 +201,8 @@ public class RedlineProductController extends BaseController
}
/**
* 异步导入 Weishi B2C 工作簿。返回 taskId;轮询 /import/task/{taskId}。
* effectiveDate 必填:写入 product / product_country;原有未失效记录在该日期失效。
* Async import Weishi B2C workbook. Returns taskId; poll /import/task/{taskId}.
* effectiveDate is required: written to product / product_country; previous open rows expire at this date.
*/
@Log(title = "产品列表", businessType = BusinessType.IMPORT)
@PreAuthorize("@ss.hasPermi('price:redline:import')")
@@ -217,8 +217,8 @@ public class RedlineProductController extends BaseController
}
/**
* 异步导入单国家成本表。返回 taskId;轮询 /import/task/{taskId}。
* effectiveDate 必填:写入 product / product_country;原有未失效记录在该日期失效。
* Async single-country cost-sheet import. Returns taskId; poll /import/task/{taskId}.
* effectiveDate is required: written to product / product_country; previous open rows expire at this date.
*/
@Log(title = "产品列表", businessType = BusinessType.IMPORT)
@PreAuthorize("@ss.hasPermi('price:redline:import')")
@@ -233,7 +233,7 @@ public class RedlineProductController extends BaseController
return success(payload);
}
/** 仅解析的异步预览;返回 taskId,轮询 /import/task/{taskId} 获取结果。 */
/** Async parse-only preview; returns taskId, poll /import/task/{taskId} for result. */
@PreAuthorize("@ss.hasPermi('price:redline:import')")
@PostMapping("/previewImport")
public AjaxResult previewImport(
@@ -246,7 +246,7 @@ public class RedlineProductController extends BaseController
return success(payload);
}
/** 单国家仅解析的异步预览;返回 taskId,轮询 /import/task/{taskId}。 */
/** Async single-country parse-only preview; returns taskId, poll /import/task/{taskId}. */
@PreAuthorize("@ss.hasPermi('price:redline:import')")
@PostMapping("/previewImportCountry")
public AjaxResult previewImportCountry(
@@ -20,7 +20,6 @@ import com.bms.common.core.page.TableDataInfo;
import com.bms.common.enums.BusinessType;
import com.bms.price.domain.PrcSurcharge;
import com.bms.price.domain.PrcSurchargeRel;
import com.bms.price.service.IPayPostcodeService;
import com.bms.price.service.IPrcSurchargeRelService;
import com.bms.price.service.IPrcSurchargeService;
@@ -39,9 +38,6 @@ public class SurchargeController extends BaseController
@Autowired
private IPrcSurchargeRelService surchargeRelService;
@Autowired
private IPayPostcodeService payPostcodeService;
@PreAuthorize("@ss.hasPermi('price:surcharge:list')")
@GetMapping("/list")
public TableDataInfo list(PrcSurcharge surcharge)
@@ -51,15 +47,6 @@ public class SurchargeController extends BaseController
return getDataTable(list);
}
/** 按国家从应付邮编配置拉取区块/分区选项 */
@PreAuthorize("@ss.hasPermi('price:surcharge:list') or @ss.hasPermi('price:surcharge:query') or @ss.hasPermi('price:surcharge:add') or @ss.hasPermi('price:surcharge:edit')")
@GetMapping("/blockOptions")
public AjaxResult blockOptions(@RequestParam(required = false) String countryCode,
@RequestParam(required = false) String countryName)
{
return success(payPostcodeService.selectBlockTitlesByCountry(countryCode, countryName));
}
@PreAuthorize("@ss.hasPermi('price:surcharge:query')")
@GetMapping("/{scId}")
public AjaxResult getInfo(@PathVariable Long scId)
@@ -6,16 +6,16 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.bms.receivable.profit.ArCommissionRate;
import com.bms.receivable.profit.ArCommissionService;
import com.bms.receivable.profit.ArProfitQuery;
import com.bms.receivable.profit.ArProfitService;
import com.bms.receivable.profit.ArWriteoffRate;
import com.bms.ar.profit.ArCommissionRate;
import com.bms.ar.profit.ArCommissionService;
import com.bms.ar.profit.ArProfitQuery;
import com.bms.ar.profit.ArProfitService;
import com.bms.ar.profit.ArWriteoffRate;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.PathVariable;
import jakarta.servlet.http.HttpServletResponse;
import com.bms.receivable.profit.ArProfitRow;
import com.bms.receivable.profit.ArCommissionResult;
import com.bms.ar.profit.ArProfitRow;
import com.bms.ar.profit.ArCommissionResult;
import com.bms.common.utils.poi.ExcelUtil;
import com.bms.price.enums.ProfitAreaRule;
import com.bms.common.annotation.Log;
@@ -47,7 +47,7 @@ public class SysFileTemplateController extends BaseController
return success(fileTemplateService.selectSysFileTemplateById(templateId));
}
/** 按文件编码获取元信息(需登录,供导入弹窗使用)。 */
/** Meta by file code (authenticated; used by import dialogs). */
@GetMapping("/code/{fileCode}")
public AjaxResult getByCode(@PathVariable String fileCode)
{
@@ -91,7 +91,7 @@ public class SysFileTemplateController extends BaseController
}
/**
* 按 file_code 下载。需要登录,但不校验管理员权限,便于导入页面调用。
* Download by file_code. Login required; no admin perm so import pages can use it.
*/
@GetMapping("/download/{fileCode}")
public void download(@PathVariable String fileCode, HttpServletResponse response) throws Exception
@@ -1,204 +0,0 @@
package com.bms.web.controller.tool;
import java.util.List;
import java.util.Map;
import org.quartz.SchedulerException;
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.DeleteMapping;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.bms.common.annotation.Log;
import com.bms.common.constant.ScheduleConstants;
import com.bms.common.core.controller.BaseController;
import com.bms.common.core.domain.AjaxResult;
import com.bms.common.core.page.TableDataInfo;
import com.bms.common.enums.BusinessType;
import com.bms.common.exception.ServiceException;
import com.bms.common.exception.job.TaskException;
import com.bms.common.utils.StringUtils;
import com.bms.quartz.domain.SysJob;
import com.bms.quartz.service.ISysJobService;
import com.bms.quartz.util.CronUtils;
import com.bms.system.domain.SysDataMigration;
import com.bms.system.domain.SysDataMigrationLog;
import com.bms.system.service.ISysDataMigrationService;
/** 系统工具 - 数据迁移:保存 SQL、手动/定时执行(Quartz) */
@RestController
@RequestMapping("/tool/migration")
public class DataMigrationController extends BaseController
{
private static final String JOB_GROUP = "DATA_MIGRATION";
@Autowired
private ISysDataMigrationService migrationService;
@Autowired
private ISysJobService jobService;
@PreAuthorize("@ss.hasPermi('tool:migration:list')")
@GetMapping("/list")
public TableDataInfo list(SysDataMigration query)
{
startPage();
List<SysDataMigration> list = migrationService.selectSysDataMigrationList(query);
return getDataTable(list);
}
@PreAuthorize("@ss.hasPermi('tool:migration:query')")
@GetMapping("/{migrationId}")
public AjaxResult getInfo(@PathVariable Long migrationId)
{
return success(migrationService.selectSysDataMigrationById(migrationId));
}
@PreAuthorize("@ss.hasPermi('tool:migration:add')")
@Log(title = "数据迁移", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody SysDataMigration row) throws SchedulerException, TaskException
{
row.setCreateBy(getUsername());
migrationService.insertSysDataMigration(row);
syncJob(row);
return success(row);
}
@PreAuthorize("@ss.hasPermi('tool:migration:edit')")
@Log(title = "数据迁移", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@Validated @RequestBody SysDataMigration row) throws SchedulerException, TaskException
{
row.setUpdateBy(getUsername());
migrationService.updateSysDataMigration(row);
SysDataMigration fresh = migrationService.selectSysDataMigrationById(row.getMigrationId());
syncJob(fresh);
return success(fresh);
}
@PreAuthorize("@ss.hasPermi('tool:migration:remove')")
@Log(title = "数据迁移", businessType = BusinessType.DELETE)
@DeleteMapping("/{migrationIds}")
public AjaxResult remove(@PathVariable Long[] migrationIds) throws SchedulerException
{
if (migrationIds != null)
{
for (Long id : migrationIds)
{
SysDataMigration row = migrationService.selectSysDataMigrationById(id);
if (row != null)
{
removeJob(row);
}
}
}
return toAjax(migrationService.deleteSysDataMigrationByIds(migrationIds));
}
@PreAuthorize("@ss.hasPermi('tool:migration:run')")
@Log(title = "数据迁移执行", businessType = BusinessType.OTHER)
@PostMapping("/run/{migrationId}")
public AjaxResult run(@PathVariable Long migrationId)
{
Map<String, Object> result = migrationService.executeMigration(migrationId, "MANUAL", getUsername());
return success(result);
}
@PreAuthorize("@ss.hasPermi('tool:migration:log')")
@GetMapping("/log/list")
public TableDataInfo logList(SysDataMigrationLog query)
{
startPage();
List<SysDataMigrationLog> list = migrationService.selectSysDataMigrationLogList(query);
return getDataTable(list);
}
private void syncJob(SysDataMigration row) throws SchedulerException, TaskException
{
if (row == null || row.getMigrationId() == null)
{
return;
}
boolean enable = "0".equals(row.getStatus()) && StringUtils.isNotEmpty(row.getCronExpression());
if (enable && !CronUtils.isValid(row.getCronExpression()))
{
throw new ServiceException("Cron 表达式不正确");
}
if (!enable)
{
removeJob(row);
return;
}
String invokeTarget = "dataMigrationTask.executeById(" + row.getMigrationId() + "L)";
String jobName = "data-migration-" + row.getMigrationId();
if (row.getJobId() != null)
{
SysJob existed = jobService.selectJobById(row.getJobId());
if (existed != null)
{
existed.setJobName(jobName);
existed.setJobGroup(JOB_GROUP);
existed.setInvokeTarget(invokeTarget);
existed.setCronExpression(row.getCronExpression());
existed.setMisfirePolicy("3");
existed.setConcurrent("1");
existed.setUpdateBy(getUsername());
existed.setRemark(row.getMigrationName());
jobService.updateJob(existed);
existed.setStatus(ScheduleConstants.Status.NORMAL.getValue());
jobService.changeStatus(existed);
return;
}
}
SysJob job = new SysJob();
job.setJobName(jobName);
job.setJobGroup(JOB_GROUP);
job.setInvokeTarget(invokeTarget);
job.setCronExpression(row.getCronExpression());
job.setMisfirePolicy("3");
job.setConcurrent("1");
job.setCreateBy(getUsername());
job.setRemark(row.getMigrationName());
jobService.insertJob(job);
job.setStatus(ScheduleConstants.Status.NORMAL.getValue());
jobService.changeStatus(job);
SysDataMigration patch = new SysDataMigration();
patch.setMigrationId(row.getMigrationId());
patch.setJobId(job.getJobId());
patch.setUpdateBy(getUsername());
migrationService.updateSysDataMigration(patch);
row.setJobId(job.getJobId());
}
private void removeJob(SysDataMigration row) throws SchedulerException
{
if (row == null || row.getJobId() == null)
{
return;
}
SysJob job = jobService.selectJobById(row.getJobId());
if (job != null)
{
jobService.deleteJob(job);
}
SysDataMigration patch = new SysDataMigration();
patch.setMigrationId(row.getMigrationId());
patch.setJobId(null);
// keep job_id clear: update XML only sets jobId when non-null; force via dedicated clear
clearJobId(row.getMigrationId());
}
private void clearJobId(Long migrationId)
{
migrationService.clearJobId(migrationId);
}
}
@@ -42,14 +42,14 @@ bms:
order-source-sync-threads: 16
# Order-list local rebill workers (2~24). Pair with Druid maxActive >= threads + 8.
local-rebill-threads: 16
# Auto-rebill: every hour all day, pull 500 (all statuses) -> bill -> next 500 until empty.
# Empty page => no billing. Window [start-hour, end-hour).
# Night auto-rebill: hours 0-8, pull 500 (all statuses) -> bill -> next 500 until empty / 09:00.
# Empty page => no billing.
night-retry:
enabled: true
start-hour: 0
end-hour: 24
end-hour: 9
batch-size: 500
cron: "0 0 * * * ?"
cron: "0 0/1 0-8 * * ?"
oracle:
# SLAVE datasource Oracle main-order table (schema.table allowed)
order-table: WEISHI_USER.TAB_ORDER
@@ -191,7 +191,7 @@ xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice,/tool/migration
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
@@ -228,44 +228,6 @@ class PayCostCalculatorTest
assertTrue(hasFee(result, "偏远附加费", "0.70"));
}
@Test
void countryRemoteHitWithoutAZoneUsesTheFeePostcodeTier()
{
PayProduct product = new PayProduct();
product.setProductName("CTT 48H");
product.setCurrency("EUR");
product.setWeightBands(new ArrayList<>(List.of(band("0", "5", "2.05", "马德里"))));
PaySurcharge balearic = fee(1L, "偏远附加费", "7.1", "EUR", "POSTCODE", "07");
balearic.setZoneName("Balearic Islands 07邮编开头");
balearic.setPostcodeParam("07");
PaySurcharge canary = fee(2L, "偏远附加费", "8.9", "EUR", "POSTCODE", "35,38");
canary.setZoneName("Canary Islands 35、38邮编开头");
canary.setPostcodeParam("35,38");
PaySurcharge extra = fee(3L, "偏远附加费", "4", "EUR", "EXTRA_KG", "1");
extra.setZoneName("Balearic Islands 07邮编开头");
extra.setPostcodeParam("07");
extra.setPriceUnit("EUR/KG");
product.setSurcharges(List.of(balearic, canary, extra));
List<PayRemoteBook.Hit> hits = List.of(new PayRemoteBook.Hit("", null, "", ""));
PayOrderCostResult light = PayCostCalculator.calculate(product, new BigDecimal("0.8"),
null, null, null, "07008", "马德里", null, 1, hits, false, false, "ES", "西班牙");
assertTrue(hasFee(light, "偏远附加费", "7.1"));
assertTrue(light.getFees().stream().noneMatch(fee -> fee.getFeeName().equals("偏远附加费")
&& fee.getAmount().compareTo(new BigDecimal("8.9")) == 0));
assertTrue(light.getFees().stream().noneMatch(fee -> fee.getAmount().compareTo(new BigDecimal("4")) == 0));
PayOrderCostResult heavy = PayCostCalculator.calculate(product, new BigDecimal("3"),
null, null, null, "07008", "马德里", null, 1, hits, false, false, "ES", "西班牙");
assertTrue(hasFee(heavy, "偏远附加费", "7.1"));
assertTrue(hasFee(heavy, "偏远附加费", "8"));
List<PayRemoteBook.Hit> named = List.of(new PayRemoteBook.Hit("", null, "", "Aland Islands"));
PayOrderCostResult skipped = PayCostCalculator.calculate(product, new BigDecimal("0.8"),
null, null, null, "07008", "马德里", null, 1, named, false, false, "ES", "西班牙");
assertTrue(skipped.getFees().stream().noneMatch(fee -> "偏远附加费".equals(fee.getFeeName())));
}
@Test
void remoteHitsFollowTheProductGroupAndCountry()
{
@@ -1,212 +0,0 @@
package com.bms.price.pay;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import com.bms.price.domain.PayOrderCostResult;
import com.bms.price.domain.PayProduct;
import com.bms.price.domain.PaySurcharge;
import com.bms.price.domain.PaySurchargeRule;
import com.bms.price.domain.PayWeightBand;
/** 用 DE-iMile 价卡和附加费配置,按测试订单的重量、尺寸、日期测算。 */
class PaySurchargeConfigCalcTest
{
@Test
void lightOctoberOrderChargesFreightAndCarbonOnly()
{
PayOrderCostResult result = PayCostCalculator.calculate(imile(), new BigDecimal("0.451"),
new BigDecimal("35.70"), new BigDecimal("32.80"), new BigDecimal("4.00"),
null, null, day(2026, 10, 17), 8);
assertTrue(hasFee(result, "基础运费", "2.99"));
assertTrue(hasFee(result, "CO₂附加费", "0.03"));
assertTrue(result.getFees().stream().noneMatch(fee -> fee.getFeeName().contains("超尺寸")));
assertTrue(result.getFees().stream().noneMatch(fee -> fee.getFeeName().contains("旺季")));
assertEquals(new BigDecimal("3.02"), result.getPayTotal());
}
@Test
void heavierOctoberOrderUsesTheNextWeightBand()
{
PayOrderCostResult result = PayCostCalculator.calculate(imile(), new BigDecimal("1.246"),
new BigDecimal("41.10"), new BigDecimal("34.20"), new BigDecimal("6.10"),
null, null, day(2026, 10, 17), 5);
assertTrue(hasFee(result, "基础运费", "3.12"));
assertTrue(hasFee(result, "CO₂附加费", "0.03"));
assertEquals(new BigDecimal("3.15"), result.getPayTotal());
}
@Test
void oversizeAndPeakSeasonUseTheConfiguredRules()
{
PayOrderCostResult oversize = PayCostCalculator.calculate(imile(), new BigDecimal("1.246"),
new BigDecimal("130"), new BigDecimal("70"), new BigDecimal("40"),
null, null, day(2026, 11, 12), 5);
assertTrue(hasFee(oversize, "基础运费", "3.12"));
assertTrue(hasFee(oversize, "CO₂附加费", "0.03"));
assertTrue(hasFee(oversize, "异形件/超尺寸处理费", "3.60"));
assertTrue(hasFee(oversize, "旺季附加费", "0.19"));
assertEquals(new BigDecimal("6.94"), oversize.getPayTotal());
PayOrderCostResult peak = PayCostCalculator.calculate(dhl(), new BigDecimal("0.664"),
new BigDecimal("25.50"), new BigDecimal("18.90"), new BigDecimal("7.80"),
null, null, day(2026, 11, 4), 11);
assertTrue(hasFee(peak, "基础运费", "3.13"));
assertTrue(hasFee(peak, "燃油附加费", "0.15"));
assertTrue(hasFee(peak, "旺季附加费", "0.19"));
assertTrue(peak.getFees().stream().noneMatch(fee -> "高峰旺季附加费".equals(fee.getFeeName())));
assertTrue(peak.getFees().stream().noneMatch(fee -> fee.getFeeName().contains("超尺寸")));
}
@Test
void sumOverThreeHundredChargesEachExtraTenCentimetres()
{
PayOrderCostResult result = PayCostCalculator.calculate(sumStep(), new BigDecimal("0.039"),
new BigDecimal("300"), new BigDecimal("300"), new BigDecimal("300"),
null, null, null, 8);
assertTrue(hasFee(result, "基础运费", "2.40"));
assertTrue(hasFee(result, "超尺寸附加费", "2.43"));
assertTrue(hasFee(result, "超尺寸附加费", "378.00"));
assertEquals(new BigDecimal("382.83"), result.getPayTotal());
PayOrderCostResult oneStep = PayCostCalculator.calculate(sumStep(), new BigDecimal("0.039"),
new BigDecimal("100"), new BigDecimal("100"), new BigDecimal("101"),
null, null, null, 8);
assertTrue(hasFee(oneStep, "超尺寸附加费", "2.43"));
assertTrue(hasFee(oneStep, "超尺寸附加费", "6.30"));
}
private static boolean hasFee(PayOrderCostResult result, String name, String amount)
{
return result.getFees().stream().anyMatch(fee -> name.equals(fee.getFeeName())
&& fee.getAmount().compareTo(new BigDecimal(amount)) == 0);
}
private static Date day(int year, int month, int day)
{
return Date.from(LocalDate.of(year, month, day).atStartOfDay(ZoneId.systemDefault()).toInstant());
}
private static PayProduct sumStep()
{
PayProduct product = new PayProduct();
product.setProductName("CTT 48H");
product.setCurrency("EUR");
product.setWeightBands(List.of(band("0", "1", "2.40")));
PaySurcharge flat = fee("超尺寸附加费", "2.43", "SIZE");
PaySurcharge step = fee("超尺寸附加费", "6.30", "SIZE");
step.setChargeText("6.3EUR/增加10cm");
product.setSurcharges(List.of(flat, step));
PaySurchargeRule rule = new PaySurchargeRule();
rule.setCalcRule("SIZE");
rule.setEnabled("1");
Map<String, Object> detail = new LinkedHashMap<>();
detail.put("matchMode", "ANY");
detail.put("sumMin", new BigDecimal("300"));
detail.put("exceedStepCm", new BigDecimal("10"));
rule.setDetail(detail);
product.setBillingRules(List.of(rule));
return product;
}
private static PayProduct imile()
{
PayProduct product = new PayProduct();
product.setProductName("DE-iMile");
product.setCurrency("EUR");
product.setWeightBands(List.of(
band("0", "1", "2.99"),
band("1", "2", "3.12")));
product.setSurcharges(List.of(
fee("CO₂附加费", "0.03", "ALWAYS"),
fee("异形件/超尺寸处理费", "3.60", "SIZE"),
fee("旺季附加费", "0.19", "ALWAYS")));
product.setBillingRules(List.of(
always(null, null, null),
always("旺季附加费", "11-01", "12-31"),
sizeBox()));
return product;
}
private static PayProduct dhl()
{
PayProduct product = new PayProduct();
product.setProductName("德国DHL");
product.setCurrency("EUR");
product.setWeightBands(List.of(band("0", "1", "3.13")));
product.setSurcharges(List.of(
fee("燃油附加费", "0.15", "ALWAYS"),
fee("超尺寸超重费", "14.00", "SIZE"),
fee("旺季附加费", "0.19", "ALWAYS"),
fee("高峰旺季附加费", "0.50", "ALWAYS")));
product.setBillingRules(List.of(
always(null, null, null),
always("旺季附加费", "11-01", "12-31"),
always("高峰旺季附加费", "11-24", "12-07"),
sizeBox()));
return product;
}
private static PaySurchargeRule always(String feeName, String start, String end)
{
PaySurchargeRule rule = new PaySurchargeRule();
rule.setCalcRule("ALWAYS");
rule.setEnabled("1");
Map<String, Object> detail = new LinkedHashMap<>();
if (feeName != null)
{
detail.put("feeName", feeName);
detail.put("periodStart", start);
detail.put("periodEnd", end);
}
rule.setDetail(detail);
return rule;
}
private static PaySurchargeRule sizeBox()
{
PaySurchargeRule rule = new PaySurchargeRule();
rule.setCalcRule("SIZE");
rule.setEnabled("1");
Map<String, Object> detail = new LinkedHashMap<>();
detail.put("matchMode", "ANY");
detail.put("boxMode", "OVER");
detail.put("boxLength", new BigDecimal("120"));
detail.put("boxWidth", new BigDecimal("60"));
detail.put("boxHeight", new BigDecimal("60"));
rule.setDetail(detail);
return rule;
}
private static PayWeightBand band(String min, String max, String price)
{
PayWeightBand band = new PayWeightBand();
band.setWeightMin(new BigDecimal(min));
band.setWeightMax(new BigDecimal(max));
band.setWeightLabel(min + "-" + max);
band.setPrice(new BigDecimal(price));
band.setPriceUnit("EUR/ITEM");
return band;
}
private static PaySurcharge fee(String name, String amount, String rule)
{
PaySurcharge fee = new PaySurcharge();
fee.setFeeName(name);
fee.setAmount(new BigDecimal(amount));
fee.setCurrency("EUR");
fee.setCalcRule(rule);
fee.setChargeText(amount + " EUR/ITEM");
return fee;
}
}
@@ -1,122 +0,0 @@
package com.bms.receivable;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import java.math.BigDecimal;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.util.ReflectionTestUtils;
import com.bms.common.core.domain.entity.SysUser;
import com.bms.common.core.domain.model.LoginUser;
import com.bms.common.exception.ServiceException;
import com.bms.receivable.domain.ArBill;
import com.bms.receivable.domain.ArBillLog;
import com.bms.receivable.mapper.ArBillDetailMapper;
import com.bms.receivable.mapper.ArBillExportMapper;
import com.bms.receivable.mapper.ArBillMapper;
import com.bms.receivable.security.ArBillScopeResolver;
import com.bms.receivable.service.IArBillLogService;
import com.bms.receivable.service.impl.ArBillServiceImpl;
class ArBillConfirmTest {
private final ArBillMapper billMapper = mock(ArBillMapper.class);
private final ArBillDetailMapper detailMapper = mock(ArBillDetailMapper.class);
private final ArBillExportMapper exportMapper = mock(ArBillExportMapper.class);
private final ArBillScopeResolver scope = mock(ArBillScopeResolver.class);
private final IArBillLogService logService = mock(IArBillLogService.class);
private final ArBillServiceImpl service = new ArBillServiceImpl();
@BeforeEach
void setUp() {
ReflectionTestUtils.setField(service, "billMapper", billMapper);
ReflectionTestUtils.setField(service, "detailMapper", detailMapper);
ReflectionTestUtils.setField(service, "exportMapper", exportMapper);
ReflectionTestUtils.setField(service, "billScope", scope);
ReflectionTestUtils.setField(service, "logService", logService);
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
new LoginUser(7L, 1L, new SysUser(), Set.of()), null));
}
@AfterEach
void clearSecurity() {
SecurityContextHolder.clearContext();
}
@Test
void readyDraftCanBeConfirmedWithoutReadingDetails() {
ArBill draft = bill("DRAFT", "READY");
draft.setTotalAmount(new BigDecimal("123.45"));
ArBill confirmed = bill("CONFIRMED", "READY");
when(billMapper.selectArBillByIdForUpdate(11L)).thenReturn(draft);
when(billMapper.updateArBillStatus(11L, "CONFIRMED", null, "finance", "DRAFT")).thenReturn(1);
when(billMapper.selectArBillByIdScoped(11L, null)).thenReturn(confirmed);
when(exportMapper.selectByBillId(11L)).thenReturn(List.of());
assertSame(confirmed, service.confirmBill(11L, null, "finance", "request-1"));
verify(scope).requireBillAccessible(11L);
verifyNoInteractions(detailMapper);
verify(logService).record(any(ArBillLog.class));
}
@Test
void unfinishedOrNonDraftBillStillCannotBeConfirmed() {
when(billMapper.selectArBillByIdForUpdate(11L)).thenReturn(
bill("DRAFT", "GENERATING"), bill("CONFIRMED", "READY"));
ServiceException unfinished = assertThrows(ServiceException.class,
() -> service.confirmBill(11L, null, "finance", "request-1"));
assertEquals("账单尚未生成完成,暂不能确认", unfinished.getMessage());
ServiceException notDraft = assertThrows(ServiceException.class,
() -> service.confirmBill(11L, null, "finance", "request-2"));
assertEquals("只有草稿账单可以确认,当前状态为 CONFIRMED", notDraft.getMessage());
verify(billMapper, never()).updateArBillStatus(eq(11L), any(), nullable(String.class), any(), any());
verifyNoInteractions(detailMapper);
}
@Test
void inaccessibleBillStillCannotBeConfirmed() {
when(billMapper.selectArBillByIdForUpdate(11L)).thenReturn(bill("DRAFT", "READY"));
doThrow(new ServiceException("账单不存在或无权访问")).when(scope).requireBillAccessible(11L);
assertThrows(ServiceException.class, () -> service.confirmBill(11L, null, "finance", "request-1"));
verify(billMapper, never()).updateArBillStatus(eq(11L), any(), nullable(String.class), any(), any());
verifyNoInteractions(detailMapper);
}
@Test
void concurrentStatusChangeStillPreventsConfirmation() {
when(billMapper.selectArBillByIdForUpdate(11L)).thenReturn(bill("DRAFT", "READY"));
ServiceException error = assertThrows(ServiceException.class,
() -> service.confirmBill(11L, null, "finance", "request-1"));
assertEquals("账单状态已发生变化,请刷新后重试", error.getMessage());
verify(logService, never()).record(any(ArBillLog.class));
verifyNoInteractions(detailMapper);
}
private static ArBill bill(String status, String generationStatus) {
ArBill bill = new ArBill();
bill.setArBillId(11L);
bill.setStatus(status);
bill.setGenerationStatus(generationStatus);
return bill;
}
}
@@ -1,112 +0,0 @@
package com.bms.receivable;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.test.util.ReflectionTestUtils;
import com.github.pagehelper.PageHelper;
import com.bms.common.config.BmsConfig;
import com.bms.common.core.domain.AjaxResult;
import com.bms.common.core.redis.RedisCache;
import com.bms.price.domain.PrcOrderBill;
import com.bms.receivable.domain.vo.ArBillGenerateQuery;
import com.bms.receivable.security.ArBillScopeResolver;
import com.bms.receivable.service.IArBillService;
import com.bms.receivable.task.ArBillGeneratePreviewExportRunner;
import com.bms.web.controller.receivable.ArBillController;
class ArBillGeneratePreviewTest {
@TempDir
Path tempDir;
@Test
void previewDoesNotCalculateAmountUntilAmountEndpointIsCalled() {
IArBillService service = mock(IArBillService.class);
ArBillController controller = new ArBillController();
ReflectionTestUtils.setField(controller, "billService", service);
ArBillGenerateQuery query = new ArBillGenerateQuery();
when(service.previewBill(query)).thenReturn(List.of(new PrcOrderBill()));
when(service.sumPreviewBill(query)).thenReturn(new BigDecimal("12.34"));
try {
AjaxResult preview = controller.preview(query);
assertFalse(preview.containsKey("totalAmount"));
verify(service, never()).sumPreviewBill(query);
} finally {
PageHelper.clearPage();
}
assertEquals(new BigDecimal("12.34"), controller.previewAmount(query).get("data"));
verify(service).sumPreviewBill(query);
}
@Test
void exportEndsWithTheSumOfTheExportedAmounts() throws Exception {
String previousProfile = BmsConfig.getProfile();
try {
new BmsConfig().setProfile(tempDir.toString());
IArBillService service = mock(IArBillService.class);
ArBillGenerateQuery query = new ArBillGenerateQuery();
when(service.previewBill(same(query), isNull())).thenReturn(List.of(
bill("A", "0.10"), bill("B", "0.20"), bill("C", "-0.05"), bill("D", null)));
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor() {
@Override
public void execute(Runnable task) {
task.run();
}
};
ArBillGeneratePreviewExportRunner runner = new ArBillGeneratePreviewExportRunner(
service, mock(RedisCache.class), mock(ArBillScopeResolver.class), executor);
for (String dateType : List.of("BIZ_DATE", "PICKUP_DATE")) {
query.setDateType(dateType);
String taskId = runner.submit(query, 1L);
Path file = tempDir.resolve("download/arBillGeneratePreviewExport/" + taskId + ".xlsx");
try (InputStream in = Files.newInputStream(file); Workbook workbook = WorkbookFactory.create(in)) {
Sheet sheet = workbook.getSheetAt(0);
Row header = sheet.getRow(0);
Row lastRow = sheet.getRow(sheet.getLastRowNum());
assertEquals("合计", lastRow.getCell(column(header, "订单号")).getStringCellValue());
assertEquals(0.25, lastRow.getCell(column(header, "应收金额")).getNumericCellValue(), 0.000001);
assertEquals(5, sheet.getLastRowNum());
}
}
} finally {
new BmsConfig().setProfile(previousProfile);
}
}
private static PrcOrderBill bill(String orderNo, String amount) {
PrcOrderBill bill = new PrcOrderBill();
bill.setOrderNo(orderNo);
bill.setIncomeTotal(amount == null ? null : new BigDecimal(amount));
return bill;
}
private static int column(Row header, String name) {
for (int i = 0; i < header.getLastCellNum(); i++) {
if (name.equals(header.getCell(i).getStringCellValue())) return i;
}
throw new AssertionError("缺少导出列: " + name);
}
}
@@ -1,11 +1,9 @@
package com.bms.report;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
@@ -13,93 +11,18 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import com.bms.common.core.domain.entity.SysUser;
import com.bms.common.core.domain.model.LoginUser;
import com.bms.receivable.domain.ArBalanceWriteoff;
import com.bms.receivable.domain.ArBill;
import com.bms.receivable.mapper.ArBillMapper;
import com.bms.receivable.mapper.ArCommissionMapper;
import com.bms.receivable.profit.ArCommissionRate;
import com.bms.receivable.profit.ArCommissionResult;
import com.bms.receivable.profit.ArCommissionService;
import com.bms.receivable.profit.ArProfitScope;
import com.bms.receivable.profit.ArProfitScopeResolver;
import com.bms.receivable.profit.ArWriteoffRate;
import com.bms.ar.domain.ArBalanceWriteoff;
import com.bms.ar.domain.ArBill;
import com.bms.ar.mapper.ArCommissionMapper;
import com.bms.ar.profit.ArCommissionRate;
import com.bms.ar.profit.ArCommissionResult;
import com.bms.ar.profit.ArCommissionService;
import com.bms.ar.profit.ArProfitScope;
import com.bms.ar.profit.ArProfitScopeResolver;
import com.bms.common.exception.ServiceException;
class ArCommissionServiceTest
{
@Test
void rateConfirmationRequiresCompleteProfitDataBeforeAnyWrite()
{
List<String> calls = new ArrayList<>();
AtomicReference<Integer> complete = new AtomicReference<>(0);
ArCommissionMapper mapper = (ArCommissionMapper) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class<?>[] { ArCommissionMapper.class }, (proxy, method, args) -> {
calls.add(method.getName());
return switch (method.getName()) {
case "selectBillIdByWriteoffId" -> 25L;
case "isBillProfitDataComplete" -> complete.get();
case "confirmMissingRate", "reverseResults" -> 1;
case "countMissingRates" -> 0;
case "selectSettlementInputs" -> List.of();
default -> throw new AssertionError(method.getName());
};
});
ArBill bill = new ArBill();
bill.setProfitSalesUserId(7L);
bill.setGenerationStatus("READY");
ArBillMapper billMapper = (ArBillMapper) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class<?>[] { ArBillMapper.class }, (proxy, method, args) -> {
if ("selectArBillByIdForUpdate".equals(method.getName())) return bill;
throw new AssertionError(method.getName());
});
ArProfitScopeResolver resolver = new ArProfitScopeResolver() {
@Override public ArProfitScope resolve() { return new ArProfitScope(true, false, false, 1L, 1L); }
};
ArCommissionService service = new ArCommissionService(mapper, resolver, null, billMapper);
ArWriteoffRate input = new ArWriteoffRate();
input.setWriteoffId(18L);
input.setRate(new BigDecimal("5"));
input.setRemark("已核对");
for (String status : List.of("CONFIRMED", "PARTIAL_WRITTEN_OFF", "WRITTEN_OFF"))
{
bill.setStatus(status);
calls.clear();
ServiceException error = assertThrows(ServiceException.class, () -> service.confirmMissingRate(input));
assertTrue(error.getMessage().contains("请先处理后再确认比例"));
assertFalse(calls.contains("confirmMissingRate"));
assertFalse(calls.contains("reverseResults"));
}
complete.set(null);
assertThrows(ServiceException.class, () -> service.confirmMissingRate(input));
complete.set(1);
SysUser user = new SysUser();
user.setUserName("finance");
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(new LoginUser(user, java.util.Set.of()), null));
try
{
bill.setStatus("PARTIAL_WRITTEN_OFF");
calls.clear();
service.confirmMissingRate(input);
assertTrue(calls.contains("confirmMissingRate"));
assertFalse(calls.contains("reverseResults"));
bill.setStatus("WRITTEN_OFF");
calls.clear();
service.confirmMissingRate(input);
assertTrue(calls.indexOf("isBillProfitDataComplete") < calls.indexOf("confirmMissingRate"));
assertTrue(calls.contains("reverseResults"));
assertTrue(calls.contains("selectSettlementInputs"));
}
finally
{
SecurityContextHolder.clearContext();
}
}
@Test
void commissionReadsUseServerResolvedScopeAndManagementRemainsRestricted()
{
@@ -8,47 +8,22 @@ import java.util.Map;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.session.Configuration;
import org.junit.jupiter.api.Test;
import com.bms.receivable.profit.ArProfitQuery;
import com.bms.receivable.profit.ArProfitScope;
import com.bms.receivable.profit.ArCommissionResult;
import com.bms.ar.profit.ArProfitQuery;
import com.bms.ar.profit.ArProfitScope;
import com.bms.ar.profit.ArCommissionResult;
class ArProfitMapperSqlTest
{
@Test
void snapshotCalculationIsScopedToTheGeneratingBill() throws Exception
{
Configuration configuration = new Configuration();
configuration.getTypeAliasRegistry().registerAlias("ArBill", com.bms.receivable.domain.ArBill.class);
for (String resource : new String[] {"mapper/receivable/ArBillScopeMapper.xml", "mapper/receivable/ArBillMapper.xml"})
{
try (InputStream input = getClass().getClassLoader().getResourceAsStream(resource))
{
new XMLMapperBuilder(input, configuration, resource, configuration.getSqlFragments()).parse();
}
}
var bound = configuration.getMappedStatement("com.bms.receivable.mapper.ArBillMapper.updateProfitSnapshot")
.getBoundSql(Map.of("arBillId", 42L, "shenzhenArea", "C", "guangzhouArea", "GZ"));
String sql = bound.getSql();
assertTrue(sql.contains("fd.ar_bill_id = ?"));
assertTrue(sql.contains("d.ar_bill_id = ?"));
assertTrue(sql.contains("b.ar_bill_id = ? and b.generation_status = 'GENERATING'"));
assertTrue(sql.contains("f.sales_amount = d.total_amount"));
assertTrue(sql.contains("a.detail_total != b.total_amount"));
assertTrue(sql.contains("'MISSING_PRICE'"));
assertTrue(sql.contains("'CURRENCY_MISMATCH'"));
assertTrue(sql.contains("'PRICE_OVERFLOW'"));
}
@Test
void commissionInReportSummaryUsesBillFiltersAndDataScope() throws Exception
{
Configuration configuration = new Configuration();
String resource = "mapper/receivable/ArProfitMapper.xml";
String resource = "mapper/ar/ArProfitMapper.xml";
try (InputStream input = getClass().getClassLoader().getResourceAsStream(resource))
{
new XMLMapperBuilder(input, configuration, resource, configuration.getSqlFragments()).parse();
}
var statement = configuration.getMappedStatement("com.bms.receivable.mapper.ArProfitMapper.selectSummary");
var statement = configuration.getMappedStatement("com.bms.ar.mapper.ArProfitMapper.selectSummary");
ArProfitQuery query = new ArProfitQuery();
query.setCustomerId(5L);
query.setBillNo("AR123");
@@ -90,7 +65,7 @@ class ArProfitMapperSqlTest
{
Configuration configuration = new Configuration();
// 提成 Mapper 先加载时也必须能解析引用的报表权限片段。
for (String resource : new String[] {"mapper/receivable/ArCommissionMapper.xml", "mapper/receivable/ArProfitMapper.xml"})
for (String resource : new String[] { "mapper/ar/ArCommissionMapper.xml", "mapper/ar/ArProfitMapper.xml" })
{
try (InputStream input = getClass().getClassLoader().getResourceAsStream(resource))
{
@@ -99,7 +74,7 @@ class ArProfitMapperSqlTest
}
for (String id : new String[] { "selectBills", "selectSummary", "selectBillDetails", "selectFees" })
{
String name = "com.bms.receivable.mapper.ArProfitMapper." + id;
String name = "com.bms.ar.mapper.ArProfitMapper." + id;
String self = configuration.getMappedStatement(name).getBoundSql(Map.of(
"query", new ArProfitQuery(), "scope", new ArProfitScope(false, true, false, 8L, 10L))).getSql();
String manager = configuration.getMappedStatement(name).getBoundSql(Map.of(
@@ -109,26 +84,18 @@ class ArProfitMapperSqlTest
assertTrue(manager.contains("find_in_set"));
assertTrue(manager.contains("b.status in"));
}
String bills = configuration.getMappedStatement("com.bms.receivable.mapper.ArProfitMapper.selectBills")
String bills = configuration.getMappedStatement("com.bms.ar.mapper.ArProfitMapper.selectBills")
.getBoundSql(Map.of("query", new ArProfitQuery(), "scope", new ArProfitScope(true, false, false, 8L, 10L))).getSql();
assertTrue(bills.contains("from ar_bill b"));
assertTrue(bills.contains("b.bill_name"));
assertTrue(bills.contains("b.profit_calc_status = 'READY'"));
assertTrue(bills.contains("b.product_amount"));
assertTrue(bills.contains("b.redline_amount"));
assertFalse(bills.contains("ar_bill_detail"));
assertFalse(bills.contains("order_no"));
String summary = configuration.getMappedStatement("com.bms.receivable.mapper.ArProfitMapper.selectSummary")
.getBoundSql(Map.of("query", new ArProfitQuery(), "scope", new ArProfitScope(true, false, false, 8L, 10L))).getSql();
assertFalse(summary.contains("ar_bill_detail"));
assertTrue(summary.contains("ar_profit_commission_result"));
String billCommission = configuration.getMappedStatement("com.bms.receivable.mapper.ArCommissionMapper.selectCommissionSummary")
String billCommission = configuration.getMappedStatement("com.bms.ar.mapper.ArCommissionMapper.selectCommissionSummary")
.getBoundSql(Map.of("billId", 9L, "scope", new ArProfitScope(true, false, false, 8L, 10L))).getSql();
assertTrue(billCommission.contains("r.ar_bill_id = ?"));
assertTrue(billCommission.contains("then null"));
for (String id : new String[] { "selectResults", "selectCommissionSummary" })
{
var statement = configuration.getMappedStatement("com.bms.receivable.mapper.ArCommissionMapper." + id);
var statement = configuration.getMappedStatement("com.bms.ar.mapper.ArCommissionMapper." + id);
String self = statement.getBoundSql(Map.of("billId", 9L, "includeReversed", true,
"scope", new ArProfitScope(false, true, false, 8L, 10L))).getSql();
String department = statement.getBoundSql(Map.of("includeReversed", false,
@@ -146,7 +113,7 @@ class ArProfitMapperSqlTest
assertFalse(all.contains("b.profit_sales_user_id = ?"));
assertFalse(all.contains("find_in_set"));
}
var salesSql = configuration.getMappedStatement("com.bms.receivable.mapper.ArProfitMapper.selectSalesOptions")
var salesSql = configuration.getMappedStatement("com.bms.ar.mapper.ArProfitMapper.selectSalesOptions")
.getBoundSql(Map.of("keyword", "sales"));
assertTrue(salesSql.getSql().contains("from sys_user u"));
assertTrue(salesSql.getSql().contains("u.nick_name like"));
@@ -154,22 +121,11 @@ class ArProfitMapperSqlTest
assertFalse(salesSql.getSql().contains("ar_bill"));
assertFalse(salesSql.getSql().contains("sys_role"));
assertTrue(salesSql.getSql().contains("limit 50"));
String settlement = configuration.getMappedStatement("com.bms.receivable.mapper.ArCommissionMapper.selectSettlementInputs")
String settlement = configuration.getMappedStatement("com.bms.ar.mapper.ArCommissionMapper.selectSettlementInputs")
.getBoundSql(Map.of("billId", 1L)).getSql();
assertTrue(settlement.contains("w.status = 'EFFECTIVE'"));
assertTrue(settlement.contains("b.status = 'WRITTEN_OFF'"));
String validation = configuration.getMappedStatement("com.bms.receivable.mapper.ArCommissionMapper.isBillProfitDataComplete")
.getBoundSql(Map.of("billId", 1L)).getSql();
assertTrue(validation.contains("detail.ar_bill_id = ?"));
assertTrue(validation.contains("d.ar_bill_id = ?"));
assertTrue(validation.contains("a.detail_total = b.total_amount"));
assertTrue(validation.contains("f.sales_amount = d.total_amount"));
assertTrue(validation.contains("f.fee_currency = d.currency"));
assertTrue(validation.contains("f.product_complete = 1"));
assertTrue(validation.contains("f.redline_complete = 1"));
assertFalse(validation.contains("b.status = 'WRITTEN_OFF'"));
assertFalse(validation.contains("profit_calc_status"));
var insert = configuration.getMappedStatement("com.bms.receivable.mapper.ArCommissionMapper.insertResults")
var insert = configuration.getMappedStatement("com.bms.ar.mapper.ArCommissionMapper.insertResults")
.getBoundSql(Map.of("items", java.util.List.of(new ArCommissionResult(), new ArCommissionResult()), "operator", "test"));
assertTrue(insert.getSql().contains("ar_profit_commission_result"));
assertTrue(insert.getParameterMappings().size() == 16);
@@ -4,9 +4,10 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.Test;
import com.bms.receivable.profit.ArProfitScopeResolver;
import com.bms.ar.profit.ArProfitScopeResolver;
import com.bms.common.core.domain.entity.SysRole;
import com.bms.common.core.domain.entity.SysUser;
import com.bms.common.core.domain.model.LoginUser;
@@ -6,9 +6,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import java.math.BigDecimal;
import java.util.List;
import com.bms.receivable.profit.CommissionCalculator;
import com.bms.receivable.profit.ArCommissionResult;
import com.bms.receivable.profit.CommissionCalculator.Writeoff;
import com.bms.ar.profit.CommissionCalculator;
import com.bms.ar.profit.ArCommissionResult;
import com.bms.ar.profit.CommissionCalculator.Writeoff;
import org.junit.jupiter.api.Test;
class CommissionCalculatorTest
@@ -164,11 +164,10 @@ public class Constants
* 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
* com.bms.customer 覆盖客户/供应商资料同步、客户授信到期等业务任务包;
* com.bms.masterdata.task 为渠道产品主数据同步任务;com.bms.notify.task 为客户超额通知;
* com.bms.price.task 为价格/产品状态刷新任务;com.bms.system.migration 为数据迁移任务
* com.bms.price.task 为价格/产品状态刷新任务
*/
public static final String[] JOB_WHITELIST_STR = { "com.bms.quartz.task", "com.bms.customer",
"com.bms.masterdata.task", "com.bms.notify.task", "com.bms.price.task",
"com.bms.system.migration" };
"com.bms.masterdata.task", "com.bms.notify.task", "com.bms.price.task" };
/**
* 定时任务违规的字符
@@ -1,236 +0,0 @@
package com.bms.common.enums;
import java.util.HashMap;
import java.util.Map;
import com.bms.common.exception.ServiceException;
/**
* 应付报错。只收没有拼接变量的固定提示。
* code 以 BMS_PAY 开头,name 是这条提示用在哪一步,cnMes 是弹出的中文,enMes 先留空。
*/
public enum PaymentMesEnum
{
BMS_PAY_PICK_ORDER("BMS_PAY_PICK_ORDER", "生成应付校验订单", "请选择订单"),
BMS_PAY_ORDER_MISS("BMS_PAY_ORDER_MISS", "生成应付校验订单", "订单不存在"),
BMS_PAY_NO_COST("BMS_PAY_NO_COST", "生成应付计算结果", "没有算出应付费用"),
BMS_PAY_NO_ZONE("BMS_PAY_NO_ZONE", "生成应付匹配邮编", "邮编没有对上价卡地区"),
BMS_PAY_NO_CHANNEL("BMS_PAY_NO_CHANNEL", "生成应付匹配渠道", "订单没有渠道服务商"),
BMS_PAY_CH_SYNC("BMS_PAY_CH_SYNC", "生成应付匹配渠道", "订单没有渠道服务商,或渠道尚未同步"),
BMS_PAY_NO_PRODUCT("BMS_PAY_NO_PRODUCT", "应付计费匹配产品", "没有应付产品"),
BMS_PAY_NO_WEIGHT("BMS_PAY_NO_WEIGHT", "订单判断重量", "订单没有计费重"),
BMS_PAY_PICK_PROD("BMS_PAY_PICK_PROD", "应付产品保存", "请先勾选要保存的产品"),
BMS_PAY_PROD_LIMIT("BMS_PAY_PROD_LIMIT", "应付产品保存", "一次最多保存80个产品"),
BMS_PAY_PROD_FIELD("BMS_PAY_PROD_FIELD", "应付产品保存", "产品名称、工作表和文件摘要不能为空"),
BMS_PAY_PICK_EDIT("BMS_PAY_PICK_EDIT", "应付产品修改", "请选择要修改的产品"),
BMS_PAY_PROD_MISS("BMS_PAY_PROD_MISS", "应付产品修改", "产品不存在或已删除"),
BMS_PAY_TPL_ONLY("BMS_PAY_TPL_ONLY", "应付产品修改", "渠道价格请在渠道页面保存,这里只改公共模板"),
BMS_PAY_SAVE_CH("BMS_PAY_SAVE_CH", "渠道应付价格", "请先保存渠道,再维护价格"),
BMS_PAY_NO_PRICE("BMS_PAY_NO_PRICE", "渠道应付价格", "没有可保存的价格"),
BMS_PAY_REIMPORT("BMS_PAY_REIMPORT", "应付价卡导入", "重新导入时请使用单个产品文件,或先选定渠道上的应付产品"),
BMS_PAY_PICK_IMP("BMS_PAY_PICK_IMP", "应付价卡导入", "请先选择一个已导入的应付产品"),
BMS_PAY_DICT_EMPTY("BMS_PAY_DICT_EMPTY", "应付产品字典", "应付产品字典还没有数据"),
BMS_PAY_TERM_START("BMS_PAY_TERM_START", "应付有效期", "请填写生效开始"),
BMS_PAY_TERM_END("BMS_PAY_TERM_END", "应付有效期", "生效结束不能早于生效开始"),
BMS_PAY_PICK_VER("BMS_PAY_PICK_VER", "应付有效期", "请选择要修改的版本"),
BMS_PAY_TERM_MISS("BMS_PAY_TERM_MISS", "应付有效期", "有效期不存在"),
BMS_PAY_TERM_STAT("BMS_PAY_TERM_STAT", "应付有效期", "状态只能是待生效、生效或已失效"),
BMS_PAY_FILE_DOWN("BMS_PAY_FILE_DOWN", "应付价卡源文件", "源文件下载失败"),
BMS_PAY_NO_SOURCE("BMS_PAY_NO_SOURCE", "应付价卡源文件", "这一版没有可下载的源文件"),
BMS_PAY_FILE_UP("BMS_PAY_FILE_UP", "应付价卡源文件", "价卡文件上传失败"),
BMS_PAY_FILE_WAIT("BMS_PAY_FILE_WAIT", "应付价卡源文件", "源文件还没有上传成功,请重新选择价卡后再保存"),
BMS_PAY_CH_FIRST("BMS_PAY_CH_FIRST", "渠道绑定产品", "请先保存渠道,再选择产品"),
BMS_PAY_CH_MISS("BMS_PAY_CH_MISS", "渠道绑定产品", "渠道不存在或渠道编码为空"),
BMS_PAY_PICK_CH("BMS_PAY_PICK_CH", "渠道绑定产品", "请先勾选渠道"),
BMS_PAY_PICK_SIDE("BMS_PAY_PICK_SIDE", "渠道绑定产品", "请至少选择应付或应收产品"),
BMS_PAY_BIND_NONE("BMS_PAY_BIND_NONE", "渠道绑定产品", "没有可绑定的渠道"),
BMS_PAY_CH_PROD("BMS_PAY_CH_PROD", "渠道应付产品", "渠道还没有选择应付产品"),
BMS_PAY_OUT_TERM("BMS_PAY_OUT_TERM", "应付价格有效期", "订单时间没有落在价格有效期内"),
BMS_PAY_TWO_VER("BMS_PAY_TWO_VER", "应付价格对比", "请选择两个价格版本"),
BMS_PAY_AR_AP("BMS_PAY_AR_AP", "收付类型", "收付类型只能是应收AR或应付AP"),
BMS_PAY_AP_MISS("BMS_PAY_AP_MISS", "应付产品查询", "应付产品不存在或已删除"),
BMS_PAY_PICK_FILE("BMS_PAY_PICK_FILE", "应付价卡文件", "请选择价卡文件"),
BMS_PAY_XLSX("BMS_PAY_XLSX", "应付价卡文件", "请上传xlsx价卡"),
BMS_PAY_FILE_30M("BMS_PAY_FILE_30M", "应付价卡文件", "价卡文件不能超过30MB"),
BMS_PAY_NO_SHEET("BMS_PAY_NO_SHEET", "应付价卡解析", "文件里没有工作表"),
BMS_PAY_NO_QUOTE("BMS_PAY_NO_QUOTE", "应付价卡解析", "目录里没有找到产品报价表"),
BMS_PAY_BAD_XLS("BMS_PAY_BAD_XLS", "应付价卡解析", "价卡无法读取,请确认是 Excel 报价文件"),
BMS_PAY_PICK_XLS("BMS_PAY_PICK_XLS", "应付邮编导入", "请选择 Excel 文件"),
BMS_PAY_XLS_30M("BMS_PAY_XLS_30M", "应付邮编导入", "文件不能超过30MB"),
BMS_PAY_ZIP_READ("BMS_PAY_ZIP_READ", "应付邮编导入", "邮编表无法读取,请确认是 Excel 文件"),
BMS_PAY_XLSX_ONLY("BMS_PAY_XLSX_ONLY", "应付邮编导入", "只支持 xlsx 文件"),
BMS_PAY_ZIP_EDIT("BMS_PAY_ZIP_EDIT", "应付邮编配置", "请选择要修改的邮编配置"),
BMS_PAY_ZIP_MISS("BMS_PAY_ZIP_MISS", "应付邮编配置", "邮编配置不存在或已删除"),
BMS_PAY_ZIP_TYPE("BMS_PAY_ZIP_TYPE", "应付邮编配置", "类型只能是附加费或邮编分区"),
BMS_PAY_ZIP_HEAD("BMS_PAY_ZIP_HEAD", "应付邮编导入", "没有识别到邮编或附加费邮编,请确认表头里有邮编、ZIP 或附加费"),
BMS_PAY_ZIP_ATTR("BMS_PAY_ZIP_ATTR", "应付邮编配置", "邮编属性只能是可达、不可达或偏远"),
BMS_PAY_BILL_MISS("BMS_PAY_BILL_MISS", "应付账单查询", "应付账单不存在"),
BMS_PAY_PICK_SUP("BMS_PAY_PICK_SUP", "应付账单生成", "请选择供应商"),
BMS_PAY_SUP_MISS("BMS_PAY_SUP_MISS", "应付账单生成", "供应商不存在"),
BMS_PAY_NO_GEN("BMS_PAY_NO_GEN", "应付账单生成", "没有可生成的订单"),
BMS_PAY_BILL_FEE("BMS_PAY_BILL_FEE", "应付账单生成", "订单没有应付费用"),
BMS_PAY_CURRENCY("BMS_PAY_CURRENCY", "应付账单生成", "所选订单币种不一致,请按币种分开生成"),
BMS_PAY_DRAFT("BMS_PAY_DRAFT", "应付账单确认", "只有草稿可以确认"),
BMS_PAY_CONFIRM("BMS_PAY_CONFIRM", "应付账单付款", "请先确认账单,再登记付款"),
BMS_PAY_PICK_BILL("BMS_PAY_PICK_BILL", "应付账单作废", "请选择应付账单"),
BMS_PAY_VOID_WHY("BMS_PAY_VOID_WHY", "应付账单作废", "请填写作废原因"),
BMS_PAY_VOIDED("BMS_PAY_VOIDED", "应付账单作废", "账单已经作废"),
BMS_PAY_PAID("BMS_PAY_PAID", "应付账单作废", "已付款的账单不能直接作废,请先冲回付款"),
BMS_PAY_ORDER_NO("BMS_PAY_ORDER_NO", "应付账单查询", "请填写订单号"),
BMS_PAY_BIZ_DATE("BMS_PAY_BIZ_DATE", "应付账单查询", "请选择计费日起止"),
BMS_PAY_LADING_NO("BMS_PAY_LADING_NO", "应付账单查询", "请填写提单号"),
BMS_PAY_QUERY("BMS_PAY_QUERY", "应付账单查询", "请选择查询方式"),
BMS_PAY_NO_ALLOC("BMS_PAY_NO_ALLOC", "提单费用分摊", "所选提单没有需要分摊的费用"),
BMS_PAY_ALLOC_ST("BMS_PAY_ALLOC_ST", "提单费用分摊", "费用状态已变化,请重新打开后再分摊"),
BMS_PAY_PICK_LAD("BMS_PAY_PICK_LAD", "提单费用分摊", "请勾选提单"),
BMS_PAY_LAD_LIMIT("BMS_PAY_LAD_LIMIT", "提单费用分摊", "单次最多处理5条提单"),
BMS_PAY_NO_CHANGE("BMS_PAY_NO_CHANGE", "提单费用保存", "没有需要保存的变更"),
BMS_PAY_IMP_EMPTY("BMS_PAY_IMP_EMPTY", "提单费用导入", "导入文件没有费用数据"),
BMS_PAY_IMP_DEMO("BMS_PAY_IMP_DEMO", "提单费用导入", "请删除示例行后再导入有效费用"),
BMS_PAY_FEE_BODY("BMS_PAY_FEE_BODY", "提单费用保存", "请提交要保存的费用"),
BMS_PAY_DEL_ID("BMS_PAY_DEL_ID", "提单费用删除", "要删除的费用缺少编号"),
BMS_PAY_FEE_DUP("BMS_PAY_FEE_DUP", "提单费用保存", "同一条费用不能重复提交"),
BMS_PAY_FEE_GONE("BMS_PAY_FEE_GONE", "提单费用保存", "费用已删除或不属于该提单"),
BMS_PAY_FEE_LOCK("BMS_PAY_FEE_LOCK", "提单费用删除", "已分摊或已确定的费用不能删除,请使用作废"),
BMS_PAY_EDIT_ID("BMS_PAY_EDIT_ID", "提单费用修改", "要修改的费用缺少编号"),
BMS_PAY_FEE_EDIT("BMS_PAY_FEE_EDIT", "提单费用修改", "已分摊或已确定的费用不能修改"),
BMS_PAY_PICK_OK("BMS_PAY_PICK_OK", "提单费用确定", "请选择要确定的费用"),
BMS_PAY_OK_STAT("BMS_PAY_OK_STAT", "提单费用确定", "只有已分摊的费用可以确定"),
BMS_PAY_OK_CHG("BMS_PAY_OK_CHG", "提单费用确定", "费用状态已变化,请刷新后再确定"),
BMS_PAY_PICK_VOID("BMS_PAY_PICK_VOID", "提单费用作废", "请选择要作废的费用"),
BMS_PAY_VOID_STAT("BMS_PAY_VOID_STAT", "提单费用作废", "只有已分摊或已确定的费用可以作废"),
BMS_PAY_VOID_CHG("BMS_PAY_VOID_CHG", "提单费用作废", "费用状态已变化,请刷新后再作废"),
BMS_PAY_NO_PERM("BMS_PAY_NO_PERM", "提单费用权限", "没有权限修改该费用"),
BMS_PAY_FEE_ENTRY("BMS_PAY_FEE_ENTRY", "提单费用保存", "费用入口不正确"),
BMS_PAY_FORBIDDEN("BMS_PAY_FORBIDDEN", "提单费用权限", "没有权限"),
BMS_PAY_PICK_ITEM("BMS_PAY_PICK_ITEM", "提单费用校验", "请选择费用项"),
BMS_PAY_ITEM_MISS("BMS_PAY_ITEM_MISS", "提单费用校验", "费用项不存在"),
BMS_PAY_LEVEL("BMS_PAY_LEVEL", "提单费用校验", "费用阶段不在字典中"),
BMS_PAY_PRICE("BMS_PAY_PRICE", "提单费用校验价格", "请填写价格"),
BMS_PAY_PRICE_NEG("BMS_PAY_PRICE_NEG", "提单费用校验价格", "价格不能小于0"),
BMS_PAY_PRICE_LEN("BMS_PAY_PRICE_LEN", "提单费用校验价格", "价格整数部分不能超过16位"),
BMS_PAY_CUR_PICK("BMS_PAY_CUR_PICK", "提单费用校验币种", "请选择收费币种"),
BMS_PAY_CUR_DICT("BMS_PAY_CUR_DICT", "提单费用校验币种", "收费币种不在币种字典中"),
BMS_PAY_REMARK("BMS_PAY_REMARK", "提单费用校验备注", "备注不能超过500字"),
BMS_PAY_LAD_BLANK("BMS_PAY_LAD_BLANK", "提单费用校验提单", "提单号不能为空"),
BMS_PAY_LAD_MISS("BMS_PAY_LAD_MISS", "提单费用校验提单", "提单不存在"),
BMS_PAY_USE_AUDIT("BMS_PAY_USE_AUDIT", "应付账单确认", "请先提交审核,审核通过后账单才确认"),
BMS_PAY_USE_PAY("BMS_PAY_USE_PAY", "应付账单付款", "请到应付付款登记付款并核销"),
BMS_PAY_SUBMIT("BMS_PAY_SUBMIT", "应付账单提交", "只有草稿可以提交审核"),
BMS_PAY_DIFF_OPEN("BMS_PAY_DIFF_OPEN", "应付账单提交", "还有未处理的对账差异"),
BMS_PAY_NO_LINE("BMS_PAY_NO_LINE", "应付账单提交", "账单没有费用行"),
BMS_PAY_PENDING("BMS_PAY_PENDING", "应付账单审核", "只有待审核的账单可以审核"),
BMS_PAY_NO_FILE("BMS_PAY_NO_FILE", "应付账单审核", "请先上传发票或对账凭证"),
BMS_PAY_REJECT_WHY("BMS_PAY_REJECT_WHY", "应付账单审核", "请填写驳回意见"),
BMS_PAY_LINE_MISS("BMS_PAY_LINE_MISS", "应付账单差异", "费用行不存在"),
BMS_PAY_LINE_LOCK("BMS_PAY_LINE_LOCK", "应付账单差异", "只有草稿可以调整费用行"),
BMS_PAY_NO_DIFF("BMS_PAY_NO_DIFF", "应付账单差异", "这行没有待处理的差异"),
BMS_PAY_NO_VOID("BMS_PAY_NO_VOID", "应付账单作废", "已确认的账单不能直接作废,请做红字"),
BMS_PAY_FEE_TAKEN("BMS_PAY_FEE_TAKEN", "应付账单生成", "费用已经进入其他账单"),
BMS_PAY_RED_STAT("BMS_PAY_RED_STAT", "应付账单红字", "只有已确认或部分付款的账单可以红冲"),
BMS_PAY_RED_PAID("BMS_PAY_RED_PAID", "应付账单红字", "账单已有付款,请先处理付款后再红冲"),
BMS_PAY_RED_KIND("BMS_PAY_RED_KIND", "应付账单红字", "红字账单不能再红冲"),
BMS_PAY_FILE_NEED("BMS_PAY_FILE_NEED", "应付凭证", "请先上传文件"),
BMS_PAY_FILE_MISS("BMS_PAY_FILE_MISS", "应付凭证", "凭证不存在"),
BMS_PAY_FILE_LOCK("BMS_PAY_FILE_LOCK", "应付凭证", "已确认的账单不能删除凭证"),
BMS_PAY_PAY_MISS("BMS_PAY_PAY_MISS", "应付付款", "付款单不存在"),
BMS_PAY_PAY_DRAFT("BMS_PAY_PAY_DRAFT", "应付付款", "只有草稿付款单可以确认或作废"),
BMS_PAY_PAY_DATE("BMS_PAY_PAY_DATE", "应付付款", "请填写付款日期"),
BMS_PAY_PAY_AMT("BMS_PAY_PAY_AMT", "应付付款", "请填写付款金额"),
BMS_PAY_ALLOC_NEED("BMS_PAY_ALLOC_NEED", "应付付款", "请填写本次核销金额"),
BMS_PAY_ALLOC_AMT("BMS_PAY_ALLOC_AMT", "应付付款", "核销金额超过账单未付金额"),
BMS_PAY_ALLOC_DUP("BMS_PAY_ALLOC_DUP", "应付付款", "同一张账单不能重复核销"),
BMS_PAY_ALLOC_OVER("BMS_PAY_ALLOC_OVER", "应付付款", "核销合计不能超过付款金额"),
BMS_PAY_PAY_BILL("BMS_PAY_PAY_BILL", "应付付款", "只能核销已确认或部分付款的账单"),
BMS_PAY_PAY_SAME("BMS_PAY_PAY_SAME", "应付付款", "付款单和账单的供应商、币种需要一致"),
BMS_PAY_SAME_USER("BMS_PAY_SAME_USER", "应付审核", "提交人和审核人不能是同一人"),
BMS_PAY_SAME_PAY("BMS_PAY_SAME_PAY", "应付付款", "登记人和确认人不能是同一人"),
BMS_PAY_INV_NEED("BMS_PAY_INV_NEED", "应付发票", "审核通过前请上传发票,并填写发票号和价税合计"),
BMS_PAY_INV_AMT("BMS_PAY_INV_AMT", "应付发票", "发票价税合计与账单金额不一致"),
BMS_PAY_INV_TAX("BMS_PAY_INV_TAX", "应付发票", "发票税额与账单税额不一致"),
BMS_PAY_INV_DUP("BMS_PAY_INV_DUP", "应付发票", "这张发票号已经在该供应商下勾稽过"),
BMS_PAY_INV_NO("BMS_PAY_INV_NO", "应付发票", "请填写发票号"),
BMS_PAY_TAX_RATE("BMS_PAY_TAX_RATE", "应付价税", "税率要在 0 到 1 之间,例如 0.06"),
BMS_PAY_RATE("BMS_PAY_RATE", "应付汇率", "请填写大于 0 的汇率"),
BMS_PAY_NO_REVERSE("BMS_PAY_NO_REVERSE", "应付付款", "只有已付款的单据可以反核销或退款"),
BMS_PAY_NO_BACK("BMS_PAY_NO_BACK", "应付付款", "没有可冲回的核销"),
BMS_PAY_XLS_LINE("BMS_PAY_XLS_LINE", "应付对账单", "对账单里没有可导入的行号");
private static final Map<String, PaymentMesEnum> BY_CODE = new HashMap<>();
static
{
for (PaymentMesEnum item : values())
{
BY_CODE.put(item.code, item);
}
}
private final String code;
private final String name;
private final String cnMes;
private final String enMes;
PaymentMesEnum(String code, String name, String cnMes)
{
this(code, name, cnMes, "");
}
PaymentMesEnum(String code, String name, String cnMes, String enMes)
{
this.code = code;
this.name = name;
this.cnMes = cnMes;
this.enMes = enMes == null ? "" : enMes;
}
public String getCode()
{
return code;
}
public String getName()
{
return name;
}
public String getCnMes()
{
return cnMes;
}
public String getEnMes()
{
return enMes;
}
/** 按编码取出提示。没有对应编码时返回 null。 */
public static PaymentMesEnum of(String code)
{
if (code == null || code.isBlank())
{
return null;
}
return BY_CODE.get(code.trim());
}
/** 弹出给用户的中文提示。英文未配置时仍返回中文。 */
public String text()
{
return cnMes == null || cnMes.isBlank() ? enMes : cnMes;
}
/** 转成业务异常,页面只看到中文提示。 */
public ServiceException exception()
{
return new ServiceException(text()).setDetailMessage(code);
}
}
@@ -169,9 +169,9 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
}
/**
* 规范化目的地邮编,用于分区匹配/持久化。
* 移除所有 Unicode 空白字符(包括 NBSP / U+2005)和 ASCII 空格;
* 去除纯数字末尾 Excel 风格的 ".0"。
* Normalize destination postcode for zone match / persist.
* Removes all Unicode whitespace (incl. NBSP / U+2005) and ASCII spaces;
* strips Excel-style trailing ".0" on pure digits.
*/
public static String normalizePostcode(String postcode)
{
@@ -681,12 +681,12 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
}
/**
* 将给定的 {@code string} 与 {@code searchStrings} 可变参数中的字符序列进行比较,
* 如果 {@code string} 与其中任意一个 {@code searchStrings} 相等(忽略大小写),则返回 {@code true}。
* Compares given {@code string} to a CharSequences vararg of {@code searchStrings},
* returning {@code true} if the {@code string} is equal to any of the {@code searchStrings}, ignoring case.
*
* @param string 要比较的字符串,可能为 {@code null}。
* @param searchStrings 字符串可变参数,可能为 {@code null}。
* @return 如果字符串与 {@code searchStrings} 中的任意其他元素相等(不区分大小写),则返回 {@code true};
* @param string to compare, may be {@code null}.
* @param searchStrings a vararg of strings, may be {@code null}.
* @return {@code true} if the string is equal (case-insensitive) to any other element of {@code searchStrings};
*/
public static boolean equalsAnyIgnoreCase(final CharSequence string, final CharSequence... searchStrings)
{
@@ -29,13 +29,13 @@ public class MimeTypeUtils
public static final String[] DEFAULT_ALLOWED_EXTENSION = {
// 图片
"bmp", "gif", "jpg", "jpeg", "png",
// Word、Excel、PowerPoint 文档
// word excel powerpoint
"doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt",
// 压缩文件
"rar", "zip", "gz", "bz2",
// 视频格式
"mp4", "avi", "rmvb",
// pdf 文档
// pdf
"pdf" };
public static String getExtension(String prefix)
@@ -1,25 +1,18 @@
package com.bms.common.utils.file;
import java.net.URL;
import java.net.URLEncoder;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Date;
import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.HttpMethod;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.ResponseHeaderOverrides;
import com.bms.common.config.OssProperties;
import com.bms.common.exception.ServiceException;
import org.springframework.stereotype.Component;
@@ -74,28 +67,6 @@ public class OssStorage {
}
}
/** 为私有 OSS 对象生成带附件文件名的限时下载链接。 */
public String generatePresignedDownloadUrl(String storageKey, String fileName, Date expiresAt) {
String objectKey = resolveObjectKey(storageKey);
OSS client = createClient();
try {
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(
properties.getBucket(), objectKey, HttpMethod.GET);
request.setExpiration(expiresAt);
String downloadName = StringUtils.hasText(fileName) ? fileName : "账单导出.xlsx";
String encodedFileName = URLEncoder.encode(downloadName, StandardCharsets.UTF_8).replace("+", "%20");
ResponseHeaderOverrides responseHeaders = new ResponseHeaderOverrides();
responseHeaders.setContentDisposition("attachment; filename=" + encodedFileName
+ ";filename*=utf-8''" + encodedFileName);
request.setResponseHeaders(responseHeaders);
URL signedUrl = client.generatePresignedUrl(request);
return signedUrl.toExternalForm();
} finally {
client.shutdown();
}
}
public void delete(String storageKey) {
String objectKey = resolveObjectKey(storageKey);
OSS client = createClient();
@@ -20,7 +20,7 @@ public class EscapeUtil
TEXT[i] = new char[] { (char) i };
}
// 特殊的 HTML 字符
// special HTML characters
TEXT['\''] = "&#039;".toCharArray(); // 单引号
TEXT['"'] = "&#34;".toCharArray(); // 双引号
TEXT['&'] = "&#38;".toCharArray(); // &符
@@ -18,7 +18,7 @@ import java.util.regex.Pattern;
public final class HTMLFilter
{
/**
* 表示 php 中 /si 修饰符的正则表达式标志组合
* regex flag union representing /si modifiers in php
**/
private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL;
private static final Pattern P_COMMENTS = Pattern.compile("<!--(.*?)-->", Pattern.DOTALL);
@@ -45,60 +45,60 @@ public final class HTMLFilter
private static final Pattern P_RIGHT_ARROW = Pattern.compile(">");
private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>");
// @xxx 可能会变得很大……也许可以使用 sesat 的 ReferenceMap
// @xxx could grow large... maybe use sesat's ReferenceMap
private static final ConcurrentMap<String, Pattern> P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, Pattern> P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>();
/**
* 允许的 html 元素集合,以及每个元素允许的属性
* set of allowed html elements, along with allowed attributes for each element
**/
private final Map<String, List<String>> vAllowed;
/**
* 每个(允许的)html 元素当前打开的标签计数
* counts of open tags for each (allowable) html element
**/
private final Map<String, Integer> vTagCounts = new HashMap<>();
/**
* 必须始终自闭合的 html 元素(例如 "<img />")
* html elements which must always be self-closing (e.g. "<img />")
**/
private final String[] vSelfClosingTags;
/**
* 必须始终具有独立开始和结束标签的 html 元素(例如 "<b></b>")
* html elements which must always have separate opening and closing tags (e.g. "<b></b>")
**/
private final String[] vNeedClosingTags;
/**
* 不允许的 html 元素集合
* set of disallowed html elements
**/
private final String[] vDisallowed;
/**
* 需要校验协议是否有效的属性
* attributes which should be checked for valid protocols
**/
private final String[] vProtocolAtts;
/**
* 允许的协议
* allowed protocols
**/
private final String[] vAllowedProtocols;
/**
* 内容为空时应移除的标签(例如 "<b></b>" 或 "<b />")
* tags which should be removed if they contain no content (e.g. "<b></b>" or "<b />")
**/
private final String[] vRemoveBlanks;
/**
* html 标记中允许的实体
* entities allowed within html markup
**/
private final String[] vAllowedEntities;
/**
* 用于标识输入字符串中是否允许注释的标志。
* flag determining whether comments are allowed in input String.
*/
private final boolean stripComment;
private final boolean encodeQuotes;
/**
* 用于标识在遇到“不配对”的尖括号时是否尝试构造标签的标志(例如 "<b text </b>"
* 会变成 "<b> text </b>")。如果设置为 false,不配对的尖括号将被 html 转义。
* flag determining whether to try to make tags when presented with "unbalanced" angle brackets (e.g. "<b text </b>"
* becomes "<b> text </b>"). If set to false, unbalanced angle brackets will be html escaped.
*/
private final boolean alwaysMakeTags;
/**
* 默认构造方法。
* Default constructor.
*/
public HTMLFilter()
{
@@ -125,7 +125,7 @@ public final class HTMLFilter
vSelfClosingTags = new String[] { "img" };
vNeedClosingTags = new String[] { "a", "b", "strong", "i", "em" };
vDisallowed = new String[] {};
vAllowedProtocols = new String[] { "http", "mailto", "https" }; // 不支持 ftp。
vAllowedProtocols = new String[] { "http", "mailto", "https" }; // no ftp.
vProtocolAtts = new String[] { "src", "href" };
vRemoveBlanks = new String[] { "a", "b", "strong", "i", "em" };
vAllowedEntities = new String[] { "amp", "gt", "lt", "quot" };
@@ -135,9 +135,9 @@ public final class HTMLFilter
}
/**
* 可通过 Map 参数配置的构造方法。
* Map-parameter configurable constructor.
*
* @param conf 包含配置的 map。键与字段名一致。
* @param conf map containing configuration. keys match field names.
*/
@SuppressWarnings("unchecked")
public HTMLFilter(final Map<String, Object> conf)
@@ -171,7 +171,7 @@ public final class HTMLFilter
}
// ---------------------------------------------------------------
// 我实现的某些 PHP 库函数版本
// my versions of some PHP library functions
public static String chr(final int decimal)
{
return String.valueOf((char) decimal);
@@ -190,10 +190,10 @@ public final class HTMLFilter
// ---------------------------------------------------------------
/**
* 对用户提交的输入字符串,过滤掉任何无效或受限的 html。
* given a user submitted input String, filter out any invalid or restricted html.
*
* @param input 可能包含 html 的文本(即用户提交的内容)
* @return 输入的“干净”版本,仅允许有效且在 html 白名单中的元素
* @param input text (i.e. submitted by a user) than may contain html
* @return "clean" version of input, with only valid, whitelisted html elements allowed
*/
public String filter(final String input)
{
@@ -242,7 +242,7 @@ public final class HTMLFilter
if (alwaysMakeTags)
{
//
// 尝试构造 html
// try and form html
//
s = regexReplace(P_END_ARROW, "", s);
// 不追加结束标签
@@ -253,15 +253,15 @@ public final class HTMLFilter
else
{
//
// 转义多余的括号
// escape stray brackets
//
s = regexReplace(P_STRAY_LEFT_ARROW, "&lt;$1", s);
s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2&gt;<", s);
//
// 上一个正则表达式会导致出现 "<>" 实体
// (我们需要使用先行断言,以便最后一个括号
// 可以在正则表达式的下一次匹配中使用)
// the last regexp causes '<>' entities to appear
// (we need to do a lookahead assertion so that the last bracket can
// be used in the next pass of the regexp)
//
s = regexReplace(P_BOTH_ARROWS, "", s);
}
@@ -282,8 +282,8 @@ public final class HTMLFilter
}
m.appendTail(buf);
// 这些计数在 processTag 中进行累加
// (记住在后续调用 filter 方法之前重置计数)
// these get tallied in processTag
// (remember to reset before subsequent calls to filter method)
final StringBuilder sBuilder = new StringBuilder(buf.toString());
for (String key : vTagCounts.keySet())
{
@@ -325,7 +325,7 @@ public final class HTMLFilter
private String processTag(final String s)
{
// 结束标签
// ending tags
Matcher m = P_END_TAG.matcher(s);
if (m.find())
{
@@ -343,7 +343,7 @@ public final class HTMLFilter
}
}
// 开始标签
// starting tags
m = P_START_TAG.matcher(s);
if (m.find())
{
@@ -424,7 +424,7 @@ public final class HTMLFilter
}
}
// 注释
// comments
m = P_COMMENT.matcher(s);
if (!stripComment && m.find())
{
@@ -443,7 +443,7 @@ public final class HTMLFilter
final String protocol = m.group(1);
if (!inArray(protocol, vAllowedProtocols))
{
// 协议不合法,改为本地锚点链接
// bad protocol, turn into local anchor link instead
s = "#" + s.substring(protocol.length() + 1);
if (s.startsWith("#//"))
{
@@ -499,7 +499,7 @@ public final class HTMLFilter
{
StringBuffer buf = new StringBuffer();
// 校验整个字符串中的实体
// validate entities throughout the string
Matcher m = P_VALID_ENTITIES.matcher(s);
while (m.find())
{
@@ -569,12 +569,12 @@ public class ExcelUtil<T>
}
/**
* 供控制器使用的通用一行式导出方法。
* Common one-liner export for controllers.
*
* @param response HTTP 响应
* @param list 导出的数据行
* @param clazz 带有 @Excel 注解字段的数据行类
* @param sheetName sheet / 文件标题
* @param response HTTP response
* @param list export rows
* @param clazz row class with @Excel fields
* @param sheetName sheet / file title
*/
public static <E> void export(HttpServletResponse response, List<E> list, Class<E> clazz, String sheetName)
{
@@ -75,10 +75,10 @@ public final class Base64
}
/**
* 将十六进制字节编码为 Base64
* Encodes hex octects into Base64
*
* @param binaryData 包含二进制数据的数组
* @return 编码后的 Base64 数组
* @param binaryData Array containing binaryData
* @return Encoded Base64 array
*/
public static String encode(byte[] binaryData)
{
@@ -124,7 +124,7 @@ public final class Base64
encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f];
}
// 组成完整的 6 位组
// form integral number of 6-bit groups
if (fewerThan24bits == EIGHTBIT)
{
b1 = binaryData[dataIndex];
@@ -154,10 +154,10 @@ public final class Base64
}
/**
* 将 Base64 数据解码为字节
* Decodes Base64 data into octects
*
* @param encoded 包含 Base64 数据的字符串
* @return 包含解码后数据的数组。
* @param encoded string containing Base64 data
* @return Array containind decoded data.
*/
public static byte[] decode(String encoded)
{
@@ -167,12 +167,12 @@ public final class Base64
}
char[] base64Data = encoded.toCharArray();
// 去除空白字符
// remove white spaces
int len = removeWhiteSpace(base64Data);
if (len % FOURBYTE != 0)
{
return null;// 应为 4 的倍数
return null;// should be divisible by four
}
int numberQuadruple = (len / FOURBYTE);
@@ -198,7 +198,7 @@ public final class Base64
|| !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++])))
{
return null;
} // 如果未找到数据,直接返回 null
} // if found "no data" just return null
b1 = base64Alphabet[d1];
b2 = base64Alphabet[d2];
@@ -212,7 +212,7 @@ public final class Base64
if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])))
{
return null;// 如果未找到数据,直接返回 null
return null;// if found "no data" just return null
}
b1 = base64Alphabet[d1];
@@ -221,10 +221,10 @@ public final class Base64
d3 = base64Data[dataIndex++];
d4 = base64Data[dataIndex++];
if (!isData((d3)) || !isData((d4)))
{// 检查它们是否为 PAD 字符
{// Check if they are PAD characters
if (isPad(d3) && isPad(d4))
{
if ((b2 & 0xf) != 0)// 最后 4 位应为零
if ((b2 & 0xf) != 0)// last 4 bits should be zero
{
return null;
}
@@ -236,7 +236,7 @@ public final class Base64
else if (!isPad(d3) && isPad(d4))
{
b3 = base64Alphabet[d3];
if ((b3 & 0x3) != 0)// 最后 2 位应为零
if ((b3 & 0x3) != 0)// last 2 bits should be zero
{
return null;
}
@@ -252,7 +252,7 @@ public final class Base64
}
}
else
{ // 没有 PAD,例如 3cQl
{ // No PAD e.g 3cQl
b3 = base64Alphabet[d3];
b4 = base64Alphabet[d4];
decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
@@ -264,10 +264,10 @@ public final class Base64
}
/**
* 从包含已编码 Base64 数据的 MIME 中移除空白字符。
* remove WhiteSpace from MIME containing encoded Base64 data.
*
* @param data Base64 数据的字节数组(含空白字符)
* @return 新的长度
* @param data the byte array of base64 data (with WS)
* @return the new length
*/
private static int removeWhiteSpace(char[] data)
{
@@ -276,7 +276,7 @@ public final class Base64
return 0;
}
// 统计非空白字符的数量
// count characters that's not whitespace
int newSize = 0;
int len = data.length;
for (int i = 0; i < len; i++)
@@ -97,10 +97,10 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
byte[] randomBytes = new byte[16];
ng.nextBytes(randomBytes);
randomBytes[6] &= 0x0f; /* 清除版本位 */
randomBytes[6] |= 0x40; /* 设置为版本 4 */
randomBytes[8] &= 0x3f; /* 清除变体位 */
randomBytes[8] |= 0x80; /* 设置为 IETF 变体 */
randomBytes[6] &= 0x0f; /* clear version */
randomBytes[6] |= 0x40; /* set to version 4 */
randomBytes[8] &= 0x3f; /* clear variant */
randomBytes[8] |= 0x80; /* set to IETF variant */
return new UUID(randomBytes);
}
@@ -123,10 +123,10 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
throw new InternalError("MD5 not supported");
}
byte[] md5Bytes = md.digest(name);
md5Bytes[6] &= 0x0f; /* 清除版本位 */
md5Bytes[6] |= 0x30; /* 设置为版本 3 */
md5Bytes[8] &= 0x3f; /* 清除变体位 */
md5Bytes[8] |= 0x80; /* 设置为 IETF 变体 */
md5Bytes[6] &= 0x0f; /* clear version */
md5Bytes[6] |= 0x30; /* set to version 3 */
md5Bytes[8] &= 0x3f; /* clear variant */
md5Bytes[8] |= 0x80; /* set to IETF variant */
return new UUID(md5Bytes);
}
@@ -198,7 +198,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
*/
public int version()
{
// 版本号是最高有效长整型中被 0x000000000000F000 掩码的位
// Version is bits masked by 0x000000000000F000 in MS long
return (int) ((mostSigBits >> 12) & 0x0f);
}
@@ -217,11 +217,11 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
*/
public int variant()
{
// 该字段由数量可变的位组成。
// 0 - - 为 NCS 向后兼容保留
// 1 0 - IETF 即 Leach-Salz 变体(本类使用)
// 1 1 0 保留,微软向后兼容
// 1 1 1 保留供以后定义使用。
// This field is composed of a varying number of bits.
// 0 - - Reserved for NCS backward compatibility
// 1 0 - The IETF aka Leach-Salz variant (used by this class)
// 1 1 0 Reserved, Microsoft backward compatibility
// 1 1 1 Reserved for future definition.
return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63));
}
@@ -403,7 +403,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits);
}
// 比较操作
// Comparison Operations
/**
* 将此 UUID 与指定的 UUID 比较。
@@ -419,8 +419,8 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
@Override
public int compareTo(UUID val)
{
// 此处有意这样设置比较顺序,使得 UUID
// 可以简单地作为两个数字进行数值比较
// The ordering is intentionally set up so that the UUIDs
// can simply be numerically compared as two numbers
return (this.mostSigBits < val.mostSigBits ? -1 : //
(this.mostSigBits > val.mostSigBits ? 1 : //
(this.leastSigBits < val.leastSigBits ? -1 : //
@@ -429,7 +429,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
}
// -------------------------------------------------------------------------------------------------------------------
// 私有方法开始
// Private method start
/**
* 返回指定数字对应的hex值
*
@@ -106,7 +106,7 @@ public class MyBatisConfig
}
catch (IOException e)
{
// 忽略
// ignore
}
}
}
@@ -13,7 +13,7 @@ import org.springframework.stereotype.Component;
public class TmsApiProperties
{
/**
* 约定的 API 标识码(请求头 X-Api-Code 或请求体 apiCode)。
* Agreed API identification code (header X-Api-Code or body apiCode).
*/
private String apiCode = "BMS.TMS.ZYaOvlQzpcfau8TUaCFfO3Hp8ieHIqGw";
@@ -1,4 +1,4 @@
package com.bms.receivable.config;
package com.bms.ar.config;
import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.context.annotation.Bean;
@@ -9,17 +9,6 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
public class ArBillExportTaskConfig
{
@Bean(name = "arBillVoidTaskExecutor")
public ThreadPoolTaskExecutor arBillVoidTaskExecutor()
{
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setMaxPoolSize(1);
executor.setQueueCapacity(5);
executor.setThreadNamePrefix("ar-bill-void-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
return executor;
}
/**
* 账单导出会同时占用数据库连接、CPU 和磁盘,最多允许两个任务并发生成。
* 队列满时由提交器将任务标记为失败,避免回退到 Web 请求线程执行。
@@ -1,4 +1,4 @@
package com.bms.receivable.domain;
package com.bms.ar.domain;
import java.math.BigDecimal;
import java.util.Date;
@@ -1,4 +1,4 @@
package com.bms.receivable.domain;
package com.bms.ar.domain;
import java.io.Serializable;
import java.util.Date;
@@ -1,4 +1,4 @@
package com.bms.receivable.domain;
package com.bms.ar.domain;
import java.math.BigDecimal;
import java.util.Date;
@@ -70,10 +70,6 @@ public class ArBill extends BaseEntity {
* 状态:DRAFT草稿、CONFIRMED已确认、PARTIAL_WRITTEN_OFF部分核销、WRITTEN_OFF已核销、VOID已作废。
*/
private String status;
/** 非空表示账单有正在后台执行的作废任务。 */
private Long voidTaskId;
/** 最近一次作废失败的原因。 */
private String voidTaskError;
/**
* 生成状态:GENERATING生成中、READY可用、FAILED生成失败。
*/
@@ -1,4 +1,4 @@
package com.bms.receivable.domain;
package com.bms.ar.domain;
import java.io.Serializable;
import java.math.BigDecimal;
@@ -17,7 +17,7 @@ public class ArBillDetail implements Serializable {
private Long detailId;
private Long arBillId;
private Long prcOrderBillId;
private String prcOrderBillId;
private Integer detailNo;
private String orderNo;
private String trackingNo;
@@ -1,4 +1,4 @@
package com.bms.receivable.domain;
package com.bms.ar.domain;
import java.io.Serializable;
import java.math.BigDecimal;
@@ -16,7 +16,7 @@ public class ArBillDetailFee implements Serializable {
private Long detailFeeId;
private Long detailId;
private Long prcIncomeBillFeeId;
private String prcIncomeBillFeeId;
private Integer feeNo;
private String feeType;
private String feeName;
@@ -1,4 +1,4 @@
package com.bms.receivable.domain;
package com.bms.ar.domain;
import java.io.Serializable;
import java.util.Date;
@@ -1,4 +1,4 @@
package com.bms.receivable.domain;
package com.bms.ar.domain;
import java.io.Serializable;
import java.math.BigDecimal;
@@ -42,7 +42,7 @@ public class ArBillGenerateTask implements Serializable {
/**
* 按 bill_id 字典序处理的游标。
*/
private Long lastSourceBillId;
private String lastSourceBillId;
/**
* 待处理总数;全选模式统计完成前使用 -1。
*/
@@ -1,4 +1,4 @@
package com.bms.receivable.domain;
package com.bms.ar.domain;
import java.io.Serializable;
import java.util.Date;
@@ -1,4 +1,4 @@
package com.bms.receivable.domain;
package com.bms.ar.domain;
import java.io.Serializable;
import java.util.Date;
@@ -1,4 +1,4 @@
package com.bms.receivable.domain.vo;
package com.bms.ar.domain.vo;
import lombok.Data;
@@ -1,4 +1,4 @@
package com.bms.receivable.domain.vo;
package com.bms.ar.domain.vo;
import java.io.Serializable;
import java.math.BigDecimal;
@@ -1,4 +1,4 @@
package com.bms.receivable.domain.vo;
package com.bms.ar.domain.vo;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
@@ -1,4 +1,4 @@
package com.bms.receivable.domain.vo;
package com.bms.ar.domain.vo;
import java.util.Date;
import java.util.List;
@@ -8,7 +8,7 @@ import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import com.bms.common.config.serializer.StrictDateDeserializer;
import com.bms.receivable.enums.ArBillSelectionMode;
import com.bms.ar.enums.ArBillSelectionMode;
import lombok.Data;
/** 从订单计费结果生成正式账单的参数。 */
@@ -29,11 +29,11 @@ public class ArBillGenerateRequest
/** 截止日筛选字段:BIZ_DATE=计费时间,PICKUP_DATE=收货时间;历史任务默认计费时间。 */
private String dateType = "BIZ_DATE";
/** 手动模式选中的来源订单 ID;全选模式为空。 */
private List<Long> sourceBillIds;
private List<String> sourceBillIds;
/** 选择模式:EXPLICIT 手动选择,ALL_MATCHED 全选查询结果。 */
private String selectionMode = ArBillSelectionMode.EXPLICIT.getCode();
/** 全选模式中用户明确排除的来源订单 ID。 */
private List<Long> excludedSourceBillIds;
private List<String> excludedSourceBillIds;
/** 全选模式下按订单号、运单号或转单号排除;后台生成任务按批次应用该条件。 */
private String excludedOrderOrTrackingNo;
@JsonFormat(pattern = "yyyy-MM-dd")
@@ -1,11 +1,11 @@
package com.bms.receivable.domain.vo;
package com.bms.ar.domain.vo;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import com.bms.receivable.domain.ArBill;
import com.bms.receivable.enums.ArBillStatus;
import com.bms.ar.domain.ArBill;
import com.bms.ar.enums.ArBillStatus;
import com.bms.common.annotation.Excel;
import lombok.Data;
@@ -1,9 +1,9 @@
package com.bms.receivable.domain.vo;
package com.bms.ar.domain.vo;
import java.util.Date;
import java.util.List;
import org.springframework.format.annotation.DateTimeFormat;
import com.bms.receivable.domain.ArBill;
import com.bms.ar.domain.ArBill;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -1,4 +1,4 @@
package com.bms.receivable.domain.vo;
package com.bms.ar.domain.vo;
import java.io.Serializable;
import java.math.BigDecimal;
@@ -1,4 +1,4 @@
package com.bms.receivable.enums;
package com.bms.ar.enums;
/**
* 应收账单余额核销单状态。
@@ -1,4 +1,4 @@
package com.bms.receivable.enums;
package com.bms.ar.enums;
/** 应收账单生成时用于截止日筛选的来源订单日期字段。 */
public enum ArBillDateType
@@ -1,4 +1,4 @@
package com.bms.receivable.enums;
package com.bms.ar.enums;
/** 应收账单异步生成任务状态。 */
public final class ArBillGenerateTaskStatus
@@ -1,4 +1,4 @@
package com.bms.receivable.enums;
package com.bms.ar.enums;
/** 应收账单生成状态,与账单业务状态分离。 */
public final class ArBillGenerationStatus
@@ -1,4 +1,4 @@
package com.bms.receivable.enums;
package com.bms.ar.enums;
/**
* 应收账单生成时的订单选择模式。
@@ -1,4 +1,4 @@
package com.bms.receivable.enums;
package com.bms.ar.enums;
/** 应收账单状态。数据库保存值使用 code,避免业务代码散落状态字符串。 */
public enum ArBillStatus
@@ -1,4 +1,4 @@
package com.bms.receivable.enums;
package com.bms.ar.enums;
/**
* 应收账单导出任务状态。
@@ -1,4 +1,4 @@
package com.bms.receivable.enums;
package com.bms.ar.enums;
/** 应收费用明细是否已经生成正式应收账单。 */
public enum ArIncomeBillFeeStatus
@@ -1,4 +1,4 @@
package com.bms.receivable.enums;
package com.bms.ar.enums;
/** 应收日志关联对象类型。 */
public enum ArLogObjectType
@@ -1,4 +1,4 @@
package com.bms.receivable.enums;
package com.bms.ar.enums;
/** 应收日志操作来源。 */
public enum ArLogOperationSource
@@ -1,4 +1,4 @@
package com.bms.receivable.enums;
package com.bms.ar.enums;
/** 应收日志操作类型。 */
public enum ArLogOperationType
@@ -1,4 +1,4 @@
package com.bms.receivable.enums;
package com.bms.ar.enums;
/** 应收操作日志结果。 */
public enum ArLogResult
@@ -1,4 +1,4 @@
package com.bms.receivable.enums;
package com.bms.ar.enums;
/** 计费订单的应收账单状态。 */
public enum ArOrderBillStatus
@@ -1,4 +1,4 @@
package com.bms.receivable.event;
package com.bms.ar.event;
/**
* 应收账单导出任务已提交事件。
@@ -1,4 +1,4 @@
package com.bms.receivable.event;
package com.bms.ar.event;
/**
* 应收账单异步生成任务已提交事件。
@@ -1,10 +1,10 @@
package com.bms.receivable.mapper;
package com.bms.ar.mapper;
import java.util.List;
import com.bms.receivable.security.ArBillRowScope;
import com.bms.ar.security.ArBillRowScope;
import org.apache.ibatis.annotations.Param;
import com.bms.receivable.domain.ArBalanceWriteoff;
import com.bms.ar.domain.ArBalanceWriteoff;
/**
* 应收账单余额核销 Mapper
@@ -1,9 +1,9 @@
package com.bms.receivable.mapper;
package com.bms.ar.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.bms.receivable.domain.ArBankAccount;
import com.bms.ar.domain.ArBankAccount;
public interface ArBankAccountMapper {
ArBankAccount selectDefaultByCurrency(@Param("currency") String currency);
@@ -1,10 +1,10 @@
package com.bms.receivable.mapper;
package com.bms.ar.mapper;
import java.util.List;
import com.bms.receivable.security.ArBillRowScope;
import com.bms.ar.security.ArBillRowScope;
import org.apache.ibatis.annotations.Param;
import com.bms.receivable.domain.ArBillDetailFee;
import com.bms.ar.domain.ArBillDetailFee;
public interface ArBillDetailFeeMapper {
List<ArBillDetailFee> selectByBillIdAndDetailIdScoped(@Param("arBillId") Long arBillId, @Param("detailId") Long detailId, @Param("scope") ArBillRowScope scope);
@@ -1,12 +1,12 @@
package com.bms.receivable.mapper;
package com.bms.ar.mapper;
import java.util.List;
import com.bms.receivable.security.ArBillRowScope;
import com.bms.ar.security.ArBillRowScope;
import java.util.Map;
import org.apache.ibatis.annotations.Param;
import com.bms.receivable.domain.ArBillDetail;
import com.bms.ar.domain.ArBillDetail;
public interface ArBillDetailMapper {
List<ArBillDetail> selectByBillIdScoped(@Param("arBillId") Long arBillId, @Param("scope") ArBillRowScope scope);
@@ -14,7 +14,12 @@ public interface ArBillDetailMapper {
List<ArBillDetail> selectByBillId(Long arBillId);
List<ArBillDetail> selectByBillIdAndSourceIds(@Param("arBillId") Long arBillId,
@Param("sourceBillIds") List<Long> sourceBillIds);
@Param("sourceBillIds") List<String> sourceBillIds);
/**
* 查询账单明细及费用明细,避免详情页逐层查询。
*/
List<ArBillDetail> selectByBillIdWithFees(Long arBillId);
List<ArBillDetail> selectExportPage(@Param("arBillId") Long arBillId,
@Param("lastDetailNo") Integer lastDetailNo, @Param("pageSize") Integer pageSize);
@@ -1,10 +1,10 @@
package com.bms.receivable.mapper;
package com.bms.ar.mapper;
import java.util.Date;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.bms.receivable.domain.ArBillExport;
import com.bms.ar.domain.ArBillExport;
public interface ArBillExportMapper {
List<ArBillExport> selectByBillId(Long arBillId);
@@ -1,17 +1,17 @@
package com.bms.receivable.mapper;
package com.bms.ar.mapper;
import java.util.Date;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.bms.receivable.domain.ArBillGenerateTask;
import com.bms.ar.domain.ArBillGenerateTask;
/**
* 应收账单异步生成任务 Mapper。
*/
public interface ArBillGenerateTaskMapper {
List<ArBillGenerateTask> selectActiveByCreateByScoped(@Param("createBy") String createBy,
@Param("scope") com.bms.receivable.security.ArBillRowScope scope);
@Param("scope") com.bms.ar.security.ArBillRowScope scope);
ArBillGenerateTask selectById(Long taskId);
@@ -27,7 +27,7 @@ public interface ArBillGenerateTaskMapper {
int updateTotalCount(@Param("taskId") Long taskId, @Param("totalCount") Integer totalCount);
int updateProgress(@Param("taskId") Long taskId, @Param("lastSourceBillId") Long lastSourceBillId,
int updateProgress(@Param("taskId") Long taskId, @Param("lastSourceBillId") String lastSourceBillId,
@Param("processedCount") Integer processedCount, @Param("successCount") Integer successCount,
@Param("totalAmount") java.math.BigDecimal totalAmount, @Param("heartbeatTime") Date heartbeatTime);
@@ -1,10 +1,10 @@
package com.bms.receivable.mapper;
package com.bms.ar.mapper;
import java.util.List;
import com.bms.receivable.security.ArBillRowScope;
import com.bms.ar.security.ArBillRowScope;
import org.apache.ibatis.annotations.Param;
import com.bms.receivable.domain.ArBillLog;
import com.bms.ar.domain.ArBillLog;
public interface ArBillLogMapper
{
@@ -1,14 +1,14 @@
package com.bms.receivable.mapper;
package com.bms.ar.mapper;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import com.bms.receivable.security.ArBillRowScope;
import com.bms.ar.security.ArBillRowScope;
import org.apache.ibatis.annotations.Param;
import com.bms.receivable.domain.ArBill;
import com.bms.receivable.domain.vo.ArBillQuery;
import com.bms.receivable.domain.vo.ArBillSummary;
import com.bms.ar.domain.ArBill;
import com.bms.ar.domain.vo.ArBillQuery;
import com.bms.ar.domain.vo.ArBillSummary;
public interface ArBillMapper {
List<ArBill> selectArBillList(@Param("query") ArBillQuery query, @Param("scope") ArBillRowScope scope);
@@ -36,10 +36,6 @@ public interface ArBillMapper {
int insertArBill(ArBill bill);
int updateProfitSnapshot(@Param("arBillId") Long arBillId,
@Param("shenzhenArea") String shenzhenArea,
@Param("guangzhouArea") String guangzhouArea);
int updateArBillStatus(@Param("arBillId") Long arBillId, @Param("status") String status,
@Param("remark") String remark, @Param("operator") String operator,
@Param("fromStatus") String fromStatus);
@@ -1,12 +1,12 @@
package com.bms.receivable.mapper;
package com.bms.ar.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.bms.receivable.security.ArBillRowScope;
import com.bms.ar.security.ArBillRowScope;
import com.bms.customer.customerinfo.domain.BmsCustomerOption;
public interface ArBillScopeMapper {
int countCustomerSourceBills(@Param("customerId") Long customerId, @Param("sourceIds") List<Long> sourceIds);
int countCustomerSourceBills(@Param("customerId") Long customerId, @Param("sourceIds") List<String> sourceIds);
int countAccessibleBill(@Param("arBillId") Long arBillId, @Param("scope") ArBillRowScope scope);
@@ -1,13 +1,13 @@
package com.bms.receivable.mapper;
package com.bms.ar.mapper;
import java.util.Date;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.bms.receivable.profit.ArCommissionRate;
import com.bms.receivable.profit.ArWriteoffRate;
import com.bms.receivable.profit.ArCommissionResult;
import com.bms.receivable.profit.ArCommissionSummary;
import com.bms.receivable.profit.ArProfitScope;
import com.bms.ar.profit.ArCommissionRate;
import com.bms.ar.profit.ArWriteoffRate;
import com.bms.ar.profit.ArCommissionResult;
import com.bms.ar.profit.ArCommissionSummary;
import com.bms.ar.profit.ArProfitScope;
public interface ArCommissionMapper
{
@@ -23,7 +23,6 @@ public interface ArCommissionMapper
List<ArWriteoffRate> selectMissingRates();
List<ArWriteoffRate> selectWriteoffRates(@Param("billId") Long billId);
Long selectBillIdByWriteoffId(@Param("writeoffId") Long writeoffId);
Integer isBillProfitDataComplete(@Param("billId") Long billId);
int countMissingRates(@Param("billId") Long billId);
int confirmMissingRate(@Param("writeoffId") Long writeoffId, @Param("rate") java.math.BigDecimal rate,
@Param("operator") String operator, @Param("remark") String remark);
@@ -1,9 +1,9 @@
package com.bms.receivable.mapper;
package com.bms.ar.mapper;
import java.util.Date;
import org.apache.ibatis.annotations.Param;
import com.bms.receivable.domain.ArExportTemplate;
import com.bms.ar.domain.ArExportTemplate;
public interface ArExportTemplateMapper {
/**
@@ -1,14 +1,14 @@
package com.bms.receivable.mapper;
package com.bms.ar.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.bms.receivable.profit.ArProfitQuery;
import com.bms.receivable.profit.ArProfitRow;
import com.bms.receivable.profit.ArProfitScope;
import com.bms.receivable.profit.ArProfitSummary;
import com.bms.receivable.profit.ArProfitSalesOption;
import com.bms.receivable.domain.ArBillDetailFee;
import com.bms.receivable.domain.ArBillDetail;
import com.bms.ar.profit.ArProfitQuery;
import com.bms.ar.profit.ArProfitRow;
import com.bms.ar.profit.ArProfitScope;
import com.bms.ar.profit.ArProfitSummary;
import com.bms.ar.profit.ArProfitSalesOption;
import com.bms.ar.domain.ArBillDetailFee;
import com.bms.ar.domain.ArBillDetail;
public interface ArProfitMapper
{
@@ -1,4 +1,4 @@
package com.bms.receivable.profit;
package com.bms.ar.profit;
import java.math.BigDecimal;
import java.util.Date;
@@ -1,4 +1,4 @@
package com.bms.receivable.profit;
package com.bms.ar.profit;
import java.math.BigDecimal;
import java.util.Date;
@@ -1,17 +1,17 @@
package com.bms.receivable.profit;
package com.bms.ar.profit;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.bms.receivable.domain.ArBalanceWriteoff;
import com.bms.receivable.domain.ArBill;
import com.bms.receivable.mapper.ArCommissionMapper;
import com.bms.receivable.mapper.ArBillMapper;
import com.bms.receivable.enums.ArBillStatus;
import com.bms.receivable.enums.ArBillGenerationStatus;
import com.bms.receivable.security.ArBillRoleKeys;
import com.bms.ar.domain.ArBalanceWriteoff;
import com.bms.ar.domain.ArBill;
import com.bms.ar.mapper.ArCommissionMapper;
import com.bms.ar.mapper.ArBillMapper;
import com.bms.ar.enums.ArBillStatus;
import com.bms.ar.enums.ArBillGenerationStatus;
import com.bms.ar.security.ArBillRoleKeys;
import com.bms.common.exception.ServiceException;
import com.bms.common.core.domain.entity.SysUser;
import com.bms.common.utils.SecurityUtils;
@@ -134,8 +134,6 @@ public class ArCommissionService
|| ArBillStatus.PARTIAL_WRITTEN_OFF.getCode().equals(bill.getStatus())
|| ArBillStatus.WRITTEN_OFF.getCode().equals(bill.getStatus())))
throw new ServiceException("账单未生成完成或状态不允许确认提成比例");
if (!Integer.valueOf(1).equals(mapper.isBillProfitDataComplete(billId)))
throw new ServiceException("账单价格、金额或币种数据不完整或不一致,请先处理后再确认比例");
String operator = SecurityUtils.getUsername();
if (mapper.confirmMissingRate(input.getWriteoffId(), input.getRate(), operator, input.getRemark().trim()) != 1)
throw new ServiceException("比例已确认或核销状态已变化,请刷新");
@@ -1,4 +1,4 @@
package com.bms.receivable.profit;
package com.bms.ar.profit;
import java.math.BigDecimal;
import lombok.Data;
@@ -1,4 +1,4 @@
package com.bms.receivable.profit;
package com.bms.ar.profit;
import java.util.Date;
import lombok.Data;
@@ -1,4 +1,4 @@
package com.bms.receivable.profit;
package com.bms.ar.profit;
import java.math.BigDecimal;
import java.util.Date;
@@ -1,4 +1,4 @@
package com.bms.receivable.profit;
package com.bms.ar.profit;
import lombok.Data;
@@ -1,4 +1,4 @@
package com.bms.receivable.profit;
package com.bms.ar.profit;
/** 后端构造的数据范围,不接收前端传入的权限参数。 */
public record ArProfitScope(boolean all, boolean self, boolean department, Long userId, Long deptId)
@@ -1,9 +1,9 @@
package com.bms.receivable.profit;
package com.bms.ar.profit;
import java.util.Arrays;
import java.util.Set;
import org.springframework.stereotype.Component;
import com.bms.receivable.security.ArBillRoleKeys;
import com.bms.ar.security.ArBillRoleKeys;
import com.bms.common.constant.Constants;
import com.bms.common.core.domain.entity.SysRole;
import com.bms.common.core.domain.entity.SysUser;
@@ -1,10 +1,10 @@
package com.bms.receivable.profit;
package com.bms.ar.profit;
import java.util.List;
import org.springframework.stereotype.Service;
import com.bms.receivable.mapper.ArProfitMapper;
import com.bms.receivable.domain.ArBillDetailFee;
import com.bms.receivable.domain.ArBillDetail;
import com.bms.ar.mapper.ArProfitMapper;
import com.bms.ar.domain.ArBillDetailFee;
import com.bms.ar.domain.ArBillDetail;
@Service
public class ArProfitService

Some files were not shown because too many files have changed in this diff Show More