мой код на примере сервисного класса

← разместить еще код

мой код на примере сервисного класса

Расшаренный код:

package ru.bakapp.service.store.impl;

import com.google.common.collect.Lists;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions;
import org.apache.poi.ss.formula.functions.T;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.bakapp.base.PartyStatusType;
import ru.bakapp.dto.mapper.EntityDtoMapper;
import ru.bakapp.dto.store.MovePartiesDto;
import ru.bakapp.dto.store.StoreProductPartyDto;
import ru.bakapp.entity.backoffice.BakAbility;
import ru.bakapp.entity.backoffice.BakUser;
import ru.bakapp.entity.store.*;
import ru.bakapp.repository.BakRepositoryCustom;
import ru.bakapp.repository.merchant.MerchantRepository;
import ru.bakapp.repository.store.StoreProductGroupRepository;
import ru.bakapp.repository.store.StoreProductPartyRepository;
import ru.bakapp.repository.store.StoreProductRepository;
import ru.bakapp.repository.store.StoreRepository;
import ru.bakapp.repository.supplier.SupplierInvoiceRepository;
import ru.bakapp.service.helper.ExpirationServiceHelper;
import ru.bakapp.service.store.StoreProductPartyService;
import ru.bakapp.util.CollectionUtils;
import ru.bakapp.util.EntityUtils;
import ru.bakapp.util.SecurityUtils;

import javax.annotation.PostConstruct;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import static ru.bakapp.base.AbilityType.*;
import static ru.bakapp.util.CollectionUtils.asStreamSafe;
import static ru.bakapp.util.DateUtils.getEarliestDate;

/**
 * @author grushetskiy@bakapp.ru
 */
@Service
@Transactional
public class StoreProductPartyServiceImpl extends StoreProductPartyService {

    private final Logger LOGGER = LoggerFactory.getLogger(StoreProductPartyServiceImpl.class);

    @Autowired
    private StoreProductPartyRepository repository;
    @Autowired
    private EntityDtoMapper<BakStoreProductParty, StoreProductPartyDto> mapper;

    @Autowired
    private StoreProductRepository productRepository;
    @Autowired
    private StoreRepository storeRepository;
    @Autowired
    private SupplierInvoiceRepository invoiceRepository;
    @Autowired
    private StoreProductGroupRepository productGroupsRepository;
    @Autowired
    private MerchantRepository merchantRepository;
    @Autowired
    ExpirationServiceHelper expirationServiceHelper;
    @Autowired
    StoreProductPartyRepository storeProductPartyRepository;


    @PostConstruct
    public void init() {
/*
        savers = Arrays.asList(r ->
                new RelationSaveContext<>(r, BakStoreProductParty::getProduct, BakStoreProduct::getPartyList, BakStoreProduct::setPartyList, productRepository::save)
        );
        updaters = Arrays.asList(r ->
                new RelationUpdateContext<>(r, BakStoreProductParty::getProduct, BakStoreProduct::getPartyList, BakStoreProduct::setPartyList, productRepository::save)
        );
*/
    }

    @Override
    protected BooleanExpression getPartyStatusAndQuantityPredicate() {
        return QBakStoreProductParty.bakStoreProductParty.status.eq(PartyStatusType.IN)
                .and(QBakStoreProductParty.bakStoreProductParty.quantity.gt(0.0d))
                ;
    }

    @Override
    public BakStoreProductParty diminishAndCreateOutAndMovingParty(BakStoreProductParty parentParty, Double quantity, BakStoreProduct newProduct) {
        parentParty.setQuantity(parentParty.getQuantity() - quantity);
        return BakStoreProductParty.createAsMoving(createPartyWithStatusOut(parentParty, quantity), newProduct);
    }

    @Override
    public BakStoreProductParty diminishAndCreateOutAndInParty(BakStoreProductParty parentParty, Double quantity, BakStoreProduct newProduct) {
        parentParty.setQuantity(parentParty.getQuantity() - quantity);
        return BakStoreProductParty.createAsIn(createPartyWithStatusOut(parentParty, quantity), newProduct);
    }

    @Override
    protected BakStoreProductParty createPartyWithStatusOut(BakStoreProductParty parentParty) {
        return saveDirect(BakStoreProductParty.createChildParty(parentParty, parentParty.getQuantity(), PartyStatusType.OUT));
    }

    @Override
    protected BakStoreProductParty createPartyWithStatusOut(BakStoreProductParty parentParty, Double quantity) {
        return saveDirect(BakStoreProductParty.createChildParty(parentParty, quantity, PartyStatusType.OUT));
    }

    @Override
    public List<BakStoreProductParty> markAsExpiredEntities() {
        List<BakStoreProductParty> parties = expirationServiceHelper.decommissionExpiredEntities(() -> repository);
        LOGGER.debug("Decommission following storeProductParty {}", parties);
        return parties;
    }

