package com.jeeplus.psimanage.foodPurchase.service;

import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jeeplus.common.TokenProvider;
import com.jeeplus.flowable.feign.IFlowableApi;
import com.jeeplus.psimanage.foodPurchase.domain.PsiFoodPurchaseBasic;
import com.jeeplus.psimanage.foodPurchase.domain.PsiFoodPurchaseChange;
import com.jeeplus.psimanage.foodPurchase.domain.PsiFoodPurchaseChangeDetail;
import com.jeeplus.psimanage.foodPurchase.domain.PsiFoodPurchaseDetail;
import com.jeeplus.psimanage.foodPurchase.mapper.PsiFoodPurchaseBasicMapper;
import com.jeeplus.psimanage.foodPurchase.mapper.PsiFoodPurchaseChangeDetailMapper;
import com.jeeplus.psimanage.foodPurchase.mapper.PsiFoodPurchaseChangeMapper;
import com.jeeplus.psimanage.foodPurchase.mapper.PsiFoodPurchaseDetailMapper;
import com.jeeplus.psimanage.oss.service.dto.WorkAttachmentDto;
import com.jeeplus.psimanage.purchase.service.PsiMaterialService;
import com.jeeplus.psimanage.foodPurchase.service.dto.PsiFoodPurchaseDto;
import com.jeeplus.psimanage.foodPurchase.service.dto.PsiFoodPurchaseExportDto;
import com.jeeplus.psimanage.serialNumTpl.service.PsiSerialnumTplService;
import com.jeeplus.sys.feign.IUserApi;
import com.jeeplus.sys.feign.IWorkAttachmentApi;
import com.jeeplus.sys.service.dto.UserDTO;
import com.jeeplus.utils.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

@Service
@Transactional
public class PsiFoodPurchaseService {

    private static final String CHANGE_STATUS_DRAFT = "1";
    private static final String CHANGE_STATUS_IN_APPROVAL = "2";
    private static final String CHANGE_STATUS_REVOKED = "3";
    private static final String CHANGE_STATUS_REJECTED = "4";
    private static final String CHANGE_STATUS_APPROVED = "5";

    @Resource
    private PsiFoodPurchaseBasicMapper basicMapper;

    @Resource
    private PsiFoodPurchaseDetailMapper detailMapper;

    @Resource
    private PsiFoodPurchaseChangeMapper changeMapper;

    @Resource
    private PsiFoodPurchaseChangeDetailMapper changeDetailMapper;

    @Resource
    private PsiSerialnumTplService psiSerialnumTplService;

    @Resource
    private PsiMaterialService psiMaterialService;

    @Resource
    private IFlowableApi flowTaskService;

    @Resource
    private IWorkAttachmentApi workAttachmentApi;

    public void updateStatusById(PsiFoodPurchaseDto dto) {
        basicMapper.updateStatusById(dto.getId(), dto.getStatus());
    }

    public void updateChangeStatusById(PsiFoodPurchaseDto dto) {
        changeMapper.updateStatusById(dto.getId(), dto.getStatus());
    }

    public String removeChange(String changeId) {
        PsiFoodPurchaseChange change = changeMapper.selectById(changeId);
        if (change == null) {
            throw new RuntimeException("修改单不存在");
        }
        workAttachmentApi.deleteByAttachmentId(changeId);
        changeDetailMapper.deleteByChangeId(changeId);
        changeMapper.deleteById(changeId);
        return "操作成功";
    }

    public String remove(String id) {
        workAttachmentApi.deleteByAttachmentId(id);
        basicMapper.deleteById(id);
        detailMapper.deleteByBasicId(id);
        List<String> changeIds = changeMapper.getIdsBySourceBasicId(id);
        if (CollectionUtils.isNotEmpty(changeIds)) {
            changeIds.forEach(changeId -> {
                workAttachmentApi.deleteByAttachmentId(changeId);
                changeMapper.deleteById(changeId);
                changeDetailMapper.deleteByChangeId(changeId);
            });
        }
        return "操作成功";
    }

