package cn.iocoder.yudao.module.mes.service.qc.ncr;
|
|
import cn.hutool.core.util.StrUtil;
|
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrDisposeReqVO;
|
import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrDisposeRespVO;
|
import cn.iocoder.yudao.module.mes.controller.admin.wm.miscissue.vo.MesWmMiscIssueSaveReqVO;
|
import cn.iocoder.yudao.module.mes.controller.admin.wm.miscissue.vo.line.MesWmMiscIssueLineSaveReqVO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.miscissue.MesWmMiscIssueDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transaction.MesWmTransactionDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO;
|
import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper;
|
import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcMapper;
|
import cn.iocoder.yudao.module.mes.dal.mysql.wm.batch.MesWmBatchMapper;
|
import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper;
|
import cn.iocoder.yudao.module.mes.dal.mysql.wm.miscissue.MesWmMiscIssueMapper;
|
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper;
|
import cn.iocoder.yudao.module.mes.dal.mysql.wm.transaction.MesWmTransactionMapper;
|
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
|
import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
|
import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderSourceTypeEnum;
|
import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderStatusEnum;
|
import cn.iocoder.yudao.module.mes.enums.qc.MesNcrDispositionEnum;
|
import cn.iocoder.yudao.module.mes.enums.wm.MesWmMiscIssueStatusEnum;
|
import cn.iocoder.yudao.module.mes.enums.wm.MesWmMiscIssueTypeEnum;
|
import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum;
|
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
|
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
|
import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
|
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
|
import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
|
import cn.iocoder.yudao.module.mes.service.wm.miscissue.MesWmMiscIssueLineService;
|
import cn.iocoder.yudao.module.mes.service.wm.miscissue.MesWmMiscIssueService;
|
import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesService;
|
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
|
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService;
|
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
|
import jakarta.annotation.Resource;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.math.BigDecimal;
|
import java.time.LocalDateTime;
|
import java.util.List;
|
import java.util.Objects;
|
import java.util.stream.Collectors;
|
|
/**
|
* 出货检验(OQC)不合格返工/返修闭环。
|
*
|
* <p>处置时扣减原成品库存并生成返工领用出库单,自动取消原销售出库单,
|
* 再复用原生产工单克隆一张返工工单;复检通过后由 NCR 关闭流程核验。</p>
|
*/
|
@Service
|
public class MesQcNcrOqcReworkService {
|
|
/** 返工领用出库单的来源单据类型 */
|
private static final String MISC_ISSUE_SOURCE_TYPE_NCR = "QC_NCR";
|
|
@Resource private MesWmBatchMapper batchMapper;
|
@Resource private MesProWorkOrderMapper orderMapper;
|
@Resource private MesQcOqcMapper oqcMapper;
|
@Resource private MesWmMaterialStockMapper stockMapper;
|
@Resource private MesWmMiscIssueMapper miscIssueMapper;
|
@Resource private MesWmTransactionMapper transactionMapper;
|
@Resource private MesWmProductSalesMapper salesMapper;
|
|
@Resource private MesWmMaterialStockService stockService;
|
@Resource private MesWmProductSalesService salesService;
|
@Resource private MesWmMiscIssueService miscIssueService;
|
@Resource private MesWmMiscIssueLineService miscIssueLineService;
|
@Resource private MesMdAutoCodeRecordService autoCodeRecordService;
|
@Resource private MesMdItemService itemService;
|
@Resource private MesQcNcrOqcReproductionService reproductionService;
|
@Resource private MesWmWarehouseService warehouseService;
|
@Resource private MesWmWarehouseLocationService warehouseLocationService;
|
@Resource private MesWmWarehouseAreaService warehouseAreaService;
|
|
@Transactional(rollbackFor = Exception.class)
|
public MesQcNcrDisposeRespVO dispose(MesQcNcrDO ncr, MesQcNcrOqcSourceClaim.OqcClaimedSource source,
|
MesQcNcrDisposeReqVO req) {
|
validateSource(ncr, source);
|
MesWmBatchDO batch = validateBatch(ncr, source);
|
MesProWorkOrderDO original = validateOriginalWorkOrder(batch, source);
|
MesWmMaterialStockDO stock = resolveAndLockStock(ncr, source, req);
|
|
// 原销售出库单整单取消,释放占用;必须在扣减库存前完成
|
salesService.cancelProductSales(source.sales().getId());
|
|
String issueCode = createReworkIssue(ncr, stock, source.sales().getCode());
|
String namePrefix = Objects.equals(req.getDisposition(), MesNcrDispositionEnum.REWORK.getType())
|
? "返工-" : "返修-";
|
MesQcNcrDisposeRespVO result = reproductionService.createReworkFromOriginal(
|
ncr, req.getDisposition(), original, source.sales().getClientId(), batch.getCode(), namePrefix);
|
result.setMessage(String.format(
|
"已生成返工领用出库单[%s]并完成出库,原销售出库单[%s]已取消;返工工单[%s]已下达。"
|
+ "请完成返工后做过程检验复检,复检合格后关闭本单",
|
issueCode, source.sales().getCode(), result.getDocCode()));
|
return result;
|
}
|
|
/**
|
* 关闭返工/返修 NCR 前核验:返工工单已完成、原销售出库单已取消、返工领用出库已执行。
|
*/
|
public void validateBeforeClose(MesQcNcrDO ncr) {
|
List<MesProWorkOrderDO> orders = orderMapper.selectList(new LambdaQueryWrapperX<MesProWorkOrderDO>()
|
.eq(MesProWorkOrderDO::getOrderSourceType, MesProWorkOrderSourceTypeEnum.STORE.getType())
|
.eq(MesProWorkOrderDO::getOrderSourceCode, ncr.getCode()));
|
if (orders.size() != 1) {
|
throw new ServiceException(400, String.format(
|
"无法唯一确定不合格单[%s]对应的返工/返修生产订单(当前匹配[%s]张),请先核对来源编码",
|
ncr.getCode(), orders.size()));
|
}
|
MesProWorkOrderDO order = orders.get(0);
|
if (!Objects.equals(order.getStatus(), MesProWorkOrderStatusEnum.FINISHED.getStatus())
|
|| order.getFinishDate() == null) {
|
throw new ServiceException(400, String.format(
|
"返工/返修生产订单[%s]尚未完成或缺少完成时间,请完成返工并报工后再关闭",
|
order.getCode()));
|
}
|
|
MesWmProductSalesDO sales = resolveOriginalSales(ncr);
|
if (sales == null) {
|
throw new ServiceException(400, "未找到不合格单关联的原销售出库单,无法核验返工闭环");
|
}
|
if (!Objects.equals(sales.getStatus(), MesWmProductSalesStatusEnum.CANCELED.getStatus())) {
|
throw new ServiceException(400, String.format(
|
"原销售出库单[%s]当前状态为[%s],返工处置应已自动取消该出库单;"
|
+ "请核对是否被重新提交或人工修改", sales.getCode(), salesStatusName(sales.getStatus())));
|
}
|
|
List<MesWmMiscIssueDO> issues = miscIssueMapper.selectList(new LambdaQueryWrapperX<MesWmMiscIssueDO>()
|
.eq(MesWmMiscIssueDO::getSourceDocType, MISC_ISSUE_SOURCE_TYPE_NCR)
|
.eq(MesWmMiscIssueDO::getSourceDocId, ncr.getId())
|
.eq(MesWmMiscIssueDO::getType, MesWmMiscIssueTypeEnum.REWORK.getType()));
|
if (issues.size() != 1) {
|
throw new ServiceException(400, String.format(
|
"不合格单[%s]关联的返工领用出库单数量为[%s]张,无法核验返工领用库存",
|
ncr.getCode(), issues.size()));
|
}
|
MesWmMiscIssueDO issue = issues.get(0);
|
if (!Objects.equals(issue.getStatus(), MesWmMiscIssueStatusEnum.FINISHED.getStatus())) {
|
throw new ServiceException(400, String.format(
|
"返工领用出库单[%s]尚未完成出库,请先在杂项出库中完成后再关闭", issue.getCode()));
|
}
|
|
List<MesWmTransactionDO> flows = transactionMapper.selectList(new LambdaQueryWrapperX<MesWmTransactionDO>()
|
.eq(MesWmTransactionDO::getBizType, MesBizTypeConstants.WM_MISC_ISSUE)
|
.eq(MesWmTransactionDO::getBizId, issue.getId())
|
.eq(MesWmTransactionDO::getType, MesWmTransactionTypeEnum.OUT.getType()));
|
BigDecimal out = BigDecimal.ZERO;
|
for (MesWmTransactionDO flow : flows) {
|
if (!Objects.equals(flow.getItemId(), ncr.getItemId())
|
|| flow.getQuantity() == null || flow.getQuantity().signum() >= 0) {
|
throw new ServiceException(400, String.format(
|
"返工领用出库单[%s]存在物料或数量不一致的出库流水,请核对后重试", issue.getCode()));
|
}
|
out = out.subtract(flow.getQuantity());
|
}
|
if (out.compareTo(ncr.getUnqualifiedQuantity()) != 0) {
|
throw new ServiceException(400, String.format(
|
"返工领用出库数量[%s]与不合格数量[%s]不一致,请核对返工领用出库单[%s]",
|
plain(out), plain(ncr.getUnqualifiedQuantity()), issue.getCode()));
|
}
|
}
|
|
// ==================== 校验与定位 ====================
|
|
private void validateSource(MesQcNcrDO ncr, MesQcNcrOqcSourceClaim.OqcClaimedSource source) {
|
if (source == null || source.qc() == null || source.sales() == null || source.line() == null) {
|
throw new ServiceException(400, "出货不合格返工缺少已认领的来源检验单、销售出库单或出库行");
|
}
|
MesWmProductSalesLineDO line = source.line();
|
if (ncr.getUnqualifiedQuantity() == null || ncr.getUnqualifiedQuantity().signum() <= 0) {
|
throw new ServiceException(400, "不合格数量必须大于 0 才能返工/返修");
|
}
|
if (!Objects.equals(source.sales().getStatus(), MesWmProductSalesStatusEnum.CONFIRMED.getStatus())
|
|| !Objects.equals(line.getSalesId(), source.sales().getId())
|
|| !Objects.equals(line.getOqcId(), source.qc().getId())
|
|| !Objects.equals(line.getQualityStatus(), MesWmQualityStatusEnum.FAIL.getStatus())
|
|| !Objects.equals(line.getItemId(), ncr.getItemId())
|
|| line.getBatchId() == null || StrUtil.isBlank(line.getBatchCode())
|
|| line.getQuantity() == null
|
|| ncr.getUnqualifiedQuantity().compareTo(line.getQuantity()) > 0) {
|
throw new ServiceException(400, "出货检验来源、销售出库行、物料、批次或不合格数量不一致,无法返工/返修");
|
}
|
}
|
|
private MesWmBatchDO validateBatch(MesQcNcrDO ncr, MesQcNcrOqcSourceClaim.OqcClaimedSource source) {
|
MesWmProductSalesLineDO line = source.line();
|
MesWmBatchDO batch = batchMapper.selectById(line.getBatchId());
|
if (batch == null || !Objects.equals(batch.getCode(), line.getBatchCode())
|
|| !Objects.equals(batch.getItemId(), line.getItemId())
|
|| !Objects.equals(batch.getClientId(), source.sales().getClientId())
|
|| batch.getWorkOrderId() == null) {
|
throw new ServiceException(400, String.format(
|
"出货批次[%s]未绑定一致的物料[%s]、客户或原生产订单,无法返工/返修",
|
StrUtil.blankToDefault(line.getBatchCode(), "未填写"), itemLabel(ncr.getItemId())));
|
}
|
return batch;
|
}
|
|
private MesProWorkOrderDO validateOriginalWorkOrder(MesWmBatchDO batch,
|
MesQcNcrOqcSourceClaim.OqcClaimedSource source) {
|
MesProWorkOrderDO original = orderMapper.selectById(batch.getWorkOrderId());
|
if (original == null || original.getProductId() == null
|
|| !Objects.equals(original.getClientId(), source.sales().getClientId())) {
|
throw new ServiceException(400, String.format(
|
"出货批次[%s]关联的原生产订单不存在、缺少产品或不属于客户[ID=%s],无法返工/返修",
|
batch.getCode(), source.sales().getClientId()));
|
}
|
return original;
|
}
|
|
/**
|
* 定位并锁定原成品库存:优先使用前端选定的库存记录;未指定时要求同批次仅一条可用库存。
|
*/
|
private MesWmMaterialStockDO resolveAndLockStock(MesQcNcrDO ncr, MesQcNcrOqcSourceClaim.OqcClaimedSource source,
|
MesQcNcrDisposeReqVO req) {
|
MesWmProductSalesLineDO line = source.line();
|
BigDecimal need = ncr.getUnqualifiedQuantity();
|
MesWmMaterialStockDO stock;
|
if (req.getMaterialStockId() != null) {
|
stock = stockService.getMaterialStock(req.getMaterialStockId());
|
if (stock == null) {
|
throw new ServiceException(400, String.format(
|
"所选返工库存记录[ID=%s]不存在,请重新选择该批次成品库存", req.getMaterialStockId()));
|
}
|
if (!Objects.equals(stock.getItemId(), line.getItemId())
|
|| !Objects.equals(stock.getBatchId(), line.getBatchId())) {
|
throw new ServiceException(400, String.format(
|
"所选库存与不合格出货批次不一致:所选库存物料[%s]、批次[%s],"
|
+ "出货行物料[%s]、批次[%s]。请重新选择该批次成品库存",
|
itemLabel(stock.getItemId()), batchLabel(stock.getBatchId(), stock.getBatchCode()),
|
itemLabel(line.getItemId()), batchLabel(line.getBatchId(), line.getBatchCode())));
|
}
|
if (stock.getWarehouseId() == null || stock.getLocationId() == null || stock.getAreaId() == null) {
|
throw new ServiceException(400, String.format(
|
"所选库存[ID=%s]缺少仓库/库区/库位信息,无法出库;请重新选择完整的库存记录",
|
stock.getId()));
|
}
|
} else {
|
List<MesWmMaterialStockDO> candidates = stockMapper.selectList(new LambdaQueryWrapperX<MesWmMaterialStockDO>()
|
.eq(MesWmMaterialStockDO::getItemId, line.getItemId())
|
.eq(MesWmMaterialStockDO::getBatchId, line.getBatchId())
|
.gt(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO));
|
if (candidates.isEmpty()) {
|
throw new ServiceException(400, String.format(
|
"未找到物料[%s]、批次[%s]的成品库存,无法返工领用;请先确认成品已入库或刷新后重试",
|
itemLabel(line.getItemId()), batchLabel(line.getBatchId(), line.getBatchCode())));
|
}
|
List<MesWmMaterialStockDO> available = candidates.stream()
|
.filter(item -> available(item).compareTo(need) >= 0)
|
.toList();
|
if (available.size() != 1) {
|
String detail = candidates.stream()
|
.map(item -> String.format("%s(可用量[%s])", stockLabel(item), plain(available(item))))
|
.collect(Collectors.joining(";"));
|
throw new ServiceException(400, String.format(
|
"批次[%s]库存无法自动确定返工领用记录:%s。"
|
+ "请在处置弹窗的[返工库存]中选择具体库存记录后重试",
|
batchLabel(line.getBatchId(), line.getBatchCode()), detail));
|
}
|
stock = available.get(0);
|
}
|
|
// 重新加锁后计算可用量,避免并发出库导致超领
|
MesWmMaterialStockDO locked = stockMapper.lockById(stock.getId());
|
if (locked == null) {
|
throw new ServiceException(400, String.format(
|
"返工库存记录[ID=%s]已不存在,请刷新后重新选择", stock.getId()));
|
}
|
BigDecimal available = available(locked);
|
if (available.compareTo(need) < 0) {
|
throw new ServiceException(400, String.format(
|
"批次[%s]库存可用量[%s]不足,本次返工需领用[%s];"
|
+ "请先处理库存占用/冻结,或核对成品入库数量",
|
batchLabel(locked.getBatchId(), locked.getBatchCode()), plain(available), plain(need)));
|
}
|
return locked;
|
}
|
|
// ==================== 单据生成 ====================
|
|
private String createReworkIssue(MesQcNcrDO ncr, MesWmMaterialStockDO stock, String salesCode) {
|
String code = autoCodeRecordService.generateAutoCode(
|
MesMdAutoCodeRuleCodeEnum.WM_MISC_ISSUE_CODE.getCode());
|
MesWmMiscIssueSaveReqVO issueReqVO = new MesWmMiscIssueSaveReqVO();
|
issueReqVO.setCode(code);
|
issueReqVO.setName("返工领用出库单-" + ncr.getCode());
|
issueReqVO.setType(MesWmMiscIssueTypeEnum.REWORK.getType());
|
issueReqVO.setSourceDocType(MISC_ISSUE_SOURCE_TYPE_NCR);
|
issueReqVO.setSourceDocId(ncr.getId());
|
issueReqVO.setSourceDocCode(ncr.getCode());
|
issueReqVO.setIssueDate(LocalDateTime.now());
|
issueReqVO.setRemark(String.format("由不合格品处理单[%s]返工领用自动生成;原销售出库单[%s]已取消",
|
ncr.getCode(), salesCode));
|
Long issueId = miscIssueService.createMiscIssue(issueReqVO);
|
|
MesWmMiscIssueLineSaveReqVO lineReqVO = new MesWmMiscIssueLineSaveReqVO();
|
lineReqVO.setIssueId(issueId);
|
lineReqVO.setItemId(ncr.getItemId());
|
lineReqVO.setQuantity(ncr.getUnqualifiedQuantity());
|
lineReqVO.setMaterialStockId(stock.getId());
|
lineReqVO.setBatchId(stock.getBatchId());
|
lineReqVO.setBatchCode(stock.getBatchCode());
|
lineReqVO.setWarehouseId(stock.getWarehouseId());
|
lineReqVO.setLocationId(stock.getLocationId());
|
lineReqVO.setAreaId(stock.getAreaId());
|
lineReqVO.setRemark(String.format("来源不合格品处理单[%s]返工领用", ncr.getCode()));
|
miscIssueLineService.createMiscIssueLine(lineReqVO);
|
|
miscIssueService.submitMiscIssue(issueId);
|
miscIssueService.finishMiscIssue(issueId);
|
return code;
|
}
|
|
/**
|
* 通过来源检验单定位原销售出库单(仅用于关闭核验)。
|
*/
|
private MesWmProductSalesDO resolveOriginalSales(MesQcNcrDO ncr) {
|
if (ncr.getQcId() == null) {
|
return null;
|
}
|
MesQcOqcDO qc = oqcMapper.selectById(ncr.getQcId());
|
return qc == null || qc.getSourceDocId() == null ? null : salesMapper.selectById(qc.getSourceDocId());
|
}
|
|
// ==================== 展示辅助 ====================
|
|
private static BigDecimal available(MesWmMaterialStockDO stock) {
|
BigDecimal quantity = stock.getQuantity() == null ? BigDecimal.ZERO : stock.getQuantity();
|
BigDecimal frozen = stock.getFrozenQuantity() == null ? BigDecimal.ZERO : stock.getFrozenQuantity();
|
BigDecimal reserved = stock.getReservedQuantity() == null ? BigDecimal.ZERO : stock.getReservedQuantity();
|
return quantity.subtract(frozen).subtract(reserved);
|
}
|
|
private String itemLabel(Long itemId) {
|
MesMdItemDO item = itemId == null ? null : itemService.getItem(itemId);
|
if (item == null) {
|
return "ID=" + itemId;
|
}
|
String label = (StrUtil.blankToDefault(item.getCode(), "") + " "
|
+ StrUtil.blankToDefault(item.getName(), "")).trim();
|
return label.isEmpty() ? "ID=" + itemId : label;
|
}
|
|
private String batchLabel(Long batchId, String batchCode) {
|
if (StrUtil.isNotBlank(batchCode)) {
|
return batchCode;
|
}
|
return batchId == null ? "未填写" : "ID=" + batchId;
|
}
|
|
private String stockLabel(MesWmMaterialStockDO stock) {
|
return String.format("%s/%s/%s", warehouseName(stock.getWarehouseId()),
|
locationName(stock.getLocationId()), areaName(stock.getAreaId()));
|
}
|
|
private String warehouseName(Long id) {
|
MesWmWarehouseDO warehouse = id == null ? null : warehouseService.getWarehouse(id);
|
return warehouse != null && StrUtil.isNotBlank(warehouse.getName()) ? warehouse.getName() : "ID=" + id;
|
}
|
|
private String locationName(Long id) {
|
MesWmWarehouseLocationDO location = id == null ? null : warehouseLocationService.getWarehouseLocation(id);
|
return location != null && StrUtil.isNotBlank(location.getName()) ? location.getName() : "ID=" + id;
|
}
|
|
private String areaName(Long id) {
|
MesWmWarehouseAreaDO area = id == null ? null : warehouseAreaService.getWarehouseArea(id);
|
return area != null && StrUtil.isNotBlank(area.getName()) ? area.getName() : "ID=" + id;
|
}
|
|
private static String salesStatusName(Integer status) {
|
for (MesWmProductSalesStatusEnum value : MesWmProductSalesStatusEnum.values()) {
|
if (Objects.equals(value.getStatus(), status)) {
|
return value.getName();
|
}
|
}
|
return "ID=" + status;
|
}
|
|
private static String plain(BigDecimal value) {
|
return value == null ? "0" : value.stripTrailingZeros().toPlainString();
|
}
|
}
|