diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c70320..2c957ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ # Changelog ## [4.x - Unreleased] - 2026-xx-xx - +### Fixed +- In some rare cases `WeightRedistributor` would silently drop items + ## [4.2.0] - 2026-07-05 ### Added - A mechanism to allow forcing certain items to be packed together [RubenKluft] diff --git a/src/WeightRedistributor.php b/src/WeightRedistributor.php index d5f7c3c..5ad60e7 100644 --- a/src/WeightRedistributor.php +++ b/src/WeightRedistributor.php @@ -169,7 +169,12 @@ private function doVolumeRepack(iterable $items, Box $currentBox): PackedBoxList $packer->setBoxQuantity($currentBox, $this->boxQuantitiesAvailable[$currentBox] + 1); $packer->setItems($items); - return $packer->doBasicPacking(true); + $packedBoxes = $packer->doBasicPacking(true); + if ($packedBoxes->count() !== 1 || $packer->getUnpackedItems()->count() !== 0) { + return new PackedBoxList($this->packedBoxSorter); + } + + return $packedBoxes; } /** diff --git a/tests/WeightRedistributorTest.php b/tests/WeightRedistributorTest.php index 1f8438a..6570a4f 100644 --- a/tests/WeightRedistributorTest.php +++ b/tests/WeightRedistributorTest.php @@ -10,6 +10,7 @@ namespace DVDoug\BoxPacker; use DVDoug\BoxPacker\Test\ConstrainedPlacementNoStackingTestItem; +use DVDoug\BoxPacker\Test\LimitedSupplyTestBox; use DVDoug\BoxPacker\Test\TestBox; use DVDoug\BoxPacker\Test\TestItem; use PHPUnit\Framework\Attributes\CoversClass; @@ -67,4 +68,35 @@ public function testWeightDistributionWorks(): void self::assertEquals(0, $packedBoxes->getWeightVariance()); } + + /** + * Test to ensure no items are silently dropped during weight redistribution. + */ + public function testWeightRedistributionDoesNotSilentlyDropItems(): void + { + $packer = new Packer(); + $packer->addBox(new LimitedSupplyTestBox('Box', 29, 29, 29, 0, 29, 29, 29, 68, 2)); + + $packer->addItem(new TestItem('Item 0', 10, 10, 10, 2, Rotation::BestFit), 2); + $packer->addItem(new TestItem('Item 1', 10, 10, 10, 3, Rotation::BestFit)); + $packer->addItem(new TestItem('Item 2', 10, 10, 10, 4, Rotation::BestFit)); + $packer->addItem(new TestItem('Item 3', 10, 10, 10, 8, Rotation::BestFit), 5); + $packer->addItem(new TestItem('Item 4', 10, 10, 10, 18, Rotation::BestFit), 3); + + // packer initially packs 6 items into each box, + // the imbalance in weights will be attempted to be corrected by the WeightRedistributor + $packedBoxes = $packer->pack(); + self::assertCount(2, $packedBoxes); + + $packedItemCount = 0; + foreach ($packedBoxes as $packedBox) { + $packedItemCount += $packedBox->items->count(); + } + + self::assertSame( + 12, + $packedItemCount + $packer->getUnpackedItems()->count(), + 'No items should be lost during weight redistribution' + ); + } }