    public String save(PsiFoodPurchaseDto dto) throws Exception {
        UserDTO userDTO = SpringUtil.getBean(IUserApi.class).getByToken(TokenProvider.getCurrentToken());
        if (StringUtils.isNotBlank(dto.getId()) && !"false".equals(dto.getId())) {
            return update(dto, userDTO);
        }
        return add(dto, userDTO);
    }

    public String saveChange(PsiFoodPurchaseDto dto) throws Exception {
        UserDTO userDTO = SpringUtil.getBean(IUserApi.class).getByToken(TokenProvider.getCurrentToken());
        if (StringUtils.isBlank(dto.getId()) || "false".equals(dto.getId())) {
            throw new RuntimeException("修改单不存在");
        }
        return updateChange(dto, userDTO);
    }

    public String approveChange(PsiFoodPurchaseDto dto) throws Exception {
        UserDTO userDTO = SpringUtil.getBean(IUserApi.class).getByToken(TokenProvider.getCurrentToken());
        String changeId = updateChange(dto, userDTO);
        PsiFoodPurchaseChange change = changeMapper.selectById(changeId);
        if (change == null) {
            throw new RuntimeException("修改单不存在");
        }
        PsiFoodPurchaseBasic oldBasic = basicMapper.selectById(change.getSourceBasicId());
        if (oldBasic == null) {
            throw new RuntimeException("食材采购单不存在");
        }

        PsiFoodPurchaseBasic basic = new PsiFoodPurchaseBasic();
        basic.setId(oldBasic.getId());
        basic.setPurchaseNo(oldBasic.getPurchaseNo());
        basic.setHandledBy(change.getHandledBy());
        basic.setHandledByOffice(change.getHandledByOffice());
        basic.setPurchaseTime(change.getPurchaseTime());
        basic.setTotalPrice(change.getTotalPrice());
        basic.setRemarks(change.getRemarks());
        basic.setStatus(CHANGE_STATUS_APPROVED);
        basic.setProcInsId(change.getProcInsId());
        basic.setProcessDefinitionId(change.getProcessDefinitionId());
        basic.setUpdateById(userDTO.getId());
        basic.setUpdateTime(new Date());
        basicMapper.updateById(basic);

        syncFormalDetails(convertChangeDetails(changeDetailMapper.getByChangeId(changeId)), oldBasic.getId(), userDTO);
        updateAttachmentFiles(resolveAttachmentFiles(dto.getFiles(), changeId), userDTO, oldBasic.getId());
        changeMapper.updateStatusById(changeId, CHANGE_STATUS_APPROVED);
        basicMapper.updateFlowInfoById(oldBasic.getId(), CHANGE_STATUS_APPROVED, change.getProcInsId(), change.getProcessDefinitionId());
        return changeId;
    }

    public String add(PsiFoodPurchaseDto dto, UserDTO userDTO) throws Exception {
        validateDetailInfos(dto.getDetailInfos());
        String id = UUID.randomUUID().toString().replace("-", "");

        PsiFoodPurchaseBasic basic = new PsiFoodPurchaseBasic();
        BeanUtils.copyProperties(dto, basic);
        basic.setId(id);
        basic.setPurchaseNo(buildPurchaseNo(userDTO));
        basic.setHandledBy(StringUtils.isNotBlank(dto.getHandledById()) ? dto.getHandledById() : userDTO.getId());
        basic.setHandledByOffice(resolveOfficeId(dto.getHandledByOffice(), userDTO));
        basic.setPurchaseTime(Objects.nonNull(dto.getPurchaseTime()) ? dto.getPurchaseTime() : new Date());
        basic.setTotalPrice(calculateBasicTotal(dto.getDetailInfos()));
        basic.setStatus(StringUtils.isNotBlank(dto.getStatus()) ? dto.getStatus() : "0");
        basic.setCreateById(userDTO.getId());
        basic.setCreateTime(new Date());
        basic.setUpdateById(userDTO.getId());
        basic.setUpdateTime(new Date());
        basic.setDelFlag(0);
        basicMapper.insert(basic);

        saveDetails(dto.getDetailInfos(), id, userDTO);
        saveAttachmentFiles(dto.getFiles(), userDTO, id);
        return id;
    }

