<?php
declare(strict_types=1);
class CanStorageCalculator
{
public function __construct(
public int $maxLayers,
public int $cansPerLayer,
public int|float $canHeight,
public int|float $maxPalletHeight,
public int|float $palletBaseHeight,
public int|float $tierSheetHeight,
public int|float $topFrameHeight
) {
}
/**
* Calculate the storage requirements for the given number of cans and artworks
*/
public function calculateStorageRequirements(int $totalCans, array $artworks): array
{
// Calculate the total number of layers required and round the number up to the nearest whole number
$totalLayers = ceil($totalCans / $this->cansPerLayer);
// Calculate the max number of layers that can fit on a pallet, considering the following:
// the max pallet height, can height, tier sheet height, base and top frames height
$maxLayersPerPallet = $this->getMaxLayersPerPallet();
// Calculate the number of pallets required and round the number up to the nearest whole number
$palletCount = ceil($totalLayers / $maxLayersPerPallet);
// Distribute the layers evenly across the pallets
$layersPerPallet = $this->distributeLayersEvenly($totalLayers, $palletCount, $maxLayersPerPallet);
// Calculate the total number of layers per artwork
$numberOfLayersPerArtwork = (new DunnageCalculator())->numberOfLayersPerArtwork($artworks, $this->cansPerLayer);
// Get the artworks distribution on each pallet
$palletDetails = $this->getPalletDetails($layersPerPallet, $artworks, $numberOfLayersPerArtwork);
// Return the pallet count, layers per pallet and total tier sheets required
return [
'palletCount' => $palletCount,
'layersPerPallet' => $layersPerPallet,
'totalTierSheets' => $palletCount + array_sum($layersPerPallet),
];
}
/**
* Calculate the max number of layers that can fit on a pallet
*/
private function getMaxLayersPerPallet(): int|float
{
// Calculate the max layer height based on the can height and tier sheet height
$maxLayerHeight = $this->canHeight + $this->tierSheetHeight;
// Calculate the total pallet height based on the max pallet height, pallet base height and top frame height
$totalPalletHeight = $this->maxPalletHeight - $this->palletBaseHeight - $this->topFrameHeight;
// Calculate the max number of layers that can fit on a pallet based on the total pallet height
$maxLayersBasedOnHeight = floor($totalPalletHeight / $maxLayerHeight);
// Return the min of the max layers based on height and the max layers
return min($maxLayersBasedOnHeight, $this->maxLayers);
}
/**
* Distribute the layers evenly across the pallets
*/
private function distributeLayersEvenly(int|float $totalLayers, $palletCount, int|float $maxLayersPerPallet): array
{
$layersPerPallet = [];
$remainingLayers = $totalLayers;
// Check if the total layers can be evenly distributed across the pallets
$evenDistribution = ($totalLayers % $palletCount === 0);
if (! $evenDistribution) {
// Calculate the ideal layers per pallet
$idealLayersPerPallet = floor($totalLayers / $palletCount);
// Calculate the remainder of the total layers divided by the pallet count
$remainder = $totalLayers % $palletCount;
// Distribute the layers across the pallets
for ($i = 0; $i < $remainder; $i++) {
$layersPerPallet[] = $idealLayersPerPallet + 1;
}
// Distribute the remaining layers across the pallets
for ($i = $remainder; $i < $palletCount; $i++) {
$layersPerPallet[] = $idealLayersPerPallet;
}
} else {
// Distribute the layers evenly across the pallets
$layersPerPallet = array_fill(0, $palletCount, $totalLayers / $palletCount);
}
// Return the layers per pallet
return $layersPerPallet;
}
public function getPalletDetails(array $layersPerPallet, array $artworks, array $numberOfLayersPerArtwork): array
{
$palletDetails = [];
$remainingLayers = $numberOfLayersPerArtwork;
$palletIndex = 0;
foreach ($layersPerPallet as $layers) {
$palletDetails[$palletIndex] = [];
foreach ($artworks as $artwork => $quantity) {
if ($remainingLayers[$artwork] > 0) {
$layersForArtwork = min($remainingLayers[$artwork], $layers);
$palletDetails[$palletIndex][$artwork] = $layersForArtwork;
$remainingLayers[$artwork] -= $layersForArtwork;
$layers -= $layersForArtwork;
}
if ($layers === 0) {
break;
}
}
// Remove artworks with zero layers from the pallet details
$palletDetails[$palletIndex] = array_filter($palletDetails[$palletIndex], function ($layerCount) {
return $layerCount > 0;
});
$palletIndex++;
}
return $palletDetails;
}
private function getTotalHeight(int $layers): int|float
{
$totalHeight = $this->palletBaseHeight;
$totalHeight += ($layers * ($this->canHeight + $this->tierSheetHeight));
$totalHeight += $this->topFrameHeight;
return $totalHeight;
}
}
class DunnageCalculator
{
private $artworks = [];
private $canSize;
private $combineSkus = false;
private $maxPalletHeight;
/**
* Set the array of artwork items
*/
public function artworks(ArtworkItem ...$artworks): self
{
foreach ($artworks as $artwork) {
$this->artworks[$artwork->name] = $artwork->quantity;
}
return $this;
}
/**
* Set the can size
*/
public function canSize(string $canSize): self
{
$this->canSize = $canSize;
return $this;
}
/**
* Combine SKUs
*/
public function combineSkus(bool $combineSkus): self
{
$this->combineSkus = $combineSkus;
return $this;
}
/**
* Set the maximum pallet height
*/
public function maxPalletHeight(int $maxPalletHeight): self
{
$this->maxPalletHeight = $maxPalletHeight;
return $this;
}
/**
* Calculate dunnage according to the given parameters
*/
public function calculate(): array
{
// Get the can size
$canSizeConfig = $this->getCanSizeConfig($this->canSize);
// Get the pallet item sizes
$getPalletItemSizes = $this->getPalletItemSizes();
// Create a new instance of the CanStorageCalculator
$canStorageCalculator = new CanStorageCalculator(
$canSizeConfig['maxLayers'],
$canSizeConfig['cansPerLayer'],
$canSizeConfig['canHeight'],
$this->maxPalletHeight,
$getPalletItemSizes['palletBaseHeight'],
$getPalletItemSizes['tierSheetHeight'],
$getPalletItemSizes['topFrameHeight'],
);
// Combine SKUs
// We only combine SKUs if we have more than one artwork
if ($this->combineSkus && count($this->artworks) > 1) {
// Calculate the total number of cans of all artworks
$totalCans = array_sum($this->artworks);
// Calculate the number of pallets, layers per pallet and total tier sheets required
$requirements = $canStorageCalculator->calculateStorageRequirements($totalCans, $this->artworks);
// Calculate the number of layers per artwork
$numberOfLayersPerArtwork = $this->numberOfLayersPerArtwork($this->artworks, $canSizeConfig['cansPerLayer']);
// Get the distribution of artworks on each pallet
$palletDetails = $canStorageCalculator->getPalletDetails($requirements['layersPerPallet'], $this->artworks, $numberOfLayersPerArtwork);
return [
'palletCount' => $requirements['palletCount'],
'layersPerPallet' => $requirements['layersPerPallet'],
'totalTierSheets' => $requirements['totalTierSheets'],
'palletDetails' => $palletDetails,
];
}
$result = [];
// Not combining SKUs
// Iterate through each artwork
foreach ($this->artworks as $artwork => $quantity) {
// Calculate the number of pallets, layers per pallet and total tier sheets required
$requirements = $canStorageCalculator->calculateStorageRequirements($quantity, [$artwork => $quantity]);
// Add the artwork to the result array
$result[$artwork] = [
'palletCount' => $requirements['palletCount'],
'layersPerPallet' => $requirements['layersPerPallet'],
'totalTierSheets' => $requirements['totalTierSheets'],
];
}
return ['artworks' => $result];
}
/**
* Calculate the number of layers per artwork
* To perform this calculation, we need consider the number of cans per layer
*/
public function numberOfLayersPerArtwork(array $artworks, int $cansPerLayer): array
{
$layersPerArtwork = [];
// Iterate through the artworks
foreach ($artworks as $artwork => $quantity) {
// Calculate the number of layers required and round the number up to the nearest whole number
$layersPerArtwork[$artwork] = ceil($quantity / $cansPerLayer);
}
return $layersPerArtwork;
}
/**
* Get the can size configuration
*/
private function getCanSizeConfig(string $canSize): array
{
return match ($canSize) {
'8.4_oz_sleek' => [
'maxLayers' => 22,
'cansPerLayer' => 506,
'canHeight' => 4.35,
],
'10_oz_sleek' => [
'maxLayers' => 19,
'cansPerLayer' => 506,
'canHeight' => 5.28,
],
'12_oz_sleek' => [
'maxLayers' => 16,
'cansPerLayer' => 506,
'canHeight' => 6.28,
],
'12_oz' => [
'maxLayers' => 20,
'cansPerLayer' => 389,
'canHeight' => 4.84,
],
'16_oz' => [
'maxLayers' => 16,
'cansPerLayer' => 389,
'canHeight' => 6.28,
],
'19.2_oz' => [
'maxLayers' => 13,
'cansPerLayer' => 389,
'canHeight' => 7.432,
],
};
}
/**
* Get the pallet item sizes
* Here you can have different pallets with different sizes
*/
private function getPalletItemSizes(string $type = 'default'): array
{
return match ($type) {
default => [
'palletBaseHeight' => 4.5,
'tierSheetHeight' => 0.026,
'topFrameHeight' => 1.0,
]
};
}
}
class ArtworkItem
{
public string $name;
public int $quantity;
/**
* Set the name of the artwork item
*/
public function name(string $name): self
{
$this->name = $name;
return $this;
}
/**
* Set the quantity of the artwork item
*/
public function quantity(int $quantity): self
{
$this->quantity = $quantity;
return $this;
}
}
$calculator = new DunnageCalculator();
$result = $calculator
->artworks(
(new ArtworkItem())->name('artwork_1')->quantity(778),
(new ArtworkItem())->name('artwork_2')->quantity(5_835),
(new ArtworkItem())->name('artwork_3')->quantity(389),
)
->canSize('16_oz')
->combineSkus(true)
->maxPalletHeight(88)
->calculate();
print_r($result);
Fatal error: Uncaught TypeError: array_fill(): Argument #2 ($count) must be of type int, float given in /in/8rpL2n:97
Stack trace:
#0 /in/8rpL2n(97): array_fill(0, 2.0, 9.0)
#1 /in/8rpL2n(34): CanStorageCalculator->distributeLayersEvenly(18.0, 2.0, 13.0)
#2 /in/8rpL2n(228): CanStorageCalculator->calculateStorageRequirements(7002, Array)
#3 /in/8rpL2n(373): DunnageCalculator->calculate()
#4 {main}
thrown in /in/8rpL2n on line 97
Process exited with code 255.