src/FMT/Data/Entity/Campaign.php line 30

Open in your IDE?
  1. <?php
  2. namespace FMT\Data\Entity;
  3. use Doctrine\Common\Collections\Collection;
  4. use Doctrine\Common\Collections\ArrayCollection;
  5. use Doctrine\ORM\Mapping as ORM;
  6. use FMT\Data\Traits\EnumTrait;
  7. use FMT\Infrastructure\Helper\CurrencyHelper;
  8. use Gedmo\Timestampable\Traits\TimestampableEntity;
  9. /**
  10.  * Campaign
  11.  *
  12.  * @ORM\Table(
  13.  *     name="campaign",
  14.  *     indexes={
  15.  *          @ORM\Index(name="IX_campaign_status", columns={"status"}),
  16.  *          @ORM\Index(name="FK_campaign_user", columns={"user_id"})
  17.  *     }
  18.  * )
  19.  * @ORM\Entity(repositoryClass="FMT\Data\Repository\CampaignRepository")
  20.  * @ORM\HasLifecycleCallbacks()
  21.  * @SuppressWarnings(PHPMD.ExcessivePublicCount)
  22.  * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
  23.  * @SuppressWarnings(PHPMD.ExcessiveClassLength)
  24.  * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  25.  * @SuppressWarnings(PHPMD.TooManyFields)
  26.  */
  27. class Campaign implements EntityInterface
  28. {
  29.     use TimestampableEntity;
  30.     use EnumTrait;
  31.     // @deprecated, but use as an example with todo-text:
  32.     const STATUS_INACTIVE 0;
  33.     const STATUS_ACTIVE 1;
  34.     const STATUS_PAUSED 2;
  35.     // TODO: added new status? Check messages.en.yml!
  36.     /**
  37.      * @var integer
  38.      *
  39.      * @ORM\Column(name="id", type="integer")
  40.      * @ORM\Id
  41.      * @ORM\GeneratedValue(strategy="IDENTITY")
  42.      */
  43.     private $id;
  44.     /**
  45.      * @var \FMT\Data\Entity\User
  46.      *
  47.      * @ORM\ManyToOne(targetEntity="FMT\Data\Entity\User", inversedBy="campaigns")
  48.      * @ORM\JoinColumns({
  49.      *   @ORM\JoinColumn(name="user_id", referencedColumnName="id")
  50.      * })
  51.      */
  52.     private $user;
  53.     /**
  54.      * @var \DateTime
  55.      *
  56.      * @ORM\Column(name="start_date", type="datetime", nullable=false)
  57.      */
  58.     private $startDate;
  59.     /**
  60.      * @var \DateTime
  61.      *
  62.      * @ORM\Column(name="end_date", type="datetime", nullable=false)
  63.      */
  64.     private $endDate;
  65.     /**
  66.      * @var integer
  67.      *
  68.      * @ORM\Column(name="shipping_option", type="integer", options={"default": 0})
  69.      */
  70.     private $shippingOption;
  71.     /**
  72.      * @var integer
  73.      *
  74.      * @ORM\Column(name="estimated_shipping", type="integer", nullable=true)
  75.      */
  76.     private $estimatedShipping;
  77.     /**
  78.      * @var string
  79.      *
  80.      * @ORM\Column(name="shipping_name", type="string", length=255, nullable=true)
  81.      */
  82.     private $shippingName;
  83.     /**
  84.      * @var integer
  85.      * TODO is used?
  86.      *
  87.      * @ORM\Column(name="estimated_tax", type="integer", nullable=true)
  88.      */
  89.     private $estimatedTax;
  90.     /**
  91.      * @var integer
  92.      *
  93.      * @ORM\Column(name="estimated_cost", type="integer", nullable=true)
  94.      */
  95.     private $estimatedCost;
  96.     /**
  97.      * @var integer
  98.      *
  99.      * @ORM\Column(name="funded_total", type="integer", nullable=false)
  100.      */
  101.     private $fundedTotal 0;
  102.     /**
  103.      * @var integer
  104.      *
  105.      * @ORM\Column(name="purchased_total", type="integer", nullable=false, options={"default": 0})
  106.      */
  107.     private $purchasedTotal 0;
  108.     /**
  109.      * @var integer
  110.      *
  111.      * @ORM\Column(name="donations_from_previous", type="integer", nullable=false, options={"default": 0})
  112.      */
  113.     private $donationsFromPrevious 0;
  114.     /**
  115.      * @var integer
  116.      *
  117.      * @ORM\Column(name="status", type="integer", nullable=false)
  118.      */
  119.     private $status 0;
  120.     /**
  121.      * @var bool
  122.      *
  123.      * @ORM\Column(name="is_paused", type="smallint", nullable=false, options={"default": 0})
  124.      */
  125.     private $isPaused false;
  126.     /**
  127.      * @var \DateTime|null
  128.      *
  129.      * @ORM\Column(name="paused_at", type="datetime", nullable=true)
  130.      */
  131.     private $pausedAt;
  132.     /**
  133.      * @var CampaignBook[]|ArrayCollection
  134.      *
  135.      * @ORM\OneToMany(targetEntity="CampaignBook", mappedBy="campaign", orphanRemoval=true, cascade={"persist"})
  136.      */
  137.     private $books;
  138.     /**
  139.      * @var CampaignContact[]|ArrayCollection
  140.      *
  141.      * @ORM\OneToMany(targetEntity="CampaignContact", mappedBy="campaign", cascade={"persist"})
  142.      */
  143.     private $contacts;
  144.     /**
  145.      * @var Order[]|ArrayCollection
  146.      *
  147.      * @ORM\OneToMany(targetEntity="Order", mappedBy="campaign", orphanRemoval=true, cascade={"persist"})
  148.      */
  149.     private $orders;
  150.     /**
  151.      * @var UserTransaction[]|ArrayCollection
  152.      *
  153.      * @ORM\OneToMany(targetEntity="FMT\Data\Entity\UserTransaction", mappedBy="campaign")
  154.      */
  155.     private $transactions;
  156.     /**
  157.      * @var boolean
  158.      *
  159.      * @ORM\Column(name="mass_mailing_called", type="smallint", nullable=false, options={"default": 0})
  160.      */
  161.     private $massMailingCalled false;
  162.     /**
  163.      * @var UserSchool
  164.      *
  165.      * @ORM\ManyToOne(targetEntity="FMT\Data\Entity\UserSchool", cascade={"persist"})
  166.      * @ORM\JoinColumns({
  167.      *   @ORM\JoinColumn(name="school_id", referencedColumnName="id")
  168.      * })
  169.      */
  170.     private $school;
  171.     /**
  172.      * @ORM\OneToMany(
  173.      *     targetEntity="FMT\Data\Entity\TransactionHistory",
  174.      *     mappedBy="campaign",
  175.      *     cascade={"persist", "remove"},
  176.      *     orphanRemoval=true,
  177.      *     fetch="EAGER"
  178.      * )
  179.      */
  180.     private $transactionHistory;
  181.     /**
  182.      * Campaign constructor.
  183.      */
  184.     public function __construct()
  185.     {
  186.         $this->books = new ArrayCollection();
  187.         $this->contacts = new ArrayCollection();
  188.         $this->orders = new ArrayCollection();
  189.         $this->transactions = new ArrayCollection();
  190.         $this->transactionHistory = new ArrayCollection();
  191.     }
  192.     /**
  193.      * @return int
  194.      */
  195.     public function getId()
  196.     {
  197.         return $this->id;
  198.     }
  199.     /**
  200.      * @return User
  201.      */
  202.     public function getUser()
  203.     {
  204.         return $this->user;
  205.     }
  206.     /**
  207.      * @return string
  208.      */
  209.     public function getFormattedUserId()
  210.     {
  211.         return (string) str_pad($this->user->getID(), 10"0"STR_PAD_LEFT);
  212.     }
  213.     /**
  214.      * @param User $user
  215.      * @return $this
  216.      */
  217.     public function setUser($user)
  218.     {
  219.         $this->user $user;
  220.         return $this;
  221.     }
  222.     /**
  223.      * @return \DateTime
  224.      */
  225.     public function getStartDate()
  226.     {
  227.         return $this->startDate;
  228.     }
  229.     /**
  230.      * @param \DateTime $startDate
  231.      * @return $this
  232.      */
  233.     public function setStartDate($startDate)
  234.     {
  235.         $this->startDate $startDate;
  236.         return $this;
  237.     }
  238.     /**
  239.      * @return \DateTime
  240.      */
  241.     public function getEndDate()
  242.     {
  243.         return $this->endDate;
  244.     }
  245.     /**
  246.      * @param \DateTime $endDate
  247.      * @return $this
  248.      */
  249.     public function setEndDate($endDate)
  250.     {
  251.         $this->endDate $endDate;
  252.         return $this;
  253.     }
  254.     /**
  255.      * @return int
  256.      */
  257.     public function getShippingOption()
  258.     {
  259.         return $this->shippingOption;
  260.     }
  261.     /**
  262.      * @param int $shippingOption
  263.      * @return $this
  264.      */
  265.     public function setShippingOption($shippingOption)
  266.     {
  267.         $this->shippingOption $shippingOption;
  268.         return $this;
  269.     }
  270.     /**
  271.      * @return string
  272.      */
  273.     public function getEstimatedShippingPrice()
  274.     {
  275.         return CurrencyHelper::priceFilter(0); // $this->estimatedShipping
  276.     }
  277.     /**
  278.      * @return int
  279.      */
  280.     public function getEstimatedShipping()
  281.     {
  282.         return 0;
  283.         //return $this->estimatedShipping;
  284.     }
  285.     /**
  286.      * @param int $estimatedShipping
  287.      * @return $this
  288.      */
  289.     public function setEstimatedShipping($estimatedShipping)
  290.     {
  291.         $this->estimatedShipping 0;
  292.         return $this;
  293.     }
  294.     /**
  295.      * @param string|null $name
  296.      * @return $this
  297.      */
  298.     public function setShippingName(?string $name)
  299.     {
  300.         $this->shippingName $name;
  301.         return $this;
  302.     }
  303.     /**
  304.      * @return int
  305.      */
  306.     public function getShippingName()
  307.     {
  308.         return $this->shippingName;
  309.     }
  310.     /**
  311.      * @return string
  312.      */
  313.     public function getEstimatedTaxPrice()
  314.     {
  315.         return CurrencyHelper::priceFilter($this->estimatedTax);
  316.     }
  317.     /**
  318.      * @return int
  319.      */
  320.     public function getEstimatedTax()
  321.     {
  322.        
  323.         return $this->estimatedTax;
  324.     }
  325.     /**
  326.      * @param int $estimatedTax
  327.      * @return $this
  328.      */
  329.     public function setEstimatedTax($estimatedTax)
  330.     {
  331.         $this->estimatedTax $estimatedTax;
  332.         return $this;
  333.     }
  334.    
  335.    
  336.     /**
  337.      * @param int $estimatedCost
  338.      * @return $this
  339.      */
  340.     public function setEstimatedCost($estimatedCost)
  341.     {
  342.         $this->estimatedCost $estimatedCost;
  343.         return $this;
  344.     }
  345.     
  346.     /**
  347.      * Get funded total with option for all campaigns
  348.      * @param bool $allCampaigns Whether to include all campaigns for this user
  349.      * @return int
  350.      */
  351.     public function getFundedTotal(bool $allCampaigns false): int
  352.     {
  353.         if (!$allCampaigns) {
  354.             // Existing logic for current campaign only
  355.             $funded 0;
  356.             foreach ($this->getTransactions() as $transaction) {
  357.                 $transactionNet $transaction->getNet();
  358.                 switch ($transaction->getSubType()) {
  359.                     case Transaction::TXN_DONATION:
  360.                     case Transaction::TXN_BOOK_REFUND:
  361.                         $funded += $transactionNet;
  362.                         break;
  363.                     case Transaction::TXN_BOOK_PURCHASE:
  364.                         $funded += $transactionNet;
  365.                         break;
  366.                 }
  367.             }
  368.             return $funded;
  369.         }
  370.         
  371.         // All campaigns logic
  372.         $totalFunded 0;
  373.         if ($this->user && $this->user->getCampaigns()) {
  374.             foreach ($this->user->getCampaigns() as $campaign) {
  375.                 $totalFunded += $campaign->getFundedTotal(false); // Prevent recursion
  376.             }
  377.         }
  378.         return $totalFunded;
  379.     }
  380.     /**
  381.      * @return string
  382.      */
  383.     public function getFundedTotalPrice()
  384.     {
  385.         return CurrencyHelper::priceFilter($this->getFundedTotal());
  386.     }
  387.     /**
  388.      * @param int $fundedTotal
  389.      * @return $this
  390.      */
  391.     public function setFundedTotal($fundedTotal)
  392.     {
  393.         $this->fundedTotal $fundedTotal;
  394.         return $this;
  395.     }
  396.     /**
  397.      * @deprecated
  398.      * @return int
  399.      */
  400.     public function getStatus()
  401.     {
  402.         return 1//$this->status;
  403.     }
  404.     /**
  405.      * @deprecated
  406.      * @param int $status
  407.      * @return $this
  408.      */
  409.     public function setStatus($status)
  410.     {
  411.         $this->status $status;
  412.         return $this;
  413.     }
  414.     /**
  415.      * @return bool
  416.      */
  417.     public function isPaused()
  418.     {
  419.         return $this->isPaused;
  420.     }
  421.     /**
  422.      * @param bool $boolean
  423.      * @return $this
  424.      */
  425.     public function setPaused(bool $boolean)
  426.     {
  427.         $this->isPaused $boolean;
  428.         return $this;
  429.     }
  430.     /**
  431.      * @return $this
  432.      */
  433.     public function togglePaused()
  434.     {
  435.         $this->isPaused = !$this->isPaused;
  436.         return $this;
  437.     }
  438.     /**
  439.      * @return \DateTime|null
  440.      */
  441.     public function getPausedAt()
  442.     {
  443.         return $this->pausedAt;
  444.     }
  445.     /**
  446.      * @param \DateTime|null $time
  447.      * @return $this
  448.      */
  449.     public function setPausedAt(\DateTime $time null)
  450.     {
  451.         $this->pausedAt $time;
  452.         return $this;
  453.     }
  454.     /**
  455.      * @return bool
  456.      */
  457.     public function isInactive()
  458.     {
  459.         return !$this->isActive();
  460.     }
  461.     /**
  462.      * @return bool
  463.      */
  464.     public function isActive()
  465.     {
  466.         $result false;
  467.         if ($this->startDate && $this->endDate) {
  468.             $now = new \DateTime();
  469.             $now->setTime(00);
  470.             $result $this->startDate <= $now && $this->endDate >= $now;
  471.         }
  472.         return $result;
  473.     }
  474.     /**
  475.      * @return bool
  476.      */
  477.     public function isStarted()
  478.     {
  479.         $result false;
  480.         if ($this->startDate) {
  481.             $now = new \DateTime();
  482.             $now->setTime(00);
  483.             $result $this->startDate <= $now;
  484.         }
  485.         return $result;
  486.     }
  487.     /**
  488.      * @return bool
  489.      */
  490.     public function isFinished()
  491.     {
  492.         $result false;
  493.         if (!$this->startDate || !$this->endDate) {
  494.             return $result;
  495.         }
  496.         $now = new \DateTime();
  497.         $now->setTime(00);
  498.         
  499.         if ($this->endDate <= $now) {
  500.             $result true;
  501.         }
  502.         return $result;
  503.     }
  504.     /**
  505.      * @return CampaignBook[]|ArrayCollection
  506.      */
  507.     public function getBooks()
  508.     {
  509.         return $this->books;
  510.     }
  511.     /**
  512.      * @param ArrayCollection $books
  513.      * @return $this
  514.      */
  515.     public function setBooks(ArrayCollection $books)
  516.     {
  517.         $this->books $books;
  518.         return $this;
  519.     }
  520.     /**
  521.      * @param CampaignBook $book
  522.      * @return $this
  523.      */
  524.     public function addBook(CampaignBook $book)
  525.     {
  526.         if (!$this->books->contains($book)) {
  527.             $this->books->add($book);
  528.             $book->setCampaign($this);
  529.         }
  530.         return $this;
  531.     }
  532.     /**
  533.      * @param CampaignBook $book
  534.      * @return $this
  535.      */
  536.     public function removeBook(CampaignBook $book)
  537.     {
  538.         $this->books->removeElement($book);
  539.         return $this;
  540.     }
  541.     /**
  542.      * @return ArrayCollection|CampaignContact[]
  543.      */
  544.     public function getContacts()
  545.     {
  546.         return $this->contacts;
  547.     }
  548.     /**
  549.      * @param UserContact $contact
  550.      * @return CampaignContact|null
  551.      */
  552.     public function findContact(UserContact $contact)
  553.     {
  554.         $id $contact->getId();
  555.         $existing $this->contacts->filter(function (CampaignContact $item) use ($id) {
  556.             return $item->getContact() && $item->getContact()->getId() === $id;
  557.         });
  558.         return $existing->isEmpty() ? null $existing->first();
  559.     }
  560.     /**
  561.      * @param UserContact $contact
  562.      * @return bool
  563.      */
  564.     public function hasContact(UserContact $contact)
  565.     {
  566.         return $this->findContact($contact) !== null;
  567.     }
  568.     /**
  569.      * @param UserContact $contact
  570.      * @return CampaignContact
  571.      */
  572.     public function addContact(UserContact $contact)
  573.     {
  574.         $result = new CampaignContact();
  575.         $result->setCampaign($this);
  576.         $result->setContact($contact);
  577.         $result->setStatus(CampaignContact::STATUS_UNCONFIRMED);
  578.         $this->contacts->add($result);
  579.         return $result;
  580.     }
  581.     /**
  582.      * @return Order[]
  583.      */
  584.     public function getOrders()
  585.     {
  586.         return $this->orders;
  587.     }
  588.     /**
  589.      * @return float
  590.      */
  591.     public function getOrdersTotal()
  592.     {
  593.         return array_sum(
  594.             array_map(
  595.                 function (Order $order) {
  596.                     return $order->isCompleted() ? $order->getTotal() : 0;
  597.                 },
  598.                 $this->orders->getValues()
  599.             )
  600.         );
  601.     }
  602.     /**
  603.      * @return float
  604.      */
  605.     public function getOrdersPrice()
  606.     {
  607.         return array_sum(
  608.             array_map(
  609.                 function (Order $order) {
  610.                     return $order->isCompleted() ? $order->getPrice() : 0;
  611.                 },
  612.                 $this->orders->getValues()
  613.             )
  614.         );
  615.     }
  616.     /**
  617.      * @return int
  618.      */
  619.     public function daysLeft()
  620.     {
  621.         $days 0;
  622.         $now = new \DateTime();
  623.         if ($this->startDate $now) {
  624.             $start = clone $this->startDate;
  625.             $days $start->diff($now)->days;
  626.         }
  627.         return $days;
  628.     }
  629.     /**
  630.      * @return float
  631.      */
  632.     public function getPercentOfFunded()
  633.     {
  634.         
  635.         $campaignGoal $this->getCampaignGoal();
  636.         if ($campaignGoal) {
  637.             return round(floor(100*($this->getFundedTotal() + $this->getPurchasedTotal()) / $campaignGoal) / 1002);
  638.         } else {
  639.             return 0;
  640.         }
  641.     }
  642.     /**
  643.      * @return float
  644.      */
  645.     public function isСollectedFullAmount()
  646.     {
  647.         return $this->getCampaignGoal() <= $this->getFundedTotal() + $this->getPurchasedTotal() + $this->getDonationsFromPrevious();
  648.     }
  649.     /**
  650.      * @return UserMajor|null
  651.      */
  652.     public function getMajor()
  653.     {
  654.         return $this->user $this->user->getMajor() : null;
  655.     }
  656.     /**
  657.      * @return int
  658.      */
  659.     public function getCampaignGoal()
  660.     {
  661.         return $this->estimatedCost// $this->estimatedShipping + 
  662.     }
  663.     /**
  664.      * @return ArrayCollection|UserTransaction[]
  665.      */
  666.     public function getTransactions()
  667.     {
  668.         return $this->transactions;
  669.     }
  670.     /**
  671.      * @param ArrayCollection|UserTransaction[] $transactions
  672.      * @return Campaign
  673.      */
  674.     public function setTransactions($transactions)
  675.     {
  676.         $this->transactions $transactions;
  677.         return $this;
  678.     }
  679.     /**
  680.      * @return int
  681.      */
  682.     public function getPurchasedTotal(): int
  683.     {
  684.         return $this->purchasedTotal;
  685.     }
  686.     /**
  687.      * @return string
  688.      */
  689.     public function getPurchasedTotalPrice()
  690.     {
  691.         return CurrencyHelper::priceFilter($this->getPurchasedTotal());
  692.     }
  693.     /**
  694.      * @param int $purchasedTotal
  695.      * @return Campaign
  696.      */
  697.     public function setPurchasedTotal(int $purchasedTotal): self
  698.     {
  699.         $this->purchasedTotal $purchasedTotal;
  700.         return $this;
  701.     }
  702.     /**
  703.      * @return int
  704.      */
  705.     public function getDonationsFromPrevious(): int
  706.     {
  707.         return $this->donationsFromPrevious;
  708.     }
  709.     /**
  710.      * @param int $donationsFromPrevious
  711.      * @return Campaign
  712.      */
  713.     public function setDonationsFromPrevious(int $donationsFromPrevious): Campaign
  714.     {
  715.         $this->donationsFromPrevious $donationsFromPrevious;
  716.         return $this;
  717.     }
  718.     /**
  719.      * @return int
  720.      */
  721.     public function getAllowedDonateAmount(): int
  722.     {
  723.         return max($this->getCampaignGoal() - $this->getFundedTotal() - $this->getPurchasedTotal(), 0);
  724.     }
  725.     /**
  726.      * @return string
  727.      */
  728.     public function getAllowedDonateAmountPrice()
  729.     {
  730.         return CurrencyHelper::priceFilter($this->getAllowedDonateAmount());
  731.     }
  732.     /**
  733.      * @return void
  734.      */
  735.     public function recalcTotalsByTransactions()
  736.     {
  737.         $funded 0//$this->getDonationsFromPrevious();
  738.         $purchased 0;
  739.         $estimatedCost 0;
  740.         $estimatedTax 0;
  741.         // Include User Transactions
  742.         foreach ($this->getTransactions() as $transaction) {
  743.             $transactionNet $transaction->getNet();
  744.             switch ($transaction->getSubType()) {
  745.                 case Transaction::TXN_DONATION:
  746.                 case Transaction::TXN_BOOK_REFUND:
  747.                     $funded += $transactionNet;
  748.                     break;
  749.                 case Transaction::TXN_BOOK_PURCHASE:
  750.                     $estimatedCost += $transaction->getOrder()->getShipping();
  751.                     $funded += $transactionNet;
  752.                     $purchased += $transaction->getOrder()->getShipping();
  753.                     break;
  754.                 case Transaction::TXN_DIRECT_PURCHASE:
  755.                 case Transaction::TXN_DONOR_REFUND:
  756.                     break;
  757.             }
  758.         }
  759.         // Include Transaction History
  760.         foreach ($this->getTransactionHistory() as $history) {
  761.             //$funded += $history->getNet();
  762.         }
  763.         // Include Book Data
  764.         foreach ($this->getBooks() as $book) {
  765.             if (!$book->getTax()) {
  766.                 $tax = (float)$_ENV['calculate_tax_percent'];
  767.                 $book->setTax($book->getPrice() * $tax);
  768.             }
  769.             $estimatedTax += $book->getTax();
  770.             $bookPrice $book->getPrice() ?? $book->getNet() + $book->getTax() + $book->getFee();
  771.             $estimatedCost += $bookPrice $book->getTax() + $book->getFee();
  772.             switch ($book->getStatus()) {
  773.                 case CampaignBook::STATUS_AVAILABLE:
  774.                     break;
  775.                 case CampaignBook::STATUS_RETURNED:
  776.                 case CampaignBook::STATUS_ORDERED:
  777.                 case CampaignBook::STATUS_OUT_OF_STOCK:
  778.                     $purchased += $bookPrice;
  779.                     break;
  780.             }
  781.         }
  782.         if ($purchased == $this->estimatedCost) {
  783.             $this->setEstimatedShipping(0);
  784.         }
  785.         $this->setEstimatedCost($estimatedCost);
  786.         $this->setEstimatedTax($estimatedTax);
  787.         $this->setFundedTotal($funded);
  788.         $this->setPurchasedTotal($purchased);
  789.     }
  790.     /**
  791.      * original function
  792.      */
  793.     public function _recalcTotalsByTransactions()
  794.     {
  795.         $funded $this->getDonationsFromPrevious();
  796.         $purchased 0;
  797.         $estimatedCost 0;
  798.         $estimatedTax 0;
  799.         foreach ($this->getTransactions() as $transaction) {
  800.             $transactionNet $transaction->getNet();
  801.             switch ($transaction->getSubType()) {
  802.                 case Transaction::TXN_DONATION:
  803.                 case Transaction::TXN_BOOK_REFUND:
  804.                     $funded += $transactionNet;
  805.                     break;
  806.                 case Transaction::TXN_BOOK_PURCHASE:
  807.                     $estimatedCost+= $transaction->getOrder()->getShipping();
  808.                     $funded += $transactionNet;
  809.                     $purchased += $transaction->getOrder()->getShipping();
  810.                     break;
  811.                 case Transaction::TXN_DIRECT_PURCHASE:
  812.                 case Transaction::TXN_DONOR_REFUND:
  813.                     break;
  814.             }
  815.         }
  816.         foreach ($this->getBooks() as $book) {
  817.             if(!$book->getTax()){
  818.                 $tax = (float)$_ENV['calculate_tax_percent'];
  819.                 $book->setTax($book->getPrice() * $tax);
  820.                 
  821.             }
  822.             
  823.             $estimatedTax += $book->getTax();
  824.             $bookPrice $book->getPrice() ?? $book->getNet() + $book->getTax() + $book->getFee();
  825.             $estimatedCost += $bookPrice $book->getTax() + $book->getFee();
  826.             
  827.             switch ($book->getStatus()) {
  828.                 case CampaignBook::STATUS_AVAILABLE:
  829.                     break;
  830.                 case CampaignBook::STATUS_RETURNED:
  831.                 case CampaignBook::STATUS_ORDERED:
  832.                 case CampaignBook::STATUS_OUT_OF_STOCK:
  833.                     $purchased += $bookPrice;
  834.                     break;
  835.             }
  836.             
  837.            
  838.         }
  839.         if ($purchased == $this->estimatedCost) {
  840.             $this->setEstimatedShipping(0);
  841.         }
  842.         
  843.         $this->setEstimatedCost($estimatedCost);
  844.         $this->setEstimatedTax($estimatedTax);
  845.         $this->setFundedTotal($funded);
  846.         $this->setPurchasedTotal($purchased);
  847.     }
  848.     /**
  849.      * @return bool
  850.      */
  851.     public function isPositiveBalance()
  852.     {
  853.         return $this->fundedTotal 0;
  854.     }
  855.     /**
  856.      * @return bool
  857.      */
  858.     public function isMassMailingCalled()
  859.     {
  860.         return $this->massMailingCalled;
  861.     }
  862.     /**
  863.      * @param bool $massMailedCalled
  864.      * @return $this
  865.      */
  866.     public function setMassMailedCalled(bool $massMailingCalled)
  867.     {
  868.         $this->massMailingCalled $massMailingCalled;
  869.         return $this;
  870.     }
  871.     /**
  872.      * @return bool
  873.      */
  874.     public function isMassMailedAvailable(){
  875.         $now = new \DateTime();
  876.         $now->setTime(00);
  877.         if ($this->startDate $now ) {
  878.             $daysToStart date_diff($this->startDate$now)->days;
  879.             return $daysToStart <= 15 && !$this->isMassMailingCalled() ? true false;
  880.         }
  881.         return !$this->isMassMailingCalled() && !$this->isStarted();
  882.     }
  883.     public function getTransactionHistory(): Collection
  884.     {
  885.         return $this->transactionHistory;
  886.     }
  887.     /**
  888.      * Add a transaction history record.
  889.      *
  890.      * @param TransactionHistory $transactionHistory
  891.      * @return $this
  892.      */
  893.     public function addTransactionHistory(TransactionHistory $transaction): self
  894.     {
  895.         if (!$this->transactionHistory->contains($transaction)) {
  896.             $this->transactionHistory[] = $transaction;
  897.             $transaction->setCampaign($this); // Set the owning side
  898.         }
  899.         return $this;
  900.     }
  901.    
  902.     /**
  903.      * Get transfer total with option for all campaigns
  904.      * @param bool $allCampaigns Whether to include all campaigns for this user
  905.      * @return int
  906.      */
  907.     public function getTransferTotal(bool $allCampaigns false): int
  908.     {
  909.         if (!$allCampaigns) {
  910.             // Existing logic for current campaign
  911.             return array_reduce($this->transactionHistory->toArray(), function ($sumTransactionHistory $history) {
  912.                 return $sum $history->getNet();
  913.             }, 0);
  914.         }
  915.         
  916.         // All campaigns logic
  917.         $totalTransfer 0;
  918.         if ($this->user && $this->user->getCampaigns()) {
  919.             foreach ($this->user->getCampaigns() as $campaign) {
  920.                 $totalTransfer += $campaign->getTransferTotal(false); // Prevent recursion
  921.             }
  922.         }
  923.         return $totalTransfer;
  924.     }
  925.     public function getEaUsed(): string
  926.     {
  927.         foreach ($this->transactionHistory as $history) {
  928.             if ($history->getComment() == 1) {
  929.                 return 'Includes EA';
  930.             }
  931.         }
  932.         return '';
  933.     }
  934.     /**
  935.      * Get remaining funds with option for all campaigns
  936.      * @param bool $allCampaigns Whether to include all campaigns for this user
  937.      * @return int
  938.      */
  939.     public function getRemainingFunds(bool $allCampaigns false): int
  940.     {
  941.         return $this->getFundedTotal($allCampaigns) - $this->getTransferTotal($allCampaigns);
  942.     }
  943.     /**
  944.      * Admin-specific methods (convenience wrappers)
  945.      */
  946.     public function getAllTimeFundedTotal(): int
  947.     {
  948.         return $this->getFundedTotal(true);
  949.     }
  950.     public function getAllTimeTransferTotal(): int
  951.     {
  952.         return $this->getTransferTotal(true);
  953.     }
  954.     public function getAllTimeRemainingFunds(): int
  955.     {
  956.         return $this->getRemainingFunds(true);
  957.     }
  958.     /**
  959.      * Get estimated cost with option for all campaigns
  960.      * @param bool $allCampaigns Whether to include all campaigns for this user
  961.      * @return int
  962.      */
  963.     public function getEstimatedCost(bool $allCampaigns false): int
  964.     {
  965.         if (!$allCampaigns) {
  966.             // Existing logic for current campaign only
  967.             return $this->estimatedCost ?? 0;
  968.         }
  969.         
  970.         // All campaigns logic
  971.         $totalEstimatedCost 0;
  972.         if ($this->user && $this->user->getCampaigns()) {
  973.             foreach ($this->user->getCampaigns() as $campaign) {
  974.                 $totalEstimatedCost += $campaign->getEstimatedCost(false); // Prevent recursion
  975.             }
  976.         }
  977.         return $totalEstimatedCost ?? 0;
  978.     }
  979.     /**
  980.      * Get estimated cost for all campaigns (admin convenience method)
  981.      * @return int
  982.      */
  983.     public function getAllTimeEstimatedCost(): int
  984.     {
  985.         return $this->getEstimatedCost(true);
  986.     }
  987.     /**
  988.      * Get formatted all-time totals
  989.      */
  990.     public function getAllTimeFundedTotalPrice(): string
  991.     {
  992.         return CurrencyHelper::priceFilter($this->getAllTimeFundedTotal());
  993.     }
  994.     public function getAllTimeTransferTotalPrice(): string
  995.     {
  996.         return CurrencyHelper::priceFilter($this->getAllTimeTransferTotal());
  997.     }
  998.     public function getAllTimeRemainingFundsPrice(): string
  999.     {
  1000.         return CurrencyHelper::priceFilter($this->getAllTimeRemainingFunds());
  1001.     }
  1002.     public function getAllTimeEstimatedCostPrice(): string
  1003.     {
  1004.         return CurrencyHelper::priceFilter($this->getAllTimeEstimatedCost());
  1005.     }
  1006.     /**
  1007.      * Get formatted estimated cost with option for all campaigns
  1008.      * @param bool $allCampaigns Whether to include all campaigns for this user
  1009.      * @return string
  1010.      */
  1011.     public function getEstimatedCostPrice(bool $allCampaigns false): string
  1012.     {
  1013.         return CurrencyHelper::priceFilter($this->getEstimatedCost($allCampaigns));
  1014.     }
  1015.     /**
  1016.      * Remove a transaction history record.
  1017.      *
  1018.      * @param TransactionHistory $transactionHistory
  1019.      * @return $this
  1020.      */
  1021.     public function removeTransactionHistory(TransactionHistory $transactionHistory): self
  1022.     {
  1023.         if ($this->transactionHistory->contains($transactionHistory)) {
  1024.             $this->transactionHistory->removeElement($transactionHistory);
  1025.             if ($transactionHistory->getCampaign() === $this) {
  1026.                 $transactionHistory->setCampaign(null); // Set the owning side to null
  1027.             }
  1028.         }
  1029.     
  1030.         return $this;
  1031.     }
  1032.     /**
  1033.      * Get all transaction history for the user across all campaigns
  1034.      * This is a virtual property for admin display purposes
  1035.      * 
  1036.      * @return array
  1037.      */
  1038.     public function getAllTimeTransactionHistoryData(): array
  1039.     {
  1040.         if (!$this->user) {
  1041.             return [];
  1042.         }
  1043.         
  1044.         $allTransactions = [];
  1045.         $totalFunded 0;
  1046.         
  1047.         // Calculate total funded across all campaigns
  1048.         foreach ($this->user->getCampaigns() as $campaign) {
  1049.             $totalFunded += $campaign->getFundedTotal(false);
  1050.         }
  1051.         
  1052.         $runningBalance $totalFunded;
  1053.         
  1054.         // Collect all transactions with campaign context
  1055.         foreach ($this->user->getCampaigns() as $campaign) {
  1056.             foreach ($campaign->getTransactionHistory() as $transaction) {
  1057.                 $allTransactions[] = [
  1058.                     'transaction' => $transaction,
  1059.                     'campaign' => $campaign,
  1060.                     'campaign_id' => $campaign->getId(),
  1061.                     'campaign_dates' => $campaign->getStartDate()->format('M d') . ' - ' $campaign->getEndDate()->format('M d, Y'),
  1062.                     'amount' => $transaction->getNet(),
  1063.                     'balance_after' => $runningBalance $transaction->getNet(),
  1064.                     'is_current_campaign' => $campaign->getId() === $this->getId()
  1065.                 ];
  1066.                 $runningBalance -= $transaction->getNet();
  1067.             }
  1068.         }
  1069.         
  1070.         // Sort by date descending (most recent first)
  1071.         usort($allTransactions, function($a$b) {
  1072.             $dateA $a['transaction']->getTransactionDate();
  1073.             $dateB $b['transaction']->getTransactionDate();
  1074.             if (!$dateA || !$dateB) {
  1075.                 return 0;
  1076.             }
  1077.             return $dateB <=> $dateA;
  1078.         });
  1079.         
  1080.         return [
  1081.             'transactions' => $allTransactions,
  1082.             'starting_balance' => $totalFunded,
  1083.             'current_balance' => $totalFunded array_sum(array_column($allTransactions'amount'))
  1084.         ];
  1085.     }
  1086.     /**
  1087.      * Returns the campaign's public URL.
  1088.      */
  1089.     public function getCampaignUrl(): string
  1090.     {
  1091.         return sprintf('/campaign/%d/view'$this->getId());
  1092.     }
  1093.     public function getSchool()
  1094.     {
  1095.         return $this->school;
  1096.     }
  1097.     public function setSchool(UserSchool $school)
  1098.     {
  1099.         $this->school $school;
  1100.         return $this;
  1101.     }
  1102.     public function __toString()
  1103.     {
  1104.         return (string) $this->getId();
  1105.     }
  1106. }