package com.fjhx.wms.service.stock.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fjhx.common.constant.SourceConstant;
import com.fjhx.common.utils.Assert;
import com.fjhx.item.entity.product.po.ProductInfo;
import com.fjhx.item.service.product.ProductInfoService;
import com.fjhx.wms.entity.purchase.po.PurchaseDetailPo;
import com.fjhx.wms.entity.stock.bo.InOutBo;
import com.fjhx.wms.entity.stock.dto.StockWaitDetailsDto;
import com.fjhx.wms.entity.stock.dto.StockWaitDto;
import com.fjhx.wms.entity.stock.dto.StockWaitSelectDto;
import com.fjhx.wms.entity.stock.emums.InOutType;
import com.fjhx.wms.entity.stock.emums.JournalType;
import com.fjhx.wms.entity.stock.emums.StockWaitType;
import com.fjhx.wms.entity.stock.po.*;
import com.fjhx.wms.entity.stock.vo.StockWaitDetailsVo;
import com.fjhx.wms.entity.stock.vo.StockWaitVo;
import com.fjhx.wms.mapper.stock.StockWaitMapper;
import com.fjhx.wms.service.MyPurchaseService;
import com.fjhx.wms.service.WmsService;
import com.fjhx.wms.service.arrival.ArrivalStockRecordsDetailsService;
import com.fjhx.wms.service.arrival.ArrivalStockRecordsService;
import com.fjhx.wms.service.stock.*;
import com.fjhx.wms.service.warehouse.WarehouseService;
import com.fjhx.wms.utils.CodeEnum;
import com.ruoyi.common.core.domain.BasePo;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.wrapper.IWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.Date;
import java.util.List;


/**
 * <p>
 * 待出入库 服务实现类
 * </p>
 *
 * @author
 * @since 2023-03-22
 */
@DS(SourceConstant.WMS)
@Service
public class StockWaitServiceImpl extends ServiceImpl<StockWaitMapper, StockWait> implements StockWaitService {
    @Autowired
    private ProductInfoService productInfoService;
    @Autowired
    private StockService stockService;
    @Autowired
    private StockJournalService stockJournalService;
    @Autowired
    private StockJournalDetailsService stockJournalDetailsService;
    @Autowired
    private StockWaitDetailsService stockWaitDetailsService;
    @Autowired
    private WarehouseService warehouseService;
    @Autowired
    private ArrivalStockRecordsService arrivalStockRecordsService;
    @Autowired
    private ArrivalStockRecordsDetailsService arrivalStockRecordsDetailsService;
    @Autowired
    private WmsService wmsService;
    @Autowired
    private StockFrozenService stockFrozenService;
    @Autowired
    private MyPurchaseService myPurchaseService;

    @Override
    public Page<StockWaitVo> getPage(StockWaitSelectDto dto) {
        IWrapper<StockWait> wrapper = getWrapper();
        //过滤入库/出库
        wrapper.eq(StockWait::getType, dto.getType());
        wrapper.eq("sw", StockWait::getBusinessType, dto.getBusinessType());
        wrapper.eq(StockWait::getBusinessCode, dto.getKeyword());
        wrapper.orderByDesc("sw", StockWait::getId);
        Page<StockWaitVo> page = this.baseMapper.getPage1(dto.getPage(), wrapper);

        List<StockWaitVo> records = page.getRecords();
        // 赋值产品属性
        productInfoService.attributeAssign(records, StockWaitVo::getProductId, (item, productInfo) -> {
            item.setProductCode(productInfo.getCode());
            item.setProductUnit(productInfo.getUnit());
            item.setProductType(productInfo.getType());
            item.setProductName(productInfo.getName());
            item.setProductSpec(productInfo.getSpec());
        });

        return page;
    }

