<?php
namespace App\Helpers\Bookings;
use App\Entity\Animals\Animal;
use App\Entity\Animals\MixerCategory;
use App\Entity\Animals\Type;
use App\Entity\Bookings\Booking;
use App\Entity\Bookings\Item;
use App\Entity\Bookings\Payment;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
use Symfony\Component\Yaml\Parser;
use Doctrine\ORM\EntityManagerInterface;
use \App\Helpers\System\ConfigHelper;
use \App\Helpers\Services\ServiceHelper;
use \App\Helpers\Animals\AnimalHelper;
class BookingHelper
{
protected $container;
protected $em;
protected $config;
protected $housingByAnimalType;
public function __construct(Container $container, EntityManagerInterface $em, ConfigHelper $configHelper, ServiceHelper $serviceHelper, AnimalHelper $animalHelper)
{
$this->container = $container;
$this->em = $em;
$this->configHelper = $configHelper;
$this->serviceHelper = $serviceHelper;
$this->animalHelper = $animalHelper;
$this->boardingService = $this->em->getRepository(\App\Entity\Services\Service::class)->findOneFiltered(array(
'id' => $this->configHelper->getValue("boarding_service_id")
));
}
public function getItem(\App\Entity\Bookings\Booking $booking, \App\Entity\Animals\Animal $animal, \App\Entity\Services\Service $service)
{
return $this->em->getRepository(\App\Entity\Bookings\Item::class)->findOneFiltered(array(
'booking' => $booking,
'animal' => $animal,
'service' => $service,
'parent' => null
));
}
public function getBoardingItemForAnimal(\App\Entity\Bookings\Booking $booking, \App\Entity\Animals\Animal $animal)
{
return $this->em->getRepository(\App\Entity\Bookings\Item::class)->findOneFiltered(array(
'booking' => $booking,
'animal' => $animal,
'service' => $this->getBoardingService(),
'parent' => null
));
}
public function getBoardingItems(\App\Entity\Bookings\Booking $booking, $extraCriteria = array())
{
return $this->em->getRepository(\App\Entity\Bookings\Item::class)->findFiltered(array_merge(array(
'booking' => $booking,
'service' => $this->getBoardingService(),
'parent' => null
), $extraCriteria));
}
public function bookingHasService(\App\Entity\Bookings\Booking $booking, \App\Entity\Animals\Animal $animal, \App\Entity\Services\Service $service)
{
// Get items for this booking
$item = $this->em->getRepository(\App\Entity\Bookings\Item::class)->findOneFiltered(array(
'booking' => $booking,
'animal' => $animal,
'service' => $service
));
if ($item)
return true;
return false;
}
public function getBoardingService()
{
$boardingService = $this->boardingService;
// Make sure it exists
if (!$boardingService)
throw new \Exception("Boarding service not found!");
return $boardingService;
}
public function calculateBoardingPrice(\App\Entity\Bookings\Item $boardingItem)
{
// Totals
$total = 0;
// Get service
$service = $this->getBoardingService();
// Get animal
$animal = $boardingItem->getAnimal();
// Get start and end date
$arrivalDate = $boardingItem->getArrivalDate();
// Afternoon or morning?
if ($boardingItem->getArrivalTime() == "pm")
$arrivalDate->setTime(13, 0, 0);
else
$arrivalDate->setTime(9, 0, 0);
// Get departure date
$departureDate = $boardingItem->getDepartureDate();
// Afternoon or morning?
if ($boardingItem->getDepartureTime() == "pm")
$departureDate->setTime(13, 0, 0);
else
$departureDate->setTime(9, 0, 0);
// Get global variables
$dayStart = $this->configHelper->getValue('daytime_start');
$nightStart = $this->configHelper->getValue('nightitme_start');
// Calculate the period length
$period = new \DatePeriod($arrivalDate, \DateInterval::createFromDateString('1 day'), $departureDate);
// Keep track of days
$dayCount = 1;
// Loop the days
foreach ($period as $singleDay)
{
// Get the price for this day
$price = $this->serviceHelper->getPrice($service, $animal->getType(), $animal->getSize(), ($boardingItem ? $boardingItem->getMixerCategory() : null), null, $singleDay);
if (!$price)
continue;
// Create the cut off times
$dayPeriodStart = new \DateTime(date("Y-m-d H:i:0", strtotime($singleDay->format("Y-m-d") . ' ' . $dayStart)));
$nightPeriodStart = new \DateTime(date("Y-m-d H:i:0", strtotime($singleDay->format("Y-m-d") . ' ' . $nightStart)));
// Weekend?
if (in_array($singleDay->format("D"), array("Sun", "Sat")))
$isWeekend = true;
else
$isWeekend = false;
// Are we there for the night?
if ($departureDate >= $nightPeriodStart)
$total += floatval($isWeekend ? $price->getWeekendNightPrice() : $price->getWeekdayNightPrice());
// Are we there for the day?
elseif (($arrivalDate > $dayPeriodStart) || (($departureDate <= $nightPeriodStart)))
{
// Only animals staying for 1 day will fall in here.
// We will charge them a day rate for 1 day, otherwise they will be charged a night rate.
if (($singleDay->format("Y-m-d") == $departureDate->format("Y-m-d")) && ($dayCount == 1))
{
$total += floatval($isWeekend ? $price->getWeekendDayPrice() : $price->getWeekdayDayPrice());
}
}
// Increment the day count
$dayCount++;
}
return $total;
}
public function calculatePrice(\App\Entity\Bookings\Item $item)
{
// Define variables
$total = 0;
$serviceDate = null;
$animal = $item->getAnimal();
$service = $item->getService();
// Assume weekday pricing by default
$isWeekend = false;
// Get the day of the service
if ($item->getServiceDate())
$serviceDate = $item->getServiceDate();
$savedPrice = false;
// Get the price for this day
if ($service->getPricingType() == "complex")
{
// Got a saved price?
if ($item->getWeekdayDayPrice())
{
$price = $item->getWeekdayDayPrice();
$savedPrice = true;
}
else
$price = $this->serviceHelper->getPrice($service, $animal->getType(), $animal->getSize(), $item->getMixerCategory(), null, $serviceDate);
}
else
{
// Got a saved price?
if ($item->getWeekdayDayPrice())
{
$price = $item->getWeekdayDayPrice();
$savedPrice = true;
}
else
$price = $this->serviceHelper->getPrice($service, $animal->getType(), $animal->getSize(), null, null, $serviceDate);
}
if ($serviceDate)
{
// Weekend?
if (in_array($serviceDate->format("D"), array("Sun", "Sat")))
$isWeekend = true;
}
// Saved price?
if ($savedPrice)
{
$total += $price;
return array(
'total' => $total,
'weekdayDayPrice' => $price,
'weekdayNightPrice' => 0,
'weekendDayPrice' => 0,
'weekendNightPrice' => 0
);
}
else
{
if ($item->getService()->getPricingType() == "complex")
{
// Always assume day pricing for any services other than boarding.
if ($price)
$total += floatval($isWeekend ? $price->getWeekendDayPrice() : $price->getWeekdayDayPrice());
}
else
{
// Simple pricing always uses the weekday day price
if ($price)
$total += floatval($price->getWeekdayDayPrice());
}
return array(
'total' => $total,
'weekdayDayPrice' => ($price ? $price->getWeekdayDayPrice() : 0),
'weekdayNightPrice' => ($price ? $price->getWeekendNightPrice() : 0),
'weekendDayPrice' => ($price ? $price->getWeekendDayPrice() : 0),
'weekendNightPrice' => ($price ? $price->getWeekendNightPrice() : 0)
);
}
}
public function getAnimalMeals(\App\Entity\Bookings\Booking $booking, \App\Entity\Animals\Animal $animal)
{
// Missing booking?
if (!$booking)
throw new \Exception("Booking should be set in getAnimalMeals()");
// Missing animal?
if (!$animal)
throw new \Exception("Animal should be set in getAnimalMeals()");
// Array to hold meals
$mealArray = array();
// Get slots
$slots = $this->em->getRepository(\App\Entity\Meals\Slot::class)->findFiltered();
// Loop slots
foreach ($slots as $someSlot)
{
$mealArray[] = array(
"slot" => $someSlot,
"booking_meal" => $this->em->getRepository(\App\Entity\Bookings\Meal::class)->findOneFiltered(array(
'animal' => $animal,
'booking' => $booking,
'slot' => $someSlot
)),
"animal_meal" => $this->em->getRepository(\App\Entity\Animals\Meal::class)->findOneFiltered(array(
'animal' => $animal,
'slot' => $someSlot
))
);
}
return $mealArray;
}
public function getMealByPeriod(Animal $animal, Booking $booking, string $period)
{
// Get slot
$slot = $this->em->getRepository(\App\Entity\Meals\Slot::class)->findOneFiltered([
"period" => $period
]);
// Not found?
if (!$slot)
return null;
// Grab the meal
return $this->em->getRepository(\App\Entity\Bookings\Meal::class)->findOneFiltered(array(
'animal' => $animal,
'booking' => $booking,
'slot' => $slot
));
}
public function getMealCheck(\App\Entity\Bookings\Meal $bookingMeal, \DateTime $selectedDate, string $period)
{
// Find item
$item = $this->getItem($bookingMeal->getBooking(), $bookingMeal->getAnimal(), $this->getBoardingService());
if (!$item)
return null;
// Check if this meal has been issued
$mealCheck = $this->em->getRepository(\App\Entity\Bookings\MealCheck::class)->findOneFiltered(array(
"animal" => $bookingMeal->getAnimal(),
"item" => $item,
"period" => $period,
"selectedDate" => new \DateTime(date("Y-m-d", strtotime($selectedDate->format("Y-m-d")))),
));
return $mealCheck;
}
public function getOtherItems(\App\Entity\Bookings\Booking $booking, \App\Entity\Animals\Animal $animal)
{
// Get items
$items = $this->em->getRepository(\App\Entity\Bookings\Item::class)->findFiltered(array(
"otherItems" => true,
"animal" => $animal,
"booking" => $booking
));
return $items;
}
public function criteriaMet(\App\Entity\Bookings\Booking $booking)
{
// Get booking animals
$bookingAnimals = $booking->getAnimals();
// Loop animals
foreach ($bookingAnimals as $someAnimal)
{
// Error on the animal?
if (count($this->animalHelper->animalHasErrors($someAnimal)))
return false;
// Get vaccinations
$vaccinations = $this->animalHelper->isVaccinated(null, $someAnimal);
foreach ($vaccinations as $someVaccination)
{
if (!$someVaccination["verified"])
return false;
}
}
return true;
}
public function checkVaccinations(\App\Entity\Bookings\Booking $booking)
{
// Get animals
$animals = $booking->getAnimals();
// Vaccination flag
$fullyVaccinated = true;
// Loop animals
foreach ($animals as $someAnimal)
{
// Get all vaccination for this animal type
$vaccinationTypes = $this->em->getRepository(\App\Entity\Vaccinations\Type::class)->findFiltered(array(
"animalType" => $someAnimal->getType()
));
// Loop vaccinations
foreach ($vaccinationTypes as $someVaccinationType)
{
if (!$this->animalHelper->isVaccinated($someVaccinationType, $someAnimal))
$fullyVaccinated = false;
}
}
// Fully vaccinated?
if ($fullyVaccinated)
{
// Complete vaccinations
$booking->setVaccinationsCompleted(new \DateTime());
}
else
{
$booking->setVaccinationsCompleted(null);
}
// Persist booking
$this->em->persist($booking);
return $booking;
}
function getDepositRequirement(\App\Entity\Bookings\Item $item)
{
// Default to 0
$depositRequired = 0;
if ($item->getId())
$arrivalDate = $item->getArrivalDate();
else
$arrivalDate = null;
if ($arrivalDate)
{
// Is this item a boarding service?
if ($item->getService() === $this->getBoardingService())
{
// Get the price for one night
$price = $this->serviceHelper->getPrice($item->getService(), $item->getAnimal()->getType(), $item->getAnimal()->getSize(), $item->getMixerCategory(), null, $arrivalDate);
// Found a price?
if ($price && $price->getWeekdayNightPrice())
{
// Less than 4 nights?
if ($item->getDurationInDays() < 4)
{
// Deposit is equal to 1 night
$depositRequired = $price->getWeekdayNightPrice();
}
else
{
// Query for a deposit amount
$deposit = $this->em->getRepository(\App\Entity\Services\Deposit::class)->findOneFiltered(array(
"dateBetween" => $arrivalDate
));
if ($deposit && $deposit->getDepositPercentage() > 0)
{
// Calculate the deposit required
$depositRequired = $deposit->getDepositPercentage() * $item->getTotal();
}
}
}
else
throw new \Exception("Cannot get price!");
}
else
{
// Deposit is zero
$depositRequired = 0;
}
return $depositRequired;
}
// Query for the default
$deposit = $this->em->getRepository(\App\Entity\Services\Deposit::class)->findOneFiltered(array(
"startDate" => null,
"endDate" => null
));
if ($deposit && $deposit->getDepositPercentage() > 0)
{
// Calculate the deposit required
$depositRequired = $deposit->getDepositPercentage() * $item->getTotal();
}
// Is this item a boarding service?
if ($item->getService() === $this->getBoardingService())
{
// Deposit too low?
if ($depositRequired < $this->configHelper->getValue('minimum_boarding_deposit'))
$depositRequired = $this->configHelper->getValue('minimum_boarding_deposit');
}
// Make sure the deposit isn't greater than the actual item cost
if ($depositRequired > $item->getTotal())
$depositRequired = $item->getTotal();
return $depositRequired;
}
public function getBookingBuddies(\App\Entity\Bookings\Item $bookingItem)
{
// No animal on booking item?
if (!$bookingItem->getAnimal())
throw new \Exception("No animal on booking item");
// No buddies?
if (!count($bookingItem->getAnimal()->getBuddies()))
return [];
$buddyArray = array();
foreach ($bookingItem->getAnimal()->getBuddies() as $someBuddy)
$buddyArray[] = $someBuddy;
// Find booking items
$otherBookingItems = $this->em->getRepository(\App\Entity\Bookings\Item::class)->findFiltered(array(
"conflictingDates" => array(
$bookingItem->getArrivalDate(),
$bookingItem->getDepartureDate(),
),
"animal" => array("in", $buddyArray)
));
// Array to hold matching animals
$animalArray = array();
// Loop bookings
foreach ($otherBookingItems as $someItem)
{
// No animal
if (!$someItem->getAnimal())
continue;
if (in_array($someItem->getAnimal(), $buddyArray))
{
// Not in the array
if (!in_array($someItem->getAnimal(), $animalArray))
$animalArray[] = $someItem->getAnimal();
}
}
return $animalArray;
}
public function getCapacity(\App\Entity\Bookings\Housing $housing, \App\Entity\Animals\Size $size)
{
// Query for capacity
$capacity = $this->em->getRepository(\App\Entity\Bookings\Capacity::class)->findOneFiltered(array(
"housing" => $housing,
"size" => $size
));
if (!$capacity)
return null;
return $capacity->getValue();
}
public function generateAdditionalCharges(\App\Entity\Bookings\Booking $booking)
{
// Get booking items
$items = $booking->getItems();
// No items?
if (!count($items))
return $booking;
// Get first day of booking
$firstArrivalDate = $booking->getFirstArrivalDate();
$lastDepartureDate = $booking->getLastDepartureDate();
// Not got a valid set of dates?
if (!$firstArrivalDate || !$lastDepartureDate)
{
// No dates to calculate additional charges. This implies there are no animals. Remove everything on the booking
foreach ($items as $someItem)
{
// Remove and delete the item
$booking->removeItem($someItem);
$this->em->remove($someItem);
}
return $booking;
}
// Array to keep track of charges
$chargeArray = array();
// Service only booking?
if ($booking->getIsServiceOnlyBooking())
{
// This is a service only booking.
// Remove any additional charges associated with housing
// Get the the additional boarding charge items for this booking
$boardingAdditionalCharges = $this->em->getRepository(\App\Entity\Bookings\Item::class)->findFiltered(array(
"service" => $this->getBoardingService(),
"additionalCharge" => array("not_null", true), // Not null
"booking" => $booking
));
// Loop the charges
foreach ($boardingAdditionalCharges as $someItem)
{
// Remove from booking
$booking->removeItem($someItem);
// Remove item
$this->em->remove($someItem);
}
}
else
{
// Loop items
foreach ($items as $someItem)
{
// Not a boarding service?
if ($someItem->getService() != $this->getBoardingService())
{
// Skip it
continue;
}
// Calculate the period length
$period = new \DatePeriod($someItem->getArrivalDate(), \DateInterval::createFromDateString('1 day'), $someItem->getDepartureDate());
// Keep track of the number of days
$dayCount = 1;
// Loop the days
foreach ($period as $singleDay)
{
// Don't charge if this is the last day
if ($singleDay->format("Y-m-d") != $someItem->getDepartureDate()->format("Y-m-d"))
{
// Get any additional charges for this day
$additionalCharges = $this->em->getRepository(\App\Entity\Services\AdditionalCharge::class)->findFiltered(array(
"service" => $this->getBoardingService(),
"animalType" => $someItem->getAnimal()->getType(),
"dateBetween" => $singleDay
));
// Got any?
if (count($additionalCharges))
{
// Loop additional charges
foreach ($additionalCharges as $someAdditionalCharge)
{
// Not in the array yet?
if (!array_key_exists($someAdditionalCharge->getId() . '_' . $someItem->getAnimal()->getId(), $chargeArray))
{
// Add to charge tracking array
$chargeArray[$someAdditionalCharge->getId() . '_' . $someItem->getAnimal()->getId()] = array(
"additionalCharge" => $someAdditionalCharge,
"animal" => $someItem->getAnimal(),
"quantity" => 0
);
}
// Add onto total
$chargeArray[$someAdditionalCharge->getId() . '_' . $someItem->getAnimal()->getId()]['quantity']++;
}
}
}
// Increment day count
$dayCount++;
}
}
// Loop the charge array
foreach ($chargeArray as $someAdditionalCharge)
{
// Check for existing item
$additionalChargeItem = $this->em->getRepository(\App\Entity\Bookings\Item::class)->findOneFiltered(array(
"parent" => null,
"additionalCharge" => $someAdditionalCharge['additionalCharge'],
"animal" => $someAdditionalCharge['animal'],
"booking" => $booking
));
// Not found
if (!$additionalChargeItem)
{
// New instance of additional charge item
$additionalChargeItem = new \App\Entity\Bookings\Item(array(
"booking" => $booking,
"description" => $someAdditionalCharge['additionalCharge']->getName() . " - " . $someAdditionalCharge['additionalCharge']->getAnimalType()->getName(),
"additionalCharge" => $someAdditionalCharge['additionalCharge'],
"quantity" => $someAdditionalCharge['quantity'],
"taxRate" => $this->configHelper->getValue("vat_rate"),
"animal" => $someAdditionalCharge['animal']
));
// Persist additional charge
$this->em->persist($additionalChargeItem);
// Add to item
$booking->addItem($additionalChargeItem);
}
// Update charge item
$additionalChargeItem->setRate($someAdditionalCharge['additionalCharge']->getPrice());
$additionalChargeItem->setQuantity($someAdditionalCharge['quantity']);
// Persist additional charge
$this->em->persist($additionalChargeItem);
}
// Persist booking
$this->em->persist($booking);
}
// Get all other items that aren't boarding
$nonBoardingItems = $this->em->getRepository(\App\Entity\Bookings\Item::class)->findFiltered(array(
"service" => array("not_in", [$this->getBoardingService()]),
"booking" => $booking,
));
// Loop non boarding items such as walks and washes
foreach ($nonBoardingItems as $someItem)
{
// Is there a service date on the item?
if ($someItem->getServiceDate())
{
// Check for any additional charges
$additionalCharges = $this->em->getRepository(\App\Entity\Services\AdditionalCharge::class)->findFiltered(array(
"service" => $someItem->getService(),
"animalType" => $someItem->getAnimal()->getType(),
"dateBetween" => $someItem->getServiceDate()
));
// Loop additional charges
foreach ($additionalCharges as $someAdditionalCharge)
{
// Check fo existing item
$additionalChargeItem = $this->em->getRepository(\App\Entity\Bookings\Item::class)->findOneFiltered(array(
"parent" => $someItem,
"additionalCharge" => $someAdditionalCharge
));
// Not found
if (!$additionalChargeItem)
{
// New instance of additional charge item
$additionalChargeItem = new \App\Entity\Bookings\Item(array(
"animal" => $someItem->getAnimal(),
"booking" => $someItem->getBooking(),
"parent" => $someItem,
"description" => $someAdditionalCharge->getName(),
"service" => null,
"additionalCharge" => $someAdditionalCharge,
"quantity" => 1,
"taxRate" => $this->configHelper->getValue("vat_rate")
));
// Add to item
$someItem->addChild($additionalChargeItem);
$booking->addItem($additionalChargeItem);
// Persist item
$this->em->persist($someItem);
}
// Update rate
$additionalChargeItem->setRate($someAdditionalCharge->getPrice());
// Persist additional charge
$this->em->persist($additionalChargeItem);
// Persist booking
$this->em->persist($booking);
}
}
}
return $booking;
}
public function refreshItemTotals(Item $item)
{
// Got children
if (count($item->getChildren()))
{
// Loop and refresh totals
foreach ($item->getChildren() as $someChildItem)
$this->refreshItemTotals($someChildItem);
$item->setTotalTax(0);
$item->setTotal(0);
$item->setSubtotal(0);
$item->setRate(0);
$item->setDeposit(0);
}
else
{
$item->setTotalTax(($item->getRate() * $item->getQuantity()) - (($item->getRate() * $item->getQuantity()) / (1 + ($item->getTaxRate() / 100))));
$item->setTotal(($item->getRate() * $item->getQuantity()));
$item->setSubtotal($item->getTotal() - $item->getTotalTax());
}
// Is this the boarding service?
if ($item->getService() === $this->getBoardingService())
{
// Clear it
$item->setServiceTotal(0);
// Set it
$item->setBoardingTotal($item->getTotal());
// Calculate deposit required
$itemDepositRequired = $this->getDepositRequirement($item);
$item->setDeposit($itemDepositRequired);
// Is the deposit too high?
if ($itemDepositRequired > $item->getTotal())
{
// Reduce deposit down to total
$item->setDeposit($item->getTotal());
}
}
else
{
// Clear it
$item->setBoardingTotal(0);
// Set it
$item->setServiceTotal($item->getTotal());
}
// Is this a top level item?
if (!$item->getParent())
{
// Grab payments (for items with child items, payments go against the top/parent item)
$payments = $this->em->getRepository(Payment::class)->findFiltered([
"item" => $item->getParent() ? $item->getParent() : $item
]);
// Grab payments
$totalPaid = 0;
foreach ($payments as $somePayment)
$totalPaid += $somePayment->getAmount();
// Set it
$item->setPaid($totalPaid);
}
else
{
// Clear paid amount
$item->setPaid(0);
}
}
public function refreshTotals(\App\Entity\Bookings\Booking $booking, bool $flushChanges = false)
{
// Refresh the booking (i.e. pull it fresh from DB)
$this->em->refresh($booking);
// Generate additional charges on booking
$booking = $this->generateAdditionalCharges($booking);
// Calculate boarding and service totals
$animalTotalArray = array();
// Track some things
$discountTotal = 0;
$boardingItemCount = 0;
$total = 0;
$subTotal = 0;
$depositTotal = 0;
// Get the items
$items = $this->em->getRepository(Item::class)->findFiltered([
"booking" => $booking,
"parent" => null
], [
"i.id" => "ASC"
]);
// Count how many boarding items there are
foreach ($items as $someItem)
{
// Boarding Service?
if ($someItem->getService() == $this->getBoardingService() && !$booking->getIsServiceOnlyBooking())
$boardingItemCount++;
}
// Loop items
foreach ($items as $someItem)
{
// Boarding Service?
if ($someItem->getService() == $this->getBoardingService())
{
// Service only booking?
if ($booking->getIsServiceOnlyBooking())
{
// Default to 0
$someItem->setRate(0);
}
else
{
// Calculate the boarding price
$boardingRate = $this->calculateBoardingPrice($someItem);
// Does this item have a late checkout?
if ($someItem->isOverdue())
{
// Original total not set?
if (!$someItem->getOriginalTotal())
$someItem->setOriginalTotal($someItem->getTotal());
// Update the overdue charge
$someItem->setOverdueCharge($boardingRate - $someItem->getOriginalTotal());
}
// Percentage discount?
$discountRate = 0;
if ($booking->getDiscountPercentage() > 0)
{
$discountRate = ($boardingRate * (1 + $booking->getDiscountPercentage())) - $boardingRate;
$discountTotal += $discountRate;
}
// Fixed discount?
elseif ($booking->getDiscount() > 0)
{
// We spread the fixed discount across all the booking items
$discountRate = $booking->getDiscount() / $boardingItemCount;
$discountTotal += $discountRate;
}
// Set item rate
$someItem->setRate($boardingRate - $discountRate);
}
}
elseif ($someItem->getService())
{
// Calculate the price
$calculatedPrice = $this->calculatePrice($someItem);
// Set item rate
$someItem->setRate($calculatedPrice['total']);
$someItem->setServiceTotal($calculatedPrice['total']);
// Save prices on item
$someItem->setWeekdayDayPrice($calculatedPrice['weekdayDayPrice']);
$someItem->setWeekdayNightPrice($calculatedPrice['weekdayNightPrice']);
$someItem->setWeekendDayPrice($calculatedPrice['weekendDayPrice']);
$someItem->setWeekendNightPrice($calculatedPrice['weekendNightPrice']);
}
// Refresh the item totals
$this->refreshItemTotals($someItem);
// Grab totals
$total += $someItem->getCalculatedTotal();
$subTotal += $someItem->getSubTotal();
// Persist item
$this->em->persist($someItem);
// Got an animal?
if ($someItem->getAnimal())
{
// Animal not in array yet
if (!array_key_exists($someItem->getAnimal()->getId(), $animalTotalArray))
{
$animalTotalArray[$someItem->getAnimal()->getId()] = array(
"animal" => $someItem->getAnimal(),
"boardingTotal" => 0,
"serviceTotal" => 0
);
// Boarding?
if ($someItem->getService() == $this->getBoardingService())
$animalTotalArray[$someItem->getAnimal()->getId()]["boardingTotal"] += $someItem->getCalculatedTotal();
elseif ($someItem->getService())
$animalTotalArray[$someItem->getAnimal()->getId()]["serviceTotal"] += $someItem->getCalculatedTotal();
}
}
}
// Set totals
$booking->setSubTotal($subTotal);
$booking->setTotal($total);
$booking->setTotalDiscount($discountTotal);
// Is the deposit more than the total?
if ($depositTotal > $booking->getTotal())
$depositTotal = $booking->getTotal();
// Set deposit
$booking->setDeposit($depositTotal);
// Persist the booking
$this->em->persist($booking);
// Flushing?
if ($flushChanges)
{
$this->em->flush();
}
return $booking;
}
public function getTotals(\App\Entity\Bookings\Booking $booking)
{
return array(
"subTotal" => number_format($booking->getSubTotal(), 2, '.', ''),
"total" => number_format($booking->getTotal(), 2, '.', ''),
"outstanding" => number_format($booking->getOutstanding(), 2, '.', ''),
"paid" => number_format($booking->getCalculatedPaid(), 2, '.', ''),
"discount" => number_format($booking->getDiscount(), 2, '.', ''),
"outstandingdeposit" => number_format($booking->getCalculatedOutstandingDeposit(), 2, '.', ''),
"deposit" => number_format($booking->getCalculatedDeposit(), 2, '.', '')
);
}
/* This method is designed to assign a mixer category to each animal that is boarding on this booking. */
public function refreshMixerCategories(\App\Entity\Bookings\Booking $booking)
{
// Get animals
$animals = $booking->getAnimals();
// No animals?
if (!count($animals))
return $booking;
// Array to hold those who can share
$animalsThatCanShare = array();
$animalsThatCannotShare = array();
// Keep track of housing used
$housingUsed = array();
// Loop animals
foreach ($animals as $someAnimal)
{
// Find boarding item
$boardingItem = $this->getItem($booking, $someAnimal, $this->getBoardingService());
if (!$boardingItem)
throw new \Exception("Unable to find boarding item for " . $someAnimal->getName());
// Animal type not added to housing array?
if (!array_key_exists($someAnimal->getType()->getId(), $housingUsed))
$housingUsed[$someAnimal->getType()->getId()] = 0;
// Add to mixer array
$housingUsed[$someAnimal->getType()->getId()]++;
// Get the active agreement for this animal
$activeAgreement = $this->animalHelper->getActiveAgreement($someAnimal);
// Does the agreement allow sharing?
if ($activeAgreement && $activeAgreement->getCanShareHousing())
{
// Add it
$animalsThatCanShare[] = $someAnimal;
}
else
{
// Add it
$animalsThatCannotShare[] = $someAnimal;
}
}
// Built an array of animals which can share?
if (count($animalsThatCanShare))
{
// Used for splitting animals into groups
$groupSet = 1;
$capacityCount = array();
// Group animals that can share
$sharerGroups = $this->groupAnimalHousing($animalsThatCanShare);
// Loop the sharing groups
foreach ($sharerGroups as $key => $someGroup)
{
// Extract the animal type from the key
$animalTypeId = explode("_", $key)[1];
// Animal type not added to housing array?
if (!array_key_exists($animalTypeId, $housingUsed))
$housingUsed[$animalTypeId] = 0;
// Add to mixer array
$housingUsed[$animalTypeId]++;
// Loop the animals
foreach ($someGroup['animals'] as $someAnimal)
{
// Find boarding item
$boardingItem = $this->getItem($booking, $someAnimal, $this->getBoardingService());
if (!$boardingItem)
throw new \Exception("Unable to find boarding item for " . $someAnimal->getName());
// Only 1 in the group?
if (count($someGroup['animals']) == 1)
{
// Find mixer category
$mixerCategory = $this->em->getRepository(\App\Entity\Animals\MixerCategory::class)->findOneFiltered(array(
"quantity" => 1,
"isSameHousehold" => true
));
if (!$mixerCategory)
throw new \Exception("Unable to find mixer category for solo.");
// Set mixercategory
$boardingItem->setMixerCategory($mixerCategory);
}
else
{
// Find mixer category
$mixerCategory = $this->em->getRepository(\App\Entity\Animals\MixerCategory::class)->findOneFiltered(array(
"quantity" => count($someGroup['animals']),
"isSameHousehold" => true
));
if (!$mixerCategory)
throw new \Exception("Unable to find mixer category for sharing: " . count($someGroup['animals']));
// Set category
$boardingItem->setMixerCategory($mixerCategory);
}
// Persist
$this->em->persist($boardingItem);
}
}
}
// Loop animals that cannot share
foreach ($animalsThatCannotShare as $key => $someAnimal)
{
// Find boarding item
$boardingItem = $this->getItem($booking, $someAnimal, $this->getBoardingService());
if (!$boardingItem)
throw new \Exception("Unable to find boarding item for " . $someAnimal->getName());
// Get mixer category from animal
$mixerCategory = $someAnimal->getMixerCategory();
// No mixer category?
if(!$mixerCategory)
{
// Find mixer category
$mixerCategory = $this->em->getRepository(\App\Entity\Animals\MixerCategory::class)->findOneFiltered(array(
"quantity" => 1,
"isSameHousehold" => true
));
}
if (!$mixerCategory)
throw new \Exception("Unable to find mixer category");
// Set mixercategory
$boardingItem->setMixerCategory($mixerCategory);
// Persist
$this->em->persist($boardingItem);
}
// Flush
$this->em->flush();
// Set housing used on booking
$booking->setHousingUsed($housingUsed);
// Persist booking
$this->em->persist($booking);
// Flush
$this->em->flush();
return $booking;
}
public function calculateNumberOfHousingUsed(\DateTime $boardingDate, \App\Entity\Bookings\Booking $booking, bool $excludeDepartures = true)
{
// Get items for this day
$boardingItems = $this->em->getRepository(\App\Entity\Bookings\Item::class)->findFiltered(array(
'booking' => $booking,
'service' => $this->getBoardingService(),
'boardingDate' => $boardingDate
));
// No boarding items found for this day, so no housing used!
if (!count($boardingItems))
return [];
$housingUsed = array(); // Keep track of housing used
$animalsThatCanShare = array(); // Animals that can share together
// Loop the boarding items
foreach ($boardingItems as $someBoardingItem)
{
// Are we excluding departures and this animal departs today
if ($excludeDepartures && $someBoardingItem->getDepartureDate() == $boardingDate)
continue;
// Got a mixer category?
if (!$someBoardingItem->getMixerCategory())
continue;
// Get animal
$animal = $someBoardingItem->getAnimal();
// Grab the animal type
$animalType = $animal->getType();
// Grab the housing for this animal type
$housing = $animalType->getHousing();
// Housing type not found?
if (!$housing)
throw new \Exception("Cannot determine housing for animal type: " . $animal->getType()->getId());
// Index it by housing and animal type (to ensure different animal types wont share a house even if they use the same housing)
$index = $housing->getId() . "_" . $animalType->getId();
// Solo mixer category
if ($someBoardingItem->getMixerCategory()->getQuantity() == 1)
{
// Animal housing not added to housing array?
if (!array_key_exists($index, $housingUsed))
{
$housingUsed[$index] = array(
"housing" => $housing,
"type" => $animalType,
"used" => 0,
"animals" => []
);
}
// Increment housing usage
$housingUsed[$index]["used"]++;
$housingUsed[$index]["animals"][] = $animal;
}
else
{
$animalsThatCanShare[] = $animal;
}
}
// Work out who is sharing with who
$sharerGroups = $this->groupAnimalHousing($animalsThatCanShare);
// Loop the sharer group. Each group is a new house, e.g kennel
foreach ($sharerGroups as $key => $someGroup)
{
if (!$someGroup["housing"])
continue;
// Animal housing not added to housing array?
if (!array_key_exists($someGroup["housing"]->getId(), $housingUsed))
{
$housingUsed[$someGroup["housing"]->getId()] = array(
"housing" => $someGroup["housing"],
"type" => $someGroup['type'],
"used" => 0,
"animals" => []
);
}
// Add to count
$housingUsed[$someGroup["housing"]->getId()]["used"]++;
$housingUsed[$someGroup["housing"]->getId()]["animals"] = array_merge($housingUsed[$someGroup["housing"]->getId()]["animals"], $someGroup['animals']);
}
return $housingUsed;
}
public function groupAnimalHousing(array $animalsThatCanShare = array())
{
// Track the capacity
$capacityCount = array();
// Number each group
$groupSet = 0;
// Group animals together
$sharerGroups = array();
// Loop animals that can share, try to position them together
foreach ($animalsThatCanShare as $someAnimal)
{
// Grab the animal type
$animalType = $someAnimal->getType();
// Grab the housing for this animal type
$housing = $animalType->getHousing();
// Index it by housing and animal type (to ensure different animal types wont share a house even if they use the same housing)
$index = $housing->getId() . "_" . $animalType->getId();
// Animal type not looked at yet
if (!array_key_exists($index, $capacityCount))
$capacityCount[$index] = 0;
// Add onto capacity count
$capacityCount[$index] += $this->getCapacityValue($someAnimal);
// Reached limit of 1.0, put the other animal in a seperate group
if ($capacityCount[$index] > 1)
{
$groupSet += 1;
$capacityCount[$index] = 0;
}
// Group does not exist
if (!array_key_exists("group_" . $index . "_" . $groupSet, $sharerGroups))
{
$sharerGroups["group_" . $index . "_" . $groupSet] = array(
"housing" => $someAnimal->getType()->getHousing(),
"animals" => array(),
"type" => $animalType
);
}
// Add to group
$sharerGroups["group_" . $index . "_" . $groupSet]["animals"][] = $someAnimal;
}
return $sharerGroups;
}
public function animalHasConsent(\App\Entity\Bookings\Booking $booking, \App\Entity\Animals\Animal $animal)
{
// Find item for animal on booking
$item = $this->em->getRepository(\App\Entity\Bookings\Item::class)->findOneFiltered(array(
"booking" => $booking,
"animal" => $animal,
"service" => $this->getBoardingService()
));
// Grab the mixer category
$mixerCategory = $item->getMixerCategory();
// Got item but no mixer category
if ($item && !$mixerCategory)
{
// Default to single
$mixerCategory = $this->em->getRepository(MixerCategory::class)->findOneFiltered(array(
"quantity" => 1,
"isSameHousehold" => true
));
}
if ($item && $mixerCategory)
{
// Solo?
if ($mixerCategory->getQuantity() == 1)
{
// Find a valid agreement for this animal (solo is always permitted)
$agreement = $this->em->getRepository(\App\Entity\Customers\Agreement::class)->findOneFiltered(array(
"customer" => $animal->getCustomer(),
"animal" => $animal,
"status" => "completed",
"expiryDate" => array("gte", new \DateTime())
));
if (!$agreement)
return false;
else
{
return [
'agreement' => $agreement,
'validForBooking' => true,
'bookingItem' => $item
];
}
}
// Share with same household
if ($mixerCategory->getQuantity() > 1 && $mixerCategory->getIsSameHousehold())
{
// Find a valid agreement
$agreement = $this->em->getRepository(\App\Entity\Customers\Agreement::class)->findOneFiltered(array(
"customer" => $animal->getCustomer(),
"animal" => $animal,
"status" => "completed",
"expiryDate" => array("gte", new \DateTime())
));
if (!$agreement)
return false;
else
{
return [
'agreement' => $agreement,
'validForBooking' => $agreement->getCanShareHousing(),
'bookingItem' => $item
];
}
}
// Share with different households
if ($mixerCategory->getQuantity() > 1 && !$mixerCategory->getIsSameHousehold())
{
// Find a valid agreement
$agreement = $this->em->getRepository(\App\Entity\Customers\Agreement::class)->findOneFiltered(array(
"customer" => $animal->getCustomer(),
"animal" => $animal,
"status" => "completed",
"expiryDate" => array("gte", new \DateTime())
));
if (!$agreement)
return false;
else
{
return [
'agreement' => $agreement,
'validForBooking' => $agreement->getCanMixHousing(),
'bookingItem' => $item
];
}
}
}
return false;
}
public function getCapacityValue(\App\Entity\Animals\Animal $animal)
{
// Get housing
$capacity = $this->em->getRepository(\App\Entity\Bookings\Capacity::class)->findOneFiltered(array(
"housing" => $animal->getType()->getHousing(),
"size" => $animal->getSize()
));
if ($capacity)
return floatval($capacity->getValue());
return 0;
}
public function animalIsCheckedIn(Booking $booking, Animal $animal)
{
// Get boarding items for this booking
$boardingItems = $this->getBoardingItems($booking);
foreach ($boardingItems as $someItem)
{
if ($someItem->isCheckedIn() && $someItem->getAnimal() == $animal)
return true;
}
return false;
}
public function bookingIsCheckedIn(Booking $booking, $inFull = false)
{
// Get boarding items for this booking
$boardingItems = $this->getBoardingItems($booking);
// Got no items
if (!count($boardingItems))
return false;
// Could how many of the items have checked in
$hasCheckedIn = 0;
foreach ($boardingItems as $someItem)
{
if ($someItem->isCheckedIn())
$hasCheckedIn++;
}
// All checked in or at least one?
if ($inFull)
{
if ($hasCheckedIn == count($boardingItems))
return true;
}
else
{
if ($hasCheckedIn > 0)
return true;
}
return false;
}
public function bookingIsCheckedOut(\App\Entity\Bookings\Booking $booking, $inFull = false)
{
// Get boarding items for this booking
$boardingItems = $this->getBoardingItems($booking);
// Got no items
if (!count($boardingItems))
return false;
// Could how many of the items have checked out
$hasCheckedOut = 0;
foreach ($boardingItems as $someItem)
{
if ($someItem->isCheckedOut())
$hasCheckedOut++;
}
// All checked in or at least one?
if ($inFull)
{
if ($hasCheckedOut == count($boardingItems))
return true;
}
else
{
if ($hasCheckedOut > 0)
return true;
}
return false;
}
public function bookingHasMedicalRecord(\App\Entity\Bookings\Item $bookingItem, \App\Entity\Animals\MedicalRecord $medicalRecord)
{
// Get medical record on booking
$bookingMedicalRecord = $this->em->getRepository(\App\Entity\Bookings\MedicalRecord::class)->findOneFiltered(array(
"medicalRecord" => $medicalRecord,
"bookingItem" => $bookingItem
));
return $bookingMedicalRecord;
}
public function animalHasCurrentMedication(Animal $animal)
{
// Get medical record on booking
$animalMedicalRecord = $this->em->getRepository(\App\Entity\Animals\MedicalRecord::class)->findOneFiltered(array(
"animal" => $animal,
"requiresCheck" => false,
"status" => "current"
));
return $animalMedicalRecord ? true : false;
}
public function getBookingBreakdown(\App\Entity\Bookings\Booking $booking): array
{
// Array to animal breakdowns
$animalBreakdown = array(
"animals" => array(),
"miscItems" => array(),
"totals" => array(
"subTotal" => $booking->getSubTotal(),
"discount" => $booking->getDiscount(),
"total" => $booking->getTotal(),
"paid" => $booking->getCalculatedPaid(),
"deposit" => 0
)
);
// Get the boarding service
$boardingService = $this->getBoardingService();
// Loop animals on booking
if (count($booking->getAnimals()))
{
foreach ($booking->getAnimals() as $someAnimal)
{
// Is animal in array?
if (!array_key_exists($someAnimal->getId(), $animalBreakdown))
{
// Get boarding item
$boardingItem = $this->getItem($booking, $someAnimal, $boardingService);
// Hold eligible service items
$eligibleServiceItems = [];
// Get all top level service items for this animals
$allServiceItems = $this->em->getRepository(\App\Entity\Bookings\Item::class)->findFiltered(array(
"booking" => $booking,
"animal" => $someAnimal,
"parent" => null,
"service" => array("not_in", [$boardingService])
));
foreach ($allServiceItems as $someItem)
{
// Got children?
if (count($someItem->getChildren()))
{
// Loop the children
foreach ($someItem->getChildren() as $someChildItem)
{
// Add it to our eligible array
$eligibleServiceItems[] = $someChildItem;
}
}
else
{
// Add it to our eligible array
$eligibleServiceItems[] = $someItem;
}
}
$animalBreakdown["animals"][$someAnimal->getId()] = array(
"animal" => $someAnimal,
"boardingService" => $boardingItem,
"services" => $eligibleServiceItems,
"miscItems" => $this->em->getRepository(\App\Entity\Bookings\Item::class)->findFiltered(array(
"booking" => $booking,
"animal" => $someAnimal,
"otherItems" => true
)),
'totals' => array(
'subTotal' => 0,
'total' => 0,
'deposit' => 0,
'paid' => 0,
'tax' => 0
)
);
// Get all animal items
$allAnimalItems = $this->em->getRepository(\App\Entity\Bookings\Item::class)->findFiltered(array(
"booking" => $booking,
"animal" => $someAnimal,
));
// Loop the tiems
foreach ($allAnimalItems as $someItem)
{
// Add onto totals
$animalBreakdown["animals"][$someAnimal->getId()]['totals']['subTotal'] += $someItem->getSubTotal();
$animalBreakdown["animals"][$someAnimal->getId()]['totals']['total'] += $someItem->getTotal();
$animalBreakdown["animals"][$someAnimal->getId()]['totals']['deposit'] += $someItem->getDeposit();
$animalBreakdown["animals"][$someAnimal->getId()]['totals']['paid'] += $someItem->getPaid();
$animalBreakdown["animals"][$someAnimal->getId()]['totals']['tax'] += $someItem->getTotalTax();
}
}
}
}
// Get all non animal specific items
$animalBreakdown["miscItems"] = $this->em->getRepository(\App\Entity\Bookings\Item::class)->findFiltered(array(
"booking" => $booking,
"animal" => null
));
return $animalBreakdown;
}
public function calculateRemainingAmounts(\App\Entity\Bookings\Item $boardingItem)
{
// Declare totals
$depositLeftToPay = 0;
$serviceLeftToPay = 0;
$itemPaid = floatval($boardingItem->getPaid());
$itemDeposit = floatval($boardingItem->getDeposit());
$itemTotal = $boardingItem->getTotal();
// Paying off the deposit?
if ($itemPaid < $itemDeposit)
{
$depositLeftToPay = $itemDeposit - $itemPaid;
$serviceLeftToPay = $itemTotal - $itemDeposit;
}
else
{
// Deposit has been paid off
$depositLeftToPay = 0;
// Paid more than the deposit?
if ($itemPaid > $itemDeposit)
{
// Service amount - payments made off service
$serviceLeftToPay = ($itemTotal - $itemDeposit) - ($itemPaid - $itemDeposit);
}
else
$serviceLeftToPay = 0;
}
return array(
"depositAmount" => $depositLeftToPay,
"serviceAmount" => $serviceLeftToPay
);
}
/**
* Build structured array of payments by batching together payments which are for the same item (paid at the same time)
*/
public function buildPaymentsArray($payments = array())
{
$paymentsArray = array();
// Loop payments
foreach ($payments as $somePayment)
{
// Default batch id
$batchId = $somePayment->getId();
// Does the payment have a batch id?
if ($somePayment->getBatchId())
$batchId = $somePayment->getBatchId();
// Payment not in array yet?
if (!array_key_exists($batchId, $paymentsArray))
{
// Add to array
$paymentsArray[$batchId] = array(
"payment" => $somePayment,
"description" => $somePayment->getDescription() ? $somePayment->getDescription() : ($somePayment->getItem() ? $somePayment->getItem()->getDescription() : null),
"childPayments" => array(),
"qty" => 0,
"depositAmount" => 0,
"serviceAmount" => 0,
"total" => 0
);
}
// Add to child payments. This is so the expand payment view on the frontend will show all the payments in detail.
$paymentsArray[$batchId]["childPayments"][] = $somePayment;
// Add totals, deposits, service charges...
$paymentsArray[$batchId]["total"] += $somePayment->getAmount();
$paymentsArray[$batchId]["depositAmount"] += $somePayment->getDepositAmount();
$paymentsArray[$batchId]["serviceAmount"] += $somePayment->getServiceAmount();
$paymentsArray[$batchId]["qty"]++;
}
return $paymentsArray;
}
public function getLongStayChecklistsDue()
{
// Get booking items
$bookingItemsDueChecklist = $this->em->getRepository(\App\Entity\Bookings\Item::class)->findFiltered(array(
'service' => $this->getBoardingService(),
'isOnSite' => true,
'dueLongStayChecklist' => $this->configHelper->getValue('longstay_check_interval')
));
return $bookingItemsDueChecklist;
}
public function getHandsOnChecklistsDue()
{
// Get booking items
$bookingItemsDueChecklist = $this->em->getRepository(\App\Entity\Bookings\Item::class)->findFiltered(array(
'service' => $this->getBoardingService(),
//'isOnSite' => true,
'dueHandsOnChecklist' => $this->configHelper->getValue('handson_check_interval')
));
return $bookingItemsDueChecklist;
}
public function getHousingTicketWeeks(Booking $booking)
{
// Grab an array of the days
$arrayOfDays = [];
$date = new \DateTime($booking->getFirstArrivalDate()->format("Y-m-d"));
$endDate = $booking->getLastDepartureDate();
while ($date <= $endDate)
{
$newDate = new \DateTime($date->format("Y-m-d"));
$arrayOfDays[] = $newDate;
$date->modify('+1 day');
}
// Break it up into weeks
$arrayOfWeeks = array_chunk($arrayOfDays, 14);
return $arrayOfWeeks;
}
public function getOutstandingBalanceByBookingAndAnimal(Booking $booking, Animal $animal)
{
$outstanding = 0;
// Get booking items
$items = $this->em->getRepository(\App\Entity\Bookings\Item::class)->findFiltered([
"booking" => $booking,
"animal" => $animal
]);
foreach ($items as $someItem)
$outstanding += $someItem->getCalculatedOutstanding();
return $outstanding;
}
/*
/* Calculate the capacity used by animal type and housing type for a given date
*/
public function getCapacityByDate(\DateTime $boardingDate, ?\DateTime $excludedDepartureDate = null)
{
// Get items for this day
$boardingItems = $this->em->getRepository(\App\Entity\Bookings\Item::class)->findFiltered(array(
'service' => $this->getBoardingService(),
'boardingDate' => $boardingDate,
'cancelled' => false
));
// Get all mixer categories
$mixerCategories = $this->em->getRepository(MixerCategory::class)->findFiltered([], [
"mc.quantity" => "ASC"
]);
// Get single mixer category
$singleMixerCategory = $this->em->getRepository(MixerCategory::class)->findOneFiltered(array(
"quantity" => 1,
"isSameHousehold" => true
));
// Build an array structure for the mixer categories to be used later
$mixerCategoryStructure = [];
foreach ($mixerCategories as $someMixerCategory)
{
// Create it
$mixerCategoryStructure[$someMixerCategory->getId()] = [
"mixerCategory" => $someMixerCategory,
"animals" => [],
"housingGroups" => []
];
}
// Loop and group by booking
$itemsByBooking = [];
foreach ($boardingItems as $someItem)
{
// Excluding a departure date?
if ($excludedDepartureDate && $someItem->getDepartureDate()->format("Y-m-d") == $excludedDepartureDate->format("Y-m-d"))
continue;
// Grab the booking
$booking = $someItem->getBooking();
// Not got this yet?
if (!array_key_exists($booking->getId(), $itemsByBooking))
{
// Create it
$itemsByBooking[$booking->getId()] = [
"booking" => $booking,
"items" => []
];
}
// Add the item
$itemsByBooking[$booking->getId()]["items"][] = $someItem;
}
// Array to hold it
$capacityByAnimalType = [];
$capacityByHousingType = [];
// Loop the bookings
foreach ($itemsByBooking as $someBooking)
{
// Loop & extract the animals
$animals = [];
foreach ($someBooking['items'] as $someItem)
{
if (!in_array($someItem->getAnimal(), $animals))
$animals[] = $someItem->getAnimal();
}
// Grab animals grouped by type
$animalsByType = $this->groupAnimalsByType($animals);
// Loop types
foreach ($animalsByType as $someType)
{
// Store the data for this booking only
$bookingCapacityByMixerCategory = $mixerCategoryStructure;
// Grab the type
$type = $someType['type'];
// Grab the housing
$housing = $someType['type']->getHousing();
// Don't have this yet?
if (!array_key_exists($type->getId(), $capacityByAnimalType))
{
// Create it
$capacityByAnimalType[$type->getId()] = [
"type" => $type,
"mixerCategories" => $mixerCategoryStructure,
"animals" => [],
"housingGroups" => []
];
}
// Don't have this housing?
if (!$housing)
continue;
// Don't have this yet?
if (!array_key_exists($housing->getId(), $capacityByHousingType))
{
// Create it
$capacityByHousingType[$housing->getId()] = [
"housing" => $housing,
"animals" => [],
"housingGroups" => []
];
}
// Loop animals
foreach ($someType['animals'] as $someAnimal)
{
// Grab the mixer category
$mixerCategory = $this->getMixerCategoryForBookingByAnimal($someAnimal, $someBooking["booking"]);
// Not got it from the booking
if (!$mixerCategory)
$mixerCategory = $someAnimal->getMixerCategory();
// Not got it from the animal? Default to single
if (!$mixerCategory)
$mixerCategory = $singleMixerCategory;
// Add the animal into relevant mixer category
$capacityByAnimalType[$type->getId()]["mixerCategories"][$mixerCategory->getId()]["animals"][] = $someAnimal;
// Add the animal into more generic array
$capacityByAnimalType[$type->getId()]["animals"][] = $someAnimal;
// Add into booking specific array
$bookingCapacityByMixerCategory[$mixerCategory->getId()]["animals"][] = $someAnimal;
// Add the animal into the housing array
$capacityByHousingType[$housing->getId()]["animals"][] = $someAnimal;
}
// Loop the mixer categories
foreach ($bookingCapacityByMixerCategory as $mixerCategoryID => $someMixerCategory)
{
// Break into chunks based on how many animals allowed
$housingGroups = array_chunk($someMixerCategory['animals'], $someMixerCategory['mixerCategory']->getQuantity());
// Add to array
$capacityByAnimalType[$type->getId()]["mixerCategories"][$mixerCategoryID]["housingGroups"] = $housingGroups;
// Add to generic array
$capacityByAnimalType[$type->getId()]["housingGroups"] = array_merge($capacityByAnimalType[$type->getId()]["housingGroups"], $housingGroups);
// Add the animal into the housing array
$capacityByHousingType[$housing->getId()]["housingGroups"] = array_merge($capacityByHousingType[$housing->getId()]["housingGroups"], $housingGroups);
}
}
}
return [
"by_type" => $capacityByAnimalType,
"by_housing" => $capacityByHousingType
];
}
/*
/* Get the mixer category for the boarding service item for a particular animal and booking
*/
public function getMixerCategoryForBookingByAnimal(Animal $animal, Booking $booking)
{
// Find the boarding item
$boardingItem = $this->em->getRepository(\App\Entity\Bookings\Item::class)->findOneFiltered(array(
"service" => $this->getBoardingService(),
"booking" => $booking,
"animal" => $animal
));
// Not found?
if (!$boardingItem)
return null;
return $boardingItem->getMixerCategory();
}
/*
/* Takes an array of animal and returns an array with the animals grouped by type
*/
public function groupAnimalsByType(iterable $animals)
{
// Loop the animals, group by type
$animalsByType = [];
foreach ($animals as $someAnimal)
{
// Grab the type
$type = $someAnimal->getType();
// Not got this one yet?
if (!array_key_exists($type->getId(), $animalsByType))
{
// Create it
$animalsByType[$type->getId()] = [
"type" => $type,
"animals" => []
];
}
// Add the animal
$animalsByType[$type->getId()]["animals"][] = $someAnimal;
}
return $animalsByType;
}
}