    public String update(PsiFoodPurchaseDto dto, UserDTO userDTO) throws Exception {
        validateDetailInfos(dto.getDetailInfos());
        PsiFoodPurchaseBasic oldInfo = basicMapper.selectById(dto.getId());
        if (oldInfo == null) {
            throw new RuntimeException("食材采购单不存在");
        }

        PsiFoodPurchaseBasic basic = new PsiFoodPurchaseBasic();
        BeanUtils.copyProperties(dto, basic);
        basic.setId(dto.getId());
        basic.setPurchaseNo(StringUtils.isNotBlank(dto.getPurchaseNo()) ? dto.getPurchaseNo() : oldInfo.getPurchaseNo());
        basic.setHandledBy(StringUtils.isNotBlank(dto.getHandledById()) ? dto.getHandledById() : oldInfo.getHandledBy());
        basic.setHandledByOffice(resolveOfficeId(dto.getHandledByOffice(), userDTO, oldInfo.getHandledByOffice()));
        basic.setPurchaseTime(Objects.nonNull(dto.getPurchaseTime()) ? dto.getPurchaseTime() : oldInfo.getPurchaseTime());
        basic.setTotalPrice(calculateBasicTotal(dto.getDetailInfos()));
        basic.setStatus(StringUtils.isNotBlank(dto.getStatus()) ? dto.getStatus() : oldInfo.getStatus());
        basic.setProcInsId(StringUtils.isNotBlank(dto.getProcInsId()) ? dto.getProcInsId() : oldInfo.getProcInsId());
        basic.setProcessDefinitionId(StringUtils.isNotBlank(dto.getProcessDefinitionId()) ? dto.getProcessDefinitionId() : oldInfo.getProcessDefinitionId());
        basic.setUpdateById(userDTO.getId());
        basic.setUpdateTime(new Date());
        basicMapper.updateById(basic);

        syncFormalDetails(dto.getDetailInfos(), dto.getId(), userDTO);
        updateAttachmentFiles(dto.getFiles(), userDTO, dto.getId());
        return dto.getId();
    }

    public String prepareChange(String sourceBasicId) throws Exception {
        UserDTO userDTO = SpringUtil.getBean(IUserApi.class).getByToken(TokenProvider.getCurrentToken());
        PsiFoodPurchaseBasic basic = basicMapper.selectById(sourceBasicId);
        if (basic == null) {
            throw new RuntimeException("食材采购单不存在");
        }
        PsiFoodPurchaseChange activeChange = changeMapper.findActiveBySourceBasicId(sourceBasicId);
        if (activeChange != null) {
            return activeChange.getId();
        }

        String changeId = UUID.randomUUID().toString().replace("-", "");
        PsiFoodPurchaseChange change = new PsiFoodPurchaseChange();
        change.setId(changeId);
        change.setSourceBasicId(sourceBasicId);
        change.setPurchaseNo(basic.getPurchaseNo());
        change.setHandledBy(basic.getHandledBy());
        change.setHandledByOffice(basic.getHandledByOffice());
        change.setPurchaseTime(basic.getPurchaseTime());
        change.setTotalPrice(basic.getTotalPrice());
        change.setRemarks(basic.getRemarks());
        change.setStatus(CHANGE_STATUS_DRAFT);
        change.setCreateById(userDTO.getId());
        change.setCreateTime(new Date());
        change.setUpdateById(userDTO.getId());
        change.setUpdateTime(new Date());
        change.setDelFlag(0);
        change.setTenantId(basic.getTenantId());
        changeMapper.insert(change);

        List<PsiFoodPurchaseDetail> details = detailMapper.getByBasicId(sourceBasicId);
        int sort = 1;
        for (PsiFoodPurchaseDetail detail : details) {
            PsiFoodPurchaseChangeDetail changeDetail = new PsiFoodPurchaseChangeDetail();
            BeanUtils.copyProperties(detail, changeDetail);
            changeDetail.setId(UUID.randomUUID().toString().replace("-", ""));
            changeDetail.setChangeId(changeId);
            changeDetail.setSort(sort++);
            changeDetail.setCreateById(userDTO.getId());
            changeDetail.setCreateTime(new Date());
            changeDetail.setUpdateById(userDTO.getId());
            changeDetail.setUpdateTime(new Date());
            changeDetail.setDelFlag(0);
            changeDetail.setTenantId(detail.getTenantId());
            changeDetailMapper.insert(changeDetail);
        }
        saveAttachmentFiles(loadAttachmentFiles(sourceBasicId), userDTO, changeId);
        return changeId;
    }

