<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="App\Repository\CartProductRepository")
*/
class CartProduct
{
protected $entityManager;
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="datetime")
*/
private $dateCreated;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Cart", inversedBy="products")
* @ORM\JoinColumn(nullable=true)
*/
private $cart;
/**
* @ORM\Column(type="integer")
*/
private $quantity=1;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Product", inversedBy="inCarts")
* @ORM\JoinColumn(nullable=false)
*/
private $product;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Pricing")
*/
private $pricing;
/**
* @ORM\Column(type="boolean")
*/
private $isShipped=false;
/**
* @ORM\Column(type="float", nullable=true)
*/
private $pricePaidShipping;
/**
* @ORM\Column(type="float", nullable=true)
* Used for historical purpose, otherwise when Product fice change, all Invoices/order and calculation would change too
*/
private $pricePaidProduct;
/**
* @ORM\Column(type="boolean")
*/
private $hasCombinedShipping=false;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\DeliveryMethod")
*/
private $companyDeliveryMethod;
/**
* @ORM\Column(type="array", nullable=true)
*/
private $pricingsUsed = [];
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $carrierName;
/**
* @ORM\Column(type="text", nullable=true)
*/
private $carrierTrackings;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\CustomOrder", inversedBy="items", cascade={"persist"})
*/
private $customOrder;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\MaturinOrder", inversedBy="items", cascade={"persist"})
*/
private $maturinOrder;
/**
* @ORM\Column(type="boolean")
*/
private $forInvoicingOnly=false;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Cart")
*/
private $invoicingCart;
/**
* @ORM\Column(type="boolean")
* Legacy... I think
*/
private $isRecurringOneTimeOnly=false;
/**
* @ORM\Column(type="integer", nullable=true)
* Currently used
*/
private $recurringFrequency;
/**
* @ORM\Column(type="boolean")
*/
private $overwriteSubscription=false;
/**
* @ORM\OneToMany(targetEntity="App\Entity\ProductAdjustment", mappedBy="cartProduct")
*/
private $productAdjustments;
/**
* @ORM\Column(type="integer")
*/
private $qtyMissingInShipment=0;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\CompanyAssociation", inversedBy="cartProducts")
*/
private $addedWhenBeingInAssociation;
public function __construct(){
$this->dateCreated = new \dateTime();
$this->productAdjustments = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getDateCreated(): ?\DateTimeInterface
{
return $this->dateCreated;
}
public function setDateCreated(\DateTimeInterface $dateCreated): self
{
$this->dateCreated = $dateCreated;
return $this;
}
public function getCart(): ?Cart
{
if($this->getForInvoicingOnly())
return $this->getInvoicingCart();
else
return $this->cart;
}
public function setCart(?Cart $cart): self
{
$this->cart = $cart;
return $this;
}
public function getQuantity($forCalculations=false): ?int
{
return $this->quantity;
}
public function setQuantity(int $quantity): self
{
$this->quantity = $quantity;
return $this;
}
public function getProduct(): ?Product
{
return $this->product;
}
public function setProduct(?Product $product): self
{
$this->product = $product;
return $this;
}
public function getPricing(): ?Pricing
{
return $this->pricing;
}
public function setPricing(?Pricing $pricing): self
{
$this->pricing = $pricing;
return $this;
}
public function getCalculatedPrice(){
$qty = $this->getQuantity(true);
$try = 50;
$used = array();
$pricing = $this->buildPricing();
if(count($pricing) > 1){
while($qty > 0){
if($try < 0){
die('Could not determine the pricing, contact support');
break;
}
foreach($pricing as $amount => $p){
if($amount <= $qty){
$used[] = $pricing[$amount];
$qty = $qty - $amount;
break;
}
}
$try--;
}
//Ok we got it all figure out, but they are not 'adding up'
//Let's refilter to add all similar together
$clean = array();
foreach($used as $u){
if(isset($clean[$u['id']])){
$clean[$u['id']]['quantity']++;
$clean[$u['id']]['total'] = $clean[$u['id']]['quantity'] * $clean[$u['id']]['price'];
}else{
$clean[$u['id']] = $u;
$clean[$u['id']]['total'] = $clean[$u['id']]['quantity'] * $clean[$u['id']]['price'];
}
}
sort($clean);
}else{
//We take for granted that the one left is a unit price
$p = current($pricing);
$clean = array();
$clean[]=$p;
$clean[0]['total']=$p['price'] * $qty;;
$clean[0]['quantity'] = $qty;
}
$this->setPricingsUsed($clean);
return $clean;
}
public function hasDiscountFromQuantity(){
if(number_format($this->product->getPricePerProduct(),2) == number_format($this->getPricePerProduct(), 2))
return false;
else
return true;
}
public function getPricePerProduct(){
if($this->getQuantity() > 0)
return round($this->getTotal(false) / $this->getQuantity(true),2);
else
return round($this->getTotal(false), 2);
}
public function getRebateOnMaturinShipping(){
$prices = $this->getCalculatedPrice();
$rebate = 0;
foreach($prices as $p){
$rebate += ($p['price']-$p['rawPrice']) * $p['quantity'];
}
return $rebate;
}
/*
* Return Maturin Fees - Cart coupon
*/
public function getMaturinFee($applyCoupon = true){
$total = $this->getTotal(false);
$maturinFeePc = $this->getProduct()->getMaturinFeePc();
$maturin = $total * $maturinFeePc;
if($applyCoupon){
//Maturin take 10%
$coupon = $this->getCouponSavings();
return round($maturin - $coupon, 2);
}else
return round($maturin, 2);
}
/*
* Return the total of consigned paid by the user
*/
public function getTotalConsigned(){
if($this->getProduct()->getIsConsigned() && $this->getProduct()->getConsignedCost() > 0){
return ($this->getQuantity(true) * $this->getProduct()->getQtyPerUnit()) * $this->getProduct()->getConsignedCost();
}else
return 0;
}
/*
* Return the price the seller see for billing
* which exclude the coupons and the 8% hidden shipping
* Based on the historical when it was purchased in case of a product pricing change
*/
public function getSellerTotal(): float
{
$total = 0;
if ($this->getFinalQuantity() == 0){
return 0;
}
if($this->getForInvoicingOnly() && !empty($this->getInvoicingCart())){
/*
* Because we convert the Multiple products as regular product in a Maturin Order
* See PaymentService.php line 720 ishhh
*
* Let's see if a number was defined as to give to producer
* or taking the origina one
*/
$cart = $this->getInvoicingCart();
$deal = $cart->findDealWithProduct($this->getProduct());
if($deal){
//@TODO bug on price change
if($deal->getPriceToCompany() > 0)
$total = $this->getFinalQuantity() * $deal->getPricetoCompany();
else
if(is_numeric($deal->getProduct()->getProductPriceWithoutFees()))
$total = $this->getFinalQuantity() * $deal->getProduct()->getProductPriceWithoutFees();
}
return $total;
}
$useds = $this->getPricingsUsed();
$qty = 0;
foreach($useds as $u){
$qty += $u['quantity'];
$total += $u['quantity'] * $u['rawPrice'];
}
if($qty != $this->getFinalQuantity()){
$total = 0;
foreach($useds as $u){
$total += $this->getFinalQuantity() * $u['rawPrice'];
break;
}
}
if($total < 1){
return floatval($this->getFinalQuantity()) * floatval($this->getProduct()->getProductPriceWithoutFees());
}
return round($total,2);
}
public function getTotal($applyCoupon=true){
$total = 0;
/*
* Once paid we rely on the paid price not the calculated one
* To don't change historical data
*/
if(empty($this->getPricePaidProduct()) || $this->getPricePaidProduct() <= 0){
$prices = $this->getCalculatedPrice();
foreach($prices as $price){
$total += $price['total'];
}
}else{
$total = $this->getPricePaidProduct();
}
if($applyCoupon){
//We check for Cart Coupon here
//as % need to be applied to every single items
//for Taxes purpose
if(!$this->getProduct()->cantUseCoupon()){
if(!empty($this->getCart()->getUsedCoupon()) && !$this->getCart()->getUsedCoupon()->getIsGiftCertificate()){
$saving = $this->getCart()->getUsedCoupon()->calculateSavings($total);
$total = $total - $saving;
}
}
}
return round($total, 2);
}
/*
* Return the savings of the coupons applied
*/
public function getCouponSavings(){
$saving = 0;
if(!$this->getProduct()->cantUseCoupon()){
if(!empty($this->getCart()->getUsedCoupon()) && !$this->getCart()->getUsedCoupon()->getIsGiftCertificate()){
//if($this->getProduct()->getCompany()->getId() != 181)
$saving = $this->getCart()->getUsedCoupon()->calculateSavings($this->getTotal(false));
}
}
return $saving;
}
/*
* Convert all the pricing and storage to be on 'Single product'
* To calculate pricing in the Cart and transactions
*/
private function buildPricing($reverse=true){
$pricings = array();
$pricing = array();
if($this->getProduct()->getHasDiscount())
$pricings[] = $this->getProduct()->getDiscount();
else
$pricings = $this->getProduct()->getPricings();
//Let'S build all the quantity
foreach($pricings as $price){
$qty = $price->getQuantity(true) * $price->getStorage()->getQuantityProduct();
$pricing[$qty] = array(
'id' => $price->getId(),
'storage' => $price->getStorage()->getTypeTranslated(),
'quantity' => 1,
'total' => 0,
'display' => $price->getDisplayPrice(),
'price' => $price->getSalePrice(),
'rawPrice' => $price->getPrice()
);
}
if($reverse)
krsort($pricing);
else
ksort($pricing);
return $pricing;
}
public function getIsShippedOrEmpty(){
if ($this->getFinalQuantity() == 0){
return true;
}
$isShipped = $this->getIsShipped();
if (!$isShipped){
if (($mo = $this->getMaturinOrder()) != null){
return $mo->getIsShipped();
} else if (($co = $this->getCustomOrder()) != null){
return $co->getIsShipped();
}
}
return $isShipped;
}
public function getIsShipped(): ?bool
{
return $this->isShipped;
}
public function setIsShipped(bool $isShipped): self
{
$this->isShipped = $isShipped;
return $this;
}
public function getPricePaidShipping(): ?float
{
return $this->pricePaidShipping;
}
public function setPricePaidShipping(?float $pricePaidShipping): self
{
$this->pricePaidShipping = $pricePaidShipping;
return $this;
}
public function getPricePaidProduct(): ?float
{
return $this->pricePaidProduct;
}
public function setPricePaidProduct(?float $pricePaidProduct): self
{
$this->pricePaidProduct = $pricePaidProduct;
return $this;
}
public function getHasCombinedShipping(): ?bool
{
return $this->hasCombinedShipping;
}
public function setHasCombinedShipping(bool $hasCombinedShipping): self
{
$this->hasCombinedShipping = $hasCombinedShipping;
return $this;
}
/*
* We put it here, as each producer can ship differently
*/
public function getShippingCompanyCost($otherProductOfSameCompanyInSameOrder=false, $totalSaleOtherProductSameCompany=0){
if($this->getProduct()->isShippedByMaturin()){
return 0;
}else{
$method = $this->getCompanyDeliveryMethod();
if(empty($method)){
$method = $this->getProduct()->getDeliveryMethods()->first();
}
if(empty($method))
return 0;
/*
* Check for price free above
*/
if($method && $method->getFreeAboveATotalOf()){
if($otherProductOfSameCompanyInSameOrder){
if($totalSaleOtherProductSameCompany >= $method->getFreeAboveATotalOf()){
return -1;
}
}
if($this->getSubTotal() >= $method->getFreeAboveATotalOf())
return 0;
}
/*
* Calculating Fixed price shipping
*/
if($method && $method->getFixedPrice() !== null){
if($otherProductOfSameCompanyInSameOrder)
return 0;
else
return $method->getFixedPrice();
}
/*
* Calculating Price for additional items
*/
if($otherProductOfSameCompanyInSameOrder){
if($method->getAdditionalItem()){
$total = $this->getQuantity(true) * $method->getAdditionalItem();
return $total;
}
return 0;
}else{
$total = $method->getFirstItem();
if($this->getQuantity(true) > 1 && $method->getAdditionalItem()){
$cmtp = $this->getQuantity(true) - 1;
$total += $cmtp * $method->getAdditionalItem();
}
return $total;
}
}
}
public function getSubTotal(){
return $this->getProduct()->getPricePerProduct() * $this->getQuantity(true);
}
public function __toString(){
if($this->getIsShipped())
$text = '(S)';
else if ($this->getIsAPickup())
$text = '(P) ';
else
$text = '';
$text .= $this->getQuantity(true).' x ';
$text .= (string)$this->getProduct();
if($this->getProduct()->getTaxable())
$text .= ' +tx';
return $text;
}
public function getCompanyDeliveryMethodString(){
$delivery = $this->getCompanyDeliveryMethod();
//$text = $delivery->getDeliveryDays().' Jour(s) /'.$delivery->getServiceName().' ('.$this->getShippingCompanyCost().'$)';
if($delivery)
return $delivery->getDisplayPrice();
else
return 'Aucune';
}
public function getCompanyDeliveryMethod(): ?DeliveryMethod
{
return $this->companyDeliveryMethod;
}
public function setCompanyDeliveryMethod(?DeliveryMethod $companyDeliveryMethod): self
{
$this->companyDeliveryMethod = $companyDeliveryMethod;
return $this;
}
public function getPricingsUsedString(){
return print_r($this->getPricingsUsed(), true);
}
public function getPricingsUsed(): ?array
{
return $this->pricingsUsed;
}
public function setPricingsUsed(?array $pricingsUsed): self
{
$this->pricingsUsed = $pricingsUsed;
return $this;
}
public function getCarrierName(): ?string
{
return $this->carrierName;
}
public function setCarrierName(?string $carrierName): self
{
$this->carrierName = $carrierName;
return $this;
}
public function getOrderNo(){
return $this->getCart()->getOrderNo().'-'.$this->getId();
}
public function getCarrierTrackings(): ?string
{
return $this->carrierTrackings;
}
public function setCarrierTrackings(?string $carrierTrackings): self
{
$this->carrierTrackings = $carrierTrackings;
return $this;
}
public function getCustomOrder(): ?CustomOrder
{
return $this->customOrder;
}
public function setCustomOrder(?CustomOrder $customOrder): self
{
$this->customOrder = $customOrder;
return $this;
}
public function getMaturinOrder(): ?MaturinOrder
{
return $this->maturinOrder;
}
public function setMaturinOrder(?MaturinOrder $maturinOrder): self
{
$this->maturinOrder = $maturinOrder;
return $this;
}
/*
* We filter here for the delivery methods that are valid for the order at hand
*/
public function getValidDeliveryMethods()
{
$valid = array();
if($this->getAddedWhenBeingInAssociation()){
$valid[]= $this->getCompanyDeliveryMethod();
}else{
$methods = $this->getProduct()->getDeliveryMethods();
foreach($methods as $m){
//In theory all those with null value will return 0
if($m->getFreeAboveATotalOf() < $this->getTotal()){
$valid[]= $m;
}else{
if(!empty($m->getFixedPrice()))
$valid[]= $m;
}
}
}
return $valid;
}
public function setRebateOnMaturinShipping(): ?float
{
}
public function getForInvoicingOnly(): ?bool
{
return $this->forInvoicingOnly;
}
public function setForInvoicingOnly(bool $forInvoicingOnly): self
{
$this->forInvoicingOnly = $forInvoicingOnly;
return $this;
}
public function getInvoicingCart(): ?Cart
{
return $this->invoicingCart;
}
public function setInvoicingCart(?Cart $invoicingCart): self
{
$this->invoicingCart = $invoicingCart;
return $this;
}
public function getIsRecurringOneTimeOnly(): ?bool
{
return $this->isRecurringOneTimeOnly;
}
public function setIsRecurringOneTimeOnly(bool $isRecurringOneTimeOnly): self
{
$this->isRecurringOneTimeOnly = $isRecurringOneTimeOnly;
return $this;
}
public function getRecurringFrequency(): ?int
{
return $this->recurringFrequency;
}
public function setRecurringFrequency(?int $recurringFrequency): self
{
$this->recurringFrequency = $recurringFrequency;
return $this;
}
public function getOverwriteSubscription(): ?bool
{
return $this->overwriteSubscription;
}
public function setOverwriteSubscription(bool $overwriteSubscription): self
{
$this->overwriteSubscription = $overwriteSubscription;
return $this;
}
/**
* @return Collection|ProductAdjustment[]
*/
public function getProductAdjustments(): Collection
{
return $this->productAdjustments;
}
public function hasProductAdjustments():bool {
return $this->getProductAdjustments()->count() > 0;
}
public function getTotalProductAdjustmentQuantity(){
$q = 0;
foreach ($this->getProductAdjustments() as $productAdjustment)
$q += $productAdjustment->getQuantity();
return $q;
}
public function getFinalQuantity(): int{
return $this->getQuantity()+$this->getTotalProductAdjustmentQuantity();
}
public function addProductAdjustment(ProductAdjustment $productAdjustment): self
{
if (!$this->productAdjustments->contains($productAdjustment)) {
$this->productAdjustments[] = $productAdjustment;
$productAdjustment->setCartProduct($this);
}
return $this;
}
public function removeProductAdjustment(ProductAdjustment $productAdjustment): self
{
if ($this->productAdjustments->contains($productAdjustment)) {
$this->productAdjustments->removeElement($productAdjustment);
// set the owning side to null (unless already changed)
if ($productAdjustment->getCartProduct() === $this) {
$productAdjustment->setCartProduct(null);
}
}
return $this;
}
public function getQtyMissingInShipment(): ?int
{
return $this->qtyMissingInShipment;
}
public function setQtyMissingInShipment(int $qtyMissingInShipment): self
{
$this->qtyMissingInShipment = $qtyMissingInShipment;
return $this;
}
public function getAddedWhenBeingInAssociation(): ?CompanyAssociation
{
return $this->addedWhenBeingInAssociation;
}
public function setAddedWhenBeingInAssociation(?CompanyAssociation $addedWhenBeingInAssociation): self
{
$this->addedWhenBeingInAssociation = $addedWhenBeingInAssociation;
return $this;
}
public function setEntityManager($em){
$this->entityManager = $em;
}
//Called here to play with the association problem
//As it make it switch from independant to Maturin
public function isShippedByMaturin($withInAssociation = true){
if($this->getAddedWhenBeingInAssociation() && $this->getIsAPickup())
return false;
return $this->getProduct()->isShippedByMaturin(false);
}
public function getIsAPickup(): bool{
return $this->getCompanyDeliveryMethod() && $this->getCompanyDeliveryMethod()->getIsAPickup();
}
public function getPickupLocationId() {
$dm = $this->getCompanyDeliveryMethod();
if ($dm) {
if ($dm->getIsAPickup()) {
return $dm->getPickupLocationId();
}
}
return false;
}
public function isWithPacking(): bool{
return $this->getCompanyDeliveryMethod() && $this->getCompanyDeliveryMethod()->getWithPacking();
}
/**
* @param Collection|CartProduct[] $cartProducts
* @return array schedules
*/
public static function getAllPickupSchedules($cartProducts): array {
$schedules = [];
foreach ($cartProducts as $cp){
if (($dm = $cp->getCompanyDeliveryMethod()) !== null && $dm->getIsAPickup()
&& !in_array($dm->getPickupSchedule(), $schedules)
){
$schedules[]=$dm->getPickupSchedule();
}
}
return $schedules;
}
/*
*return the first pickUp Location found as all product in cart has the same one
*/
public static function getPickUpAddress($cartProducts){
foreach ($cartProducts as $cp){
if (($dm = $cp->getCompanyDeliveryMethod()) !== null && $dm->getIsAPickup()){
if(!empty($dm->getPickUpAddress())){
return $dm->getPickUpAddress();
}
}
}
return false;
}
public function isMaturinPickUp(){
return $this->getIsAPickup() && $this->getAddedWhenBeingInAssociation() == null;
}
}