    @Override
    public List<BakStoreProductParty> getExpiredEntities() {
        QBakStoreProductParty qProductParty = QBakStoreProductParty.bakStoreProductParty;
        List<BakStoreProductParty> parties =
                asStream(repository.findAll(qProductParty.status.eq(PartyStatusType.EXPIRED)
                        .and(getAccessFilterPredicate())))
                        .collect(Collectors.toList());
        LOGGER.debug("Get all EXPIRED storeProductParties - {}", parties);
        return parties;
    }

    public List<StoreProductPartyDto> getPartiesByInvoiceId(Long invoiceId) {
        QBakStoreProductParty qProductParty = QBakStoreProductParty.bakStoreProductParty;
        return mapper.convertToDto(StreamSupport.stream(repository.findAll(QBakStoreProductParty.bakStoreProductParty.invoiceGood.invoice.id.eq(invoiceId)).spliterator(), false)
                .collect(Collectors.toList()));
    }

    @Override
    protected boolean checkAllowedToActOnBySelf(BakStoreProductParty e, OperationContext ctx) {
        if (!EntityUtils.isTheSame(e.getProduct().getProductGroup().getStore().getMerchant(), getCurrentMerchant(ctx))) {
            return false;
        }
        //TODO: вообще-то редко какая абилити может удалять Партии. (к другим энтити, наверное, это тоже относится)
        //      STORE_ADD_NEW_PARTY_ABILITY - создаёт (родительскую) партию,
        //      STORE_DECOMMISSION_PARTY_ABILITY -  - создаёт партию со статусом DECOMMISSIONED и изменяет родительскую партию...
        // как это оформить?
        return ctx.hasAnyOf(MERCHANT, STORE_ADD_NEW_PARTY_ABILITY, STORE_DECOMMISSION_PARTY_ABILITY)
                || (ctx.isRead() && ctx.hasAnyOf(STORE_PRODUCT_ABILITY));
    }

    @Override
    protected Predicate getAccessFilterBySelf(OperationContext ctx) {
        if (!ctx.hasAnyOf(MERCHANT, STORE_PRODUCT_ABILITY, STORE_ADD_NEW_PARTY_ABILITY, STORE_DECOMMISSION_PARTY_ABILITY)) {
            return Expressions.ONE.eq(Expressions.TWO);
        }
        QBakStoreProductParty qParty = QBakStoreProductParty.bakStoreProductParty;

        //Для Мерчанта нам достаточно этого условия
        //TODO: redo!!
        List<BakStoreProductGroup> groups = (List<BakStoreProductGroup>) productGroupsRepository.findAll(QBakStoreProductGroup.bakStoreProductGroup.store.merchant.eq(getCurrentMerchant(ctx)));
        Predicate accessPredicate = qParty.product.productGroup.in(groups);

        if (!ctx.hasAnyOf(MERCHANT)) {
            //для остальных ролей добавляем проверку прав на соотв. группу продуктов
            accessPredicate = ExpressionUtils.and(accessPredicate,
                                                  qParty.product.productGroup.profiles.contains(getCurrentProfile(ctx)));
        }
        return accessPredicate;
    }

    @Override
    protected EntityDtoMapper<BakStoreProductParty, StoreProductPartyDto> getMapper() {
        return mapper;
    }

    @Override
    protected BakRepositoryCustom<BakStoreProductParty, QBakStoreProductParty, Long> getRepository() {
        return repository;
    }

    @Override
    public Map<BakStoreProductGroup, List<BakStoreProductParty>> filterNoticed(Date date) {
        List<BakStoreProductParty> parties = repository.getPartiesWithCertainExpirationDate(date);
        return parties.stream().filter(BakStoreProductParty.class::isInstance)
                .map(party -> party.getProduct().getProductGroup()).distinct()
                .collect(Collectors.toMap(group -> group,
                        group -> parties.stream().filter(party -> party.getProduct().getProductGroup().equals(group))
                                .collect(Collectors.toList())));
    }

    @Override
    public List<StoreProductPartyDto> getProductPartiesByUser() {
        BakUser user = userRepository.findOne(SecurityUtils.getCurrentUserId());
        return isUserHaveRights(user)
                ? mapper.convertToDto(findAffectedProductParties(findAllPartiesMap(user), new Date()))
                : new ArrayList<>();
    }