    public PsiFoodPurchaseDto findById(String id) {
        PsiFoodPurchaseDto dto = new PsiFoodPurchaseDto();
        PsiFoodPurchaseBasic basic = basicMapper.selectById(id);
        if (basic == null) {
            return dto;
        }
        BeanUtils.copyProperties(basic, dto);
        dto.setHandledById(basic.getHandledBy());
        dto.setHandledBy(basicMapper.getUserNameByUserId(basic.getHandledBy()));
        dto.setHandledByOfficeName(basicMapper.getOfficeNameByOfficeId(basic.getHandledByOffice()));
        dto.setDetailInfos(detailMapper.getByBasicId(id));
        dto.setFiles(loadAttachmentFiles(id));
        return dto;
    }

    public PsiFoodPurchaseDto findChangeById(String id) {
        PsiFoodPurchaseDto dto = new PsiFoodPurchaseDto();
        PsiFoodPurchaseChange change = changeMapper.selectById(id);
        if (change == null) {
            return dto;
        }
        BeanUtils.copyProperties(change, dto);
        dto.setHandledById(change.getHandledBy());
        dto.setHandledBy(basicMapper.getUserNameByUserId(change.getHandledBy()));
        dto.setHandledByOfficeName(basicMapper.getOfficeNameByOfficeId(change.getHandledByOffice()));
        dto.setDetailInfos(convertChangeDetails(changeDetailMapper.getByChangeId(id)));
        List<WorkAttachmentDto> files = loadAttachmentFiles(id);
        if (CollectionUtils.isEmpty(files) && StringUtils.isNotBlank(change.getSourceBasicId())) {
            files = loadAttachmentFiles(change.getSourceBasicId());
        }
        dto.setFiles(files);
        return dto;
    }

    public IPage<PsiFoodPurchaseDto> list(Page<PsiFoodPurchaseDto> page, PsiFoodPurchaseDto dto) {
        QueryWrapper<PsiFoodPurchaseDto> queryWrapper = buildListWrapper(dto);
        IPage<PsiFoodPurchaseDto> pageData = basicMapper.findList(page, queryWrapper);
        pageData.getRecords().forEach(item -> {
            if (CHANGE_STATUS_IN_APPROVAL.equals(item.getStatus()) && StringUtils.isNotBlank(item.getTaskId())) {
                item.setAuditUserIds(flowTaskService.getTaskAuditUsers(item.getTaskId()));
            }
        });
        return pageData;
    }