    @Override
    public StockWaitVo detail(Long id) {
        StockWait stockWait = this.getById(id);
        StockWaitVo result = BeanUtil.toBean(stockWait, StockWaitVo.class);

        StockWaitDetails stockWaitDetails = stockWaitDetailsService.getOne(q -> q.eq(StockWaitDetails::getStockWaitId, id));
        ProductInfo byId = productInfoService.getById(stockWaitDetails.getProductId());
        result.setProductName(byId.getName());
        result.setQuantity(stockWaitDetails.getQuantity());
        result.setStockWaitDetailsId(stockWaitDetails.getId());

        //获取明细列表
        List<StockWaitDetails> stockWaitDetailsList = stockWaitDetailsService.list(q -> q.eq(StockWaitDetails::getStockWaitId, id));
        List<StockWaitDetailsVo> stockWaitDetailsVos = BeanUtil.copyToList(stockWaitDetailsList, StockWaitDetailsVo.class);
        //赋值产品信息
        productInfoService.attributeAssign(stockWaitDetailsVos, StockWaitDetailsVo::getProductId, (item, productInfo) -> {
            item.setProductName(productInfo.getName());
        });
        result.setStockWaitDetailsList(stockWaitDetailsVos);

        return result;
    }


    @DSTransactional
    @Override
    public synchronized void add(StockWaitDetailsDto dto) {
        StockWaitDetails stockWaitDetails = stockWaitDetailsService.getById(dto.getId());
        Assert.notEmpty(stockWaitDetails, "查询不到待出库明细信息");
        StockWait stockWait = this.getById(stockWaitDetails.getStockWaitId());
        Assert.notEmpty(stockWait, "查询不到待出库信息");

        //获取需要的相关参数
        Long warehouseId = dto.getWarehouseId();
        //根据待出入库类型获取流水类型
        JournalType stockJournalType = getStockJournalType(stockWait.getBusinessType());
        Long productId = stockWaitDetails.getProductId();
        BigDecimal inOutQuantity = dto.getQuantity();

        //更新待出入库明细，出入库数量，状态
        BigDecimal receiptQuantity = stockWaitDetails.getReceiptQuantity().add(inOutQuantity);
        Integer stockWaitDetailsStatus = receiptQuantity.compareTo(stockWaitDetails.getQuantity()) > 0 ? 2 : 1;
        stockWaitDetailsService.update(q -> q
                .eq(StockWaitDetails::getId, stockWaitDetails.getId())
                .set(StockWaitDetails::getReceiptQuantity, receiptQuantity)
                .set(StockWaitDetails::getStatus, stockWaitDetailsStatus)
        );

        //判断业务类型是否是采购到货
        int purchaseArrival = StockWaitType.PURCHASE_ARRIVAL_IN.getDetailType().equals(stockWait.getBusinessType()) ? 1 : 0;
        //获取采购单价（只有采购到货才有）
        PurchaseDetailPo purchaseDetail = baseMapper.getPurchaseDetail(IWrapper.getWrapper().eq("swd.id", stockWaitDetails.getId()));
        BigDecimal purchasePrice = null;
        if(ObjectUtil.isNotEmpty(purchaseDetail)){
            purchasePrice = purchaseDetail.getPrice();
        }
        //计算结存单价
        StockJournalDetails stockJournalDetails = calculateUnitPrice(stockJournalType.getType(), productId, warehouseId, inOutQuantity, purchaseArrival, purchasePrice);

        //操作库存
        InOutBo inOutBo = new InOutBo(productId, inOutQuantity);
        stockService.changeStock(Collections.singletonList(inOutBo), warehouseId, stockJournalType);

        //创建流水
        StockJournal stockJournal = new StockJournal();
        stockJournal.setOpType(stockWait.getType());//1入库2出库
        stockJournal.setType(stockJournalType.getDetailType());
        stockJournal.setCode(stockWait.getType() == 1 ? CodeEnum.SIN_CODE.getCode() : CodeEnum.SOUT_CODE.getCode());
        stockJournal.setWarehouseId(warehouseId);
        stockJournal.setBusinessId(stockWait.getId());
        //入库人员信息
        stockJournal.setExWarehousePerson(dto.getExWarehousePerson());
        stockJournal.setReceivingPerson(dto.getReceivingPerson());
        stockJournalService.save(stockJournal);
        //创建流水明细
        stockJournalDetails.setStockJournalId(stockJournal.getId());
        stockJournalDetails.setProductId(productId);
        stockJournalDetails.setQuantity(inOutQuantity);
        stockJournalDetails.setBusinessDetailsId(stockWaitDetails.getId());
        stockJournalDetailsService.save(stockJournalDetails);

        //更新待入库状态（等待明细操作完执行）
        long count = stockWaitDetailsService.count(q -> q
                .eq(StockWaitDetails::getStockWaitId, stockWait.getId())
                .ne(StockWaitDetails::getStatus, 2)
        );
        Integer StockWaitStatus = count > 0 ? 1 : 2;
        this.update(q -> q
                .eq(StockWait::getId, stockWait.getId())
                .set(StockWait::getStatus, StockWaitStatus)
                .set(BasePo::getUpdateTime, new Date())
                .set(BasePo::getUpdateUser, SecurityUtils.getUserId())
        );

        //生产任务待出库（物料出库解冻冻结库存）
        if (StockWaitType.PRODUCTION_TASK_OUT.getDetailType().equals(stockWait.getType())) {
            //减少冻结库存
            StockFrozen stockFrozen = stockFrozenService.getOne(q -> q.eq(StockFrozen::getProductId, productId));
            stockFrozenService.update(q -> q
                    .eq(StockFrozen::getId, stockFrozen.getId())
                    .setSql("frozen_quantity - " + inOutQuantity)
                    .set(BasePo::getUpdateTime, new Date())
                    .set(BasePo::getUpdateUser, SecurityUtils.getUserId())
            );
        }
    }

