<?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\PurchaseItemRepository")
*
*
* TODO - should we link all purchse meta into a PurchaseItemMeta object instead
* of storing in the associated Content object?
*
*/
class PurchaseItem implements \Serializable
{
const STATUS_CURRENT = -2; //< the item currently being edited...
const STATUS_REPLACED = -1;
const STATUS_INACTIVE = 0; //< expired...
const STATUS_ACTIVE = 1; //< active, will not renew item
const STATUS_ACTIVE_RENEWING = 2; //< active, will renew
const STATUS_ACTIVE_RENEWING_FINAL = 3; //< active, will renew - previously failed an auto-renew attempt and will now retry
// we will add more types in the future
// *is this necessary? can we just look at the product's type?
const TYPE_CLASSIFIED = "classified";
const TYPE_DIRECTORY = "directory";
const TYPE_MEMBERSHIP = "membership";
const TYPE_SHOP = "shop";
const TYPE_OTHER = "other";
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="bigint")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Product", inversedBy="purchase_items")
* @ORM\JoinColumn(nullable=true)
*/
private $product;
/**
* @ORM\Column(type="datetime")
*/
private $purchase_date;
/**
* @ORM\Column(type="integer")
*/
private $status;
/**
* @ORM\Column(type="integer")
*/
private $payment_failed;
/**
* @ORM\Column(type="integer")
*/
private $quantity;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Content", inversedBy="purchase_items", cascade={"persist"})
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/
private $content;
/**
* @ORM\Column(type="string", length=255)
*/
private $old_status;
/**
* @ORM\Column(type="integer")
*/
private $amount;
/**
* @ORM\Column(type="integer")
*/
private $tax;
/**
* @ORM\Column(type="datetime", nullable=true)
*/
private $expires_at;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Purchase", inversedBy="items")
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/
private $purchase;
/**
* @ORM\Column(type="string", length=255)
*/
private $type;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\PurchaseItem")
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/
private $original_item;
/**
* @ORM\Column(type="string", length=128)
*/
private $transaction_id;
/**
* @ORM\Column(type="integer")
*/
private $months_renewed;
/**
* @ORM\ManyToMany(targetEntity="App\Entity\ProductOptionValue", inversedBy="purchase_items")
*/
private $product_option_values;
public function __construct()
{
$this->status = 0;
$this->payment_failed = 0;
$this->quantity = 1;
$this->old_status = "";
$this->amount = 0;
$this->tax = 0;
$this->expires_at = null;
$this->purchase_date = new \DateTime("now");
$this->transaction_id = "";
$this->months_renewed = 0;
$this->purchased_data = "";
$this->product_option_values = new ArrayCollection();
}
public function getId()
{
return $this->id;
}
public function getProduct(): ?Product
{
return $this->product;
}
public function setProduct(?Product $product): self
{
$this->product = $product;
// if setting the product - update the amount
if ($product) {
$this->amount = $product->getPrice();
}
return $this;
}
public function getPurchaseDate(): ?\DateTimeInterface
{
return $this->purchase_date;
}
public function setPurchaseDate(\DateTimeInterface $purchase_date): self
{
$this->purchase_date = $purchase_date;
return $this;
}
public function getStatusText ()
{
switch ($this->status) {
/*
case self::STATUS_EXPIRED:
return "Expired";
*/
case self::STATUS_CURRENT:
return "Current";
case self::STATUS_REPLACED:
return "Replaced";
case self::STATUS_ACTIVE:
return "Active";
case self::STATUS_INACTIVE :
default:
return "Inactive";
}
}
public function getStatus(): ?int
{
return $this->status;
}
public function setStatus(int $status): self
{
$this->status = $status;
return $this;
}
public function getPaymentFailed(): ?int
{
return $this->payment_failed;
}
public function setPaymentFailed(int $payment_failed): self
{
$this->payment_failed = $payment_failed;
return $this;
}
public function getQuantity(): ?int
{
return $this->quantity;
}
public function setQuantity(int $quantity): self
{
$this->quantity = $quantity;
return $this;
}
public function getRecurringDate(): ?\DateTimeInterface
{
return $this->recurring_date;
}
public function setRecurringDate(\DateTimeInterface $recurring_date): self
{
$this->recurring_date = $recurring_date;
return $this;
}
public function getModifiedAt(): ?\DateTimeInterface
{
return $this->modified_at;
}
public function setModifiedAt(\DateTimeInterface $modified_at): self
{
$this->modified_at = $modified_at;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->created_at;
}
public function setCreatedAt(\DateTimeInterface $created_at): self
{
$this->created_at = $created_at;
return $this;
}
public function getContent(): ?Content
{
return $this->content;
}
public function setContent(?Content $content): self
{
$this->content = $content;
return $this;
}
public function getOldStatus(): ?string
{
return $this->old_status;
}
public function setOldStatus(string $old_status = null): self
{
$this->old_status = $old_status ? $old_status : "";
return $this;
}
//calculate and get the subtotal
public function getAmount ()
{
if($this->getProduct()) {
$amount = $this->getProduct()->getPrice();
$amount *= $this->getQuantity();
//$amount += $this->getTax();
if(true) { //if dynamic classified ad
$firstRegion = true;
$firstSite = true;
$featured = false;
foreach($this->product_option_values as $productOptionValue) {
if($productOptionValue->getProductOption() && $productOptionValue->getProductOption()->getTitle() == "graphics" && $productOptionValue->getValue() == "featured") {
$amount = $productOptionValue->getPrice();
$featured = true;
}
}
if($featured) {
$months = 1;
foreach($this->product_option_values as $productOptionValue) {
if($productOptionValue->getProductOption() && $productOptionValue->getProductOption()->getTitle() == "duration") {
switch ($productOptionValue->getValue()) {
case "30":
$months = 1;
break;
case "60":
$months = 2;
break;
case "90":
$months = 3;
break;
}
}
}
$amount = $amount * $months;
foreach($this->product_option_values as $productOptionValue) {
if($productOptionValue->getProductOption() && $productOptionValue->getProductOption()->getTitle() == "sites") {
if($firstSite) {
$firstSite = false;
continue;
}
$amount += $productOptionValue->getPrice();
}
}
}
else {
foreach($this->product_option_values as $productOptionValue) {
//Exception: First region is free for classified ads.
if($firstRegion && $productOptionValue->getProductOption() && $productOptionValue->getProductOption()->getTitle() == "regions"
&& $productOptionValue->getValue() != "All Regions") {
$firstRegion = false;
continue;
}
//Exception: First site is free
if($firstSite && $productOptionValue->getProductOption() && $productOptionValue->getProductOption()->getTitle() == "sites") {
$firstSite = false;
continue;
}
$amount += $productOptionValue->getPrice();
}
}
}
$this->amount = $amount;
}
return $this->amount;
}
//get the stored amount value without calculating it
public function getStoredAmount ()
{
return $this->amount;
}
public function setAmount ($amount = 0)
{
$this->amount = $amount;
return $this;
}
//calculate and get tax
//*Note: No longer used for purchase items. Instead, tax is calculated per Purchase AFTER all deductions / coupons are applied.
public function getTax ()
{
if($this->getProduct() && $this->getProduct()->getType() == Product::PRODUCT_TYPE_SHOP) {
//for now, just tax all shop items 7.25%
$tax_rate = 0.0725;
$amount = $this->getProduct()->getPrice();
$amount *= $this->getQuantity();
$this->tax = $amount * $tax_rate;
}
return $this->tax;
}
//get the stored tax value without calculating it
public function getStoredTax ()
{
return $this->tax;
}
public function setTax ($tax = 0)
{
$this->tax = $tax;
return $this;
}
public function getExpiresAt(): ?\DateTimeInterface
{
return $this->expires_at;
}
public function setExpiresAt(?\DateTimeInterface $expires_at): self
{
$this->expires_at = $expires_at;
return $this;
}
public function getPurchase(): ?Purchase
{
return $this->purchase;
}
public function setPurchase(?Purchase $purchase): self
{
$this->purchase = $purchase;
return $this;
}
public function __toString ()
{
return "Purchase Item: {$this->id}";
}
public function getType(): ?string
{
return $this->type;
}
public function setType(string $type = null): self
{
$this->type = $type ? $type : "";
return $this;
}
public function getFullAmount ()
{
return "$" . number_format(($this->getAmount() / 100), 2);
}
public function getStoredFullAmount ()
{
return "$" . number_format(($this->getStoredAmount() / 100), 2);
}
public function getFullTax ()
{
return "$" . number_format(($this->getTax() / 100), 2);
}
public function getStoredFullTax ()
{
return "$" . number_format(($this->getStoredTax() / 100), 2);
}
/**
* @return Collection|PurchaseItem[]
*/
public function getRenewedItems(): Collection
{
return $this->renewed_items;
}
public function addRenewedItem(PurchaseItem $renewedItem): self
{
if (!$this->renewed_items->contains($renewedItem)) {
$this->renewed_items[] = $renewedItem;
$renewedItem->setOriginalItem($this);
}
return $this;
}
public function removeRenewedItem(PurchaseItem $renewedItem): self
{
if ($this->renewed_items->contains($renewedItem)) {
$this->renewed_items->removeElement($renewedItem);
// set the owning side to null (unless already changed)
if ($renewedItem->getOriginalItem() === $this) {
$renewedItem->setOriginalItem(null);
}
}
return $this;
}
public function getOriginalItem(): ?self
{
return $this->original_item;
}
/**
* This is used when a new purchase item is replacing
*/
public function setOriginalItem(?self $original_item): self
{
$this->original_item = $original_item;
return $this;
}
public function getTransactionId(): ?int
{
return $this->transaction_id;
}
public function setTransactionId(string $transaction_id = null): self
{
$this->transaction_id = $transaction_id ? $transaction_id : "";
return $this;
}
public function getMonthsRenewed(): ?int
{
return $this->months_renewed;
}
public function setMonthsRenewed(int $months_renewed): self
{
$this->months_renewed = $months_renewed;
return $this;
}
public function getProductOptionValues(): Collection
{
return $this->product_option_values;
}
public function setProductOptionValues($product_option_values)
{
$this->product_option_values = $product_option_values;
return $this;
}
public function addProductOptionValue(ProductOptionValue $product_option_value): self
{
if (!$this->product_option_values->contains($product_option_value)) {
$this->product_option_values[] = $product_option_value;
$product_option_value->addPurchaseItem($this);
}
return $this;
}
public function resetProductOptionValues(): self
{
$this->product_option_values = new ArrayCollection();
return $this;
}
public function removeProductOptionValue(ProductOptionValue $product_option_value): self
{
if ($this->product_option_values->contains($product_option_value)) {
$this->product_option_values->removeElement($product_option_value);
$product_option_value->removePurchaseItem($this);
}
return $this;
}
// add fields here
public function serialize ()
{
return serialize(array(
$this->id,
$this->status,
$this->amount,
$this->quantity,
$this->type,
$this->product,
));
}
public function unserialize ($serialized)
{
list (
$this->id,
$this->status,
$this->amount,
$this->quantity,
$this->type,
$this->product
) = unserialize($serialized, array ("allowed_classes" => true));
}
public function toJSON ()
{
return json_encode([
"id" => $this->id,
"status" => $this->status,
"amount" => $this->amount,
"quantity" => $this->quantity,
"type" => $this->type,
"product" => $this->product->toJSON(),
]);
}
}