Stock/Prioritizer/PrioritizeHelper.php
public function getUnitCandidates(StockAssignmentInterface $assignment, $quantity)
    {
        $subject = $assignment->getStockUnit()->getSubject();

        // Find the subject's ready stock units
        if (empty($units = $this->unitResolver->findReady($subject))) {
            return [];
        };

        $sale = $assignment->getSaleItem()->getSale();

        $candidates = [];

        foreach ($units as $unit) {
            $this->unitResolver->getStockUnitCache()->add($unit);

            $candidate = UnitCandidate::build($unit, $sale);

            // Skip if no reservable and no releasable quantity
            if ((0 >= $candidate->reservable) && (0 >= $candidate->releasable)) {
                continue;
            }

            $add = false;
            $diff = 0;

            if (0 < $candidate->reservable) {
                // Unit has enough reservable quantity
                if (empty($candidates) && 0 >= $diff = $quantity - $candidate->reservable) {
                    return [$candidate];
                }

                $add = true;
            }

            if (null !== $candidate->getCombination(max($diff, $candidate->releasable))) {
                $add = true;
            }

            if ($add) {
                $candidates[] = $candidate;
            }
        }

        if (empty($candidates)) {
            return [];
        }

        // Sort candidates
        usort($candidates, function (UnitCandidate $a, UnitCandidate $b) use ($quantity) {
            // TODO sort by (reservable + releasable) DESC

            // TODO Simplify. Prefer:
            // - 'shippable' enough
            // - 'reservable' eq/positive/closest
            // - 'releasable' eq/positive/closest

            // Prefer units with shippable >= quantity, reservable >= quantity or releasable >= quantity
            foreach (['shippable', 'reservable', 'releasable'] as $property) {
                if (false !== $r = $this->ceilComparison($a, $b, $property, $quantity)) {
                    return $r;
                }
            }
            // Prefer units with shippable == quantity, reservable == quantity or releasable == quantity
            foreach (['reservable', 'releasable'] as $property) { // 'shippable' ?
                if (false !== $r = $this->equalComparison($a, $b, $property, $quantity)) {
                    return $r;
                }
            }

            // TODO (temporary)
            foreach (['shippable', 'reservable', 'releasable'] as $property) {
                if ($a->{$property} != $b->{$property}) {
                    return $b->{$property} - $a->{$property};
                }
            }
            return 0;

            // Prefer units with assignments combination's releasable quantity (sum) that
            // equals or is greater than (prefer closest) aimed quantity.
            if ($a->combination->sum == $b->combination->sum) {
                return 0;
            }
            if (0 <= $a->combination->sum) {
                return intval(0 > $b->combination->sum ? -1 : $a->combination->sum - $b->combination->sum);
            }

            return intval(0 < $b->combination->sum ? 1 : $b->combination->sum - $a->combination->sum);
        });

        return $candidates;
    }
public function getUnitCandidates(StockAssignmentInterface $assignment, $quantity)
    {
        $subject = $assignment->getStockUnit()->getSubject();

        // Find the subject's ready stock units
        if (empty($units = $this->unitResolver->findReady($subject))) {
            return [];
        };

        $sale = $assignment->getSaleItem()->getSale();

        $candidates = [];

        foreach ($units as $unit) {
            $this->unitResolver->getStockUnitCache()->add($unit);

            $candidate = UnitCandidate::build($unit, $sale);

            // Skip if no reservable and no releasable quantity
            if ((0 >= $candidate->reservable) && (0 >= $candidate->releasable)) {
                continue;
            }

            $add = false;
            $diff = 0;

            if (0 < $candidate->reservable) {
                // Unit has enough reservable quantity
                if (empty($candidates) && 0 >= $diff = $quantity - $candidate->reservable) {
                    return [$candidate];
                }

                $add = true;
            }

            if (null !== $candidate->getCombination(max($diff, $candidate->releasable))) {
                $add = true;
            }

            if ($add) {
                $candidates[] = $candidate;
            }
        }

        if (empty($candidates)) {
            return [];
        }

        // Sort candidates
        usort($candidates, function (UnitCandidate $a, UnitCandidate $b) use ($quantity) {
            // TODO sort by (reservable + releasable) DESC

            // TODO Simplify. Prefer:
            // - 'shippable' enough
            // - 'reservable' eq/positive/closest
            // - 'releasable' eq/positive/closest

            // Prefer units with shippable >= quantity, reservable >= quantity or releasable >= quantity
            foreach (['shippable', 'reservable', 'releasable'] as $property) {
                if (false !== $r = $this->ceilComparison($a, $b, $property, $quantity)) {
                    return $r;
                }
            }
            // Prefer units with shippable == quantity, reservable == quantity or releasable == quantity
            foreach (['reservable', 'releasable'] as $property) { // 'shippable' ?
                if (false !== $r = $this->equalComparison($a, $b, $property, $quantity)) {
                    return $r;
                }
            }

            // TODO (temporary)
            foreach (['shippable', 'reservable', 'releasable'] as $property) {
                if ($a->{$property} != $b->{$property}) {
                    return $b->{$property} - $a->{$property};
                }
            }
            return 0;

            // Prefer units with assignments combination's releasable quantity (sum) that
            // equals or is greater than (prefer closest) aimed quantity.
            if ($a->combination->sum == $b->combination->sum) {
                return 0;
            }
            if (0 <= $a->combination->sum) {
                return intval(0 > $b->combination->sum ? -1 : $a->combination->sum - $b->combination->sum);
            }

            return intval(0 < $b->combination->sum ? 1 : $b->combination->sum - $a->combination->sum);
        });

        return $candidates;
    }
Finds the best stock unit to move/merge assignment(s) into for the given quantity.

@param StockAssignmentInterface $assignment
@param float                    $quantity

@return UnitCandidate[]