    /**
     * 计算结存单价(在操作库存之前计算)
     */
    @Override
    public synchronized StockJournalDetails calculateUnitPrice(InOutType inOutType, Long productId, Long warehouseId, BigDecimal inOutQuantity, Integer purchaseArrival, BigDecimal purchasePrice) {
        StockJournalDetails stockJournalDetails = new StockJournalDetails();
        stockJournalDetails.setProductId(productId);
        stockJournalDetails.setQuantity(inOutQuantity);

        //计算结存单价(在操作库存之前计算)
        ProductInfo productInfo = productInfoService.getById(productId);
        Assert.notEmpty(productInfo, "查询不到产品信息");
        //在库结存单价(实时在库单价)
        BigDecimal unitPrice = productInfo.getUnitPrice();
        unitPrice = ObjectUtil.isEmpty(unitPrice) ? BigDecimal.ZERO : unitPrice;
        //不在库结存单价(实时不在库单价)
        BigDecimal outUnitPrice = productInfo.getOutUnitPrice();
        outUnitPrice = ObjectUtil.isEmpty(outUnitPrice) ? BigDecimal.ZERO : outUnitPrice;
        List<Stock> stockList = stockService.list(q -> q.eq(Stock::getProductId, productId));
        //在库总库存（所有仓库）
        BigDecimal stockQuantity = stockList.stream().map(Stock::getQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
        //不在库总库存（所有仓库）
        BigDecimal stockOutQuantity = stockList.stream().map(Stock::getOutQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
        //获取产品当前仓库信息
        Stock stock = stockService.getOne(q -> q.eq(Stock::getWarehouseId, warehouseId).eq(Stock::getProductId, productId));
        //库存不存在创建0库存记录
        if(ObjectUtil.isEmpty(stock)){
            stock = new Stock();
            stock.setWarehouseId(warehouseId);
            stock.setProductId(productId);
            stock.setQuantity(BigDecimal.ZERO);
            stockService.save(stock);
        }
        Stock finalStock = stock;
        if (inOutType.equals(InOutType.IN)) {
            //入库
            //【其他入库】当前价值=实时不在库单价
            BigDecimal price = outUnitPrice;

            //【采购入库】当前价值=采购单价
            if (purchaseArrival == 1) {
                price = purchasePrice;
            }
            //【入库】实时在库单价=（库存*实时在库单价 + 当前入库数量*当前价值） / （库存+当前入库数量）
            BigDecimal multiply0 = stockQuantity.multiply(unitPrice);//库存*实时在库单价
            BigDecimal multiply1 = inOutQuantity.multiply(price);//当前入库数量*当前价值
            BigDecimal add0 = multiply0.add(multiply1);//（库存*实时在库单价 + 当前入库数量*当前价值）
            BigDecimal add1 = stockQuantity.add(inOutQuantity);//（库存+当前入库数量）
            BigDecimal newUnitPrice = add0.divide(add1, 4, RoundingMode.HALF_UP);

            //【其他入库】已出库（为负数时取0） = 已出库 - 当前入库数量
            if (purchaseArrival != 1) {
                String format = String.format("out_quantity = IF((out_quantity - %s)<0,0,(out_quantity - %s))", inOutQuantity, inOutQuantity);
                stockService.update(q -> q
                        .eq(Stock::getId, finalStock.getId())
                        .setSql(format)
                        .set(BasePo::getUpdateTime, new Date())
                        .set(BasePo::getUpdateUser, SecurityUtils.getUserId())
                );
            }
            //修改产品中的库存单价
            productInfoService.update(q -> q
                    .eq(ProductInfo::getId, productId)
                    .set(ProductInfo::getUnitPrice, newUnitPrice)
                    .set(BasePo::getUpdateTime, new Date())
                    .set(BasePo::getUpdateUser, SecurityUtils.getUserId())
            );

            //赋值流水信息
            stockJournalDetails.setPrice(price);
            stockJournalDetails.setStockPrice(newUnitPrice);
            //【入库】实时不在库单价=A物料最新流水的实时不在库单价（不用动直接获取产品的不在库单价）
            stockJournalDetails.setOutStockPrice(outUnitPrice);
        } else {
            //出库
            //【出库】当前价值=实时在库单价
            BigDecimal price = unitPrice;

            //【出库】实时不在库单价=（不在库数量*实时不在库单价 + 当前出库数量*当前价值） / （不在库数量+当前出库数量）
            BigDecimal multiply0 = stockOutQuantity.multiply(outUnitPrice);//不在库数量*实时不在库单价
            BigDecimal multiply1 = inOutQuantity.multiply(price);//当前出库数量*当前价值
            BigDecimal add0 = multiply0.add(multiply1);//（不在库数量*实时不在库单价 + 当前出库数量*当前价值）
            BigDecimal add1 = stockOutQuantity.add(inOutQuantity);//（不在库数量+当前出库数量）
            BigDecimal newOutUnitPrice = add0.divide(add1, 4, RoundingMode.HALF_UP);

            //计算已出库库存-【出库】已出库 = 已出库+当前出库数量
            stockService.update(q -> q
                    .eq(Stock::getId, finalStock.getId())
                    .setSql("out_quantity = out_quantity + " + inOutQuantity)
                    .set(BasePo::getUpdateTime, new Date())
                    .set(BasePo::getUpdateUser, SecurityUtils.getUserId())
            );
            //修改产品中的已出库单价
            productInfoService.update(q -> q
                    .eq(ProductInfo::getId, productId)
                    .set(ProductInfo::getOutUnitPrice, newOutUnitPrice)
                    .set(BasePo::getUpdateTime, new Date())
                    .set(BasePo::getUpdateUser, SecurityUtils.getUserId())
            );

            //赋值流水信息
            stockJournalDetails.setPrice(price);
            //【出库】实时在库单价=A物料最新流水的实时在库单价（不用动直接获取产品的在库单价）
            stockJournalDetails.setStockPrice(unitPrice);
            stockJournalDetails.setOutStockPrice(newOutUnitPrice);
        }

        return stockJournalDetails;
    }

    @Override
    public void edit(StockWaitDto stockWaitDto) {
        this.updateById(stockWaitDto);
    }

    @Override
    public void delete(Long id) {
        this.removeById(id);
    }

    /**
     * 根据待出入库业务类型获取对应的出入库记录类型
     */
    @Override
    public JournalType getStockJournalType(int businessType) {
        if (StockWaitType.BORDER_OF_LINE_IN.getDetailType().equals(businessType)) {
            //线边回仓库
            return JournalType.BORDER_OF_LINE_IN;
        } else if (StockWaitType.COMPLETION_IN.getDetailType().equals(businessType)) {
            //完工入库
            return JournalType.COMPLETION_IN;
        } else if (StockWaitType.PURCHASE_ARRIVAL_IN.getDetailType().equals(businessType)) {
            //采购到货
            return JournalType.PURCHASE_ARRIVAL_IN;
        } else if (StockWaitType.BACK_OUT.getDetailType().equals(businessType)) {
            //退货出货
            return JournalType.BACK_OUT;
        } else if (StockWaitType.SALE_ORDER_OUT.getDetailType().equals(businessType)) {
            //销售订单出库
            return JournalType.SALES_OUT;
        } else if (StockWaitType.PRODUCTION_TASK_OUT.getDetailType().equals(businessType)) {
            //生产任务待出库
            return JournalType.PRODUCTION_TASK_OUT;
        } else if (StockWaitType.WORD_ORDER_OUT.getDetailType().equals(businessType)) {
            //工单待出库出库
            return JournalType.WORD_ORDER_OUT;
        } else {
            throw new ServiceException("未知待出入库业务类型");
        }
    }

}
