Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a58de9bb7e | |||
| a211f06663 | |||
| 41cefd5fd5 | |||
| fca999c384 | |||
| 2e5ab2bfc1 | |||
| 3e5ef25153 | |||
| 9004a1b921 | |||
| cf6b90008d | |||
| fbc138f041 | |||
| 3c49175cea | |||
| f26e03efb1 | |||
| 1a0483c7fe | |||
| da9efc422e | |||
| e6a9fcf0a2 | |||
| de5784cbac | |||
| 16afa0d590 | |||
| 2632e5aa78 | |||
| ace1eb06c0 | |||
| f31bb4c54d | |||
| f2d27f3502 |
+7
-7
@@ -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.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.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.common.annotation.Log;
|
||||
import com.bms.common.core.controller.BaseController;
|
||||
import com.bms.common.core.domain.AjaxResult;
|
||||
|
||||
+362
@@ -0,0 +1,362 @@
|
||||
package com.bms.web.controller.price;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
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.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.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.core.page.TableDataInfo;
|
||||
import com.bms.common.enums.BusinessType;
|
||||
import com.bms.common.exception.ServiceException;
|
||||
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.IPayProductService;
|
||||
import com.bms.price.service.IPrcPriceSetService;
|
||||
|
||||
/**
|
||||
* 打单套餐:套餐壳 + 应收价卡(pay_*)。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/price/label")
|
||||
public class LabelPackageController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private IPrcPriceSetService priceSetService;
|
||||
|
||||
@Autowired
|
||||
private IPayProductService payProductService;
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(PrcPriceSet set)
|
||||
{
|
||||
set.setSetCategory(PrcPriceSet.CATEGORY_LABEL);
|
||||
startPage();
|
||||
List<PrcPriceSet> list = priceSetService.selectPrcPriceSetList(set);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:query')")
|
||||
@GetMapping("/{priceSetId:[0-9]+}")
|
||||
public AjaxResult getInfo(@PathVariable Long priceSetId)
|
||||
{
|
||||
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
|
||||
return success(priceSetService.selectPrcPriceSetById(priceSetId));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:edit')")
|
||||
@Log(title = "打单套餐基本信息", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/basic")
|
||||
public AjaxResult editBasic(@RequestBody PrcPriceSet set)
|
||||
{
|
||||
priceSetService.assertSetCategory(set.getPriceSetId(), PrcPriceSet.CATEGORY_LABEL);
|
||||
set.setUpdateBy(getUsername());
|
||||
return toAjax(priceSetService.updateBasicInfo(set));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:audit')")
|
||||
@Log(title = "打单套餐停用", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/{priceSetId}/disable")
|
||||
public AjaxResult disable(@PathVariable Long priceSetId)
|
||||
{
|
||||
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
|
||||
return toAjax(priceSetService.disable(priceSetId, getUsername()));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:audit')")
|
||||
@Log(title = "打单套餐启用", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/{priceSetId}/enable")
|
||||
public AjaxResult enable(@PathVariable Long priceSetId)
|
||||
{
|
||||
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
|
||||
return toAjax(priceSetService.enable(priceSetId, getUsername()));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:remove')")
|
||||
@Log(title = "打单套餐删除", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{priceSetIds}")
|
||||
public AjaxResult remove(@PathVariable Long[] priceSetIds)
|
||||
{
|
||||
if (priceSetIds != null)
|
||||
{
|
||||
for (Long id : priceSetIds)
|
||||
{
|
||||
priceSetService.assertSetCategory(id, PrcPriceSet.CATEGORY_LABEL);
|
||||
}
|
||||
}
|
||||
return toAjax(priceSetService.deletePrcPriceSetByIds(priceSetIds));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:import')")
|
||||
@PostMapping("/preview")
|
||||
public AjaxResult preview(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam(value = "dictValue", required = false) String dictValue,
|
||||
@RequestParam(value = "progressId", required = false) String progressId,
|
||||
@RequestParam(value = "priceSetId", required = false) Long priceSetId) throws Exception
|
||||
{
|
||||
if (file == null || file.isEmpty())
|
||||
{
|
||||
throw new ServiceException("请上传价卡文件");
|
||||
}
|
||||
String name = file.getOriginalFilename();
|
||||
if (name == null || !name.toLowerCase(Locale.ROOT).endsWith(".xlsx"))
|
||||
{
|
||||
throw new ServiceException("请上传xlsx价卡");
|
||||
}
|
||||
if (priceSetId != null)
|
||||
{
|
||||
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
|
||||
}
|
||||
// 打单独立预览:已存在按本套餐判断,不与应付公共模板互挡
|
||||
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(payProductService.parseProgress(progressId));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:import')")
|
||||
@Log(title = "打单套餐导入", businessType = BusinessType.IMPORT)
|
||||
@PostMapping("/importSave")
|
||||
public AjaxResult importSave(@RequestBody PaySaveCommand command)
|
||||
{
|
||||
if (command == null)
|
||||
{
|
||||
throw new ServiceException("保存参数不能为空");
|
||||
}
|
||||
String message = payProductService.saveLabel(command);
|
||||
Map<String, Object> payload = new HashMap<String, Object>();
|
||||
payload.put("message", message);
|
||||
payload.put("priceSetId", command.getPriceSetId());
|
||||
return success(payload);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:list')")
|
||||
@GetMapping("/{priceSetId}/versions")
|
||||
public AjaxResult versions(@PathVariable Long priceSetId)
|
||||
{
|
||||
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
|
||||
return success(payProductService.selectLabelVersions(priceSetId));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:edit')")
|
||||
@Log(title = "打单套餐启动版本", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/{priceSetId}/versions/{versionNo}/activate")
|
||||
public AjaxResult activateVersion(@PathVariable Long priceSetId, @PathVariable Integer versionNo,
|
||||
@RequestBody Map<String, Object> body)
|
||||
{
|
||||
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
|
||||
Date effectiveTime = parseEffectiveTime(body == null ? null : body.get("effectiveTime"));
|
||||
String message = payProductService.activateLabelVersion(priceSetId, versionNo, effectiveTime);
|
||||
Map<String, Object> payload = new HashMap<String, Object>();
|
||||
payload.put("message", message);
|
||||
return success(payload);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:remove')")
|
||||
@Log(title = "打单套餐删除版本", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{priceSetId}/versions/{versionNo}")
|
||||
public AjaxResult deleteVersion(@PathVariable Long priceSetId, @PathVariable Integer versionNo)
|
||||
{
|
||||
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
|
||||
return toAjax(payProductService.deleteLabelVersion(priceSetId, versionNo));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:list')")
|
||||
@GetMapping("/{priceSetId}/products")
|
||||
public AjaxResult products(@PathVariable Long priceSetId,
|
||||
@RequestParam(value = "versionNo", required = false) Integer versionNo)
|
||||
{
|
||||
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
|
||||
if (versionNo != null)
|
||||
{
|
||||
return success(payProductService.selectLabelProductsByVersion(priceSetId, versionNo));
|
||||
}
|
||||
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)
|
||||
{
|
||||
if (termId != null)
|
||||
{
|
||||
return success(payProductService.selectPayProductDetailByTerm(termId));
|
||||
}
|
||||
return success(payProductService.selectPayProductDetail(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)
|
||||
{
|
||||
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
|
||||
return toAjax(payProductService.removeLabelProducts(priceSetId, productIds));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:query')")
|
||||
@GetMapping("/{priceSetId}/channels")
|
||||
public AjaxResult listChannels(@PathVariable Long priceSetId)
|
||||
{
|
||||
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
|
||||
return success(payProductService.listLabelChannels(priceSetId));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:edit')")
|
||||
@Log(title = "打单套餐绑定渠道", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/{priceSetId}/channels")
|
||||
public AjaxResult bindChannels(@PathVariable Long priceSetId, @RequestBody Map<String, Object> body)
|
||||
{
|
||||
priceSetService.assertSetCategory(priceSetId, PrcPriceSet.CATEGORY_LABEL);
|
||||
List<String> channelIds = new ArrayList<String>();
|
||||
Object rawIds = body == null ? null : body.get("channelIds");
|
||||
if (rawIds instanceof List<?>)
|
||||
{
|
||||
for (Object item : (List<?>) rawIds)
|
||||
{
|
||||
if (item != null && !String.valueOf(item).trim().isEmpty())
|
||||
{
|
||||
channelIds.add(String.valueOf(item).trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
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: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(payProductService.unbindLabelChannel(priceSetId, channelId));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:list')")
|
||||
@GetMapping("/customer/list")
|
||||
public TableDataInfo customerList(PrcCustomerPriceSet customer)
|
||||
{
|
||||
if (customer != null && customer.getPriceSetId() != null)
|
||||
{
|
||||
priceSetService.assertSetCategory(customer.getPriceSetId(), PrcPriceSet.CATEGORY_LABEL);
|
||||
}
|
||||
startPage();
|
||||
List<PrcCustomerPriceSet> list = priceSetService.selectCustomerList(customer);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:edit')")
|
||||
@Log(title = "打单套餐客户绑定", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/customer")
|
||||
public AjaxResult addCustomer(@RequestBody PrcCustomerPriceSet customer)
|
||||
{
|
||||
if (customer == null || customer.getPriceSetId() == null)
|
||||
{
|
||||
throw new ServiceException("请选择打单套餐");
|
||||
}
|
||||
priceSetService.assertSetCategory(customer.getPriceSetId(), PrcPriceSet.CATEGORY_LABEL);
|
||||
customer.setCreateBy(getUsername());
|
||||
return toAjax(priceSetService.insertCustomer(customer));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:edit')")
|
||||
@Log(title = "打单套餐客户批量绑定", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/customer/batch")
|
||||
public AjaxResult batchAddCustomer(@RequestBody PrcCustomerPriceSetBatch batch)
|
||||
{
|
||||
if (batch == null || batch.getPriceSetId() == null)
|
||||
{
|
||||
throw new ServiceException("请选择打单套餐");
|
||||
}
|
||||
priceSetService.assertSetCategory(batch.getPriceSetId(), PrcPriceSet.CATEGORY_LABEL);
|
||||
batch.setCreateBy(getUsername());
|
||||
batch.setUpdateBy(getUsername());
|
||||
return success(priceSetService.batchInsertCustomers(batch));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:edit')")
|
||||
@Log(title = "打单套餐客户绑定", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/customer")
|
||||
public AjaxResult editCustomer(@RequestBody PrcCustomerPriceSet customer)
|
||||
{
|
||||
if (customer == null)
|
||||
{
|
||||
throw new ServiceException("客户绑定参数不能为空");
|
||||
}
|
||||
if (customer.getPriceSetId() != null)
|
||||
{
|
||||
priceSetService.assertSetCategory(customer.getPriceSetId(), PrcPriceSet.CATEGORY_LABEL);
|
||||
}
|
||||
customer.setUpdateBy(getUsername());
|
||||
return toAjax(priceSetService.updateCustomer(customer));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:label:edit')")
|
||||
@Log(title = "打单套餐客户绑定", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/customer/{customerPricesetIds}")
|
||||
public AjaxResult removeCustomer(@PathVariable Long[] customerPricesetIds)
|
||||
{
|
||||
return toAjax(priceSetService.deleteCustomerByIds(customerPricesetIds));
|
||||
}
|
||||
|
||||
private Date parseEffectiveTime(Object raw)
|
||||
{
|
||||
if (raw == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (raw instanceof Date)
|
||||
{
|
||||
return (Date) raw;
|
||||
}
|
||||
String text = String.valueOf(raw).trim();
|
||||
if (text.isEmpty() || "null".equalsIgnoreCase(text))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// 兼容 yyyy-MM-dd / yyyy-MM-dd HH:mm / yyyy-MM-dd HH:mm:ss
|
||||
if (text.length() >= 16)
|
||||
{
|
||||
return com.bms.common.utils.DateUtils.parseDate(text);
|
||||
}
|
||||
if (text.length() >= 10)
|
||||
{
|
||||
return com.bms.common.utils.DateUtils.parseDate(text.substring(0, 10));
|
||||
}
|
||||
return com.bms.common.utils.DateUtils.parseDate(text);
|
||||
}
|
||||
}
|
||||
+51
-7
@@ -1,6 +1,8 @@
|
||||
package com.bms.web.controller.price;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Locale;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@@ -114,17 +116,18 @@ public class PayProductController extends BaseController
|
||||
return success(payProductService.selectTemplateOptions(payType));
|
||||
}
|
||||
|
||||
/** 渠道当前价格副本 */
|
||||
/** 渠道当前价格副本;payType 默认应付 AP */
|
||||
@PreAuthorize("@ss.hasAnyPermi('price:pay:query,data:serviceChannel:query,data:serviceChannel:edit')")
|
||||
@GetMapping("/channel/{channelId}")
|
||||
public AjaxResult channelPrice(@PathVariable String channelId)
|
||||
public AjaxResult channelPrice(@PathVariable String channelId,
|
||||
@RequestParam(value = "payType", required = false) String payType)
|
||||
{
|
||||
return success(payProductService.selectChannelPrice(channelId));
|
||||
return success(payProductService.selectChannelPrice(channelId, payType));
|
||||
}
|
||||
|
||||
/** 渠道只绑定产品编号,以后按有效期取价 */
|
||||
/** 渠道只绑定产品编号,以后按有效期取价;支持应收 AR 与应付 AP */
|
||||
@PreAuthorize("@ss.hasAnyPermi('price:pay:edit,data:serviceChannel:edit')")
|
||||
@Log(title = "渠道应付产品", businessType = BusinessType.UPDATE)
|
||||
@Log(title = "渠道产品绑定", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/channel/{channelId}/bind")
|
||||
public AjaxResult bindChannel(@PathVariable String channelId, @RequestBody ChannelProductMap link)
|
||||
{
|
||||
@@ -134,8 +137,31 @@ public class PayProductController extends BaseController
|
||||
payProductService.unbindChannel(channelId, payType);
|
||||
return success();
|
||||
}
|
||||
payProductService.bindChannel(channelId, link.getProductId(), link.getPriceSetId(), payType);
|
||||
return success(payProductService.selectChannelPrice(channelId));
|
||||
payProductService.bindChannel(channelId, link.getProductId(), payType);
|
||||
return success(payProductService.selectChannelPrice(channelId, payType));
|
||||
}
|
||||
|
||||
/** 批量绑定渠道应收/应付产品 */
|
||||
@PreAuthorize("@ss.hasAnyPermi('price:pay:edit,data:serviceChannel:edit')")
|
||||
@Log(title = "渠道批量绑定产品", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/channel/batchBind")
|
||||
public AjaxResult batchBindChannel(@RequestBody Map<String, Object> body)
|
||||
{
|
||||
List<String> channelIds = new ArrayList<String>();
|
||||
Object rawIds = body == null ? null : body.get("channelIds");
|
||||
if (rawIds instanceof List<?>)
|
||||
{
|
||||
for (Object item : (List<?>) rawIds)
|
||||
{
|
||||
if (item != null && !String.valueOf(item).trim().isEmpty())
|
||||
{
|
||||
channelIds.add(String.valueOf(item).trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
Long apProductId = toLong(body == null ? null : body.get("apProductId"));
|
||||
Long arProductId = toLong(body == null ? null : body.get("arProductId"));
|
||||
return success(payProductService.batchBindChannel(channelIds, apProductId, arProductId));
|
||||
}
|
||||
|
||||
/** 对照两个价格版本 */
|
||||
@@ -246,4 +272,22 @@ public class PayProductController extends BaseController
|
||||
throw new ServiceException("价卡文件不能超过30MB");
|
||||
}
|
||||
}
|
||||
|
||||
private Long toLong(Object raw)
|
||||
{
|
||||
if (raw == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (raw instanceof Number)
|
||||
{
|
||||
return ((Number) raw).longValue();
|
||||
}
|
||||
String text = String.valueOf(raw).trim();
|
||||
if (text.isEmpty() || "null".equalsIgnoreCase(text))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return Long.valueOf(text);
|
||||
}
|
||||
}
|
||||
|
||||
+58
@@ -16,10 +16,12 @@ import com.bms.common.core.domain.AjaxResult;
|
||||
import com.bms.common.core.page.TableDataInfo;
|
||||
import com.bms.common.enums.BusinessType;
|
||||
import com.bms.common.utils.StringUtils;
|
||||
import com.bms.price.airbill.domain.SrvBillAirAllocCommand;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFeeLog;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFeeSave;
|
||||
import com.bms.price.domain.vo.SrvBillAirQuery;
|
||||
import com.bms.price.domain.vo.SrvBillAirVo;
|
||||
import com.bms.price.service.ISrvBillAirAllocService;
|
||||
import com.bms.price.service.ISrvBillAirFeeService;
|
||||
import com.bms.price.service.ISrvBillAirService;
|
||||
|
||||
@@ -36,6 +38,9 @@ public class SrvBillAirController extends BaseController
|
||||
@Autowired
|
||||
private ISrvBillAirFeeService srvBillAirFeeService;
|
||||
|
||||
@Autowired
|
||||
private ISrvBillAirAllocService srvBillAirAllocService;
|
||||
|
||||
/**
|
||||
* 提单列表
|
||||
*/
|
||||
@@ -94,4 +99,57 @@ public class SrvBillAirController extends BaseController
|
||||
{
|
||||
return success(srvBillAirFeeService.save(command));
|
||||
}
|
||||
|
||||
/** 已分摊的费用改为已确定 */
|
||||
@PreAuthorize("@ss.hasPermi('price:airBill:feeConfirm')")
|
||||
@Log(title = "提单费用确定", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/fee/confirm")
|
||||
public AjaxResult confirmFee(@RequestParam("billCode") String billCode, @RequestParam("feeId") Long feeId)
|
||||
{
|
||||
return success(srvBillAirFeeService.confirm(billCode, feeId));
|
||||
}
|
||||
|
||||
/** 作废已分摊或已确定的费用,并逻辑删除已分摊到订单的明细 */
|
||||
@PreAuthorize("@ss.hasPermi('price:airBill:feeVoid')")
|
||||
@Log(title = "提单费用作废", businessType = BusinessType.DELETE)
|
||||
@PostMapping("/fee/void")
|
||||
public AjaxResult voidFee(@RequestParam("billCode") String billCode, @RequestParam("feeId") Long feeId)
|
||||
{
|
||||
return success(srvBillAirFeeService.voidFee(billCode, feeId));
|
||||
}
|
||||
|
||||
/** 勾选提单后,查出需要分摊到订单的费用 */
|
||||
@PreAuthorize("@ss.hasPermi('price:airBill:fee')")
|
||||
@GetMapping("/fee/alloc/preview")
|
||||
public AjaxResult allocPreview(@RequestParam("billCodes") String billCodes)
|
||||
{
|
||||
return success(srvBillAirAllocService.preview(splitCodes(billCodes)));
|
||||
}
|
||||
|
||||
/** 按重量或订单数把费用分摊到订单,并把费用状态改为已分摊 */
|
||||
@PreAuthorize("@ss.hasPermi('price:airBill:fee')")
|
||||
@Log(title = "提单费用分摊", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/fee/alloc")
|
||||
public AjaxResult allocate(@RequestBody SrvBillAirAllocCommand command)
|
||||
{
|
||||
List<String> billCodes = command == null ? null : command.getBillCodes();
|
||||
return success(srvBillAirAllocService.allocate(billCodes));
|
||||
}
|
||||
|
||||
private static List<String> splitCodes(String billCodes)
|
||||
{
|
||||
List<String> codes = new ArrayList<>();
|
||||
if (StringUtils.isEmpty(billCodes))
|
||||
{
|
||||
return codes;
|
||||
}
|
||||
for (String part : billCodes.split("[,,\\s]+"))
|
||||
{
|
||||
if (StringUtils.isNotEmpty(part))
|
||||
{
|
||||
codes.add(part.trim());
|
||||
}
|
||||
}
|
||||
return codes;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -72,7 +72,9 @@ public class SurchargeController extends BaseController
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('price:surcharge:list') or @ss.hasPermi('price:surcharge:query')"
|
||||
+ " or @ss.hasPermi('price:set:query') or @ss.hasPermi('price:set:add') or @ss.hasPermi('price:set:edit')"
|
||||
+ " or @ss.hasPermi('price:billing:bill')")
|
||||
+ " or @ss.hasPermi('price:billing:bill')"
|
||||
+ " or @ss.hasPermi('price:pay:query') or @ss.hasPermi('price:pay:edit') or @ss.hasPermi('price:pay:import')"
|
||||
+ " or @ss.hasPermi('price:airBill:fee')")
|
||||
@GetMapping("/options")
|
||||
public AjaxResult listOptions(PrcSurcharge surcharge)
|
||||
{
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package com.bms.web.controller.ar;
|
||||
package com.bms.web.controller.receivable;
|
||||
|
||||
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.ar.domain.ArBankAccount;
|
||||
import com.bms.ar.service.IArBillService;
|
||||
import com.bms.receivable.domain.ArBankAccount;
|
||||
import com.bms.receivable.service.IArBillService;
|
||||
import com.bms.common.core.controller.BaseController;
|
||||
import com.bms.common.core.domain.AjaxResult;
|
||||
|
||||
+15
-16
@@ -1,4 +1,4 @@
|
||||
package com.bms.web.controller.ar;
|
||||
package com.bms.web.controller.receivable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -23,21 +23,20 @@ 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.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.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.ArBillGeneratePreviewExportRow;
|
||||
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.enums.ArBillDateType;
|
||||
import com.bms.receivable.service.IArBillService;
|
||||
import com.bms.receivable.security.ArBillScopeResolver;
|
||||
import com.bms.common.annotation.Log;
|
||||
import com.bms.common.core.controller.BaseController;
|
||||
import com.bms.common.core.domain.AjaxResult;
|
||||
+3
-4
@@ -1,4 +1,4 @@
|
||||
package com.bms.web.controller.ar;
|
||||
package com.bms.web.controller.receivable;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
@@ -12,9 +12,8 @@ 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.ar.domain.ArBalanceWriteoff;
|
||||
import com.bms.ar.domain.ArBill;
|
||||
import com.bms.ar.service.IArBalanceWriteoffService;
|
||||
import com.bms.receivable.domain.ArBalanceWriteoff;
|
||||
import com.bms.receivable.service.IArBalanceWriteoffService;
|
||||
import com.bms.common.annotation.Log;
|
||||
import com.bms.common.core.controller.BaseController;
|
||||
import com.bms.common.core.domain.AjaxResult;
|
||||
+10
-10
@@ -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.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 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 org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import com.bms.ar.profit.ArProfitRow;
|
||||
import com.bms.ar.profit.ArCommissionResult;
|
||||
import com.bms.receivable.profit.ArProfitRow;
|
||||
import com.bms.receivable.profit.ArCommissionResult;
|
||||
import com.bms.common.utils.poi.ExcelUtil;
|
||||
import com.bms.price.enums.ProfitAreaRule;
|
||||
import com.bms.common.annotation.Log;
|
||||
@@ -99,7 +99,7 @@ public class ArProfitController extends BaseController
|
||||
return success();
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('report:arProfit:commission')")
|
||||
@PreAuthorize("@ss.hasPermi('report:arProfit:list')")
|
||||
@GetMapping("/commissions")
|
||||
public TableDataInfo commissions(@RequestParam(required = false) Long billId,
|
||||
@RequestParam(defaultValue = "false") boolean includeReversed)
|
||||
@@ -108,7 +108,7 @@ public class ArProfitController extends BaseController
|
||||
return getDataTable(commissions.results(billId, includeReversed));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('report:arProfit:commission')")
|
||||
@PreAuthorize("@ss.hasPermi('report:arProfit:list')")
|
||||
@GetMapping("/commissions/summary")
|
||||
public AjaxResult commissionSummary(@RequestParam(required = false) Long billId) { return success(commissions.summary(billId)); }
|
||||
|
||||
@@ -121,7 +121,7 @@ public class ArProfitController extends BaseController
|
||||
@PostMapping("/commissions/export")
|
||||
public void exportCommissions(HttpServletResponse response, @RequestParam(required = false) Long billId)
|
||||
{
|
||||
new ExcelUtil<>(ArCommissionResult.class).exportExcel(response, commissions.results(billId, false), "应收账单提成明细");
|
||||
new ExcelUtil<>(ArCommissionResult.class).exportExcel(response, commissions.exportResults(billId), "应收账单提成明细");
|
||||
}
|
||||
|
||||
@Log(title = "应收账单毛利分析", businessType = BusinessType.EXPORT)
|
||||
|
||||
@@ -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
|
||||
# Night auto-rebill: hours 0-8, pull 500 (all statuses) -> bill -> next 500 until empty / 09:00.
|
||||
# Empty page => no billing.
|
||||
# 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-retry:
|
||||
enabled: true
|
||||
start-hour: 0
|
||||
end-hour: 9
|
||||
end-hour: 24
|
||||
batch-size: 500
|
||||
cron: "0 0/1 0-8 * * ?"
|
||||
cron: "0 0 * * * ?"
|
||||
oracle:
|
||||
# SLAVE datasource Oracle main-order table (schema.table allowed)
|
||||
order-table: WEISHI_USER.TAB_ORDER
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import com.bms.price.domain.PayPostcodeGroup;
|
||||
|
||||
@@ -18,6 +19,7 @@ class PayPostcodeImporterTest
|
||||
private final PayPostcodeImporter importer = new PayPostcodeImporter();
|
||||
|
||||
@Test
|
||||
@Disabled("依赖开发机 D:/应付供应商 下的价卡文件,未随项目提供")
|
||||
void stripsStarAndMergesSameFee() throws Exception
|
||||
{
|
||||
List<PayPostcodeGroup> groups = importer.parse(Files.readAllBytes(file("32、")), "32.xlsx");
|
||||
@@ -88,6 +90,7 @@ class PayPostcodeImporterTest
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("依赖开发机 D:/应付供应商 下的价卡文件,未随项目提供")
|
||||
void reachableUnreachableAndRemoteKeepTheirOwnCountry() throws Exception
|
||||
{
|
||||
List<PayPostcodeGroup> may = importer.parse(Files.readAllBytes(file("20、")), "20.xlsx");
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import com.bms.price.domain.PayOrderCostResult;
|
||||
import com.bms.price.domain.PayPreview;
|
||||
@@ -23,6 +24,7 @@ class PayWorkbookParserTest
|
||||
private final PayWorkbookParser parser = new PayWorkbookParser();
|
||||
|
||||
@Test
|
||||
@Disabled("依赖开发机 D:/应付供应商 下的价卡文件,未随项目提供")
|
||||
void octoberCatalogKeepsTwelveProductSheets() throws Exception
|
||||
{
|
||||
PayPreview preview = parse("9、");
|
||||
@@ -36,6 +38,7 @@ class PayWorkbookParserTest
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("依赖开发机 D:/应付供应商 下的价卡文件,未随项目提供")
|
||||
void mayCardReadsDhlAndDpdWithoutZoneSheets() throws Exception
|
||||
{
|
||||
PayPreview preview = parse("8、");
|
||||
@@ -88,6 +91,7 @@ class PayWorkbookParserTest
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("依赖开发机 D:/应付供应商 下的价卡文件,未随项目提供")
|
||||
void upsZonesStayLeftToRightAndColissimoFuelIsMarked() throws Exception
|
||||
{
|
||||
PayPreview preview = parse("32、");
|
||||
@@ -148,6 +152,7 @@ class PayWorkbookParserTest
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("依赖开发机 D:/应付供应商 下的价卡文件,未随项目提供")
|
||||
void imilePostcodesResolveZoneAndGermanCoverage() throws Exception
|
||||
{
|
||||
PayPreview preview = parse("32、");
|
||||
@@ -168,6 +173,7 @@ class PayWorkbookParserTest
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("依赖开发机 D:/应付供应商 下的价卡文件,未随项目提供")
|
||||
void gweFileOverridesOnlyPanEuroSupplier() throws Exception
|
||||
{
|
||||
PayPreview preview = parse("8.1");
|
||||
@@ -182,6 +188,7 @@ class PayWorkbookParserTest
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("依赖开发机 D:/应付供应商 下的价卡文件,未随项目提供")
|
||||
void wholeWorkbookPreviewSerializes() throws Exception
|
||||
{
|
||||
PayPreview preview = parse("9、");
|
||||
@@ -191,6 +198,7 @@ class PayWorkbookParserTest
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("依赖开发机 D:/应付供应商 下的价卡文件,未随项目提供")
|
||||
void yideCatalogIncludesPostcodeProducts() throws Exception
|
||||
{
|
||||
PayPreview preview = parse("26、");
|
||||
@@ -439,6 +447,7 @@ class PayWorkbookParserTest
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("依赖开发机 D:/payment 下的 CTT 48H.xlsx 价卡文件,未随项目提供")
|
||||
void ctt48hFileKeepsSurchargeTablesOutOfWeightBands() throws Exception
|
||||
{
|
||||
Path file = priceCard("CTT 48H.xlsx");
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.bms.report;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
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 java.lang.reflect.Proxy;
|
||||
import java.math.BigDecimal;
|
||||
@@ -9,15 +11,55 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.junit.jupiter.api.Test;
|
||||
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.receivable.domain.ArBalanceWriteoff;
|
||||
import com.bms.receivable.domain.ArBill;
|
||||
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.common.exception.ServiceException;
|
||||
|
||||
class ArCommissionServiceTest
|
||||
{
|
||||
@Test
|
||||
void commissionReadsUseServerResolvedScopeAndManagementRemainsRestricted()
|
||||
{
|
||||
AtomicReference<ArProfitScope> actorScope = new AtomicReference<>(new ArProfitScope(false, true, false, 7L, 10L));
|
||||
AtomicReference<Object[]> selected = new AtomicReference<>();
|
||||
ArProfitScopeResolver resolver = new ArProfitScopeResolver() {
|
||||
@Override public ArProfitScope resolve() { return actorScope.get(); }
|
||||
};
|
||||
ArCommissionMapper mapper = (ArCommissionMapper) Proxy.newProxyInstance(getClass().getClassLoader(),
|
||||
new Class<?>[] { ArCommissionMapper.class }, (proxy, method, args) -> {
|
||||
if ("selectResults".equals(method.getName()) || "selectCommissionSummary".equals(method.getName())) {
|
||||
selected.set(args.clone());
|
||||
return List.of();
|
||||
}
|
||||
throw new AssertionError(method.getName());
|
||||
});
|
||||
ArCommissionService service = new ArCommissionService(mapper, resolver, null, null);
|
||||
|
||||
service.results(999L, true);
|
||||
assertEquals(999L, selected.get()[0]);
|
||||
assertSame(actorScope.get(), selected.get()[2]);
|
||||
service.summary(999L);
|
||||
assertSame(actorScope.get(), selected.get()[1]);
|
||||
assertThrows(ServiceException.class, service::selectRates);
|
||||
assertThrows(ServiceException.class, service::missingRates);
|
||||
assertThrows(ServiceException.class, () -> service.exportResults(null));
|
||||
|
||||
actorScope.set(new ArProfitScope(false, false, true, 8L, 10L));
|
||||
service.results(null, false);
|
||||
assertSame(actorScope.get(), selected.get()[2]);
|
||||
assertThrows(ServiceException.class, service::selectRates);
|
||||
|
||||
actorScope.set(new ArProfitScope(true, false, false, 9L, 10L));
|
||||
service.exportResults(null);
|
||||
assertSame(actorScope.get(), selected.get()[2]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void snapshotsTheRateOnceAtWriteoffAndPreservesMissingRates()
|
||||
{
|
||||
|
||||
@@ -8,17 +8,64 @@ 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.ar.profit.ArProfitQuery;
|
||||
import com.bms.ar.profit.ArProfitScope;
|
||||
import com.bms.ar.profit.ArCommissionResult;
|
||||
import com.bms.receivable.profit.ArProfitQuery;
|
||||
import com.bms.receivable.profit.ArProfitScope;
|
||||
import com.bms.receivable.profit.ArCommissionResult;
|
||||
|
||||
class ArProfitMapperSqlTest
|
||||
{
|
||||
@Test
|
||||
void commissionInReportSummaryUsesBillFiltersAndDataScope() throws Exception
|
||||
{
|
||||
Configuration configuration = new Configuration();
|
||||
String resource = "mapper/receivable/ArProfitMapper.xml";
|
||||
try (InputStream input = getClass().getClassLoader().getResourceAsStream(resource))
|
||||
{
|
||||
new XMLMapperBuilder(input, configuration, resource, configuration.getSqlFragments()).parse();
|
||||
}
|
||||
var statement = configuration.getMappedStatement("com.bms.ar.mapper.ArProfitMapper.selectSummary");
|
||||
ArProfitQuery query = new ArProfitQuery();
|
||||
query.setCustomerId(5L);
|
||||
query.setBillNo("AR123");
|
||||
query.setStatus("WRITTEN_OFF");
|
||||
query.setSalesUserId(8L);
|
||||
query.setBeginBillDate(new java.util.Date(0));
|
||||
query.setEndBillDate(new java.util.Date());
|
||||
String sql = statement.getBoundSql(Map.of("query", query,
|
||||
"scope", new ArProfitScope(false, true, false, 8L, 10L))).getSql();
|
||||
int commissionStart = sql.indexOf("from ar_bill b join ar_profit_commission_result r");
|
||||
assertTrue(commissionStart >= 0);
|
||||
String commission = sql.substring(commissionStart);
|
||||
assertTrue(commission.contains("r.result_status = 'READY'"));
|
||||
assertTrue(commission.contains("r.currency = b.currency"));
|
||||
assertTrue(commission.contains("b.status = 'WRITTEN_OFF'"));
|
||||
assertTrue(commission.contains("b.profit_sales_user_id = ?"));
|
||||
assertTrue(commission.contains("b.customer_id = ?"));
|
||||
assertTrue(commission.contains("b.bill_no like"));
|
||||
assertTrue(commission.contains("b.status = ?"));
|
||||
assertTrue(commission.contains("b.bill_date >= ?"));
|
||||
assertTrue(commission.contains("b.bill_date <= ?"));
|
||||
assertTrue(commission.contains("group by b.currency"));
|
||||
|
||||
for (ArProfitScope scope : new ArProfitScope[] {
|
||||
new ArProfitScope(false, false, true, 8L, 10L),
|
||||
new ArProfitScope(false, false, false, 8L, 10L),
|
||||
new ArProfitScope(true, false, false, 8L, 10L) })
|
||||
{
|
||||
String scopedSql = statement.getBoundSql(Map.of("query", new ArProfitQuery(), "scope", scope)).getSql();
|
||||
String scopedCommission = scopedSql.substring(scopedSql.indexOf("from ar_bill b join ar_profit_commission_result r"));
|
||||
if (scope.department()) assertTrue(scopedCommission.contains("find_in_set"));
|
||||
else if (scope.all()) assertFalse(scopedCommission.contains("b.profit_sales_user_id = ?"));
|
||||
else assertTrue(scopedCommission.contains("and 1 = 0"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void parsesReportAndCommissionMappersAndScopesEachReportQuery() throws Exception
|
||||
{
|
||||
Configuration configuration = new Configuration();
|
||||
for (String resource : new String[] { "mapper/ar/ArProfitMapper.xml", "mapper/ar/ArCommissionMapper.xml" })
|
||||
// 提成 Mapper 先加载时也必须能解析引用的报表权限片段。
|
||||
for (String resource : new String[] {"mapper/receivable/ArCommissionMapper.xml", "mapper/receivable/ArProfitMapper.xml"})
|
||||
{
|
||||
try (InputStream input = getClass().getClassLoader().getResourceAsStream(resource))
|
||||
{
|
||||
@@ -37,17 +84,35 @@ class ArProfitMapperSqlTest
|
||||
assertTrue(manager.contains("find_in_set"));
|
||||
assertTrue(manager.contains("b.status in"));
|
||||
}
|
||||
ArProfitQuery orderFilter = new ArProfitQuery();
|
||||
orderFilter.setOrderNo("ORDER-1");
|
||||
String bills = configuration.getMappedStatement("com.bms.ar.mapper.ArProfitMapper.selectBills")
|
||||
.getBoundSql(Map.of("query", orderFilter, "scope", new ArProfitScope(true, false, false, 8L, 10L))).getSql();
|
||||
.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("exists (select 1 from ar_bill_detail matched"));
|
||||
assertFalse(bills.contains("and d.order_no like"));
|
||||
assertTrue(bills.contains("b.bill_name"));
|
||||
assertFalse(bills.contains("order_no"));
|
||||
String billCommission = configuration.getMappedStatement("com.bms.ar.mapper.ArCommissionMapper.selectCommissionSummary")
|
||||
.getBoundSql(Map.of("billId", 9L)).getSql();
|
||||
.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.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,
|
||||
"scope", new ArProfitScope(false, false, true, 8L, 10L))).getSql();
|
||||
String denied = statement.getBoundSql(Map.of("includeReversed", false,
|
||||
"scope", new ArProfitScope(false, false, false, 8L, 10L))).getSql();
|
||||
String all = statement.getBoundSql(Map.of("includeReversed", false,
|
||||
"scope", new ArProfitScope(true, false, false, 8L, 10L))).getSql();
|
||||
assertTrue(self.contains("b.profit_sales_user_id = ?"));
|
||||
assertTrue(self.contains("r.ar_bill_id = ?"));
|
||||
assertFalse(self.contains("find_in_set"));
|
||||
assertTrue(department.contains("u.user_id = b.profit_sales_user_id"));
|
||||
assertTrue(department.contains("find_in_set"));
|
||||
assertTrue(denied.contains("and 1 = 0"));
|
||||
assertFalse(all.contains("b.profit_sales_user_id = ?"));
|
||||
assertFalse(all.contains("find_in_set"));
|
||||
}
|
||||
var salesSql = configuration.getMappedStatement("com.bms.ar.mapper.ArProfitMapper.selectSalesOptions")
|
||||
.getBoundSql(Map.of("keyword", "sales"));
|
||||
assertTrue(salesSql.getSql().contains("from sys_user u"));
|
||||
|
||||
@@ -4,10 +4,9 @@ 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.ar.profit.ArProfitScopeResolver;
|
||||
import com.bms.receivable.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.ar.profit.CommissionCalculator;
|
||||
import com.bms.ar.profit.ArCommissionResult;
|
||||
import com.bms.ar.profit.CommissionCalculator.Writeoff;
|
||||
import com.bms.receivable.profit.CommissionCalculator;
|
||||
import com.bms.receivable.profit.ArCommissionResult;
|
||||
import com.bms.receivable.profit.CommissionCalculator.Writeoff;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class CommissionCalculatorTest
|
||||
|
||||
+2
-2
@@ -15,8 +15,8 @@ import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import com.bms.common.exception.ServiceException;
|
||||
import com.bms.common.utils.SecurityUtils;
|
||||
import com.bms.ar.domain.ArBankAccount;
|
||||
import com.bms.ar.mapper.ArBankAccountMapper;
|
||||
import com.bms.receivable.domain.ArBankAccount;
|
||||
import com.bms.receivable.mapper.ArBankAccountMapper;
|
||||
import com.bms.customer.customerinfo.domain.BmsCustomer;
|
||||
import com.bms.customer.customerinfo.domain.BmsCustomerAttachment;
|
||||
import com.bms.customer.customerinfo.mapper.BmsCustomerMapper;
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.bms.price.airbill.domain;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/** 提单费用分摊请求。一次最多 5 个提单号。 */
|
||||
@Data
|
||||
public class SrvBillAirAllocCommand
|
||||
{
|
||||
private List<String> billCodes;
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.bms.price.airbill.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Data;
|
||||
|
||||
/** 待分摊的一条提单费用,供费用处理弹框展示。 */
|
||||
@Data
|
||||
public class SrvBillAirAllocFee
|
||||
{
|
||||
private Long id;
|
||||
|
||||
private String billCode;
|
||||
|
||||
/** 提单主键 srv_bill_air.guid,写入订单费用的 source_id */
|
||||
private String ladingId;
|
||||
|
||||
/** 费用阶段,字典 bms_fee_level */
|
||||
private String feeLevel;
|
||||
|
||||
/** 费用状态,字典 bill_fee_status */
|
||||
private String billFeeStatus;
|
||||
|
||||
private String feeName;
|
||||
|
||||
private BigDecimal price;
|
||||
|
||||
private String currency;
|
||||
|
||||
/** 分摊维度:1订单 2重量 */
|
||||
private String allocDim;
|
||||
|
||||
/** 提单实际重量。只在按重量分摊时按提单号填入,不返回页面 */
|
||||
@JsonIgnore
|
||||
private BigDecimal actualWeight;
|
||||
|
||||
/** 干线打板重量。只在按重量分摊时按提单号填入,不返回页面 */
|
||||
@JsonIgnore
|
||||
private BigDecimal bandBoxWeight;
|
||||
|
||||
private Integer orderCount;
|
||||
|
||||
/** 不能分摊时的原因。空表示可以分摊 */
|
||||
private String blockReason;
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.bms.price.airbill.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Data;
|
||||
|
||||
/** 提单下参与分摊的一票订单。 */
|
||||
@Data
|
||||
public class SrvBillAirAllocOrder
|
||||
{
|
||||
private String billCode;
|
||||
|
||||
private String orderNo;
|
||||
|
||||
/** 计费订单 ID,对应 prc_order_bill.bill_id */
|
||||
private String billId;
|
||||
|
||||
/** 计费重,没有计费重时依次取实际重量、收货重量 */
|
||||
private BigDecimal chargeWeight;
|
||||
}
|
||||
@@ -15,9 +15,24 @@ public class SrvBillAirFee
|
||||
|
||||
private String billCode;
|
||||
|
||||
/** 字典 bms_fee_type */
|
||||
/** 附加费编号 prc_surcharge.sc_id */
|
||||
private String feeType;
|
||||
|
||||
/** 附加费名称,仅展示,不入库 */
|
||||
private String feeName;
|
||||
|
||||
/** 费用阶段,字典 bms_fee_level */
|
||||
private String feeLevel;
|
||||
|
||||
/** 提单费用状态,字典 bill_fee_status。只随分摊、作废操作变化 */
|
||||
private String billFeeStatus;
|
||||
|
||||
/** 是否分摊,字典 bill_fee_alloc:1是 0否 */
|
||||
private String allocFlag;
|
||||
|
||||
/** 分摊维度(票),字典 bill_fee_alloc_dim:1订单 2重量。是否分摊为否时为空 */
|
||||
private String allocDim;
|
||||
|
||||
private BigDecimal price;
|
||||
|
||||
/** 字典 bms_currency */
|
||||
|
||||
@@ -17,6 +17,12 @@ public class SrvBillAirFeeLog
|
||||
|
||||
public static final String DELETE = "3";
|
||||
|
||||
/** 已分摊费用确定。日志不记录字段变化。 */
|
||||
public static final String CONFIRM = "4";
|
||||
|
||||
/** 已分摊或已确定费用作废。 */
|
||||
public static final String VOID = "5";
|
||||
|
||||
private Long logId;
|
||||
|
||||
private String billCode;
|
||||
|
||||
@@ -25,7 +25,7 @@ public class BillingProperties
|
||||
* Keep below Druid maxActive with headroom for other traffic.
|
||||
*/
|
||||
private int localRebillThreads = 16;
|
||||
/** Night window auto-retry of prc_billing_task (all statuses) */
|
||||
/** Hourly auto-retry of prc_billing_task (all statuses) */
|
||||
private NightRetry nightRetry = new NightRetry();
|
||||
private Oracle oracle = new Oracle();
|
||||
|
||||
@@ -120,8 +120,8 @@ public class BillingProperties
|
||||
}
|
||||
|
||||
/**
|
||||
* Overnight rebill window: pull prc_billing_task (all success_status) and retryTask.
|
||||
* Default hours [0, 9) local time; empty result skips billing.
|
||||
* Auto rebill window: pull prc_billing_task (all success_status) and retryTask.
|
||||
* Default hours [0, 24) local time (all day); empty result skips billing.
|
||||
*/
|
||||
public static class NightRetry
|
||||
{
|
||||
@@ -129,8 +129,8 @@ public class BillingProperties
|
||||
private boolean enabled = true;
|
||||
/** Inclusive start hour (0 = 00:00) */
|
||||
private int startHour = 0;
|
||||
/** Exclusive end hour (9 = stop at 09:00) */
|
||||
private int endHour = 9;
|
||||
/** Exclusive end hour (24 = all day) */
|
||||
private int endHour = 24;
|
||||
/** Page size: bill this many then pull next page (default 500) */
|
||||
private int batchSize = 500;
|
||||
|
||||
|
||||
@@ -15,11 +15,20 @@ public class ChannelProductMap
|
||||
/** 渠道表主键 srv_service_channel.id */
|
||||
private String channelId;
|
||||
|
||||
/** 渠道编码,对应 srv_service_channel.channel_code */
|
||||
private String channelCode;
|
||||
|
||||
private Long productId;
|
||||
|
||||
/** 应收套餐 prc_price_set.price_set_id */
|
||||
/** 打单套餐 ID,对应 prc_price_set.price_set_id(应收可填) */
|
||||
private Long priceSetId;
|
||||
|
||||
/** 渠道名称(列表回显) */
|
||||
private String channelName;
|
||||
|
||||
/** 产品名称(列表回显) */
|
||||
private String productName;
|
||||
|
||||
/** AR 应收,AP 应付 */
|
||||
private String payType;
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 打单套餐价卡版本(按 pay_product_term.version_no 聚合,不走价格套餐 prc_price_set_version)。
|
||||
*/
|
||||
@Data
|
||||
public class LabelPackageVersion
|
||||
{
|
||||
private Long priceSetId;
|
||||
|
||||
private Integer versionNo;
|
||||
|
||||
/** 展示用,如 V1 */
|
||||
private String versionLabel;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private Date effectiveTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private Date expireTime;
|
||||
|
||||
/** 与 pay_product_term.status 一致:0待生效 1生效 2已失效 */
|
||||
private String status;
|
||||
|
||||
private Integer productCount;
|
||||
|
||||
/** status=1 生效条数 */
|
||||
private Integer activeCount;
|
||||
|
||||
/** status=0 待生效条数 */
|
||||
private Integer pendingCount;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -12,6 +12,9 @@ public class PayExtraLine
|
||||
|
||||
private Long productId;
|
||||
|
||||
/** 价卡周期,对应 pay_product_term.term_id;与 productId 组成明细维度 */
|
||||
private Long termId;
|
||||
|
||||
/** 邮编 或 配置 */
|
||||
private String lineType;
|
||||
|
||||
|
||||
@@ -11,7 +11,13 @@ public class PayPriceCompare
|
||||
|
||||
private PayProduct newer;
|
||||
|
||||
/** 有效期、来源文件、币种 */
|
||||
private List<PayPriceDiff> versionDiffs;
|
||||
|
||||
private List<PayPriceDiff> weightDiffs;
|
||||
|
||||
private List<PayPriceDiff> surchargeDiffs;
|
||||
|
||||
/** 邮编等不能归进重量段和附加费的原文 */
|
||||
private List<PayPriceDiff> extraDiffs;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ import lombok.Data;
|
||||
@Data
|
||||
public class PayPriceDiff
|
||||
{
|
||||
/** 版本、重量段、附加费、其他配置 */
|
||||
private String kind;
|
||||
|
||||
private String name;
|
||||
|
||||
private String zoneName;
|
||||
|
||||
@@ -22,6 +22,9 @@ public class PayProductTerm extends BaseEntity
|
||||
|
||||
private Long productId;
|
||||
|
||||
/** 打单套餐ID;应付公共模板为空 */
|
||||
private Long priceSetId;
|
||||
|
||||
private Integer versionNo;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
@@ -30,7 +33,7 @@ public class PayProductTerm extends BaseEntity
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private Date effectiveEnd;
|
||||
|
||||
/** 0 生效,1 失效 */
|
||||
/** 0待生效 1生效 2已失效 */
|
||||
private String status;
|
||||
|
||||
private String productName;
|
||||
|
||||
@@ -13,5 +13,17 @@ public class PaySaveCommand
|
||||
/** 为 true 时,同文件同表同名的旧产品先删后插 */
|
||||
private boolean overwrite;
|
||||
|
||||
/** 打单套餐ID;有值时走 LABEL 导入(稳定 product_id + term.price_set_id) */
|
||||
private Long priceSetId;
|
||||
|
||||
/** 打单套餐名称;priceSetId 为空时新建套餐壳 */
|
||||
private String priceSetName;
|
||||
|
||||
/** 打单套餐备注 */
|
||||
private String priceSetNote;
|
||||
|
||||
/** 编辑待生效版本时传入,覆盖该 version_no,不新建版本号 */
|
||||
private Integer versionNo;
|
||||
|
||||
private List<PayProduct> products = new ArrayList<>();
|
||||
}
|
||||
|
||||
@@ -15,13 +15,19 @@ public class PaySurcharge
|
||||
|
||||
private Long productId;
|
||||
|
||||
/** 所属价卡版本,跟产品版本一起变 */
|
||||
/** 价卡周期,对应 pay_product_term.term_id;与 productId 组成明细维度 */
|
||||
private Long termId;
|
||||
|
||||
/** 所属价卡版本,跟产品版本一起变(展示兼容) */
|
||||
private Integer versionNo;
|
||||
|
||||
private String sectionName;
|
||||
|
||||
private String feeName;
|
||||
|
||||
/** 附加费配置ID,对应 prc_surcharge.sc_id */
|
||||
private Long scId;
|
||||
|
||||
private String zoneName;
|
||||
|
||||
/** 收费标准原文 */
|
||||
|
||||
@@ -13,7 +13,10 @@ public class PayWeightBand
|
||||
|
||||
private Long productId;
|
||||
|
||||
/** 所属价卡版本,跟产品版本一起变 */
|
||||
/** 价卡周期,对应 pay_product_term.term_id;与 productId 组成明细维度 */
|
||||
private Long termId;
|
||||
|
||||
/** 所属价卡版本,跟产品版本一起变(展示兼容) */
|
||||
private Integer versionNo;
|
||||
|
||||
/** 报价区块,例如派送费、偏远附加费 */
|
||||
|
||||
@@ -122,7 +122,7 @@ public class PrcOrderBill extends BaseEntity
|
||||
private String creditArrearsStatus;
|
||||
/** NONE / REFUNDED */
|
||||
private String refundStatus;
|
||||
/** Billing fail: 0 ok; 1/2/3 sales miss; 23/24/25 catalog miss; soft-fail amount=0, no fee rows */
|
||||
/** Billing fail: 0 ok writes income bill+fee; non-0 amount=0, no income bill/fee */
|
||||
private Integer billingFailStatus;
|
||||
/** 失败原因 */
|
||||
private String errorMessage;
|
||||
|
||||
@@ -19,6 +19,9 @@ public class PrcPayBillFee implements Serializable
|
||||
private String feeType;
|
||||
private String feeName;
|
||||
private Long scId;
|
||||
|
||||
/** 费用所属阶段:1 a段,2 b段,3 c段,4 d段 */
|
||||
private String feeLevel;
|
||||
private BigDecimal amount;
|
||||
private String currency;
|
||||
private String roundMode;
|
||||
@@ -26,6 +29,12 @@ public class PrcPayBillFee implements Serializable
|
||||
|
||||
/** CALC 计算,LADING 提单分摊,MANUAL 人工 */
|
||||
private String feeSource;
|
||||
|
||||
/** 来源数据id */
|
||||
private String sourceId;
|
||||
|
||||
/** 是否删除:0未删除 1已删除 */
|
||||
private String isDeleted;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ public class PrcPriceSet extends BaseEntity
|
||||
public static final String CATEGORY_PACKAGE = "PACKAGE";
|
||||
/** Boss exclusive quote. */
|
||||
public static final String CATEGORY_EXCLUSIVE = "EXCLUSIVE";
|
||||
/** Label / 打单套餐(应收价卡套餐). */
|
||||
public static final String CATEGORY_LABEL = "LABEL";
|
||||
|
||||
private Long priceSetId;
|
||||
private String priceSetCode;
|
||||
@@ -26,7 +28,7 @@ public class PrcPriceSet extends BaseEntity
|
||||
private String priceSetNote;
|
||||
/** FIXED / DISCOUNT */
|
||||
private String setType;
|
||||
/** PACKAGE=ordinary set / EXCLUSIVE=boss exclusive quote */
|
||||
/** PACKAGE=ordinary set / EXCLUSIVE=boss exclusive quote / LABEL=打单套餐 */
|
||||
private String setCategory;
|
||||
private BigDecimal defaultDiscount;
|
||||
private String defaultType;
|
||||
|
||||
@@ -21,6 +21,11 @@ public class TmsPriceQuoteRequest implements Serializable
|
||||
@NotBlank(message = "productCode is required")
|
||||
private String productCode;
|
||||
|
||||
/**
|
||||
* 打单套餐(LABEL)试算/计费用渠道ID;计费也可从订单扩展 productChannelId 解析。
|
||||
*/
|
||||
private String channelId;
|
||||
|
||||
/** ISO2; countryName alternative */
|
||||
private String countryCode;
|
||||
/** country display name; countryCode alternative */
|
||||
|
||||
@@ -103,7 +103,7 @@ public class TmsPriceQuoteResult implements Serializable
|
||||
private String creditArrearsStatus;
|
||||
/** NONE / REFUNDED */
|
||||
private String refundStatus;
|
||||
/** Billing fail: 0 ok; 1/2/3 sales; 23/24/25 catalog (soft-fail on bill sync) */
|
||||
/** Billing fail: 0 ok (income bill+fee); 1/2/3 sales; 23/24/25 catalog */
|
||||
private Integer billingFailStatus;
|
||||
private Integer pieces;
|
||||
private BigDecimal actualWeight;
|
||||
|
||||
@@ -6,7 +6,8 @@ package com.bms.price.enums;
|
||||
* 1/2/3 sales-price (package) miss: product / country / weight;
|
||||
* 23/24/25 product-catalog price miss: product / country / weight;
|
||||
* 4+ map to prc_billing_task.fail_code.
|
||||
* Soft-fail income bill: 0/1/2/3/23/24/25; fee rows only for 0.
|
||||
* 26 not billed yet (order-source sync only).
|
||||
* Income bill + fee rows: only status 0.
|
||||
*/
|
||||
public enum BillBillingFailStatus
|
||||
{
|
||||
@@ -38,7 +39,9 @@ public enum BillBillingFailStatus
|
||||
/** Product catalog miss: product hit but country/zone miss. */
|
||||
CATALOG_COUNTRY(24),
|
||||
/** Product catalog miss: product+country hit but weight band miss. */
|
||||
CATALOG_WEIGHT(25);
|
||||
CATALOG_WEIGHT(25),
|
||||
/** Order landed from upstream but not billed yet. */
|
||||
NOT_BILLED(26);
|
||||
|
||||
private final int code;
|
||||
|
||||
@@ -70,16 +73,23 @@ public enum BillBillingFailStatus
|
||||
|
||||
public static boolean isFail(Integer code)
|
||||
{
|
||||
return code != null && code.intValue() > SUCCESS.code;
|
||||
if (code == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int value = code.intValue();
|
||||
return value != SUCCESS.code && value != NOT_BILLED.code;
|
||||
}
|
||||
|
||||
/** Soft-fail sales/catalog misses also persist prc_income_bill. */
|
||||
public static boolean isNotBilled(Integer code)
|
||||
{
|
||||
return code != null && code.intValue() == NOT_BILLED.code;
|
||||
}
|
||||
|
||||
/** Only success (0) persists prc_income_bill. */
|
||||
public static boolean storesIncomeBill(Integer code)
|
||||
{
|
||||
int value = code == null ? SUCCESS.code : code.intValue();
|
||||
return value == SUCCESS.code
|
||||
|| value == PRODUCT.code || value == COUNTRY.code || value == WEIGHT.code
|
||||
|| value == CATALOG_PRODUCT.code || value == CATALOG_COUNTRY.code || value == CATALOG_WEIGHT.code;
|
||||
return code == null || code.intValue() == SUCCESS.code;
|
||||
}
|
||||
|
||||
/** 仅 0 落 prc_income_bill_fee。 */
|
||||
@@ -96,7 +106,7 @@ public enum BillBillingFailStatus
|
||||
}
|
||||
for (BillBillingFailStatus status : values())
|
||||
{
|
||||
if (status == SUCCESS)
|
||||
if (status == SUCCESS || status == NOT_BILLED)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -113,6 +123,7 @@ public enum BillBillingFailStatus
|
||||
switch (this)
|
||||
{
|
||||
case SUCCESS:
|
||||
case NOT_BILLED:
|
||||
return null;
|
||||
case PRODUCT:
|
||||
return BillTaskFailCode.PRODUCT_NOT_HIT.getCode();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.bms.price.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.price.domain.ChannelProductMap;
|
||||
|
||||
@@ -10,9 +11,14 @@ public interface ChannelProductMapMapper
|
||||
{
|
||||
ChannelProductMap selectByChannelAndType(@Param("channelId") String channelId, @Param("payType") String payType);
|
||||
|
||||
List<ChannelProductMap> selectByPriceSetId(@Param("priceSetId") Long priceSetId);
|
||||
|
||||
int upsert(ChannelProductMap row);
|
||||
|
||||
int deleteByChannelAndType(@Param("channelId") String channelId, @Param("payType") String payType);
|
||||
|
||||
int deleteByChannelTypeAndPriceSet(@Param("channelId") String channelId, @Param("payType") String payType,
|
||||
@Param("priceSetId") Long priceSetId);
|
||||
|
||||
int deleteByProductId(@Param("productId") Long productId);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ public interface PayProductMapper
|
||||
|
||||
Long selectTemplateId(@Param("dictValue") String dictValue);
|
||||
|
||||
List<PayProduct> selectTemplateOptions();
|
||||
List<PayProduct> selectTemplateOptions(@Param("payType") String payType);
|
||||
|
||||
Integer selectMaxVersion(@Param("originId") Long originId);
|
||||
|
||||
@@ -38,16 +38,33 @@ public interface PayProductMapper
|
||||
|
||||
List<PayWeightBand> selectWeightBands(@Param("productIds") List<Long> productIds);
|
||||
|
||||
List<PayWeightBand> selectWeightBandsByTerm(@Param("termIds") List<Long> termIds);
|
||||
|
||||
List<PaySurcharge> selectSurcharges(@Param("productIds") List<Long> productIds);
|
||||
|
||||
List<PaySurcharge> selectSurchargesByTerm(@Param("termIds") List<Long> termIds);
|
||||
|
||||
List<PayExtraLine> selectExtraLines(@Param("productIds") List<Long> productIds);
|
||||
|
||||
List<PayExtraLine> selectExtraLinesByTerm(@Param("termIds") List<Long> termIds);
|
||||
|
||||
int deleteWeightBands(@Param("productIds") List<Long> productIds);
|
||||
|
||||
int deleteSurcharges(@Param("productIds") List<Long> productIds);
|
||||
|
||||
int deleteExtraLines(@Param("productIds") List<Long> productIds);
|
||||
|
||||
int deleteWeightBandsByTerm(@Param("termIds") List<Long> termIds);
|
||||
|
||||
int deleteSurchargesByTerm(@Param("termIds") List<Long> termIds);
|
||||
|
||||
int deleteExtraLinesByTerm(@Param("termIds") List<Long> termIds);
|
||||
|
||||
Long selectTemplateIdByType(@Param("dictValue") String dictValue, @Param("productType") String productType);
|
||||
|
||||
/** 打单专用:命中仅属于打单套餐的 AR 产品,不命中应付公共模板 */
|
||||
Long selectLabelProductIdByDict(@Param("dictValue") String dictValue);
|
||||
|
||||
int deletePayProducts(@Param("productIds") List<Long> productIds);
|
||||
|
||||
List<Long> selectFamilyIds(@Param("rootId") Long rootId);
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.bms.price.mapper;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.price.domain.LabelPackageVersion;
|
||||
import com.bms.price.domain.PayProductTerm;
|
||||
|
||||
/**
|
||||
@@ -16,6 +17,31 @@ public interface PayProductTermMapper
|
||||
|
||||
PayProductTerm selectByProductId(Long productId);
|
||||
|
||||
/** 打单产品所属套餐 ID */
|
||||
Long selectLabelPriceSetIdByProduct(@Param("productId") Long productId);
|
||||
|
||||
List<PayProductTerm> selectByPriceSetId(@Param("priceSetId") Long priceSetId);
|
||||
|
||||
List<PayProductTerm> selectByPriceSetIdAndVersion(@Param("priceSetId") Long priceSetId,
|
||||
@Param("versionNo") Integer versionNo);
|
||||
|
||||
/** 打单计费:套餐+产品+计费日命中 term(时间窗口优先) */
|
||||
Long selectTermIdBySetProductAt(@Param("priceSetId") Long priceSetId, @Param("productId") Long productId,
|
||||
@Param("bizDate") Date bizDate);
|
||||
|
||||
/** 打单套餐:按 version_no 聚合版本(含已失效) */
|
||||
List<LabelPackageVersion> selectLabelVersionGroups(@Param("priceSetId") Long priceSetId);
|
||||
|
||||
/** 打单套餐:批量改某一版的生效起止与状态 */
|
||||
int updateLabelVersionPeriod(@Param("priceSetId") Long priceSetId, @Param("versionNo") Integer versionNo,
|
||||
@Param("effectiveStart") Date effectiveStart, @Param("effectiveEnd") Date effectiveEnd,
|
||||
@Param("status") String status, @Param("updateBy") String updateBy);
|
||||
|
||||
Integer selectMaxVersionByProduct(@Param("productId") Long productId, @Param("priceSetId") Long priceSetId);
|
||||
|
||||
/** 打单套餐下最大 version_no */
|
||||
Integer selectMaxVersionByPriceSet(@Param("priceSetId") Long priceSetId);
|
||||
|
||||
int insertTerm(PayProductTerm term);
|
||||
|
||||
int updatePeriod(PayProductTerm term);
|
||||
@@ -25,7 +51,32 @@ public interface PayProductTermMapper
|
||||
/** 新版本生效时,把同一产品仍生效的旧版本关掉,结束日收到新版本开始的前一天 */
|
||||
int closeOpenTerms(@Param("dictValue") String dictValue, @Param("endDate") Date endDate, @Param("updateBy") String updateBy);
|
||||
|
||||
/** 打单套餐:按套餐+产品关闭仍生效的旧周期 */
|
||||
int closeOpenTermsBySetProduct(@Param("priceSetId") Long priceSetId, @Param("productId") Long productId,
|
||||
@Param("endDate") Date endDate, @Param("updateBy") String updateBy);
|
||||
|
||||
/** 打单:只关掉某产品本套餐待生效 term */
|
||||
int closePendingBySetProduct(@Param("priceSetId") Long priceSetId, @Param("productId") Long productId,
|
||||
@Param("endDate") Date endDate, @Param("updateBy") String updateBy);
|
||||
|
||||
/** 打单:启动某版本时,关掉本套餐其它版本的生效 term */
|
||||
int closeEffectExceptVersion(@Param("priceSetId") Long priceSetId, @Param("versionNo") Integer versionNo,
|
||||
@Param("endDate") Date endDate, @Param("updateBy") String updateBy);
|
||||
|
||||
/** 打单:预约未来生效时,只写旧版失效日,仍保持生效;并恢复被提前关掉但未到期的旧版 */
|
||||
int scheduleEndExceptVersion(@Param("priceSetId") Long priceSetId, @Param("versionNo") Integer versionNo,
|
||||
@Param("endDate") Date endDate, @Param("today") Date today, @Param("updateBy") String updateBy);
|
||||
|
||||
/** 打单:恢复「失效日未到却被标成已失效」的 term */
|
||||
int restorePrematureClosed(@Param("priceSetId") Long priceSetId, @Param("today") Date today,
|
||||
@Param("updateBy") String updateBy);
|
||||
|
||||
/** 打单:删除待生效版后,清掉旧版预约失效日 */
|
||||
int clearScheduledEnd(@Param("priceSetId") Long priceSetId, @Param("updateBy") String updateBy);
|
||||
|
||||
int deleteByProductIds(@Param("productIds") List<Long> productIds);
|
||||
|
||||
int deleteByTermIds(@Param("termIds") List<Long> termIds);
|
||||
|
||||
int updateProductEffectiveDate(@Param("productId") Long productId, @Param("effectiveDate") Date effectiveDate);
|
||||
}
|
||||
|
||||
@@ -38,4 +38,21 @@ public interface PrcPayBillFeeMapper
|
||||
* @return 影响行数
|
||||
*/
|
||||
public int deleteByBillId(String billId);
|
||||
|
||||
/**
|
||||
* 逻辑删除某一条提单费用分摊到订单上的明细。
|
||||
*
|
||||
* @param feeId 提单费用 ID,保存在 sc_id
|
||||
* @return 影响行数
|
||||
*/
|
||||
public int markLadingFeeDeleted(@Param("feeId") Long feeId);
|
||||
|
||||
/**
|
||||
* 这条提单费用分摊到的订单里,取一个已经生成应付账单的订单号。
|
||||
* 是否出账看 prc_order_bill_ext.is_meet:1 已生成。
|
||||
*
|
||||
* @param feeId 提单费用 ID,保存在 sc_id
|
||||
* @return 订单号,没有已出账订单时为空
|
||||
*/
|
||||
public String selectBilledOrderNo(@Param("feeId") Long feeId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.bms.price.mapper;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.price.airbill.domain.SrvBillAirAllocFee;
|
||||
import com.bms.price.airbill.domain.SrvBillAirAllocOrder;
|
||||
import com.bms.price.domain.PrcPayBill;
|
||||
|
||||
/**
|
||||
* 提单费用分摊到订单。
|
||||
*/
|
||||
public interface SrvBillAirAllocMapper
|
||||
{
|
||||
/**
|
||||
* 查询这些提单上尚未分摊、且标记为需要分摊的费用。
|
||||
*
|
||||
* @param billCodes 提单号
|
||||
* @return 待分摊费用
|
||||
*/
|
||||
List<SrvBillAirAllocFee> selectPendingFees(@Param("billCodes") List<String> billCodes);
|
||||
|
||||
/**
|
||||
* 按提单号取出实际重量和打板重量,只供按重量分摊计算。
|
||||
*
|
||||
* @param billCodes 提单号
|
||||
* @return 提单号、实际重量、打板重量
|
||||
*/
|
||||
List<SrvBillAirAllocFee> selectLadingWeight(@Param("billCodes") List<String> billCodes);
|
||||
|
||||
/**
|
||||
* 按提单号查出关联订单。订单号在扩展表,计费单号和重量在订单表。
|
||||
*
|
||||
* @param billCodes 提单号,对应 prc_order_bill_ext.air_no_old
|
||||
* @return 订单
|
||||
*/
|
||||
List<SrvBillAirAllocOrder> selectOrders(@Param("billCodes") List<String> billCodes);
|
||||
|
||||
/**
|
||||
* 按计费订单号取出已有的应付主表。
|
||||
*
|
||||
* @param billIds 计费订单 ID
|
||||
* @return 应付主表编号
|
||||
*/
|
||||
List<PrcPayBill> selectPayBills(@Param("billIds") List<String> billIds);
|
||||
|
||||
/**
|
||||
* 把仍是未分摊的费用改为已分摊。已经不是未分摊的行不会被改到。
|
||||
*
|
||||
* @param ids 提单费用 ID
|
||||
* @param updateBy 操作人
|
||||
* @param updateTime 操作时间
|
||||
* @return 实际改到的行数
|
||||
*/
|
||||
int markAllocated(@Param("ids") List<Long> ids, @Param("updateBy") String updateBy, @Param("updateTime") Date updateTime);
|
||||
}
|
||||
@@ -22,6 +22,14 @@ public interface SrvBillAirFeeMapper
|
||||
int deleteFee(@Param("id") Long id, @Param("billCode") String billCode, @Param("updateBy") String updateBy,
|
||||
@Param("updateTime") java.util.Date updateTime);
|
||||
|
||||
/** 已分摊改为已确定。 */
|
||||
int confirmFee(@Param("id") Long id, @Param("billCode") String billCode, @Param("updateBy") String updateBy,
|
||||
@Param("updateTime") java.util.Date updateTime);
|
||||
|
||||
/** 已分摊或已确定的费用作废:状态改为已作废,并逻辑删除。 */
|
||||
int voidFee(@Param("id") Long id, @Param("billCode") String billCode, @Param("updateBy") String updateBy,
|
||||
@Param("updateTime") java.util.Date updateTime);
|
||||
|
||||
int insertLog(SrvBillAirFeeLog log);
|
||||
|
||||
List<SrvBillAirFeeLog> selectLogs(String billCode);
|
||||
|
||||
@@ -24,6 +24,9 @@ public final class PayCostCalculator
|
||||
{
|
||||
private static final ZoneId SHANGHAI = ZoneId.of("GMT+8");
|
||||
|
||||
// 定义常量集合,建议放在类级别作为 static final 常量
|
||||
private static final Set<Integer> REFUND_STATUS_SET = Set.of(9, 10);
|
||||
|
||||
private PayCostCalculator()
|
||||
{
|
||||
}
|
||||
@@ -242,6 +245,23 @@ public final class PayCostCalculator
|
||||
return kept;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加相关附加费,根据计费规则去判断
|
||||
* @param product
|
||||
* @param weight
|
||||
* @param length
|
||||
* @param width
|
||||
* @param height
|
||||
* @param postcode
|
||||
* @param zoneName
|
||||
* @param bizDate
|
||||
* @param orderStatus
|
||||
* @param freight
|
||||
* @param currency
|
||||
* @param freightSection
|
||||
* @param remoteHits
|
||||
* @param result
|
||||
*/
|
||||
private static void addSurcharges(PayProduct product, BigDecimal weight, BigDecimal length, BigDecimal width,
|
||||
BigDecimal height, String postcode, String zoneName, Date bizDate, Integer orderStatus, BigDecimal freight, String currency,
|
||||
String freightSection, List<PayRemoteBook.Hit> remoteHits, PayOrderCostResult result)
|
||||
@@ -317,6 +337,7 @@ public final class PayCostCalculator
|
||||
addFlat(result.getFees(), fee, currency, "计费重超过 " + fee.getCalcParam() + "kg");
|
||||
}
|
||||
}
|
||||
// 超出尺寸费用
|
||||
else if (PaySurchargeRules.SIZE.equals(rule))
|
||||
{
|
||||
if (length == null || width == null || height == null)
|
||||
@@ -332,13 +353,15 @@ public final class PayCostCalculator
|
||||
}
|
||||
}
|
||||
}
|
||||
// 退件费用
|
||||
else if (PaySurchargeRules.RETURN.equals(rule))
|
||||
{
|
||||
if (orderStatus != null && orderStatus == 9)
|
||||
if (orderStatus != null && REFUND_STATUS_SET.contains(orderStatus))
|
||||
{
|
||||
addFlat(result.getFees(), fee, currency, "订单已退件");
|
||||
}
|
||||
}
|
||||
// 超重费
|
||||
else if (PaySurchargeRules.EXTRA_KG.equals(rule))
|
||||
{
|
||||
if (postcodeDriven)
|
||||
|
||||
@@ -442,6 +442,7 @@ public class PayWorkbookParser
|
||||
PaySurcharge copy = new PaySurcharge();
|
||||
copy.setSectionName(fee.getSectionName());
|
||||
copy.setFeeName(fee.getFeeName());
|
||||
copy.setScId(fee.getScId());
|
||||
copy.setZoneName(fee.getZoneName());
|
||||
copy.setChargeText(fee.getChargeText());
|
||||
copy.setAmount(fee.getAmount());
|
||||
@@ -1189,7 +1190,12 @@ public class PayWorkbookParser
|
||||
{
|
||||
detectedCurrency = product.getCurrency();
|
||||
}
|
||||
if (detectedCurrency == null || detectedCurrency.isBlank())
|
||||
{
|
||||
detectedCurrency = "EUR";
|
||||
}
|
||||
product.setCurrency(detectedCurrency);
|
||||
fillSurchargeCurrency(product, detectedCurrency);
|
||||
product.setWeightCount(product.getWeightBands().size());
|
||||
product.setSurchargeCount(product.getSurcharges().size());
|
||||
product.setExtraCount(product.getExtraLines().size());
|
||||
@@ -3294,11 +3300,37 @@ public class PayWorkbookParser
|
||||
fee.setCurrency("");
|
||||
fee.setPriceUnit("");
|
||||
}
|
||||
if (fee.getCurrency() == null || fee.getCurrency().isBlank())
|
||||
{
|
||||
String cardCurrency = product.getCurrency();
|
||||
fee.setCurrency(cardCurrency == null || cardCurrency.isBlank() ? "EUR" : cardCurrency.trim().toUpperCase(Locale.ROOT));
|
||||
}
|
||||
fee.setSortNo(product.getSurcharges().size() + 1);
|
||||
PaySurchargeRules.apply(fee);
|
||||
product.getSurcharges().add(fee);
|
||||
}
|
||||
|
||||
/** 附加费币种为空时取价卡币种,价卡也没有则默认 EUR。 */
|
||||
private static void fillSurchargeCurrency(PayProduct product, String cardCurrency)
|
||||
{
|
||||
if (product == null || product.getSurcharges() == null || product.getSurcharges().isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
String fallback = cardCurrency == null || cardCurrency.isBlank() ? "EUR" : cardCurrency.trim().toUpperCase(Locale.ROOT);
|
||||
for (PaySurcharge fee : product.getSurcharges())
|
||||
{
|
||||
if (fee == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (fee.getCurrency() == null || fee.getCurrency().isBlank())
|
||||
{
|
||||
fee.setCurrency(fallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 燃油、去碳列是整张表的包含说明,只记一条,不跟每个重量段重复。 */
|
||||
private void readFuelMark(ExcelGrid grid, PayProduct product)
|
||||
{
|
||||
|
||||
@@ -3,9 +3,11 @@ package com.bms.price.service;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.Date;
|
||||
import com.bms.price.domain.LabelPackageVersion;
|
||||
import com.bms.price.domain.PayParseProgress;
|
||||
import com.bms.price.domain.PayPreview;
|
||||
import com.bms.price.domain.PayPriceCompare;
|
||||
import com.bms.price.domain.ChannelProductMap;
|
||||
import com.bms.price.domain.PayProduct;
|
||||
import com.bms.price.domain.PayProductTerm;
|
||||
import com.bms.price.domain.PaySaveCommand;
|
||||
@@ -47,11 +49,56 @@ public interface IPayProductService
|
||||
/** 公共模板下拉。payType 为空时按应付 AP */
|
||||
List<PayProduct> selectTemplateOptions(String payType);
|
||||
|
||||
/** 渠道绑定的产品。返回的 originId 是不变的产品编号,价格是当前有效版 */
|
||||
/** 渠道绑定的产品。返回的 originId 是不变的产品编号,价格是当前有效版;默认应付 AP */
|
||||
PayProduct selectChannelPrice(String channelId);
|
||||
|
||||
/** 只绑定产品编号,不产生新版本。payType 为空时按应付 AP */
|
||||
void bindChannel(String channelId, Long productId, Long priceSetId, String payType);
|
||||
/** 按收付类型查询渠道绑定产品,payType 为空时按应付 AP */
|
||||
PayProduct selectChannelPrice(String channelId, String payType);
|
||||
|
||||
/** 只绑定产品编号,不产生新版本;同时落 channel_code。payType 为空时按应付 AP */
|
||||
void bindChannel(String channelId, Long productId, String payType);
|
||||
|
||||
/** 批量绑定渠道应收/应付产品;未传的产品不改原绑定 */
|
||||
String batchBindChannel(java.util.List<String> channelIds, Long apProductId, Long arProductId);
|
||||
|
||||
/**
|
||||
* 打单套餐预览:解析复用应付解析器,但「已存在」只按本套餐 price_set_id 判断,
|
||||
* 不与应付公共模板互相挡导入。
|
||||
*/
|
||||
PayPreview previewLabel(byte[] bytes, String filename, String dictValue, String progressId, Long priceSetId);
|
||||
|
||||
/** 打单套餐导入:稳定 product_id,明细挂 term_id,term 写 price_set_id;跳过仅限本套餐已有生效版 */
|
||||
String saveLabel(PaySaveCommand command);
|
||||
|
||||
/** 打单套餐价卡版本列表(按 term.version_no 聚合) */
|
||||
List<LabelPackageVersion> selectLabelVersions(Long priceSetId);
|
||||
|
||||
/** 启动/修改打单套餐某一版生效时间,并视情况启用套餐 */
|
||||
String activateLabelVersion(Long priceSetId, Integer versionNo, Date effectiveTime);
|
||||
|
||||
/** 删除打单套餐待生效版本(仅 status=0) */
|
||||
int deleteLabelVersion(Long priceSetId, Integer versionNo);
|
||||
|
||||
/** 打单套餐内当前生效/待生效产品列表 */
|
||||
List<PayProduct> selectLabelProducts(Long priceSetId);
|
||||
|
||||
/** 打单套餐某一版本的产品列表 */
|
||||
List<PayProduct> selectLabelProductsByVersion(Long priceSetId, Integer versionNo);
|
||||
|
||||
/** 按 term 加载明细 */
|
||||
PayProduct selectPayProductDetailByTerm(Long termId);
|
||||
|
||||
/** 从打单套餐移除产品(失效/清理对应 term 明细) */
|
||||
int removeLabelProducts(Long priceSetId, Long[] productIds);
|
||||
|
||||
/** 打单套餐已绑定渠道列表 */
|
||||
java.util.List<ChannelProductMap> listLabelChannels(Long priceSetId);
|
||||
|
||||
/** 打单套餐批量绑定渠道(写 AR + price_set_id) */
|
||||
String bindLabelChannels(Long priceSetId, java.util.List<String> channelIds, Long productId);
|
||||
|
||||
/** 打单套餐解绑渠道 */
|
||||
int unbindLabelChannel(Long priceSetId, String channelId);
|
||||
|
||||
/** 取消渠道上指定收付类型的产品。payType 为空时按应付 AP */
|
||||
void unbindChannel(String channelId, String payType);
|
||||
@@ -59,6 +106,11 @@ public interface IPayProductService
|
||||
/** 按订单时间取这一产品当时有效的价格版本 */
|
||||
PayProduct selectPriceForDate(String channelId, Date bizDate);
|
||||
|
||||
/**
|
||||
* 打单套餐计费取价:渠道 AR 映射 → 稳定 product_id,再按套餐+计费日命中 term。
|
||||
*/
|
||||
PayProduct selectLabelPriceForDate(Long priceSetId, String channelId, Date bizDate);
|
||||
|
||||
/** 对照两个版本的重量段和附加费 */
|
||||
PayPriceCompare compare(Long leftId, Long rightId);
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.bms.price.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.bms.price.airbill.domain.SrvBillAirAllocFee;
|
||||
|
||||
/**
|
||||
* 把提单上待分摊的费用均摊到关联订单。
|
||||
*/
|
||||
public interface ISrvBillAirAllocService
|
||||
{
|
||||
/**
|
||||
* 查出勾选提单上需要分摊、且尚未分摊的费用。
|
||||
*
|
||||
* @param billCodes 提单号,最多 5 个
|
||||
* @return 费用和不能分摊的原因
|
||||
*/
|
||||
List<SrvBillAirAllocFee> preview(List<String> billCodes);
|
||||
|
||||
/**
|
||||
* 按费用上的分摊维度把金额摊到每个订单,并改费用状态为已分摊。整次处理在一个事务里,写入按 1000 条一批。
|
||||
*
|
||||
* @param billCodes 提单号,最多 5 个
|
||||
* @return 给页面的处理结果
|
||||
*/
|
||||
String allocate(List<String> billCodes);
|
||||
}
|
||||
@@ -16,4 +16,22 @@ public interface ISrvBillAirFeeService
|
||||
|
||||
/** 只写入有变动的行,并按行记录修改前后。返回给页面的提示。 */
|
||||
String save(SrvBillAirFeeSave command);
|
||||
|
||||
/**
|
||||
* 已分摊的费用确认为已确定。
|
||||
*
|
||||
* @param billCode 提单号
|
||||
* @param feeId 费用 ID
|
||||
* @return 给页面的提示
|
||||
*/
|
||||
String confirm(String billCode, Long feeId);
|
||||
|
||||
/**
|
||||
* 作废已分摊或已确定的费用。订单上的分摊明细逻辑删除,这条提单费用也逻辑删除。
|
||||
*
|
||||
* @param billCode 提单号
|
||||
* @param feeId 费用 ID
|
||||
* @return 给页面的提示
|
||||
*/
|
||||
String voidFee(String billCode, Long feeId);
|
||||
}
|
||||
|
||||
+1232
-128
File diff suppressed because it is too large
Load Diff
+58
-39
@@ -6,7 +6,6 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
@@ -28,8 +27,8 @@ import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.bms.ar.enums.ArIncomeBillFeeStatus;
|
||||
import com.bms.ar.enums.ArOrderBillStatus;
|
||||
import com.bms.receivable.enums.ArIncomeBillFeeStatus;
|
||||
import com.bms.receivable.enums.ArOrderBillStatus;
|
||||
import com.bms.common.annotation.DataScope;
|
||||
import com.bms.common.core.domain.entity.SysUser;
|
||||
import com.bms.common.exception.ServiceException;
|
||||
@@ -207,21 +206,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
assertOrderBillEditable(request.getOrderNo());
|
||||
PrcOrderBill existed = orderBillMapper.selectPrcOrderBillByOrderNo(request.getOrderNo());
|
||||
BigDecimal systemWeight = request.getWeight();
|
||||
// Upstream Oracle sync: always use TAB_ORDER weight (same overwrite as weight/charge_weight).
|
||||
// Local rebill / manual charge_weight edit: keep resolveCalcWeight preference.
|
||||
BigDecimal calcWeight = isUpstreamSyncRequest(request)
|
||||
? systemWeight
|
||||
: resolveCalcWeight(existed, systemWeight);
|
||||
// Three-side sum rule disabled: upstream already applies it.
|
||||
// Manual charge_weight wins; otherwise apply active biz rules (e.g. three-side).
|
||||
// if (!isManualChargeWeight(existed))
|
||||
// {
|
||||
// BigDecimal ruled = bizRuleService.resolveWeightByRule(request, systemWeight);
|
||||
// if (ruled != null)
|
||||
// {
|
||||
// calcWeight = ruled;
|
||||
// }
|
||||
// }
|
||||
BigDecimal calcWeight = resolveBillCalcWeight(request, existed, systemWeight);
|
||||
request.setWeight(calcWeight);
|
||||
TmsPriceQuoteResult result = tmsPriceQuoteService.calculate(request);
|
||||
persistBill(request, systemWeight, bizDate, result);
|
||||
@@ -1836,8 +1821,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
{
|
||||
bill.setCountryName(result.getCountryName());
|
||||
}
|
||||
// weight = system/original; charge_weight follows weight on upstream sync,
|
||||
// otherwise may keep manual override via resolvePersistChargeWeight.
|
||||
// weight = system/original upstream weight; charge_weight may differ when VOL_8000
|
||||
// (or manual override) raised/lowered the billed weight.
|
||||
bill.setWeight(weight);
|
||||
bill.setBizDate(bizDate);
|
||||
bill.setTmsId(request.getTmsId() == null ? null : Long.valueOf(request.getTmsId()));
|
||||
@@ -1857,7 +1842,9 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
bill.setEffectiveDate(result.getEffectiveDate());
|
||||
if (isUpstreamSyncRequest(request))
|
||||
{
|
||||
bill.setChargeWeight(weight);
|
||||
// request.weight already carries ruled/calc weight used for quote
|
||||
BigDecimal cw = request.getWeight() != null ? request.getWeight() : weight;
|
||||
bill.setChargeWeight(cw);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2883,6 +2870,51 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
return systemWeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve weight used for quote/persist.
|
||||
* Upstream: TAB_ORDER weight then biz-rule. Local: manual charge_weight wins, else biz-rule.
|
||||
*/
|
||||
private BigDecimal resolveBillCalcWeight(TmsPriceBillRequest request, PrcOrderBill existed,
|
||||
BigDecimal systemWeight)
|
||||
{
|
||||
BigDecimal calcWeight = isUpstreamSyncRequest(request)
|
||||
? systemWeight
|
||||
: resolveCalcWeight(existed, systemWeight);
|
||||
if (isUpstreamSyncRequest(request) || !isManualChargeWeight(existed))
|
||||
{
|
||||
calcWeight = applyBizRuleWeight(request, calcWeight);
|
||||
}
|
||||
return calcWeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upstream sync / soft-fail: keep original weight, charge_weight may follow biz-rule.
|
||||
*/
|
||||
private void applyUpstreamWeightFields(PrcOrderBill bill, TmsPriceBillRequest request)
|
||||
{
|
||||
if (bill == null || request == null || request.getWeight() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
BigDecimal systemWeight = request.getWeight();
|
||||
bill.setWeight(systemWeight);
|
||||
bill.setChargeWeight(applyBizRuleWeight(request, systemWeight));
|
||||
}
|
||||
|
||||
/**
|
||||
* Biz-rule charge weight (VOL_8000 etc).
|
||||
* To disable: comment out the resolveWeightByRule call and always return baseWeight.
|
||||
*/
|
||||
private BigDecimal applyBizRuleWeight(TmsPriceBillRequest request, BigDecimal baseWeight)
|
||||
{
|
||||
if (request == null || baseWeight == null)
|
||||
{
|
||||
return baseWeight;
|
||||
}
|
||||
BigDecimal ruled = bizRuleService.resolveWeightByRule(request, baseWeight);
|
||||
return ruled != null ? ruled : baseWeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist charge_weight: keep manual override when calc weight != system weight,
|
||||
* or when existing bill already had a manual override.
|
||||
@@ -3057,7 +3089,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
bill.setBillId(UUID.randomUUID().toString());
|
||||
bill.setArBillStatus(ArOrderBillStatus.UNBILLED.getCode());
|
||||
bill.setRefundStatus(BillRefundStatus.NONE.getCode());
|
||||
bill.setBillingFailStatus(Integer.valueOf(BillBillingFailStatus.SUCCESS.getCode()));
|
||||
// Order-source sync only: mark not billed until quote/bill runs.
|
||||
bill.setBillingFailStatus(Integer.valueOf(BillBillingFailStatus.NOT_BILLED.getCode()));
|
||||
bill.setFreightRate(BigDecimal.ZERO);
|
||||
bill.setBaseFreightRate(BigDecimal.ZERO);
|
||||
bill.setRegistrationFee(BigDecimal.ZERO);
|
||||
@@ -3077,12 +3110,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
applyProductCodeMapping(bill, request);
|
||||
Long customerId = resolveCustomerIdByCode(request.getCustomerCode(), bill.getCustomerId());
|
||||
bill.setCustomerId(customerId == null ? Long.valueOf(0L) : customerId);
|
||||
// Order-source sync: overwrite weight and charge_weight the same way from upstream.
|
||||
if (request.getWeight() != null)
|
||||
{
|
||||
bill.setWeight(request.getWeight());
|
||||
bill.setChargeWeight(request.getWeight());
|
||||
}
|
||||
applyUpstreamWeightFields(bill, request);
|
||||
if (creating)
|
||||
{
|
||||
Date bizDate = request.getPickupDate() != null ? request.getPickupDate() : new Date();
|
||||
@@ -3194,12 +3222,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
{
|
||||
bill.setCustomerActiveStatus(BillCustomerActiveStatus.ACTIVE.getCode());
|
||||
}
|
||||
// Soft-fail snapshot: overwrite weight and charge_weight the same way from upstream.
|
||||
bill.setWeight(request.getWeight());
|
||||
if (request.getWeight() != null)
|
||||
{
|
||||
bill.setChargeWeight(request.getWeight());
|
||||
}
|
||||
applyUpstreamWeightFields(bill, request);
|
||||
Date bizDate = StringUtils.isEmpty(request.getBizDate()) ? new Date() : resolveBizDate(request.getBizDate());
|
||||
bill.setBizDate(bizDate);
|
||||
if (StringUtils.isEmpty(bill.getCurrency()))
|
||||
@@ -3267,11 +3290,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
{
|
||||
applyUpstreamBillHeaderFields(bill, request);
|
||||
applyProductCodeMapping(bill, request);
|
||||
if (request.getWeight() != null)
|
||||
{
|
||||
bill.setWeight(request.getWeight());
|
||||
bill.setChargeWeight(request.getWeight());
|
||||
}
|
||||
applyUpstreamWeightFields(bill, request);
|
||||
}
|
||||
applyBillingFailText(bill, billingFailStatus, errorMessage);
|
||||
orderBillMapper.updatePrcOrderBill(bill);
|
||||
|
||||
+10
-2
@@ -77,6 +77,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
||||
private static final String TYPE_FIXED = "FIXED";
|
||||
private static final String CATEGORY_PACKAGE = PrcPriceSet.CATEGORY_PACKAGE;
|
||||
private static final String CATEGORY_EXCLUSIVE = PrcPriceSet.CATEGORY_EXCLUSIVE;
|
||||
private static final String CATEGORY_LABEL = PrcPriceSet.CATEGORY_LABEL;
|
||||
private static final String TYPE_DISCOUNT = "DISCOUNT";
|
||||
private static final String VER_DRAFT = "0";
|
||||
private static final String VER_EFFECT = "1";
|
||||
@@ -2979,16 +2980,23 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
||||
/** PACKAGE (default) or EXCLUSIVE. */
|
||||
public static String normalizeSetCategory(String setCategory)
|
||||
{
|
||||
if (CATEGORY_EXCLUSIVE.equalsIgnoreCase(StringUtils.trim(setCategory)))
|
||||
String raw = StringUtils.trim(setCategory);
|
||||
if (CATEGORY_EXCLUSIVE.equalsIgnoreCase(raw))
|
||||
{
|
||||
return CATEGORY_EXCLUSIVE;
|
||||
}
|
||||
if (CATEGORY_LABEL.equalsIgnoreCase(raw))
|
||||
{
|
||||
return CATEGORY_LABEL;
|
||||
}
|
||||
return CATEGORY_PACKAGE;
|
||||
}
|
||||
|
||||
private static String buildPriceSetCode(String setCategory, Long priceSetId)
|
||||
{
|
||||
String prefix = CATEGORY_EXCLUSIVE.equals(normalizeSetCategory(setCategory)) ? "EXC" : "SET";
|
||||
String category = normalizeSetCategory(setCategory);
|
||||
String prefix = CATEGORY_EXCLUSIVE.equals(category) ? "EXC"
|
||||
: (CATEGORY_LABEL.equals(category) ? "LBL" : "SET");
|
||||
return String.format("%s%04d", prefix, priceSetId);
|
||||
}
|
||||
|
||||
|
||||
+472
@@ -0,0 +1,472 @@
|
||||
package com.bms.price.service.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.bms.common.core.domain.entity.SysUser;
|
||||
import com.bms.common.exception.ServiceException;
|
||||
import com.bms.common.utils.DateUtils;
|
||||
import com.bms.common.utils.SecurityUtils;
|
||||
import com.bms.price.airbill.domain.SrvBillAirAllocFee;
|
||||
import com.bms.price.airbill.domain.SrvBillAirAllocOrder;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFeeLog;
|
||||
import com.bms.price.domain.PrcPayBill;
|
||||
import com.bms.price.domain.PrcPayBillFee;
|
||||
import com.bms.price.mapper.SrvBillAirAllocMapper;
|
||||
import com.bms.price.mapper.SrvBillAirFeeMapper;
|
||||
import com.bms.price.mapper.SrvBillAirMapper;
|
||||
import com.bms.price.mapper.PrcPayBillFeeMapper;
|
||||
import com.bms.price.service.ISrvBillAirAllocService;
|
||||
|
||||
/**
|
||||
* 提单费用分摊。只处理标记为分摊、状态仍是未分摊的费用。
|
||||
*/
|
||||
@Service
|
||||
public class SrvBillAirAllocServiceImpl implements ISrvBillAirAllocService
|
||||
{
|
||||
private static final int MAX_BILLS = 5;
|
||||
|
||||
private static final int BATCH_SIZE = 1000;
|
||||
|
||||
private static final String DIM_ORDER = "1";
|
||||
|
||||
private static final String DIM_WEIGHT = "2";
|
||||
|
||||
private static final String FEE_TYPE = "LADING";
|
||||
|
||||
private static final String FEE_SOURCE = "LADING";
|
||||
|
||||
private static final BigDecimal ZERO = BigDecimal.ZERO;
|
||||
|
||||
@Autowired
|
||||
private SrvBillAirAllocMapper allocMapper;
|
||||
|
||||
@Autowired
|
||||
private SrvBillAirMapper billMapper;
|
||||
|
||||
@Autowired
|
||||
private SrvBillAirFeeMapper feeMapper;
|
||||
|
||||
@Autowired
|
||||
private PrcPayBillFeeMapper payBillFeeMapper;
|
||||
|
||||
@Override
|
||||
public List<SrvBillAirAllocFee> preview(List<String> billCodes)
|
||||
{
|
||||
List<String> codes = normalize(billCodes);
|
||||
List<SrvBillAirAllocFee> fees = dedupe(allocMapper.selectPendingFees(codes));
|
||||
fillWeight(fees);
|
||||
Map<String, List<SrvBillAirAllocOrder>> orders = ordersOf(codes);
|
||||
for (SrvBillAirAllocFee fee : fees)
|
||||
{
|
||||
List<SrvBillAirAllocOrder> rows = orders.getOrDefault(fee.getBillCode(), List.of());
|
||||
fee.setOrderCount(rows.size());
|
||||
fee.setBlockReason(blockReason(fee, rows));
|
||||
}
|
||||
return fees;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String allocate(List<String> billCodes)
|
||||
{
|
||||
List<String> codes = normalize(billCodes);
|
||||
List<SrvBillAirAllocFee> fees = dedupe(allocMapper.selectPendingFees(codes));
|
||||
fillWeight(fees);
|
||||
if (fees.isEmpty())
|
||||
{
|
||||
throw new ServiceException("所选提单没有需要分摊的费用");
|
||||
}
|
||||
Map<String, List<SrvBillAirAllocOrder>> orders = ordersOf(codes);
|
||||
for (SrvBillAirAllocFee fee : fees)
|
||||
{
|
||||
List<SrvBillAirAllocOrder> rows = orders.getOrDefault(fee.getBillCode(), List.of());
|
||||
fee.setOrderCount(rows.size());
|
||||
String reason = blockReason(fee, rows);
|
||||
if (!reason.isEmpty())
|
||||
{
|
||||
throw new ServiceException(fee.getBillCode() + " " + blankName(fee) + ":" + reason);
|
||||
}
|
||||
}
|
||||
Map<String, String> payBillIds = payBillIds(orders);
|
||||
List<PrcPayBillFee> batch = new ArrayList<>(BATCH_SIZE);
|
||||
int lineCount = 0;
|
||||
for (SrvBillAirAllocFee fee : fees)
|
||||
{
|
||||
List<SrvBillAirAllocOrder> rows = orders.get(fee.getBillCode());
|
||||
fee.setOrderCount(rows.size());
|
||||
boolean byWeight = DIM_WEIGHT.equals(fee.getAllocDim());
|
||||
List<BigDecimal> shares = split(byWeight ? weightAmount(fee) : fee.getPrice(), rows, byWeight);
|
||||
for (int i = 0; i < rows.size(); i++)
|
||||
{
|
||||
batch.add(toOrderFee(fee, rows.get(i), shares.get(i), payBillIds));
|
||||
if (batch.size() == BATCH_SIZE)
|
||||
{
|
||||
lineCount += flush(batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
lineCount += flush(batch);
|
||||
Date now = DateUtils.getNowDate();
|
||||
String operator = SecurityUtils.getUsername();
|
||||
int updated = allocMapper.markAllocated(feeIds(fees), operator, now);
|
||||
if (updated != fees.size())
|
||||
{
|
||||
throw new ServiceException("费用状态已变化,请重新打开后再分摊");
|
||||
}
|
||||
writeLogs(fees, operator, now);
|
||||
return "已将 " + fees.size() + " 条费用分摊为 " + lineCount + " 笔订单费用";
|
||||
}
|
||||
|
||||
/**
|
||||
* 按重量分摊的费用,按提单号补上实际重量和打板重量。按订单数量分摊的不查、不填。
|
||||
* 这两个值只参与计算,不返回给页面。
|
||||
*/
|
||||
private void fillWeight(List<SrvBillAirAllocFee> fees)
|
||||
{
|
||||
List<String> codes = new ArrayList<>();
|
||||
for (SrvBillAirAllocFee fee : fees)
|
||||
{
|
||||
if (fee != null && DIM_WEIGHT.equals(fee.getAllocDim()) && fee.getBillCode() != null && !codes.contains(fee.getBillCode()))
|
||||
{
|
||||
codes.add(fee.getBillCode());
|
||||
}
|
||||
}
|
||||
if (codes.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
Map<String, SrvBillAirAllocFee> weights = new LinkedHashMap<>();
|
||||
for (SrvBillAirAllocFee row : allocMapper.selectLadingWeight(codes))
|
||||
{
|
||||
if (row != null && row.getBillCode() != null)
|
||||
{
|
||||
weights.put(row.getBillCode(), row);
|
||||
}
|
||||
}
|
||||
for (SrvBillAirAllocFee fee : fees)
|
||||
{
|
||||
if (fee == null || !DIM_WEIGHT.equals(fee.getAllocDim()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SrvBillAirAllocFee weight = weights.get(fee.getBillCode());
|
||||
if (weight == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
fee.setActualWeight(weight.getActualWeight());
|
||||
fee.setBandBoxWeight(weight.getBandBoxWeight());
|
||||
}
|
||||
}
|
||||
|
||||
/** 提单号去重、去空白,并限制单次 5 条。 */
|
||||
private List<String> normalize(List<String> billCodes)
|
||||
{
|
||||
LinkedHashSet<String> codes = new LinkedHashSet<>();
|
||||
if (billCodes != null)
|
||||
{
|
||||
for (String raw : billCodes)
|
||||
{
|
||||
if (raw == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (String part : raw.split("[,,\\s]+"))
|
||||
{
|
||||
if (!part.isBlank())
|
||||
{
|
||||
codes.add(part.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (codes.isEmpty())
|
||||
{
|
||||
throw new ServiceException("请勾选提单");
|
||||
}
|
||||
if (codes.size() > MAX_BILLS)
|
||||
{
|
||||
throw new ServiceException("单次最多处理5条提单");
|
||||
}
|
||||
for (String code : codes)
|
||||
{
|
||||
if (billMapper.selectSrvBillAirByBillCode(code) == null)
|
||||
{
|
||||
throw new ServiceException("提单不存在:" + code);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(codes);
|
||||
}
|
||||
|
||||
private Map<String, List<SrvBillAirAllocOrder>> ordersOf(List<String> codes)
|
||||
{
|
||||
Map<String, List<SrvBillAirAllocOrder>> grouped = new LinkedHashMap<>();
|
||||
for (SrvBillAirAllocOrder order : allocMapper.selectOrders(codes))
|
||||
{
|
||||
if (order.getBillId() == null || order.getBillId().isBlank())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
grouped.computeIfAbsent(order.getBillCode(), key -> new ArrayList<>()).add(order);
|
||||
}
|
||||
return grouped;
|
||||
}
|
||||
|
||||
private Map<String, String> payBillIds(Map<String, List<SrvBillAirAllocOrder>> orders)
|
||||
{
|
||||
List<String> billIds = new ArrayList<>();
|
||||
for (List<SrvBillAirAllocOrder> rows : orders.values())
|
||||
{
|
||||
for (SrvBillAirAllocOrder order : rows)
|
||||
{
|
||||
billIds.add(order.getBillId());
|
||||
}
|
||||
}
|
||||
Map<String, String> payIds = new LinkedHashMap<>();
|
||||
for (int from = 0; from < billIds.size(); from += BATCH_SIZE)
|
||||
{
|
||||
List<String> chunk = billIds.subList(from, Math.min(from + BATCH_SIZE, billIds.size()));
|
||||
for (PrcPayBill payBill : allocMapper.selectPayBills(chunk))
|
||||
{
|
||||
if (payBill.getBillId() != null && payBill.getPayBillId() != null)
|
||||
{
|
||||
payIds.put(payBill.getBillId(), payBill.getPayBillId());
|
||||
}
|
||||
}
|
||||
}
|
||||
return payIds;
|
||||
}
|
||||
|
||||
private String blockReason(SrvBillAirAllocFee fee, List<SrvBillAirAllocOrder> orders)
|
||||
{
|
||||
if (fee.getPrice() == null)
|
||||
{
|
||||
return "费用没有价格";
|
||||
}
|
||||
if (fee.getPrice().signum() < 0)
|
||||
{
|
||||
return "费用价格不能小于0";
|
||||
}
|
||||
if (fee.getCurrency() == null || fee.getCurrency().isBlank())
|
||||
{
|
||||
return "费用没有币种";
|
||||
}
|
||||
if (fee.getLadingId() == null || fee.getLadingId().isBlank())
|
||||
{
|
||||
return "提单缺少主键";
|
||||
}
|
||||
if (!DIM_ORDER.equals(fee.getAllocDim()) && !DIM_WEIGHT.equals(fee.getAllocDim()))
|
||||
{
|
||||
return "未选择分摊维度";
|
||||
}
|
||||
if (orders == null || orders.isEmpty())
|
||||
{
|
||||
return "提单下没有关联订单";
|
||||
}
|
||||
if (DIM_WEIGHT.equals(fee.getAllocDim()))
|
||||
{
|
||||
if (fee.getActualWeight() == null || fee.getActualWeight().signum() <= 0)
|
||||
{
|
||||
return "提单没有实际重量,无法按重量分摊";
|
||||
}
|
||||
if (fee.getBandBoxWeight() == null || fee.getBandBoxWeight().signum() <= 0)
|
||||
{
|
||||
return "干线没有打板重量,无法按重量分摊";
|
||||
}
|
||||
if (totalWeight(orders).signum() <= 0)
|
||||
{
|
||||
return "订单没有可用重量,无法按重量分摊";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private PrcPayBillFee toOrderFee(SrvBillAirAllocFee fee, SrvBillAirAllocOrder order, BigDecimal amount, Map<String, String> payBillIds)
|
||||
{
|
||||
PrcPayBillFee row = new PrcPayBillFee();
|
||||
row.setFeeId(UUID.randomUUID().toString());
|
||||
row.setBillId(order.getBillId());
|
||||
row.setPayBillId(payBillIds.get(order.getBillId()));
|
||||
row.setFeeType(FEE_TYPE);
|
||||
row.setFeeName(blankName(fee));
|
||||
row.setScId(fee.getId() == null ? 0L : fee.getId());
|
||||
row.setFeeLevel(fee.getFeeLevel());
|
||||
row.setAmount(amount);
|
||||
row.setCurrency(fee.getCurrency().trim());
|
||||
row.setRoundMode("HALF_UP");
|
||||
row.setRemark(remark(fee));
|
||||
row.setFeeSource(FEE_SOURCE);
|
||||
row.setSourceId(fee.getLadingId().trim());
|
||||
row.setIsDeleted("0");
|
||||
return row;
|
||||
}
|
||||
|
||||
private int flush(List<PrcPayBillFee> batch)
|
||||
{
|
||||
if (batch.isEmpty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int rows = batch.size();
|
||||
payBillFeeMapper.insertBatch(batch);
|
||||
batch.clear();
|
||||
return rows;
|
||||
}
|
||||
|
||||
private void writeLogs(List<SrvBillAirAllocFee> fees, String operator, Date now)
|
||||
{
|
||||
SysUser user = SecurityUtils.getLoginUser().getUser();
|
||||
String name = user.getNickName() == null || user.getNickName().isBlank() ? operator : user.getNickName();
|
||||
for (SrvBillAirAllocFee fee : fees)
|
||||
{
|
||||
SrvBillAirFeeLog log = new SrvBillAirFeeLog();
|
||||
log.setBillCode(fee.getBillCode());
|
||||
log.setFeeId(fee.getId());
|
||||
log.setOperationType(SrvBillAirFeeLog.UPDATE);
|
||||
log.setSummary("分摊到 " + fee.getOrderCount() + " 票订单");
|
||||
log.setChangeContent(JSON.toJSONString(List.of(
|
||||
new SrvBillAirFeeLog.FieldChange("billFeeStatus", "费用状态", "未分摊", "已分摊"))));
|
||||
log.setCreateBy(operator);
|
||||
log.setCreateByName(name);
|
||||
log.setCreateTime(now);
|
||||
feeMapper.insertLog(log);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按重量分摊时,先算出这一票提单实际可分摊的金额。
|
||||
* 百分比 = 提单实际重量 srv_bill_air.actual_weight / 干线打板重量 srv_bill_air_flight.band_box_weight。
|
||||
* 可分摊金额 = 费用价格 × 百分比,保留两位,四舍五入。
|
||||
* 调用前须已确认实际重量和打板重量都大于 0。
|
||||
*
|
||||
* @param fee 待分摊的提单费用,带有实际重量和打板重量
|
||||
* @return 按百分比算出的可分摊金额
|
||||
*/
|
||||
private static BigDecimal weightAmount(SrvBillAirAllocFee fee)
|
||||
{
|
||||
BigDecimal price = fee.getPrice().setScale(2, RoundingMode.HALF_UP);
|
||||
return price.multiply(fee.getActualWeight()).divide(fee.getBandBoxWeight(), 2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把金额拆到每一票订单,结果保留两位。
|
||||
* 按订单数量:每一票先向下取整,差额补在最后一票,合计等于费用价格。
|
||||
* 按重量:传入的金额已是「费用价格 × 提单实际重量 / 干线打板重量」。
|
||||
* 再按订单重量占这些订单总重量的比例分摊;前面的订单向下取整,差额补在最后一票有重量的订单上,合计等于这笔金额。
|
||||
* 订单重量优先取计费重,没有计费重时取实际重量,再没有取收货重量。
|
||||
*
|
||||
* @param price 本次要拆开的金额。按重量时是百分比计算后的可分摊金额
|
||||
* @param orders 该提单下的订单
|
||||
* @param byWeight true 按订单重量占比,false 按订单数量均分
|
||||
* @return 与订单顺序一致的分摊金额
|
||||
*/
|
||||
private static List<BigDecimal> split(BigDecimal price, List<SrvBillAirAllocOrder> orders, boolean byWeight)
|
||||
{
|
||||
BigDecimal total = price.setScale(2, RoundingMode.HALF_UP);
|
||||
int count = orders.size();
|
||||
List<BigDecimal> shares = new ArrayList<>(count);
|
||||
if (!byWeight)
|
||||
{
|
||||
BigDecimal each = total.divide(BigDecimal.valueOf(count), 2, RoundingMode.DOWN);
|
||||
BigDecimal used = ZERO;
|
||||
for (int i = 0; i < count - 1; i++)
|
||||
{
|
||||
shares.add(each);
|
||||
used = used.add(each);
|
||||
}
|
||||
shares.add(total.subtract(used));
|
||||
return shares;
|
||||
}
|
||||
BigDecimal sum = totalWeight(orders);
|
||||
int last = count - 1;
|
||||
for (int i = count - 1; i >= 0; i--)
|
||||
{
|
||||
if (weight(orders.get(i)).signum() > 0)
|
||||
{
|
||||
last = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
BigDecimal used = ZERO;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (i == last)
|
||||
{
|
||||
shares.add(null);
|
||||
continue;
|
||||
}
|
||||
BigDecimal share = total.multiply(weight(orders.get(i))).divide(sum, 2, RoundingMode.DOWN);
|
||||
shares.add(share);
|
||||
used = used.add(share);
|
||||
}
|
||||
shares.set(last, total.subtract(used));
|
||||
return shares;
|
||||
}
|
||||
|
||||
private static BigDecimal totalWeight(List<SrvBillAirAllocOrder> orders)
|
||||
{
|
||||
BigDecimal sum = ZERO;
|
||||
for (SrvBillAirAllocOrder order : orders)
|
||||
{
|
||||
sum = sum.add(weight(order));
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
private static BigDecimal weight(SrvBillAirAllocOrder order)
|
||||
{
|
||||
return order.getChargeWeight() == null ? ZERO : order.getChargeWeight();
|
||||
}
|
||||
|
||||
private static List<SrvBillAirAllocFee> dedupe(List<SrvBillAirAllocFee> fees)
|
||||
{
|
||||
Map<Long, SrvBillAirAllocFee> unique = new LinkedHashMap<>();
|
||||
if (fees != null)
|
||||
{
|
||||
for (SrvBillAirAllocFee fee : fees)
|
||||
{
|
||||
if (fee != null && fee.getId() != null)
|
||||
{
|
||||
unique.putIfAbsent(fee.getId(), fee);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(unique.values());
|
||||
}
|
||||
|
||||
private static List<Long> feeIds(List<SrvBillAirAllocFee> fees)
|
||||
{
|
||||
List<Long> ids = new ArrayList<>();
|
||||
for (SrvBillAirAllocFee fee : fees)
|
||||
{
|
||||
ids.add(fee.getId());
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
private static String blankName(SrvBillAirAllocFee fee)
|
||||
{
|
||||
if (fee.getFeeName() != null && !fee.getFeeName().isBlank())
|
||||
{
|
||||
return fee.getFeeName().trim();
|
||||
}
|
||||
return "提单费用";
|
||||
}
|
||||
|
||||
private static String remark(SrvBillAirAllocFee fee)
|
||||
{
|
||||
String dim = DIM_WEIGHT.equals(fee.getAllocDim()) ? "重量" : "订单";
|
||||
return "提单" + fee.getBillCode() + "按" + dim + "分摊";
|
||||
}
|
||||
}
|
||||
+261
-18
@@ -21,8 +21,11 @@ import com.bms.common.utils.SecurityUtils;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFee;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFeeLog;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFeeSave;
|
||||
import com.bms.price.domain.PrcSurcharge;
|
||||
import com.bms.price.mapper.PrcPayBillFeeMapper;
|
||||
import com.bms.price.mapper.SrvBillAirFeeMapper;
|
||||
import com.bms.price.mapper.SrvBillAirMapper;
|
||||
import com.bms.price.service.IPrcSurchargeService;
|
||||
import com.bms.price.service.ISrvBillAirFeeService;
|
||||
import com.bms.system.service.ISysDictTypeService;
|
||||
|
||||
@@ -32,13 +35,29 @@ import com.bms.system.service.ISysDictTypeService;
|
||||
@Service
|
||||
public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
{
|
||||
private static final String FEE_DICT = "bms_fee_type";
|
||||
private static final String LEVEL_DICT = "bms_fee_level";
|
||||
|
||||
private static final String ALLOC_DICT = "bill_fee_alloc";
|
||||
|
||||
private static final String ALLOC_DIM_DICT = "bill_fee_alloc_dim";
|
||||
|
||||
private static final String STATUS_DICT = "bill_fee_status";
|
||||
|
||||
private static final String STATUS_ALLOCATED = "1";
|
||||
|
||||
private static final String STATUS_CONFIRMED = "3";
|
||||
|
||||
private static final String CURRENCY_DICT = "bms_currency";
|
||||
|
||||
@Autowired
|
||||
private SrvBillAirFeeMapper feeMapper;
|
||||
|
||||
@Autowired
|
||||
private PrcPayBillFeeMapper payBillFeeMapper;
|
||||
|
||||
@Autowired
|
||||
private IPrcSurchargeService surchargeService;
|
||||
|
||||
@Autowired
|
||||
private SrvBillAirMapper billMapper;
|
||||
|
||||
@@ -84,7 +103,10 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
{
|
||||
throw new ServiceException("没有需要保存的变更");
|
||||
}
|
||||
Map<String, String> feeLabels = dictLabels(FEE_DICT, "费用类型");
|
||||
Map<String, String> levelLabels = dictLabels(LEVEL_DICT, "费用阶段");
|
||||
Map<String, String> allocLabels = dictLabels(ALLOC_DICT, "是否分摊");
|
||||
Map<String, String> dimLabels = dictLabels(ALLOC_DIM_DICT, "分摊维度");
|
||||
Map<String, String> statusLabels = dictLabels(STATUS_DICT, "费用状态");
|
||||
Map<String, String> currencyLabels = dictLabels(CURRENCY_DICT, "币种");
|
||||
Operator operator = operator();
|
||||
Date now = DateUtils.getNowDate();
|
||||
@@ -107,10 +129,14 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
{
|
||||
throw new ServiceException("费用已删除或不属于该提单");
|
||||
}
|
||||
if (locked(current))
|
||||
{
|
||||
throw new ServiceException("已分摊或已确定的费用不能删除,请使用作废");
|
||||
}
|
||||
int rows = feeMapper.deleteFee(current.getId(), billCode, operator.account, now);
|
||||
if (rows > 0)
|
||||
{
|
||||
writeLog(billCode, id, SrvBillAirFeeLog.DELETE, current, null, feeLabels, currencyLabels, operator, now);
|
||||
writeLog(billCode, id, SrvBillAirFeeLog.DELETE, current, null, feeNames(current, null), levelLabels, allocLabels, dimLabels, statusLabels, currencyLabels, operator, now);
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
@@ -129,8 +155,14 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
{
|
||||
throw new ServiceException("费用已删除或不属于该提单");
|
||||
}
|
||||
SrvBillAirFee next = normalize(incoming, feeLabels, currencyLabels);
|
||||
List<SrvBillAirFeeLog.FieldChange> changes = diff(current, next, feeLabels, currencyLabels);
|
||||
if (locked(current))
|
||||
{
|
||||
throw new ServiceException("已分摊或已确定的费用不能修改");
|
||||
}
|
||||
SrvBillAirFee next = normalize(incoming, levelLabels, allocLabels, dimLabels, currencyLabels);
|
||||
next.setBillFeeStatus(current.getBillFeeStatus() == null || current.getBillFeeStatus().isBlank() ? "0" : current.getBillFeeStatus());
|
||||
Map<String, String> feeNames = feeNames(current, next);
|
||||
List<SrvBillAirFeeLog.FieldChange> changes = diff(current, next, feeNames, levelLabels, allocLabels, dimLabels, statusLabels, currencyLabels);
|
||||
if (changes.isEmpty())
|
||||
{
|
||||
continue;
|
||||
@@ -141,17 +173,19 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
next.setUpdateTime(now);
|
||||
if (feeMapper.updateFee(next) > 0)
|
||||
{
|
||||
writeLog(billCode, current.getId(), SrvBillAirFeeLog.UPDATE, current, next, feeLabels, currencyLabels, operator, now);
|
||||
writeLog(billCode, current.getId(), SrvBillAirFeeLog.UPDATE, current, next, feeNames, levelLabels, allocLabels, dimLabels, statusLabels, currencyLabels, operator, now);
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
for (SrvBillAirFee incoming : inserts)
|
||||
{
|
||||
if (incoming == null || blank(incoming.getFeeType()) && incoming.getPrice() == null && blank(incoming.getCurrency()) && blank(incoming.getRemark()))
|
||||
if (incoming == null || blank(incoming.getFeeType()) && blank(incoming.getFeeLevel()) && incoming.getPrice() == null
|
||||
&& blank(incoming.getCurrency()) && blank(incoming.getRemark()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SrvBillAirFee next = normalize(incoming, feeLabels, currencyLabels);
|
||||
SrvBillAirFee next = normalize(incoming, levelLabels, allocLabels, dimLabels, currencyLabels);
|
||||
next.setBillFeeStatus("0");
|
||||
next.setId(null);
|
||||
next.setBillCode(billCode);
|
||||
next.setCreateBy(operator.account);
|
||||
@@ -159,7 +193,7 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
next.setUpdateBy(operator.account);
|
||||
next.setUpdateTime(now);
|
||||
feeMapper.insertFee(next);
|
||||
writeLog(billCode, next.getId(), SrvBillAirFeeLog.INSERT, null, next, feeLabels, currencyLabels, operator, now);
|
||||
writeLog(billCode, next.getId(), SrvBillAirFeeLog.INSERT, null, next, feeNames(null, next), levelLabels, allocLabels, dimLabels, statusLabels, currencyLabels, operator, now);
|
||||
inserted++;
|
||||
}
|
||||
if (inserted + updated + removed == 0)
|
||||
@@ -169,23 +203,132 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
return "已保存:新增 " + inserted + " 条,修改 " + updated + " 条,删除 " + removed + " 条";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String confirm(String billCode, Long feeId)
|
||||
{
|
||||
String code = requireBill(billCode);
|
||||
if (feeId == null)
|
||||
{
|
||||
throw new ServiceException("请选择要确定的费用");
|
||||
}
|
||||
SrvBillAirFee current = liveFee(feeId, code);
|
||||
if (current == null)
|
||||
{
|
||||
throw new ServiceException("费用已删除或不属于该提单");
|
||||
}
|
||||
if (!STATUS_ALLOCATED.equals(current.getBillFeeStatus()))
|
||||
{
|
||||
throw new ServiceException("只有已分摊的费用可以确定");
|
||||
}
|
||||
Operator operator = operator();
|
||||
Date now = DateUtils.getNowDate();
|
||||
if (feeMapper.confirmFee(feeId, code, operator.account, now) != 1)
|
||||
{
|
||||
throw new ServiceException("费用状态已变化,请刷新后再确定");
|
||||
}
|
||||
writePlainLog(code, feeId, SrvBillAirFeeLog.CONFIRM, "确定费用", operator, now);
|
||||
return "费用已确定";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String voidFee(String billCode, Long feeId)
|
||||
{
|
||||
String code = requireBill(billCode);
|
||||
if (feeId == null)
|
||||
{
|
||||
throw new ServiceException("请选择要作废的费用");
|
||||
}
|
||||
SrvBillAirFee current = liveFee(feeId, code);
|
||||
if (current == null)
|
||||
{
|
||||
throw new ServiceException("费用已删除或不属于该提单");
|
||||
}
|
||||
if (!locked(current))
|
||||
{
|
||||
throw new ServiceException("只有已分摊或已确定的费用可以作废");
|
||||
}
|
||||
String billedOrderNo = payBillFeeMapper.selectBilledOrderNo(feeId);
|
||||
if (billedOrderNo != null)
|
||||
{
|
||||
String orderNo = billedOrderNo.trim();
|
||||
if (orderNo.isEmpty())
|
||||
{
|
||||
orderNo = "未知订单";
|
||||
}
|
||||
throw new ServiceException("该费用存在单笔或多笔订单已出账单-如:" + orderNo + ",不可作废");
|
||||
}
|
||||
Operator operator = operator();
|
||||
Date now = DateUtils.getNowDate();
|
||||
payBillFeeMapper.markLadingFeeDeleted(feeId);
|
||||
if (feeMapper.voidFee(feeId, code, operator.account, now) != 1)
|
||||
{
|
||||
throw new ServiceException("费用状态已变化,请刷新后再作废");
|
||||
}
|
||||
Map<String, String> levelLabels = dictLabels(LEVEL_DICT, "费用阶段");
|
||||
Map<String, String> allocLabels = dictLabels(ALLOC_DICT, "是否分摊");
|
||||
Map<String, String> dimLabels = dictLabels(ALLOC_DIM_DICT, "分摊维度");
|
||||
Map<String, String> statusLabels = dictLabels(STATUS_DICT, "费用状态");
|
||||
Map<String, String> currencyLabels = dictLabels(CURRENCY_DICT, "币种");
|
||||
writeLog(code, feeId, SrvBillAirFeeLog.VOID, current, null, feeNames(current, null),
|
||||
levelLabels, allocLabels, dimLabels, statusLabels, currencyLabels, operator, now);
|
||||
return "费用已作废";
|
||||
}
|
||||
|
||||
private SrvBillAirFee liveFee(Long id, String billCode)
|
||||
{
|
||||
return feeMapper.selectLive(id, billCode);
|
||||
}
|
||||
|
||||
private SrvBillAirFee normalize(SrvBillAirFee raw, Map<String, String> feeLabels, Map<String, String> currencyLabels)
|
||||
private static boolean locked(SrvBillAirFee fee)
|
||||
{
|
||||
String status = fee == null ? "" : text(fee.getBillFeeStatus());
|
||||
return STATUS_ALLOCATED.equals(status) || STATUS_CONFIRMED.equals(status);
|
||||
}
|
||||
|
||||
private SrvBillAirFee normalize(SrvBillAirFee raw, Map<String, String> levelLabels,
|
||||
Map<String, String> allocLabels, Map<String, String> dimLabels, Map<String, String> currencyLabels)
|
||||
{
|
||||
String feeType = trim(raw.getFeeType());
|
||||
String feeLevel = trim(raw.getFeeLevel());
|
||||
String allocFlag = trim(raw.getAllocFlag());
|
||||
String allocDim = trim(raw.getAllocDim());
|
||||
String currency = trim(raw.getCurrency());
|
||||
String remark = trim(raw.getRemark());
|
||||
if (feeType.isEmpty())
|
||||
Long scId = parseScId(feeType);
|
||||
if (scId == null)
|
||||
{
|
||||
throw new ServiceException("请选择费用项");
|
||||
}
|
||||
if (feeType.length() > 32 || !feeLabels.containsKey(feeType))
|
||||
PrcSurcharge surcharge = surchargeService.selectPrcSurchargeById(scId);
|
||||
if (surcharge == null)
|
||||
{
|
||||
throw new ServiceException("费用项不在费用类型字典中");
|
||||
throw new ServiceException("费用项不存在");
|
||||
}
|
||||
feeType = String.valueOf(scId);
|
||||
if (allocFlag.isEmpty() || !allocLabels.containsKey(allocFlag))
|
||||
{
|
||||
throw new ServiceException("请选择是否分摊");
|
||||
}
|
||||
if (!feeLevel.isEmpty() && (feeLevel.length() > 32 || !levelLabels.containsKey(feeLevel)))
|
||||
{
|
||||
throw new ServiceException("费用阶段不在字典中");
|
||||
}
|
||||
if ("1".equals(allocFlag) && feeLevel.isEmpty())
|
||||
{
|
||||
throw new ServiceException("如需分摊费用至订单,请选择费用所属阶段");
|
||||
}
|
||||
if ("1".equals(allocFlag))
|
||||
{
|
||||
if (allocDim.isEmpty() || !dimLabels.containsKey(allocDim))
|
||||
{
|
||||
throw new ServiceException("如需分摊费用至订单,请选择分摊维度");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
allocDim = "";
|
||||
}
|
||||
if (raw.getPrice() == null)
|
||||
{
|
||||
@@ -213,16 +356,36 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
}
|
||||
SrvBillAirFee fee = new SrvBillAirFee();
|
||||
fee.setFeeType(feeType);
|
||||
fee.setFeeLevel(feeLevel);
|
||||
fee.setAllocFlag(allocFlag);
|
||||
fee.setAllocDim(allocDim);
|
||||
fee.setPrice(raw.getPrice());
|
||||
fee.setCurrency(currency);
|
||||
fee.setRemark(remark);
|
||||
return fee;
|
||||
}
|
||||
|
||||
/**
|
||||
* 费用日志组装
|
||||
* @param billCode
|
||||
* @param feeId
|
||||
* @param operation
|
||||
* @param before
|
||||
* @param after
|
||||
* @param feeLabels
|
||||
* @param levelLabels
|
||||
* @param allocLabels
|
||||
* @param dimLabels
|
||||
* @param statusLabels
|
||||
* @param currencyLabels
|
||||
* @param operator
|
||||
* @param now
|
||||
*/
|
||||
private void writeLog(String billCode, Long feeId, String operation, SrvBillAirFee before, SrvBillAirFee after,
|
||||
Map<String, String> feeLabels, Map<String, String> currencyLabels, Operator operator, Date now)
|
||||
Map<String, String> feeLabels, Map<String, String> levelLabels, Map<String, String> allocLabels,
|
||||
Map<String, String> dimLabels, Map<String, String> statusLabels, Map<String, String> currencyLabels, Operator operator, Date now)
|
||||
{
|
||||
List<SrvBillAirFeeLog.FieldChange> changes = diff(before, after, feeLabels, currencyLabels);
|
||||
List<SrvBillAirFeeLog.FieldChange> changes = diff(before, after, feeLabels, levelLabels, allocLabels, dimLabels, statusLabels, currencyLabels);
|
||||
SrvBillAirFeeLog log = new SrvBillAirFeeLog();
|
||||
log.setBillCode(billCode);
|
||||
log.setFeeId(feeId);
|
||||
@@ -235,12 +398,36 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
feeMapper.insertLog(log);
|
||||
}
|
||||
|
||||
/** 确定费用不比较字段,更改前和更改后都留空。 */
|
||||
private void writePlainLog(String billCode, Long feeId, String operation, String summary, Operator operator, Date now)
|
||||
{
|
||||
SrvBillAirFeeLog log = new SrvBillAirFeeLog();
|
||||
log.setBillCode(billCode);
|
||||
log.setFeeId(feeId);
|
||||
log.setOperationType(operation);
|
||||
log.setSummary(summary);
|
||||
log.setChangeContent("[]");
|
||||
log.setCreateBy(operator.account);
|
||||
log.setCreateByName(operator.name);
|
||||
log.setCreateTime(now);
|
||||
feeMapper.insertLog(log);
|
||||
}
|
||||
|
||||
private static List<SrvBillAirFeeLog.FieldChange> diff(SrvBillAirFee before, SrvBillAirFee after,
|
||||
Map<String, String> feeLabels, Map<String, String> currencyLabels)
|
||||
Map<String, String> feeLabels, Map<String, String> levelLabels, Map<String, String> allocLabels,
|
||||
Map<String, String> dimLabels, Map<String, String> statusLabels, Map<String, String> currencyLabels)
|
||||
{
|
||||
List<SrvBillAirFeeLog.FieldChange> changes = new ArrayList<>();
|
||||
addChange(changes, "feeType", "费用类型", label(feeLabels, value(before, SrvBillAirFee::getFeeType)),
|
||||
addChange(changes, "feeType", "费用项", label(feeLabels, value(before, SrvBillAirFee::getFeeType)),
|
||||
label(feeLabels, value(after, SrvBillAirFee::getFeeType)));
|
||||
addChange(changes, "feeLevel", "费用阶段", label(levelLabels, value(before, SrvBillAirFee::getFeeLevel)),
|
||||
label(levelLabels, value(after, SrvBillAirFee::getFeeLevel)));
|
||||
addChange(changes, "allocFlag", "是否分摊", label(allocLabels, value(before, SrvBillAirFee::getAllocFlag)),
|
||||
label(allocLabels, value(after, SrvBillAirFee::getAllocFlag)));
|
||||
addChange(changes, "allocDim", "分摊维度(票)", label(dimLabels, value(before, SrvBillAirFee::getAllocDim)),
|
||||
label(dimLabels, value(after, SrvBillAirFee::getAllocDim)));
|
||||
addChange(changes, "billFeeStatus", "费用状态", label(statusLabels, value(before, SrvBillAirFee::getBillFeeStatus)),
|
||||
label(statusLabels, value(after, SrvBillAirFee::getBillFeeStatus)));
|
||||
addChange(changes, "price", "价格", priceText(before), priceText(after));
|
||||
addChange(changes, "currency", "收费币种", label(currencyLabels, value(before, SrvBillAirFee::getCurrency)),
|
||||
label(currencyLabels, value(after, SrvBillAirFee::getCurrency)));
|
||||
@@ -262,7 +449,9 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
private static String summary(String operation, List<SrvBillAirFeeLog.FieldChange> changes)
|
||||
{
|
||||
String action = SrvBillAirFeeLog.INSERT.equals(operation) ? "新增费用"
|
||||
: SrvBillAirFeeLog.DELETE.equals(operation) ? "删除费用" : "修改费用";
|
||||
: SrvBillAirFeeLog.DELETE.equals(operation) ? "删除费用"
|
||||
: SrvBillAirFeeLog.CONFIRM.equals(operation) ? "确定费用"
|
||||
: SrvBillAirFeeLog.VOID.equals(operation) ? "作废费用" : "修改费用";
|
||||
StringBuilder text = new StringBuilder(action);
|
||||
for (SrvBillAirFeeLog.FieldChange change : changes)
|
||||
{
|
||||
@@ -329,6 +518,60 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
return fee.getPrice().stripTrailingZeros().toPlainString();
|
||||
}
|
||||
|
||||
private Map<String, String> feeNames(SrvBillAirFee before, SrvBillAirFee after)
|
||||
{
|
||||
Map<String, String> names = new LinkedHashMap<>();
|
||||
putFeeName(names, before);
|
||||
putFeeName(names, after);
|
||||
return names;
|
||||
}
|
||||
|
||||
private void putFeeName(Map<String, String> names, SrvBillAirFee fee)
|
||||
{
|
||||
if (fee == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
String id = trim(fee.getFeeType());
|
||||
if (id.isEmpty() || names.containsKey(id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
names.put(id, surchargeName(id));
|
||||
}
|
||||
|
||||
private String surchargeName(String feeType)
|
||||
{
|
||||
Long scId = parseScId(feeType);
|
||||
if (scId == null)
|
||||
{
|
||||
return feeType;
|
||||
}
|
||||
PrcSurcharge surcharge = surchargeService.selectPrcSurchargeById(scId);
|
||||
if (surcharge == null || surcharge.getScName() == null || surcharge.getScName().isBlank())
|
||||
{
|
||||
return feeType;
|
||||
}
|
||||
return surcharge.getScName().trim();
|
||||
}
|
||||
|
||||
private static Long parseScId(String feeType)
|
||||
{
|
||||
if (feeType == null || feeType.isBlank() || !feeType.chars().allMatch(Character::isDigit))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
long id = Long.parseLong(feeType);
|
||||
return id > 0 ? id : null;
|
||||
}
|
||||
catch (NumberFormatException ex)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String label(Map<String, String> labels, String value)
|
||||
{
|
||||
if (value == null || value.isBlank())
|
||||
|
||||
+200
-3
@@ -28,7 +28,11 @@ import com.bms.customer.customerinfo.mapper.BmsCustomerMapper;
|
||||
import com.bms.price.domain.BmsProduct;
|
||||
import com.bms.price.domain.BmsProductCountry;
|
||||
import com.bms.price.domain.BmsRedlineProductCountry;
|
||||
import com.bms.price.domain.PayCostLine;
|
||||
import com.bms.price.domain.PayOrderCostResult;
|
||||
import com.bms.price.domain.PayProduct;
|
||||
import com.bms.price.domain.PrcCustomerPriceSet;
|
||||
import com.bms.price.domain.PrcOrderBillExt;
|
||||
import com.bms.price.domain.PrcPriceSet;
|
||||
import com.bms.price.domain.PrcPriceSetDetail;
|
||||
import com.bms.price.domain.PrcPriceSetProduct;
|
||||
@@ -37,6 +41,7 @@ import com.bms.price.domain.PrcQuote;
|
||||
import com.bms.price.domain.PrcQuoteFee;
|
||||
import com.bms.price.domain.PrcSurchargeRel;
|
||||
import com.bms.price.domain.PrcZone;
|
||||
import com.bms.price.pay.PayCostCalculator;
|
||||
import com.bms.price.mapper.PrcZoneDetailMapper;
|
||||
import com.bms.price.domain.vo.TmsPriceBillRequest;
|
||||
import com.bms.price.domain.vo.TmsPriceQuoteFeeLine;
|
||||
@@ -48,6 +53,7 @@ import com.bms.price.mapper.BmsProductCountryMapper;
|
||||
import com.bms.price.mapper.BmsProductMapper;
|
||||
import com.bms.price.mapper.BmsRedlineProductCountryMapper;
|
||||
import com.bms.price.mapper.PrcCustomerPriceSetMapper;
|
||||
import com.bms.price.mapper.PrcOrderBillExtMapper;
|
||||
import com.bms.price.mapper.PrcPriceSetDetailMapper;
|
||||
import com.bms.price.mapper.PrcPriceSetMapper;
|
||||
import com.bms.price.mapper.PrcPriceSetProductMapper;
|
||||
@@ -58,12 +64,14 @@ import com.bms.price.mapper.PrcSurchargeRelMapper;
|
||||
import com.bms.price.search.EsBizLogSupport;
|
||||
import com.bms.price.search.EsLogType;
|
||||
import com.bms.price.search.IBillSearchStore;
|
||||
import com.bms.price.service.IPayProductService;
|
||||
import com.bms.price.service.ITmsPriceQuoteService;
|
||||
import com.bms.system.service.IBmsProductCodeMappingService;
|
||||
|
||||
/**
|
||||
* 共用的试算/计费计算:按 bizDate 取客户绑定和套餐版本(版本 effective_time)。
|
||||
* 绑定的 pr_begindate/pr_enddate 不作为计费时间门槛。
|
||||
* 先按 prc_price_set.set_category 分流:LABEL 走打单 pay_*;PACKAGE/EXCLUSIVE 走销售快照。
|
||||
* 销售价只来自套餐快照(FIXED 明细 / 按目录底价 DISCOUNT)。
|
||||
* 无绑定或无生效版本:试算和计费都直接失败(不用 PRODUCT_DEFAULT)。
|
||||
* 账单同步时,产品/国家/重量段未命中为软失败(billing_fail_status 1/2/3,金额为 0)。
|
||||
@@ -78,6 +86,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
private static final String TYPE_DISCOUNT = "DISCOUNT";
|
||||
private static final String SRC_FIXED = "PACKAGE_FIXED";
|
||||
private static final String SRC_DISCOUNT = "PACKAGE_DISCOUNT";
|
||||
private static final String SRC_LABEL = "LABEL_PAY";
|
||||
|
||||
private static final String FEE_BASIC = "BASIC_FREIGHT";
|
||||
private static final String FEE_REG = "REGISTRATION_FEE";
|
||||
@@ -128,6 +137,12 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
@Autowired
|
||||
private BmsCustomerMapper bmsCustomerMapper;
|
||||
|
||||
@Autowired
|
||||
private IPayProductService payProductService;
|
||||
|
||||
@Autowired
|
||||
private PrcOrderBillExtMapper orderBillExtMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public TmsPriceQuoteResult quote(TmsPriceQuoteRequest request)
|
||||
@@ -169,7 +184,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
return toResult(cached, fees, true);
|
||||
}
|
||||
TmsPriceQuoteResult refreshed = doQuote(request, bizDate);
|
||||
applySurchargesAndFees(refreshed);
|
||||
applySalesSurchargesIfNeeded(refreshed);
|
||||
replaceQuoteFees(cached.getQuoteId(), refreshed);
|
||||
refreshed.setQuoteId(cached.getQuoteId());
|
||||
refreshed.setFromCache(false);
|
||||
@@ -179,11 +194,21 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
}
|
||||
|
||||
TmsPriceQuoteResult result = doQuote(request, bizDate);
|
||||
applySurchargesAndFees(result);
|
||||
applySalesSurchargesIfNeeded(result);
|
||||
persistQuote(quoteKey, request, weight, bizDate, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** LABEL 费用已由 PayCostCalculator 算完,不再叠加销售附加费。 */
|
||||
private void applySalesSurchargesIfNeeded(TmsPriceQuoteResult result)
|
||||
{
|
||||
if (result == null || SRC_LABEL.equals(result.getMatchSource()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
applySurchargesAndFees(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached quotes from pre-package-snapshot logic (PRODUCT_DEFAULT / missing package id)
|
||||
* must not be reused after billing rules changed.
|
||||
@@ -211,7 +236,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
TmsPriceQuoteResult result = doQuote(request, bizDate);
|
||||
if (!BillBillingFailStatus.isFail(result.getBillingFailStatus()))
|
||||
{
|
||||
applySurchargesAndFees(result);
|
||||
applySalesSurchargesIfNeeded(result);
|
||||
result.setBillingFailStatus(BillBillingFailStatus.SUCCESS.getCode());
|
||||
}
|
||||
result.setCustomerActiveStatus(customerActiveStatus);
|
||||
@@ -442,6 +467,13 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
continue;
|
||||
}
|
||||
anyEnabledSet = true;
|
||||
// 打单套餐:独立 pay_* 链路,不走销售版本明细
|
||||
if (PrcPriceSet.CATEGORY_LABEL.equals(
|
||||
PrcPriceSetServiceImpl.normalizeSetCategory(set.getSetCategory())))
|
||||
{
|
||||
return doLabelQuote(request, bind, set, productCode, productName, countryCode, countryName,
|
||||
weight, bizDate);
|
||||
}
|
||||
// Temporal gate: prc_price_set_version.effective_time / expire_time (not bind pr_begindate)
|
||||
PrcPriceSetVersion version = versionMapper.selectActiveByPriceSetAt(set.getPriceSetId(), asOf);
|
||||
if (version == null || version.getPsvId() == null)
|
||||
@@ -622,6 +654,171 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
throw new ServiceException(MessageUtils.message(missKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 打单套餐计费:渠道 AR → product_id,套餐+时间 → term,PayCostCalculator 算费。
|
||||
*/
|
||||
private TmsPriceQuoteResult doLabelQuote(TmsPriceQuoteRequest request, PrcCustomerPriceSet bind,
|
||||
PrcPriceSet set, String productCode, String productName, String countryCode, String countryName,
|
||||
BigDecimal weight, Date bizDate)
|
||||
{
|
||||
String channelId = resolveLabelChannelId(request);
|
||||
if (StringUtils.isEmpty(channelId))
|
||||
{
|
||||
if (request instanceof TmsPriceBillRequest)
|
||||
{
|
||||
return buildBillingMissResult(request, productCode, productName, countryCode, countryName,
|
||||
weight, bizDate, BillBillingFailStatus.PRICE_NOT_HIT.getCode());
|
||||
}
|
||||
throw new ServiceException("打单计费需要渠道");
|
||||
}
|
||||
PayProduct product;
|
||||
try
|
||||
{
|
||||
product = payProductService.selectLabelPriceForDate(set.getPriceSetId(), channelId, bizDate);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
if (request instanceof TmsPriceBillRequest)
|
||||
{
|
||||
return buildBillingMissResult(request, productCode, productName, countryCode, countryName,
|
||||
weight, bizDate, BillBillingFailStatus.NO_ACTIVE_PACKAGE_VERSION.getCode());
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
BigDecimal length = null;
|
||||
BigDecimal width = null;
|
||||
BigDecimal height = null;
|
||||
Integer orderStatus = null;
|
||||
if (request instanceof TmsPriceBillRequest)
|
||||
{
|
||||
TmsPriceBillRequest billReq = (TmsPriceBillRequest) request;
|
||||
length = billReq.getPkgLength();
|
||||
width = billReq.getPkgWidth();
|
||||
height = billReq.getPkgHeight();
|
||||
orderStatus = billReq.getOrderStatus();
|
||||
}
|
||||
PayOrderCostResult cost = PayCostCalculator.calculate(product, weight, length, width, height,
|
||||
request.getDestPostcode(), bizDate, orderStatus);
|
||||
if (cost == null || cost.getFees() == null || cost.getFees().isEmpty())
|
||||
{
|
||||
if (request instanceof TmsPriceBillRequest)
|
||||
{
|
||||
return buildBillingMissResult(request, productCode, productName, countryCode, countryName,
|
||||
weight, bizDate, BillBillingFailStatus.PRICE_NOT_HIT.getCode());
|
||||
}
|
||||
throw new ServiceException(cost != null && StringUtils.isNotEmpty(cost.getMessage())
|
||||
? cost.getMessage() : "打单价卡未算出费用");
|
||||
}
|
||||
TmsPriceQuoteResult r = mapLabelCostToQuote(cost, product, request, bind, set,
|
||||
productCode, productName, countryCode, countryName, weight, bizDate);
|
||||
stampRequestMeta(request, r);
|
||||
return r;
|
||||
}
|
||||
|
||||
/** 试算用 request.channelId;计费优先订单扩展 productChannelId。 */
|
||||
private String resolveLabelChannelId(TmsPriceQuoteRequest request)
|
||||
{
|
||||
if (request != null && StringUtils.isNotEmpty(request.getChannelId()))
|
||||
{
|
||||
return request.getChannelId().trim();
|
||||
}
|
||||
if (!(request instanceof TmsPriceBillRequest))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
TmsPriceBillRequest billReq = (TmsPriceBillRequest) request;
|
||||
PrcOrderBillExt ext = null;
|
||||
if (StringUtils.isNotEmpty(billReq.getOrderNo()))
|
||||
{
|
||||
ext = orderBillExtMapper.selectByOrderNo(billReq.getOrderNo().trim());
|
||||
}
|
||||
if (ext == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (StringUtils.isNotEmpty(ext.getProductChannelId()))
|
||||
{
|
||||
return ext.getProductChannelId().trim();
|
||||
}
|
||||
if (StringUtils.isNotEmpty(ext.getProductFloorCode()))
|
||||
{
|
||||
return ext.getProductFloorCode().trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private TmsPriceQuoteResult mapLabelCostToQuote(PayOrderCostResult cost, PayProduct product,
|
||||
TmsPriceQuoteRequest request, PrcCustomerPriceSet bind, PrcPriceSet set,
|
||||
String productCode, String productName, String countryCode, String countryName,
|
||||
BigDecimal weight, Date bizDate)
|
||||
{
|
||||
RoundingMode rm = RoundingMode.HALF_UP;
|
||||
String currency = normalizeCurrency(cost.getCurrency());
|
||||
List<TmsPriceQuoteFeeLine> lines = new ArrayList<TmsPriceQuoteFeeLine>();
|
||||
BigDecimal freightAmount = BigDecimal.ZERO;
|
||||
BigDecimal surchargeAmount = BigDecimal.ZERO;
|
||||
BigDecimal registrationFee = BigDecimal.ZERO;
|
||||
for (PayCostLine fee : cost.getFees())
|
||||
{
|
||||
if (fee == null || fee.getAmount() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String feeType = StringUtils.isEmpty(fee.getFeeType()) ? FEE_SUR : fee.getFeeType().trim();
|
||||
String feeName = StringUtils.isEmpty(fee.getFeeName()) ? feeNameOf(feeType) : fee.getFeeName();
|
||||
BigDecimal amount = fee.getAmount().setScale(2, rm);
|
||||
Long scId = fee.getScId() == null ? 0L : fee.getScId();
|
||||
lines.add(feeLine(feeType, feeName, scId, amount, currency, "HALF_UP", fee.getRemark()));
|
||||
if (FEE_BASIC.equals(feeType))
|
||||
{
|
||||
freightAmount = freightAmount.add(amount);
|
||||
}
|
||||
else if (FEE_REG.equals(feeType))
|
||||
{
|
||||
registrationFee = registrationFee.add(amount);
|
||||
}
|
||||
else
|
||||
{
|
||||
surchargeAmount = surchargeAmount.add(amount);
|
||||
}
|
||||
}
|
||||
BigDecimal incomeTotal = nvl(cost.getPayTotal());
|
||||
if (incomeTotal.compareTo(BigDecimal.ZERO) <= 0)
|
||||
{
|
||||
incomeTotal = freightAmount.add(registrationFee).add(surchargeAmount).setScale(2, rm);
|
||||
}
|
||||
String labelProductName = StringUtils.isNotEmpty(product.getProductName())
|
||||
? product.getProductName() : productName;
|
||||
TmsPriceQuoteResult r = new TmsPriceQuoteResult();
|
||||
r.setMatchSource(SRC_LABEL);
|
||||
r.setMatchMessage("hit label package pay card V"
|
||||
+ (product.getVersionNo() == null ? "?" : product.getVersionNo()));
|
||||
fillPackage(r, bind, set);
|
||||
r.setVersionNo(product.getVersionNo() == null ? null : ("V" + product.getVersionNo()));
|
||||
r.setCustomerId(bind.getCustomerId());
|
||||
r.setProductCode(productCode);
|
||||
r.setProductName(labelProductName);
|
||||
r.setCountryCode(countryCode);
|
||||
r.setCountryName(countryName);
|
||||
r.setWeight(weight);
|
||||
r.setChargeWeight(weight);
|
||||
r.setBizDate(bizDate);
|
||||
r.setEffectiveDate(product.getTermStart() != null ? product.getTermStart() : product.getEffectiveDate());
|
||||
r.setFreightRate(weight != null && weight.compareTo(BigDecimal.ZERO) > 0
|
||||
? freightAmount.divide(weight, 2, rm) : freightAmount);
|
||||
r.setFreightAmount(freightAmount.setScale(2, rm));
|
||||
r.setRegistrationFee(registrationFee.setScale(2, rm));
|
||||
r.setSurchargeAmount(surchargeAmount.setScale(2, rm));
|
||||
r.setPayableTotal(incomeTotal);
|
||||
r.setIncomeTotal(incomeTotal);
|
||||
r.setCurrency(currency);
|
||||
r.setRoundMode("HALF_UP");
|
||||
r.setFromCache(false);
|
||||
r.setFeeDetails(lines);
|
||||
r.setPayFeeDetails(Collections.<TmsPriceQuoteFeeLine>emptyList());
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Diagnose FIXED miss: product rows absent -> 1; country/zone absent -> 2; else weight -> 3.
|
||||
*/
|
||||
|
||||
+1
@@ -265,6 +265,7 @@ public final class OrderBillExportSupport
|
||||
case 20: return "客户已停用";
|
||||
case 21: return "运费为空";
|
||||
case 22: return "未知失败";
|
||||
case 26: return "未计费";
|
||||
default: return String.valueOf(status);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -16,13 +16,13 @@ import com.bms.price.mapper.PrcBillingTaskMapper;
|
||||
import com.bms.price.service.IPrcBillingTaskService;
|
||||
|
||||
/**
|
||||
* 夜间窗口按 prc_billing_task 自动重计费。
|
||||
* Auto retry prc_billing_task on a schedule.
|
||||
*
|
||||
* <p>在 [startHour, endHour) 内(默认 00:00-09:00):最多拉取 {@value #DEFAULT_BATCH}
|
||||
* 条任务 id(不限 success_status)-> 全部计费 -> 立刻拉下一页。
|
||||
* 空页则跳过计费并结束本轮(下次 cron 再处理新数据)。</p>
|
||||
* <p>Within [startHour, endHour) (default all day 00:00-24:00): pull up to {@value #DEFAULT_BATCH}
|
||||
* task ids (any success_status) -> bill all -> pull next page.
|
||||
* Empty page skips billing and ends this tick (next cron handles new rows).</p>
|
||||
*
|
||||
* <p>与监控手工重试 / 条件重试同一条重计费路径(先重置为 WAITING 再处理)。</p>
|
||||
* <p>Same path as monitor manual / filter retry (reset to WAITING then process).</p>
|
||||
*/
|
||||
@Component
|
||||
public class PrcBillingTaskNightRetryTask
|
||||
@@ -52,10 +52,10 @@ public class PrcBillingTaskNightRetryTask
|
||||
private IPrcBillingTaskService billingTaskService;
|
||||
|
||||
/**
|
||||
* Fire every minute; if a previous run is still paging batches, skip.
|
||||
* Fire every hour by default; if a previous run is still paging batches, skip.
|
||||
* Hour gate uses bms.billing.night-retry start/end.
|
||||
*/
|
||||
@Scheduled(cron = "${bms.billing.night-retry.cron:0 0/1 0-8 * * ?}")
|
||||
@Scheduled(cron = "${bms.billing.night-retry.cron:0 0 * * * ?}")
|
||||
public void scheduledTick()
|
||||
{
|
||||
BillingProperties.NightRetry cfg = billingProperties == null ? null : billingProperties.getNightRetry();
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.config;
|
||||
package com.bms.receivable.config;
|
||||
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.domain;
|
||||
package com.bms.receivable.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.domain;
|
||||
package com.bms.receivable.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
+1
-3
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.domain;
|
||||
package com.bms.receivable.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
@@ -41,8 +41,6 @@ public class ArBill extends BaseEntity {
|
||||
private Long profitSalesUserId;
|
||||
private String profitSalesUserCode;
|
||||
private String profitSalesUserName;
|
||||
/** NEW=生成时固化;BACKFILLED=历史补录,来源为补录时可取得的数据。 */
|
||||
private String profitSnapshotSource;
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
/** 账单所属期开始日期,导出模板展示用。 */
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.domain;
|
||||
package com.bms.receivable.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.domain;
|
||||
package com.bms.receivable.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.domain;
|
||||
package com.bms.receivable.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.domain;
|
||||
package com.bms.receivable.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.domain;
|
||||
package com.bms.receivable.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.domain;
|
||||
package com.bms.receivable.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.domain.vo;
|
||||
package com.bms.receivable.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.domain.vo;
|
||||
package com.bms.receivable.domain.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.domain.vo;
|
||||
package com.bms.receivable.domain.vo;
|
||||
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.domain.vo;
|
||||
package com.bms.receivable.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.ar.enums.ArBillSelectionMode;
|
||||
import com.bms.receivable.enums.ArBillSelectionMode;
|
||||
import lombok.Data;
|
||||
|
||||
/** 从订单计费结果生成正式账单的参数。 */
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
package com.bms.ar.domain.vo;
|
||||
package com.bms.receivable.domain.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
import com.bms.ar.domain.ArBill;
|
||||
import com.bms.ar.enums.ArBillStatus;
|
||||
import com.bms.receivable.domain.ArBill;
|
||||
import com.bms.receivable.enums.ArBillStatus;
|
||||
import com.bms.common.annotation.Excel;
|
||||
import lombok.Data;
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package com.bms.ar.domain.vo;
|
||||
package com.bms.receivable.domain.vo;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import com.bms.ar.domain.ArBill;
|
||||
import com.bms.receivable.domain.ArBill;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.domain.vo;
|
||||
package com.bms.receivable.domain.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.enums;
|
||||
package com.bms.receivable.enums;
|
||||
|
||||
/**
|
||||
* 应收账单余额核销单状态。
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.enums;
|
||||
package com.bms.receivable.enums;
|
||||
|
||||
/** 应收账单生成时用于截止日筛选的来源订单日期字段。 */
|
||||
public enum ArBillDateType
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.enums;
|
||||
package com.bms.receivable.enums;
|
||||
|
||||
/** 应收账单异步生成任务状态。 */
|
||||
public final class ArBillGenerateTaskStatus
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.enums;
|
||||
package com.bms.receivable.enums;
|
||||
|
||||
/** 应收账单生成状态,与账单业务状态分离。 */
|
||||
public final class ArBillGenerationStatus
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.enums;
|
||||
package com.bms.receivable.enums;
|
||||
|
||||
/**
|
||||
* 应收账单生成时的订单选择模式。
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.enums;
|
||||
package com.bms.receivable.enums;
|
||||
|
||||
/** 应收账单状态。数据库保存值使用 code,避免业务代码散落状态字符串。 */
|
||||
public enum ArBillStatus
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.enums;
|
||||
package com.bms.receivable.enums;
|
||||
|
||||
/**
|
||||
* 应收账单导出任务状态。
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.enums;
|
||||
package com.bms.receivable.enums;
|
||||
|
||||
/** 应收费用明细是否已经生成正式应收账单。 */
|
||||
public enum ArIncomeBillFeeStatus
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.enums;
|
||||
package com.bms.receivable.enums;
|
||||
|
||||
/** 应收日志关联对象类型。 */
|
||||
public enum ArLogObjectType
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.enums;
|
||||
package com.bms.receivable.enums;
|
||||
|
||||
/** 应收日志操作来源。 */
|
||||
public enum ArLogOperationSource
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.enums;
|
||||
package com.bms.receivable.enums;
|
||||
|
||||
/** 应收日志操作类型。 */
|
||||
public enum ArLogOperationType
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.enums;
|
||||
package com.bms.receivable.enums;
|
||||
|
||||
/** 应收操作日志结果。 */
|
||||
public enum ArLogResult
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.enums;
|
||||
package com.bms.receivable.enums;
|
||||
|
||||
/** 计费订单的应收账单状态。 */
|
||||
public enum ArOrderBillStatus
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.event;
|
||||
package com.bms.receivable.event;
|
||||
|
||||
/**
|
||||
* 应收账单导出任务已提交事件。
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bms.ar.event;
|
||||
package com.bms.receivable.event;
|
||||
|
||||
/**
|
||||
* 应收账单异步生成任务已提交事件。
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
package com.bms.ar.mapper;
|
||||
package com.bms.receivable.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.bms.ar.security.ArBillRowScope;
|
||||
import com.bms.receivable.security.ArBillRowScope;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.ar.domain.ArBalanceWriteoff;
|
||||
import com.bms.receivable.domain.ArBalanceWriteoff;
|
||||
|
||||
/**
|
||||
* 应收账单余额核销 Mapper
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package com.bms.ar.mapper;
|
||||
package com.bms.receivable.mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.ar.domain.ArBankAccount;
|
||||
import com.bms.receivable.domain.ArBankAccount;
|
||||
|
||||
public interface ArBankAccountMapper {
|
||||
ArBankAccount selectDefaultByCurrency(@Param("currency") String currency);
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
package com.bms.ar.mapper;
|
||||
package com.bms.receivable.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.bms.ar.security.ArBillRowScope;
|
||||
import com.bms.receivable.security.ArBillRowScope;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.ar.domain.ArBillDetailFee;
|
||||
import com.bms.receivable.domain.ArBillDetailFee;
|
||||
|
||||
public interface ArBillDetailFeeMapper {
|
||||
List<ArBillDetailFee> selectByBillIdAndDetailIdScoped(@Param("arBillId") Long arBillId, @Param("detailId") Long detailId, @Param("scope") ArBillRowScope scope);
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
package com.bms.ar.mapper;
|
||||
package com.bms.receivable.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.bms.ar.security.ArBillRowScope;
|
||||
import com.bms.receivable.security.ArBillRowScope;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.ar.domain.ArBillDetail;
|
||||
import com.bms.receivable.domain.ArBillDetail;
|
||||
|
||||
public interface ArBillDetailMapper {
|
||||
List<ArBillDetail> selectByBillIdScoped(@Param("arBillId") Long arBillId, @Param("scope") ArBillRowScope scope);
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
package com.bms.ar.mapper;
|
||||
package com.bms.receivable.mapper;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.ar.domain.ArBillExport;
|
||||
import com.bms.receivable.domain.ArBillExport;
|
||||
|
||||
public interface ArBillExportMapper {
|
||||
List<ArBillExport> selectByBillId(Long arBillId);
|
||||
+3
-3
@@ -1,17 +1,17 @@
|
||||
package com.bms.ar.mapper;
|
||||
package com.bms.receivable.mapper;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.ar.domain.ArBillGenerateTask;
|
||||
import com.bms.receivable.domain.ArBillGenerateTask;
|
||||
|
||||
/**
|
||||
* 应收账单异步生成任务 Mapper。
|
||||
*/
|
||||
public interface ArBillGenerateTaskMapper {
|
||||
List<ArBillGenerateTask> selectActiveByCreateByScoped(@Param("createBy") String createBy,
|
||||
@Param("scope") com.bms.ar.security.ArBillRowScope scope);
|
||||
@Param("scope") com.bms.receivable.security.ArBillRowScope scope);
|
||||
|
||||
ArBillGenerateTask selectById(Long taskId);
|
||||
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
package com.bms.ar.mapper;
|
||||
package com.bms.receivable.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.bms.ar.security.ArBillRowScope;
|
||||
import com.bms.receivable.security.ArBillRowScope;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.ar.domain.ArBillLog;
|
||||
import com.bms.receivable.domain.ArBillLog;
|
||||
|
||||
public interface ArBillLogMapper
|
||||
{
|
||||
+5
-5
@@ -1,14 +1,14 @@
|
||||
package com.bms.ar.mapper;
|
||||
package com.bms.receivable.mapper;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.bms.ar.security.ArBillRowScope;
|
||||
import com.bms.receivable.security.ArBillRowScope;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.ar.domain.ArBill;
|
||||
import com.bms.ar.domain.vo.ArBillQuery;
|
||||
import com.bms.ar.domain.vo.ArBillSummary;
|
||||
import com.bms.receivable.domain.ArBill;
|
||||
import com.bms.receivable.domain.vo.ArBillQuery;
|
||||
import com.bms.receivable.domain.vo.ArBillSummary;
|
||||
|
||||
public interface ArBillMapper {
|
||||
List<ArBill> selectArBillList(@Param("query") ArBillQuery query, @Param("scope") ArBillRowScope scope);
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
package com.bms.ar.mapper;
|
||||
package com.bms.receivable.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.ar.security.ArBillRowScope;
|
||||
import com.bms.receivable.security.ArBillRowScope;
|
||||
import com.bms.customer.customerinfo.domain.BmsCustomerOption;
|
||||
|
||||
public interface ArBillScopeMapper {
|
||||
+9
-7
@@ -1,12 +1,13 @@
|
||||
package com.bms.ar.mapper;
|
||||
package com.bms.receivable.mapper;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
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.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;
|
||||
|
||||
public interface ArCommissionMapper
|
||||
{
|
||||
@@ -25,6 +26,7 @@ public interface ArCommissionMapper
|
||||
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);
|
||||
List<ArCommissionResult> selectResults(@Param("billId") Long billId, @Param("includeReversed") boolean includeReversed);
|
||||
List<ArCommissionSummary> selectCommissionSummary(@Param("billId") Long billId);
|
||||
List<ArCommissionResult> selectResults(@Param("billId") Long billId, @Param("includeReversed") boolean includeReversed,
|
||||
@Param("scope") ArProfitScope scope);
|
||||
List<ArCommissionSummary> selectCommissionSummary(@Param("billId") Long billId, @Param("scope") ArProfitScope scope);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user