    public BigDecimal statistics(PsiFoodPurchaseDto dto) {
        QueryWrapper<PsiFoodPurchaseDto> queryWrapper = buildStatisticsWrapper(dto);
        BigDecimal total = basicMapper.statistics(queryWrapper);
        return total == null ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP) : total.setScale(2, RoundingMode.HALF_UP);
    }

    public List<PsiFoodPurchaseExportDto> exportList(Page<PsiFoodPurchaseDto> page, PsiFoodPurchaseDto dto) {
        IPage<PsiFoodPurchaseDto> pageData = basicMapper.findList(page, buildListWrapper(dto));
        List<PsiFoodPurchaseExportDto> result = new ArrayList<>();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        pageData.getRecords().forEach(item -> {
            List<PsiFoodPurchaseDetail> details = detailMapper.getByBasicId(item.getId());
            if (CollectionUtils.isEmpty(details)) {
                return;
            }
            details.forEach(detail -> {
                PsiFoodPurchaseExportDto exportDto = new PsiFoodPurchaseExportDto();
                exportDto.setId(detail.getId());
                exportDto.setPurchaseNo(item.getPurchaseNo());
                exportDto.setGoodsName(detail.getGoodsName());
                exportDto.setFoodType(detail.getFoodType());
                exportDto.setPurchaseNumber(formatNumber(detail.getPurchaseNumber()));
                exportDto.setUnit(detail.getUnit());
                exportDto.setUnitPrice(formatNumber(detail.getUnitPrice()));
                exportDto.setTotalPrice(formatNumber(detail.getTotalPrice()));
                exportDto.setStorageArea(detail.getStorageArea());
                exportDto.setShelfLife(formatShelfLife(detail.getShelfLife(), detail.getShelfLifeUnit()));
                exportDto.setExpiryDate(detail.getExpiryDate() == null ? "" : dateFormat.format(detail.getExpiryDate()));
                result.add(exportDto);
            });
        });
        return result;
    }

    private String updateChange(PsiFoodPurchaseDto dto, UserDTO userDTO) throws Exception {
        validateDetailInfos(dto.getDetailInfos());
        PsiFoodPurchaseChange oldInfo = changeMapper.selectById(dto.getId());
        if (oldInfo == null) {
            throw new RuntimeException("修改单不存在");
        }

        PsiFoodPurchaseChange change = new PsiFoodPurchaseChange();
        BeanUtils.copyProperties(dto, change);
        change.setId(dto.getId());
        change.setSourceBasicId(StringUtils.isNotBlank(dto.getSourceBasicId()) ? dto.getSourceBasicId() : oldInfo.getSourceBasicId());
        change.setPurchaseNo(StringUtils.isNotBlank(dto.getPurchaseNo()) ? dto.getPurchaseNo() : oldInfo.getPurchaseNo());
        change.setHandledBy(StringUtils.isNotBlank(dto.getHandledById()) ? dto.getHandledById() : oldInfo.getHandledBy());
        change.setHandledByOffice(resolveOfficeId(dto.getHandledByOffice(), userDTO, oldInfo.getHandledByOffice()));
        change.setPurchaseTime(Objects.nonNull(dto.getPurchaseTime()) ? dto.getPurchaseTime() : oldInfo.getPurchaseTime());
        change.setTotalPrice(calculateBasicTotal(dto.getDetailInfos()));
        change.setStatus(StringUtils.isNotBlank(dto.getStatus()) ? dto.getStatus() : oldInfo.getStatus());
        change.setProcInsId(StringUtils.isNotBlank(dto.getProcInsId()) ? dto.getProcInsId() : oldInfo.getProcInsId());
        change.setProcessDefinitionId(StringUtils.isNotBlank(dto.getProcessDefinitionId()) ? dto.getProcessDefinitionId() : oldInfo.getProcessDefinitionId());
        change.setUpdateById(userDTO.getId());
        change.setUpdateTime(new Date());
        changeMapper.updateById(change);

        syncChangeDetails(dto.getDetailInfos(), dto.getId(), userDTO);
        updateAttachmentFiles(dto.getFiles(), userDTO, dto.getId());
        return dto.getId();
    }

    private QueryWrapper<PsiFoodPurchaseDto> buildListWrapper(PsiFoodPurchaseDto dto) {
        QueryWrapper<PsiFoodPurchaseDto> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("a.del_flag", 0);
        if (StringUtils.isNotBlank(dto.getPurchaseNo())) {
            queryWrapper.eq("a.purchase_no", dto.getPurchaseNo());
        }
        if (StringUtils.isNotBlank(dto.getHandledBy())) {
            queryWrapper.eq("a.handled_by", dto.getHandledBy());
        }
        if (StringUtils.isNotBlank(dto.getHandledByOffice())) {
            List<String> childIds = basicMapper.findChildIds(dto.getHandledByOffice());
            if (CollectionUtils.isNotEmpty(childIds)) {
                childIds.add(dto.getHandledByOffice());
                queryWrapper.in("a.handled_by_office", childIds);
            } else {
                queryWrapper.eq("a.handled_by_office", dto.getHandledByOffice());
            }
        }
        if (StringUtils.isNotBlank(dto.getStatus())) {
            queryWrapper.apply("IFNULL(c.status, a.status) = {0}", dto.getStatus());
        }
        if (dto.getPurchaseTimes() != null && dto.getPurchaseTimes().length == 2
                && StringUtils.isNotBlank(dto.getPurchaseTimes()[0]) && StringUtils.isNotBlank(dto.getPurchaseTimes()[1])) {
            queryWrapper.between("a.purchase_time", dto.getPurchaseTimes()[0], dto.getPurchaseTimes()[1]);
        }
        return queryWrapper;
    }

    private QueryWrapper<PsiFoodPurchaseDto> buildStatisticsWrapper(PsiFoodPurchaseDto dto) {
        QueryWrapper<PsiFoodPurchaseDto> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("a.del_flag", 0);
        if (StringUtils.isNotBlank(dto.getPurchaseNo())) {
            queryWrapper.eq("a.purchase_no", dto.getPurchaseNo());
        }
        if (StringUtils.isNotBlank(dto.getHandledBy())) {
            queryWrapper.eq("a.handled_by", dto.getHandledBy());
        }
        if (StringUtils.isNotBlank(dto.getHandledByOffice())) {
            List<String> childIds = basicMapper.findChildIds(dto.getHandledByOffice());
            if (CollectionUtils.isNotEmpty(childIds)) {
                childIds.add(dto.getHandledByOffice());
                queryWrapper.in("a.handled_by_office", childIds);
            } else {
                queryWrapper.eq("a.handled_by_office", dto.getHandledByOffice());
            }
        }
        if (dto.getPurchaseTimes() != null && dto.getPurchaseTimes().length == 2
                && StringUtils.isNotBlank(dto.getPurchaseTimes()[0]) && StringUtils.isNotBlank(dto.getPurchaseTimes()[1])) {
            queryWrapper.between("a.purchase_time", dto.getPurchaseTimes()[0], dto.getPurchaseTimes()[1]);
        }
        return queryWrapper;
    }

    private void validateDetailInfos(List<PsiFoodPurchaseDetail> detailInfos) {
        if (CollectionUtils.isEmpty(detailInfos)) {
            throw new RuntimeException("采购明细不能为空");
        }
        for (PsiFoodPurchaseDetail detail : detailInfos) {
            if (StringUtils.isBlank(detail.getGoodsName())) {
                throw new RuntimeException("采购食材名称不能为空");
            }
        }
    }

    private void saveDetails(List<PsiFoodPurchaseDetail> detailInfos, String basicId, UserDTO userDTO) {
        int sort = 1;
        for (PsiFoodPurchaseDetail detail : detailInfos) {
            normalizeDetail(detail, sort++);
            detail.setId(UUID.randomUUID().toString().replace("-", ""));
            detail.setBasicId(basicId);
            detail.setCreateById(userDTO.getId());
            detail.setCreateTime(new Date());
            detail.setUpdateById(userDTO.getId());
            detail.setUpdateTime(new Date());
            detail.setDelFlag(0);
            detailMapper.insert(detail);
        }
    }

    private void syncFormalDetails(List<PsiFoodPurchaseDetail> detailInfos, String basicId, UserDTO userDTO) {
        List<String> dbIds = detailMapper.getIdByBasicId(basicId);
        List<String> liveIds = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(detailInfos)) {
            int sort = 1;
            for (PsiFoodPurchaseDetail detail : detailInfos) {
                normalizeDetail(detail, sort++);
                detail.setBasicId(basicId);
                if (StringUtils.isNotBlank(detail.getId())) {
                    liveIds.add(detail.getId());
                }
                if (StringUtils.isBlank(detail.getId()) || detailMapper.selectById(detail.getId()) == null) {
                    detail.setId(UUID.randomUUID().toString().replace("-", ""));
                    detail.setCreateById(userDTO.getId());
                    detail.setCreateTime(new Date());
                    detail.setUpdateById(userDTO.getId());
                    detail.setUpdateTime(new Date());
                    detail.setDelFlag(0);
                    detailMapper.insert(detail);
                    liveIds.add(detail.getId());
                } else {
                    detail.setUpdateById(userDTO.getId());
                    detail.setUpdateTime(new Date());
                    detailMapper.updateById(detail);
                }
            }
        }
        if (CollectionUtils.isNotEmpty(dbIds)) {
            dbIds.stream().filter(dbId -> !liveIds.contains(dbId)).forEach(detailMapper::deleteById);
        }
    }

    private void syncChangeDetails(List<PsiFoodPurchaseDetail> detailInfos, String changeId, UserDTO userDTO) {
        List<String> dbIds = changeDetailMapper.getIdByChangeId(changeId);
        List<String> liveIds = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(detailInfos)) {
            int sort = 1;
            for (PsiFoodPurchaseDetail detail : detailInfos) {
                normalizeDetail(detail, sort++);
                PsiFoodPurchaseChangeDetail changeDetail = new PsiFoodPurchaseChangeDetail();
                BeanUtils.copyProperties(detail, changeDetail);
                changeDetail.setChangeId(changeId);
                if (StringUtils.isNotBlank(changeDetail.getId())) {
                    liveIds.add(changeDetail.getId());
                }
                if (StringUtils.isBlank(changeDetail.getId()) || changeDetailMapper.selectById(changeDetail.getId()) == null) {
                    changeDetail.setId(UUID.randomUUID().toString().replace("-", ""));
                    changeDetail.setCreateById(userDTO.getId());
                    changeDetail.setCreateTime(new Date());
                    changeDetail.setUpdateById(userDTO.getId());
                    changeDetail.setUpdateTime(new Date());
                    changeDetail.setDelFlag(0);
                    changeDetailMapper.insert(changeDetail);
                    liveIds.add(changeDetail.getId());
                } else {
                    changeDetail.setUpdateById(userDTO.getId());
                    changeDetail.setUpdateTime(new Date());
                    changeDetailMapper.updateById(changeDetail);
                }
            }
        }
        if (CollectionUtils.isNotEmpty(dbIds)) {
            dbIds.stream().filter(dbId -> !liveIds.contains(dbId)).forEach(changeDetailMapper::deleteById);
        }
    }

    private List<PsiFoodPurchaseDetail> convertChangeDetails(List<PsiFoodPurchaseChangeDetail> changeDetails) {
        List<PsiFoodPurchaseDetail> details = new ArrayList<>();
        if (CollectionUtils.isEmpty(changeDetails)) {
            return details;
        }
        for (PsiFoodPurchaseChangeDetail changeDetail : changeDetails) {
            PsiFoodPurchaseDetail detail = new PsiFoodPurchaseDetail();
            BeanUtils.copyProperties(changeDetail, detail);
            details.add(detail);
        }
        return details;
    }

    private void normalizeDetail(PsiFoodPurchaseDetail detail, int sort) {
        detail.setSort(sort);
        if (detail.getTotalPrice() == null && detail.getPurchaseNumber() != null && detail.getUnitPrice() != null) {
            detail.setTotalPrice(detail.getPurchaseNumber().multiply(detail.getUnitPrice()).setScale(2, RoundingMode.HALF_UP));
        }
        if (detail.getPurchaseNumber() != null) {
            detail.setPurchaseNumber(detail.getPurchaseNumber().setScale(2, RoundingMode.HALF_UP));
        }
        if (detail.getUnitPrice() != null) {
            detail.setUnitPrice(detail.getUnitPrice().setScale(2, RoundingMode.HALF_UP));
        }
        if (detail.getTotalPrice() != null) {
            detail.setTotalPrice(detail.getTotalPrice().setScale(2, RoundingMode.HALF_UP));
        }
    }

    private BigDecimal calculateBasicTotal(List<PsiFoodPurchaseDetail> detailInfos) {
        BigDecimal total = BigDecimal.ZERO;
        for (PsiFoodPurchaseDetail detail : detailInfos) {
            if (detail.getTotalPrice() != null) {
                total = total.add(detail.getTotalPrice());
            } else if (detail.getPurchaseNumber() != null && detail.getUnitPrice() != null) {
                total = total.add(detail.getPurchaseNumber().multiply(detail.getUnitPrice()));
            }
        }
        return total.setScale(2, RoundingMode.HALF_UP);
    }

    private String buildPurchaseNo(UserDTO userDTO) {
        try {
            return psiSerialnumTplService.genSerialNum(userDTO.getCompanyDTO().getId(), PsiFoodPurchaseDto.BIZ_CODE, TokenProvider.getCurrentToken());
        } catch (Exception ex) {
            return "SCCG" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
        }
    }

    private String resolveOfficeId(String dtoOfficeId, UserDTO userDTO) {
        return resolveOfficeId(dtoOfficeId, userDTO, null);
    }

    private String resolveOfficeId(String dtoOfficeId, UserDTO userDTO, String fallback) {
        if (StringUtils.isNotBlank(dtoOfficeId)) {
            return dtoOfficeId;
        }
        if (userDTO != null && userDTO.getOfficeDTO() != null && StringUtils.isNotBlank(userDTO.getOfficeDTO().getId())) {
            return userDTO.getOfficeDTO().getId();
        }
        return fallback;
    }

    private String formatNumber(BigDecimal value) {
        if (value == null) {
            return "";
        }
        return value.stripTrailingZeros().toPlainString();
    }

    private String formatShelfLife(String shelfLife, String shelfLifeUnit) {
        if (StringUtils.isBlank(shelfLife)) {
            return "";
        }
        if (StringUtils.isBlank(shelfLifeUnit)) {
            return shelfLife;
        }
        return shelfLife + " " + shelfLifeUnit;
    }

    private List<WorkAttachmentDto> loadAttachmentFiles(String attachmentId) {
        List<WorkAttachmentDto> files = detailMapper.getByAttachmentId(attachmentId);
        if (CollectionUtils.isNotEmpty(files)) {
            files.forEach(file -> file.setCreateBy(SpringUtil.getBean(IUserApi.class).getById(file.getBy())));
        }
        return files;
    }

    private List<WorkAttachmentDto> resolveAttachmentFiles(List<WorkAttachmentDto> files, String fallbackAttachmentId) {
        if (CollectionUtils.isNotEmpty(files)) {
            return files;
        }
        return loadAttachmentFiles(fallbackAttachmentId);
    }

    private void saveAttachmentFiles(List<WorkAttachmentDto> files, UserDTO userDTO, String attachmentId) {
        if (CollectionUtils.isNotEmpty(files)) {
            psiMaterialService.saveFiles(files, userDTO, attachmentId);
        }
    }

    private void updateAttachmentFiles(List<WorkAttachmentDto> files, UserDTO userDTO, String attachmentId) {
        psiMaterialService.updateFiles(files == null ? new ArrayList<>() : files, userDTO, attachmentId);
    }
}