    private boolean isSuitable(Date date, BakStoreProductParty party) {
        Date expirationDay = new Date(party.getExpirationDate().getTime()
                + ChronoUnit.MILLIS.between(LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).toLocalTime(),
                LocalTime.MAX));
        return party.getExpirationDate().getTime() - party.getProduct().getNotifyPeriod() <= date.getTime()
                && expirationDay.getTime() > date.getTime();
    }

    private Map<BakStoreProduct, List<BakStoreProductParty>> findAllPartiesMap(BakUser user) {
        return user.getProfile().getGroups().stream()
                .filter(g -> g instanceof BakStoreProductGroup)
                .flatMap(g -> ((BakStoreProductGroup) g).getProducts().stream())
                .collect(Collectors.toMap(product -> product, this::getPartyListByProduct));
    }

    @Override
    public List<BakStoreProductParty> getPartyListByProduct(BakStoreProduct product) {
        return Lists.newArrayList(repository.findAll(QBakStoreProductParty.bakStoreProductParty.product.eq(product)));
    }

    private List<BakStoreProductParty> findAffectedProductParties(Map<BakStoreProduct, List<BakStoreProductParty>> allPartiesMap, Date date) {
        return allPartiesMap.values().stream().flatMap(Collection::stream)
                .filter(p -> isSuitable(date, p)).collect(Collectors.toList());
    }

    private boolean isUserHaveRights(BakUser user) {
        return user.getProfile().getAbilities()
                .stream()
                .map(BakAbility::getName)
                .anyMatch(a -> getDependant(getByName(a)).contains(STORE_ADMIN));
    }

    @Override
    public List<BakStoreProductParty> movePartiesImmediately(List<BakStoreProductParty> parties, BakStore newStore) {
        //at first we should check all parties
        //    belong to the same Store
        //    has IN-status
        //    has quantity not 0 (0 it's ok but... how to process)
        //    has

        //check rights here

        //just change store!!
        List<BakStoreProductParty> newParties = parties
                                                    .stream()
                                                    //wrong!! TODO!!
                                                    .map(p -> {
                                                        BakStoreProductParty newParty = BakStoreProductParty.create(p, newStore);
                                                        createPartyWithStatusOut(p);
                                                        return newParty;
                                                    })
                                                    .collect(Collectors.toList());
        return repository.save(newParties);
    }

    @Override
    public List<BakStoreProductParty> putPartiesOnTheMove(List<BakStoreProductParty> parties, BakStore newStore) {
        //at first we should check all parties belong to the same Store

        //check rights here

        //just change store!!
        List<BakStoreProductParty> newParties = parties
                                                    .stream()
                                                    .map(p -> {
                                                        BakStoreProductParty newParty = BakStoreProductParty.create(p, newStore, PartyStatusType.MOVING);
                                                        createPartyWithStatusOut(p);
                                                        return newParty;
                                                    })
                                                    .collect(Collectors.toList());
        return repository.save(newParties);
    }

    @Override
    public List<BakStoreProductParty> acceptPartiesFromTheMove(List<BakStoreProductParty> parties, BakStore newStore) {
        //at first we should check all parties
        //    belong to the specified Store
        //    has MOVING-status

        //check rights here

        //just change store!!
        List<BakStoreProductParty> newParties = parties
                                                    .stream()
                                                    //wrong!! TODO!!
                                                    .peek(p -> p.setStatus(PartyStatusType.IN))
                                                    .collect(Collectors.toList());
        return repository.save(newParties);
    }

    @Override
    public List<BakStoreProductParty> movePartiesImmediately(List<BakStoreProductParty> parties, BakStoreProduct newProduct) {
        List<BakStoreProductParty> newParties = parties
                .stream()
                //wrong!! TODO!!
                .map(p -> {
                    BakStoreProductParty newParty = BakStoreProductParty.create(p, newProduct);
                    createPartyWithStatusOut(p);
                    p.setQuantity(0.);
                    return newParty;
                })
                .collect(Collectors.toList());
        return repository.save(newParties);
    }

    @Override
    public List<BakStoreProductParty> putPartiesOnTheMove(List<BakStoreProductParty> parties, BakStoreProduct newProduct) {
        List<BakStoreProductParty> newParties = parties
                .stream()
                .map(p -> {
                    BakStoreProductParty newParty = BakStoreProductParty.create(p, newProduct, PartyStatusType.MOVING);
                    createPartyWithStatusOut(p);
                    p.setQuantity(0.);
                    repository.save(p);
                    return newParty;
                })
                .collect(Collectors.toList());
        return repository.save(newParties);
    }

    @Override
    public List<BakStoreProductParty> acceptPartiesFromTheMove(List<BakStoreProductParty> parties, BakStoreProduct newProduct) {
        List<BakStoreProductParty> newParties = parties
                .stream()
                //wrong!! TODO!!
                .peek(p -> p.setStatus(PartyStatusType.IN))
                .collect(Collectors.toList());
        return repository.save(newParties);
    }

    @Override
    public List<StoreProductPartyDto> movePartiesImmediately(MovePartiesDto moveDto) {
        BakStoreProduct newProduct = productRepository.findOne(moveDto.getProductId());
        List<BakStoreProductParty> dbParties = repository.findAll(moveDto.getPartyIds());
        List<BakStoreProductParty> newParties = dbParties
                .stream()
                .map(p -> diminishAndCreateOutAndInParty(p, findQtByPartyId(dbParties, moveDto, p.getId()), newProduct))
                .collect(Collectors.toList());
        return mapper.convertToDto(repository.save(newParties));
    }

    @Override
    public List<StoreProductPartyDto> putPartiesOnTheMove(MovePartiesDto moveDto) {
        BakStoreProduct newProduct = productRepository.findOne(moveDto.getProductId());
        List<BakStoreProductParty> dbParties = repository.findAll(moveDto.getPartyIds());
        List<BakStoreProductParty> newParties = dbParties
                .stream()
                .map(p -> diminishAndCreateOutAndMovingParty(p, findQtByPartyId(dbParties, moveDto, p.getId()), newProduct))
                .collect(Collectors.toList());
        return mapper.convertToDto(repository.save(newParties));
    }

    @Override
    public List<StoreProductPartyDto> acceptPartiesFromTheMove(MovePartiesDto moveDto) {
        //установим продукт при акцепте.
        //IMPORTANT: по идее, продукт устанавливается на моменте создания партий со статусом MOVING (в том числе потому, что это неNULLовое поле)
        //           но на всякий случай... мало ли как изменится ТЗ.
        BakStoreProduct newProduct = productRepository.findOne(moveDto.getProductId());

        List<BakStoreProductParty> newParties = repository.findAll(moveDto.getMoveParties().stream().map(mp -> mp.getPartyId()).collect(Collectors.toList()))
                .stream()
                //wrong!! TODO!!
                .peek(p -> {
                    p.setStatus(PartyStatusType.IN);
                    p.setProduct(newProduct);
                })
                .collect(Collectors.toList());
        return mapper.convertToDto(repository.save(newParties));
    }

    @Override
    public List<StoreProductPartyDto> acceptAllPartiesFromTheMove(Long productId) {
        QBakStoreProductParty qParty = QBakStoreProductParty.bakStoreProductParty;
        List<BakStoreProductParty> newParties =
                asStreamSafe(repository.findAll(qParty.product.id.eq(productId)
                                    .and(qParty.status.eq(PartyStatusType.MOVING))))
                .peek(p -> p.setStatus(PartyStatusType.IN))
                .collect(Collectors.toList());
        return mapper.convertToDto(repository.save(newParties));
    }

    @Override
    protected BakStoreProductParty decommissionParty(BakStoreProductParty parentParty, String decommissionReason, Double quantity) {
        if (quantity > parentParty.getQuantity()) {
            //TODO: нужно ли здесь бросить эксэпшн?
            return null;
        }
        BakStoreProductParty childParty = BakStoreProductParty.createChildParty(parentParty, quantity, PartyStatusType.DECOMMISSIONED);
        childParty.setDecommissionReason(decommissionReason);

        parentParty.setQuantity(parentParty.getQuantity() - quantity);
        saveDirect(childParty);
        saveDirect(parentParty);

        return childParty;
    }

    @Override
    public PartyStatusType getPartyStatus(@NotNull Long partyId) {
        BakStoreProductParty party = repository.findOne(partyId);
        //TODO: rights?
        if (party.getQuantity() > 0) {
            return PartyStatusType.IN;
        }
        QBakStoreProductParty qParty = QBakStoreProductParty.bakStoreProductParty;
        return StreamSupport.stream(storeProductPartyRepository.findAll(qParty.parentParty.eq(party)).spliterator(), false)
                .sorted((BakStoreProductParty p1, BakStoreProductParty p2) ->
                            getEarliestDate(p2.getCreated()).compareTo(getEarliestDate(p1.getCreated())))
                .findFirst()
                .map(BakStoreProductParty::getStatus)
                .orElse(null);
    }

    @Override
    public Page<StoreProductPartyDto> findNonZeroInParties(Predicate predicate, Pageable pageable) {
        QBakStoreProductParty qParty = QBakStoreProductParty.bakStoreProductParty;
        Predicate extra = ExpressionUtils.and(predicate,
                                              qParty.status.eq(PartyStatusType.IN)
                                                .and(qParty.quantity.gt(0))
                                             );
        return doWithMapper(m -> m.convertToDto(findAllImpl(extra, pageable)));
    }

    private Double findQtByPartyId(List<BakStoreProductParty> dbParties, MovePartiesDto moveDto, Long partyId) {
        Double qt = moveDto.getMoveParties()
                        .stream()
                        .filter(dto -> dto.getPartyId().equals(partyId))
                        .findFirst().get().getQuantity();
        return Objects.nonNull(qt) ? qt :
                    dbParties
                        .stream()
                        .filter(dto -> dto.getId().equals(partyId))
                        .findFirst().get().getQuantity();
    }
}