package com.ruoyi.sales.service.impl;
|
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.ruoyi.common.enums.ReviewStatusEnum;
|
import com.ruoyi.common.exception.base.BaseException;
|
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.sales.dto.SalesLedgerArrivalDto;
|
import com.ruoyi.sales.mapper.SalesLedgerArrivalMapper;
|
import com.ruoyi.sales.mapper.SalesLedgerMapper;
|
import com.ruoyi.sales.mapper.StockOutRecordSalesLedgerMapper;
|
import com.ruoyi.sales.pojo.SalesLedger;
|
import com.ruoyi.sales.pojo.SalesLedgerArrival;
|
import com.ruoyi.sales.pojo.StockOutRecordSalesLedger;
|
import com.ruoyi.sales.service.ISalesLedgerArrivalService;
|
import com.ruoyi.stock.mapper.StockOutRecordMapper;
|
import com.ruoyi.stock.pojo.StockOutRecord;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.util.CollectionUtils;
|
|
import java.math.BigDecimal;
|
import java.util.List;
|
|
/**
|
* 销售台账到货记录Service业务层处理
|
* <p>
|
* 到货记录挂在「一次发货」(油品出库单)下:出库单与销售台账的对应关系由绑定表
|
* stock_out_record_sales_ledger 给出,明细 id、车辆、车牌一律以出库单为准,不信前端传入。
|
* 代储台账不产绑定行,出库单的单值列 sales_ledger_id 即为权威对应关系(见 requireBinding)。
|
*
|
* @author ruoyi
|
* @date 2026-09-21
|
*/
|
@Service
|
@RequiredArgsConstructor
|
public class SalesLedgerArrivalServiceImpl extends ServiceImpl<SalesLedgerArrivalMapper, SalesLedgerArrival>
|
implements ISalesLedgerArrivalService {
|
|
private static final String LEDGER_TYPE_DEPOSIT = "代储";
|
|
private final SalesLedgerArrivalMapper salesLedgerArrivalMapper;
|
private final StockOutRecordSalesLedgerMapper stockOutRecordSalesLedgerMapper;
|
private final StockOutRecordMapper stockOutRecordMapper;
|
private final SalesLedgerMapper salesLedgerMapper;
|
|
@Override
|
public IPage<SalesLedgerArrivalDto> listPage(Page page, SalesLedgerArrivalDto req) {
|
// 该查询用 /*data_scope*/ 跳过数据权限,而 MP 默认会把 count 语句优化成「SELECT COUNT(*) FROM 主表 WHERE ...」,
|
// 优化后标记丢失、count 反被追加权限条件,total 会比实际少。关掉 count 优化后退化成「SELECT COUNT(*) FROM (原句) TOTAL」
|
page.setOptimizeCountSql(false);
|
return salesLedgerArrivalMapper.listPage(page, req);
|
}
|
|
@Override
|
public List<SalesLedgerArrivalDto> bindableVehicles(Long salesLedgerId) {
|
if (salesLedgerId == null) {
|
throw new BaseException("请传入销售台账id");
|
}
|
return salesLedgerArrivalMapper.bindableVehicles(salesLedgerId);
|
}
|
|
@Override
|
public boolean addArrival(SalesLedgerArrivalDto req) {
|
if (req == null) {
|
throw new BaseException("到货记录不能为空");
|
}
|
StockOutRecord outRecord = requireApprovedOilOut(req.getStockOutRecordId());
|
StockOutRecordSalesLedger binding = requireBinding(outRecord, req.getSalesLedgerId());
|
applyOutRecordVehicle(req, outRecord);
|
req.setSalesLedgerProductId(binding.getSalesLedgerProductId());
|
checkArrivalQuantity(req, binding, null);
|
req.setId(null);
|
return save(req);
|
}
|
|
@Override
|
public boolean updateArrival(SalesLedgerArrivalDto req) {
|
if (req == null || req.getId() == null) {
|
throw new BaseException("请传来到货记录id");
|
}
|
SalesLedgerArrivalDto existing = salesLedgerArrivalMapper.selectArrivalById(req.getId());
|
if (existing == null) {
|
throw new BaseException("该到货记录不存在,无法修改,id:" + req.getId());
|
}
|
// 出库单/台账是记录的身份:前端不回传就沿用原值,改了则按新身份重新校验
|
Long stockOutRecordId = req.getStockOutRecordId() != null
|
? req.getStockOutRecordId() : existing.getStockOutRecordId();
|
Long salesLedgerId = req.getSalesLedgerId() != null
|
? req.getSalesLedgerId() : existing.getSalesLedgerId();
|
StockOutRecord outRecord = requireApprovedOilOut(stockOutRecordId);
|
StockOutRecordSalesLedger binding = requireBinding(outRecord, salesLedgerId);
|
req.setStockOutRecordId(stockOutRecordId);
|
req.setSalesLedgerId(salesLedgerId);
|
req.setSalesLedgerProductId(binding.getSalesLedgerProductId());
|
applyOutRecordVehicle(req, outRecord);
|
if (req.getArrivalQuantity() == null) {
|
req.setArrivalQuantity(existing.getArrivalQuantity());
|
}
|
checkArrivalQuantity(req, binding, req.getId());
|
return updateById(req);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public boolean deleteArrivals(List<Long> ids) {
|
if (CollectionUtils.isEmpty(ids)) {
|
throw new BaseException("请传入要删除的到货记录id");
|
}
|
return removeByIds(ids);
|
}
|
|
/**
|
* 出库单必须是「审批通过的油品出库」(type 为空),否则它不是发货台账的父行,挂到货记录会变成孤儿
|
*/
|
private StockOutRecord requireApprovedOilOut(Long stockOutRecordId) {
|
if (stockOutRecordId == null) {
|
throw new BaseException("请选择出库单");
|
}
|
StockOutRecord outRecord = stockOutRecordMapper.selectById(stockOutRecordId);
|
if (outRecord == null) {
|
throw new BaseException("所选出库单不存在,出库单id:" + stockOutRecordId);
|
}
|
if (!ReviewStatusEnum.APPROVED.getCode().equals(outRecord.getApprovalStatus())) {
|
throw new BaseException("出库单未审批通过,不能登记到货,出库单号:" + outRecord.getOutboundBatches());
|
}
|
if (StringUtils.isNotEmpty(outRecord.getType())) {
|
throw new BaseException("该出库单不是油品出库,不能登记到货,出库单号:" + outRecord.getOutboundBatches());
|
}
|
return outRecord;
|
}
|
|
/**
|
* 台账必须与该出库单有绑定关系 —— 绑定行同时给出明细 id 和本台账分摊到的出库数量(到货上限)。
|
* 不能看 stock_out_record.sales_ledger_id,那一列只存了首项台账。
|
* 代储例外:代储不产绑定行(绑定行同时是开票/收款候选的数据源,代储不能进),但它「一次出库只能绑一个台账」,
|
* 单值列反而权威,故按整单出库量造一行临时的绑定(不落库)用于后续校验
|
*/
|
private StockOutRecordSalesLedger requireBinding(StockOutRecord outRecord, Long salesLedgerId) {
|
if (salesLedgerId == null) {
|
throw new BaseException("请选择销售台账");
|
}
|
SalesLedger salesLedger = salesLedgerMapper.selectById(salesLedgerId);
|
if (salesLedger == null) {
|
throw new BaseException("所选销售台账不存在,销售台账id:" + salesLedgerId);
|
}
|
StockOutRecordSalesLedger binding = stockOutRecordSalesLedgerMapper.selectOne(
|
Wrappers.<StockOutRecordSalesLedger>lambdaQuery()
|
.eq(StockOutRecordSalesLedger::getStockOutRecordId, outRecord.getId())
|
.eq(StockOutRecordSalesLedger::getSalesLedgerId, salesLedgerId));
|
if (binding != null) {
|
return binding;
|
}
|
if (LEDGER_TYPE_DEPOSIT.equals(salesLedger.getLedgerType())
|
&& salesLedgerId.equals(outRecord.getSalesLedgerId())) {
|
StockOutRecordSalesLedger deposit = new StockOutRecordSalesLedger();
|
deposit.setStockOutRecordId(outRecord.getId());
|
deposit.setSalesLedgerId(salesLedgerId);
|
deposit.setQuantity(outRecord.getStockOutNum());
|
return deposit;
|
}
|
throw new BaseException("该出库单未绑定此销售台账,不能登记到货");
|
}
|
|
/**
|
* 一次出库只对应一辆车,车辆与车牌一律取出库单上登记的,前端传的只用于比对。
|
* 出库单没登记车辆时(代储/客存出库不强制选车)允许留空,不阻塞登记
|
*/
|
private void applyOutRecordVehicle(SalesLedgerArrivalDto req, StockOutRecord outRecord) {
|
if (outRecord.getVehicleId() == null) {
|
req.setVehicleId(null);
|
req.setTruckPlateNo(null);
|
return;
|
}
|
if (req.getVehicleId() != null && !req.getVehicleId().equals(outRecord.getVehicleId())) {
|
throw new BaseException("所选车辆与该出库单登记的车辆不一致,出库单号:" + outRecord.getOutboundBatches());
|
}
|
req.setVehicleId(outRecord.getVehicleId());
|
req.setTruckPlateNo(outRecord.getTruckPlateNo());
|
}
|
|
/**
|
* 到货数量上限 = 该台账在本出库单上分摊到的数量(不是台账明细的合同数量,否则挡不住「到货超过实发」)
|
*/
|
private void checkArrivalQuantity(SalesLedgerArrivalDto req, StockOutRecordSalesLedger binding, Long excludeId) {
|
BigDecimal quantity = req.getArrivalQuantity();
|
if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) {
|
throw new BaseException("到货数量必须大于0");
|
}
|
BigDecimal limit = binding.getQuantity() == null ? BigDecimal.ZERO : binding.getQuantity();
|
BigDecimal arrived = salesLedgerArrivalMapper.sumArrivedQuantity(
|
binding.getStockOutRecordId(), binding.getSalesLedgerId(), excludeId);
|
if (arrived.add(quantity).compareTo(limit) > 0) {
|
throw new BaseException("到货数量超出该台账本次出库数量,出库数量:" + limit.toPlainString()
|
+ ",已到货:" + arrived.toPlainString());
|
}
|
}
|
}
|