src/Entity/Offer.php line 25

Open in your IDE?
  1. <?php
  2. namespace App\Entity;
  3. use App\Exception\CurrencyException;
  4. use App\Exception\InvalidArgumentException;
  5. use DateTime;
  6. use Doctrine\Common\Collections\ArrayCollection;
  7. use Doctrine\Common\Collections\Collection;
  8. use Doctrine\ORM\Mapping as ORM;
  9. use Exception;
  10. use Symfony\Component\Validator\Constraints as Assert;
  11. use Symfony\Component\Validator\Context\ExecutionContextInterface;
  12. /**
  13.  * @ORM\Entity(
  14.  *     repositoryClass="App\Repository\OfferRepository"
  15.  * )
  16.  * @ORM\Table(
  17.  *     name="offer",
  18.  *     options={"collate"="utf8_swedish_ci"}
  19.  * )
  20.  * @ORM\HasLifecycleCallbacks
  21.  */
  22. class Offer implements EntityInterfaceLoggableInterfaceSalesDocumentInterfaceSalesDocumentDiscountInterface
  23. {
  24.     use DocumentTrait;
  25.     use LoggableTrait;
  26.     use SalesItemListTrait;
  27.     const NUMBER_PREFIX 'OF';
  28.     const STATUS_DRAFT 'draft';
  29.     const STATUS_SENT 'sent';
  30.     const STATUS_ORDERED 'ordered';
  31.     const STATUS_EXPIRED 'expired';
  32.     const STATUS_REJECTED 'rejected';
  33.     /**
  34.      * @ORM\Id
  35.      * @ORM\Column(
  36.      *     type="integer"
  37.      * )
  38.      * @ORM\GeneratedValue(
  39.      *     strategy="AUTO"
  40.      * )
  41.      *
  42.      * @var int|null
  43.      */
  44.     protected ?int $id null;
  45.     /**
  46.      * @ORM\Column(
  47.      *     name="append_general_terms_and_conditions",
  48.      *     type="boolean"
  49.      * )
  50.      *
  51.      * @var bool
  52.      */
  53.     protected bool $appendGeneralTermsAndConditions true;
  54.     /**
  55.      * @ORM\OneToOne(
  56.      *     targetEntity="CompanyDocumentAddress",
  57.      *     cascade={"persist","remove"},
  58.      *     orphanRemoval=true
  59.      * )
  60.      * @ORM\JoinColumn(
  61.      *     name="buyer_id"
  62.      * )
  63.      * @Assert\Valid
  64.      *
  65.      * @var CompanyDocumentAddress|null
  66.      */
  67.     protected ?CompanyDocumentAddress $buyer null;
  68.     /**
  69.      * @ORM\Column(
  70.      *     type="boolean"
  71.      * )
  72.      *
  73.      * @var bool
  74.      */
  75.     protected bool $containsCoverSheet false;
  76.     /**
  77.      * @ORM\OneToMany(
  78.      *     targetEntity="CoverSheetRow",
  79.      *     mappedBy="offer",
  80.      *     cascade={"persist", "remove"},
  81.      *     orphanRemoval=true
  82.      * )
  83.      * @orm\OrderBy({"positions" = "ASC"})
  84.      *
  85.      * @var Collection<CoverSheetRow>
  86.      */
  87.     protected Collection $coverSheetRows;
  88.     /**
  89.      * @ORM\OneToOne(
  90.      *     targetEntity="CompanyDocumentAddress",
  91.      *     cascade={"persist","remove"},
  92.      *     orphanRemoval=true
  93.      * )
  94.      * @ORM\JoinColumn(
  95.      *     name="delivery_address_id"
  96.      * )
  97.      * @Assert\Valid
  98.      *
  99.      * @var CompanyDocumentAddress|null
  100.      */
  101.     protected ?CompanyDocumentAddress $deliveryAddress null;
  102.     /**
  103.      * @ORM\OneToMany(
  104.      *     targetEntity="DocumentVersionOffer",
  105.      *     mappedBy="offer",
  106.      *     cascade={"persist","remove"}
  107.      * )
  108.      * @ORM\OrderBy({"created" = "DESC"})
  109.      *
  110.      * @var Collection<DocumentVersionOffer>
  111.      */
  112.     protected Collection $documentVersions;
  113.     /**
  114.      * @ORM\Column(
  115.      *     name="estimated_lead_time",
  116.      *     type="integer",
  117.      *     nullable=true
  118.      * )
  119.      * @Assert\Range(
  120.      *     min=0
  121.      * )
  122.      *
  123.      * @var int|null
  124.      */
  125.     protected ?int $estimatedLeadTime null;
  126.     /**
  127.      * @ORM\Column(
  128.      *     name="estimated_lead_time_note",
  129.      *     type="string",
  130.      *     length=100,
  131.      *     nullable=true
  132.      * )
  133.      *
  134.      * @var string|null
  135.      */
  136.     protected ?string $estimatedLeadTimeNote 'from order';
  137.     /**
  138.      * @ORM\Column(
  139.      *     type="string",
  140.      *     length=5000,
  141.      *     nullable=true
  142.      * )
  143.      *
  144.      * @var string|null
  145.      */
  146.     protected ?string $notes null;
  147.     /**
  148.      * Log entries is a One-To-Many, Unidirectional association with Join Table
  149.      *
  150.      * @ORM\ManyToMany(
  151.      *     targetEntity="LogEntry",
  152.      *     cascade={"persist","remove"},
  153.      *     orphanRemoval=true
  154.      * )
  155.      * @ORM\JoinTable(
  156.      *     name="offer_log_entry",
  157.      *     joinColumns={
  158.      *         @ORM\JoinColumn(
  159.      *             name="offer_id",
  160.      *             referencedColumnName="id",
  161.      *             onDelete="cascade"
  162.      *         )
  163.      *     },
  164.      *     inverseJoinColumns={
  165.      *         @ORM\JoinColumn(
  166.      *             name="log_entry_id",
  167.      *             referencedColumnName="id",
  168.      *             unique=true,
  169.      *             onDelete="cascade"
  170.      *         )
  171.      *     }
  172.      * )
  173.      * @ORM\OrderBy({"time" = "ASC"})
  174.      * @Assert\Valid
  175.      *
  176.      * @var Collection<LogEntry>
  177.      */
  178.     protected $logEntries;
  179.     /**
  180.      * Note! this property is deprecated and should be removed.
  181.      * @deprecated
  182.      *
  183.      * @ORM\Column(
  184.      *     name="opening_date",
  185.      *     type="date",
  186.      *     nullable=true
  187.      * )
  188.      *
  189.      * @var DateTime|null
  190.      */
  191.     protected ?DateTime $openingDate null;
  192.     /**
  193.      * @ORM\OneToOne(
  194.      *     targetEntity="SalesCase",
  195.      *     inversedBy="offer"
  196.      * )
  197.      * @ORM\JoinColumn(
  198.      *     name="sales_case_id",
  199.      *     referencedColumnName="id",
  200.      *     onDelete="cascade"
  201.      * )
  202.      * @Assert\NotNull
  203.      *
  204.      * @var SalesCase
  205.      */
  206.     protected SalesCase $salesCase;
  207.     /**
  208.      * @ORM\ManyToMany(
  209.      *     targetEntity="SalesItem",
  210.      *     cascade={"persist","remove"},
  211.      *     orphanRemoval=true,
  212.      *     fetch="EXTRA_LAZY"
  213.      * )
  214.      * @ORM\JoinTable(
  215.      *     name="offer_sales_item",
  216.      *     joinColumns={
  217.      *         @ORM\JoinColumn(
  218.      *             name="offer_id",
  219.      *             referencedColumnName="id",
  220.      *             onDelete="cascade"
  221.      *         )
  222.      *     },
  223.      *     inverseJoinColumns={
  224.      *         @ORM\JoinColumn(
  225.      *             name="sales_item_id",
  226.      *             referencedColumnName="id",
  227.      *             unique=true,
  228.      *             onDelete="cascade"
  229.      *         )
  230.      *     }
  231.      * )
  232.      * @ORM\OrderBy({"position" = "ASC"})
  233.      * @Assert\Valid
  234.      *
  235.      * @var Collection<SalesItem>
  236.      */
  237.     protected Collection $salesItems;
  238.     /**
  239.      * @var bool
  240.      */
  241.     protected bool $salesItemsPopulated false;
  242.     /**
  243.      * @ORM\Column(
  244.      *     name="signature_date",
  245.      *     type="date",
  246.      *     nullable=true
  247.      * )
  248.      *
  249.      * @var DateTime|null
  250.      */
  251.     protected ?DateTime $signatureDate null;
  252.     /**
  253.      * @ORM\Column(
  254.      *     name="signature_details",
  255.      *     type="string",
  256.      *     length=500,
  257.      *     nullable=true
  258.      * )
  259.      *
  260.      * @var string|null
  261.      */
  262.     protected ?string $signatureDetails null;
  263.     /**
  264.      * @ORM\Column(
  265.      *     name="signature_name",
  266.      *     type="string",
  267.      *     length=200,
  268.      *     nullable=true
  269.      * )
  270.      *
  271.      * @var string|null
  272.      */
  273.     protected ?string $signatureName null;
  274.     /**
  275.      * @ORM\Column(
  276.      *     name="signature_place",
  277.      *     type="string",
  278.      *     length=200,
  279.      *     nullable=true
  280.      * )
  281.      *
  282.      * @var string|null
  283.      */
  284.     protected ?string $signaturePlace 'Helsinki, Finland';
  285.     /**
  286.      * @ORM\Column(
  287.      *     name="terms_of_delivery",
  288.      *     type="string",
  289.      *     length=500,
  290.      *     nullable=true
  291.      * )
  292.      *
  293.      * @var string|null
  294.      */
  295.     protected ?string $termsOfDelivery null;
  296.     /**
  297.      * @ORM\ManyToMany(
  298.      *     targetEntity="TermsOfPaymentRow",
  299.      *     cascade={"persist","remove"},
  300.      *     orphanRemoval=true
  301.      * )
  302.      * @ORM\JoinTable(
  303.      *     name="offer_terms_of_payment_row",
  304.      *     joinColumns={
  305.      *         @ORM\JoinColumn(
  306.      *             name="offer_id",
  307.      *             referencedColumnName="id",
  308.      *             onDelete="cascade"
  309.      *         )
  310.      *     },
  311.      *     inverseJoinColumns={
  312.      *         @ORM\JoinColumn(
  313.      *             name="terms_of_payment_row_id",
  314.      *             referencedColumnName="id",
  315.      *             unique=true,
  316.      *             onDelete="cascade"
  317.      *         )
  318.      *     }
  319.      * )
  320.      * @Assert\Valid
  321.      *
  322.      * @var Collection<TermsOfPaymentRow>
  323.      */
  324.     protected Collection $termsOfPaymentRows;
  325.     /**
  326.      * @ORM\Column(
  327.      *     type="text",
  328.      *     nullable=true
  329.      * )
  330.      *
  331.      * @var string|null
  332.      */
  333.     protected ?string $text null;
  334.     /**
  335.      * @ORM\Column(
  336.      *     type="string",
  337.      *     length=500,
  338.      *     nullable=true
  339.      * )
  340.      *
  341.      * @var string|null
  342.      */
  343.     protected ?string $transportation null;
  344.     /**
  345.      * @ORM\Column(
  346.      *     name="valid_until",
  347.      *     type="date",
  348.      *     nullable=true
  349.      * )
  350.      *
  351.      * @var DateTime|null
  352.      */
  353.     protected ?DateTime $validUntil null;
  354.     /**
  355.      * @ORM\Column(
  356.      *     type="text",
  357.      *     nullable=true
  358.      * )
  359.      *
  360.      * @var string|null
  361.      */
  362.     protected ?string $warranty null;
  363.     public function __construct(SalesCase $salesCase)
  364.     {
  365.         $this->salesCase $salesCase;
  366.         $this->date = new DateTime();
  367.         $this->documentVersions = new ArrayCollection();
  368.         $this->logEntries = new ArrayCollection();
  369.         $this->salesItems = new ArrayCollection();
  370.         $this->termsOfPaymentRows = new ArrayCollection();
  371.     }
  372.     /**
  373.      * @return string
  374.      */
  375.     public function __toString(): string
  376.     {
  377.         return $this->getNumber() . ($this->salesCase->getCompany() !== null ' (' $this->salesCase->getCompany()->getName() . ')' '');
  378.     }
  379.     /**
  380.      * @param CoverSheetRow $coverSheetRow
  381.      */
  382.     public function addCoverSheetRow(CoverSheetRow $coverSheetRow): void
  383.     {
  384.         $this->coverSheetRows->add($coverSheetRow);
  385.     }
  386.     /**
  387.      * @param DocumentVersionOffer $documentVersion
  388.      *
  389.      * @throws InvalidArgumentException
  390.      */
  391.     public function addDocumentVersion(DocumentVersionOffer $documentVersion): void
  392.     {
  393.         if ($documentVersion->getOffer() !== $this) {
  394.             throw new InvalidArgumentException('Document version has mismatching offer document.');
  395.         }
  396.         $this->documentVersions->add($documentVersion);
  397.     }
  398.     /**
  399.      * @param TermsOfPaymentRow $row
  400.      */
  401.     public function addTermsOfPaymentRow(TermsOfPaymentRow $row): void
  402.     {
  403.         $this->termsOfPaymentRows->add($row);
  404.     }
  405.     /**
  406.      * @return bool
  407.      */
  408.     public function getAppendGeneralTermsAndConditions(): bool
  409.     {
  410.         return $this->appendGeneralTermsAndConditions;
  411.     }
  412.     /**
  413.      * @return CompanyDocumentAddress|null
  414.      */
  415.     public function getBuyer(): ?CompanyDocumentAddress
  416.     {
  417.         return $this->buyer;
  418.     }
  419.     /**
  420.      * @return Collection<CoverSheetRow>
  421.      */
  422.     public function getCoverSheetRows(): Collection
  423.     {
  424.         // Sort cover sheet rows
  425.         try {
  426.             $iterator $this->coverSheetRows->getIterator();
  427.             $iterator->uasort(
  428.                 function (CoverSheetRow $aCoverSheetRow $b)
  429.                 {
  430.                     return ($a->parsePositions()[0] < $b->parsePositions()[0]) ? -1;
  431.                 }
  432.             );
  433.             return new ArrayCollection(iterator_to_array($iterator));
  434.         } catch (Exception $e) {
  435.             // Ignore
  436.         }
  437.     }
  438.     /**
  439.      * @return CompanyDocumentAddress|null
  440.      */
  441.     public function getDeliveryAddress(): ?CompanyDocumentAddress
  442.     {
  443.         return $this->deliveryAddress;
  444.     }
  445.     /**
  446.      * @return Collection<DocumentVersionOffer>
  447.      */
  448.     public function getDocumentVersions(): Collection
  449.     {
  450.         return $this->documentVersions;
  451.     }
  452.     /**
  453.      * @return int|null
  454.      */
  455.     public function getEstimatedLeadTime(): ?int
  456.     {
  457.         return $this->estimatedLeadTime;
  458.     }
  459.     /**
  460.      * @return null|string
  461.      */
  462.     public function getEstimatedLeadTimeNote(): ?string
  463.     {
  464.         return $this->estimatedLeadTimeNote;
  465.     }
  466.     /**
  467.      * Calculate offer gross margin based on item specific unit and purchase prices.
  468.      *
  469.      * @return float
  470.      *
  471.      * @throws CurrencyException
  472.      * @throws Exception
  473.      */
  474.     public function getGrossMargin(): float
  475.     {
  476.         $totalCostOfSales $this->getTotalPurchaseValue();
  477.         if (($totalSales $this->getValueNet()) <= 0) {
  478.             throw new Exception('No sales value.');
  479.         }
  480.         return ($totalSales $totalCostOfSales) / $totalSales;
  481.     }
  482.     /**
  483.      * @return int|null
  484.      */
  485.     public function getId(): ?int
  486.     {
  487.         return $this->id;
  488.     }
  489.     /**
  490.      * @return null|string
  491.      */
  492.     public function getNotes(): ?string
  493.     {
  494.         return $this->notes;
  495.     }
  496.     /**
  497.      * @return string
  498.      */
  499.     public function getNumber(): string
  500.     {
  501.         return self::NUMBER_PREFIX str_pad($this->salesCase->getId(), 6'0'STR_PAD_LEFT);
  502.     }
  503.     /**
  504.      * @return DateTime|null
  505.      */
  506.     public function getOpeningDate(): ?DateTime
  507.     {
  508.         return $this->openingDate;
  509.     }
  510.     /**
  511.      * @return string
  512.      */
  513.     public function getPreviewFileName(): string
  514.     {
  515.         return 'Offer - ' $this->getNumber() . ' - Preview ' date('Y-m-d') . '.pdf';
  516.     }
  517.     /**
  518.      * @return SalesCase
  519.      */
  520.     public function getSalesCase(): SalesCase
  521.     {
  522.         return $this->salesCase;
  523.     }
  524.     /**
  525.      * @return Collection<SalesItem>
  526.      */
  527.     public function getSalesItems(): Collection
  528.     {
  529.         if (!$this->salesItemsPopulated) {
  530.             $currency $this->getCurrency();
  531.             foreach ($this->salesItems as $salesItem) {
  532.                 $salesItem->setCurrency($currency);
  533.                 $salesItem->setCurrencyExchangeRate($this->currencyExchangeRate);
  534.             }
  535.             $this->salesItemsPopulated true;
  536.         }
  537.         return $this->salesItems;
  538.     }
  539.     /**
  540.      * @return DateTime|null
  541.      */
  542.     public function getSignatureDate(): ?DateTime
  543.     {
  544.         return $this->signatureDate;
  545.     }
  546.     /**
  547.      * @return string|null
  548.      */
  549.     public function getSignatureDetails(): ?string
  550.     {
  551.         return $this->signatureDetails;
  552.     }
  553.     /**
  554.      * @return null|string
  555.      */
  556.     public function getSignatureName(): ?string
  557.     {
  558.         return $this->signatureName;
  559.     }
  560.     /**
  561.      * @return null|string
  562.      */
  563.     public function getSignaturePlace(): ?string
  564.     {
  565.         return $this->signaturePlace;
  566.     }
  567.     /**
  568.      * @return array<string>
  569.      */
  570.     public static function getStatusChoices(): array
  571.     {
  572.         return [
  573.             self::STATUS_DRAFT,
  574.             self::STATUS_SENT,
  575.             self::STATUS_REJECTED,
  576.         ];
  577.     }
  578.     /**
  579.      * @return string
  580.      */
  581.     public function getStatusForReport(): string
  582.     {
  583.         if (null !== $orderConfirmation $this->getSalesCase()->getOrderConfirmation()) {
  584.             if (!$orderConfirmation->isDraft()) {
  585.                 return self::STATUS_ORDERED;
  586.             }
  587.         }
  588.         if ($this->isExpired()) {
  589.             return self::STATUS_EXPIRED;
  590.         }
  591.         return $this->getStatus();
  592.     }
  593.     /**
  594.      * @return null|string
  595.      */
  596.     public function getTermsOfDelivery(): ?string
  597.     {
  598.         return $this->termsOfDelivery;
  599.     }
  600.     /**
  601.      * @return Collection<TermsOfPaymentRow>
  602.      */
  603.     public function getTermsOfPaymentRows(): Collection
  604.     {
  605.         return $this->termsOfPaymentRows;
  606.     }
  607.     /**
  608.      * @return null|string
  609.      */
  610.     public function getText(): ?string
  611.     {
  612.         return $this->text;
  613.     }
  614.     /**
  615.      * @TODO: currency
  616.      *
  617.      * @return int
  618.      *
  619.      * @throws Exception
  620.      */
  621.     public function getTotalPurchaseValue(): int
  622.     {
  623.         $total 0;
  624.         $noPurchasePrice = [];
  625.         foreach ($this->salesItems as $salesItem) {
  626.             if ($salesItem->isPciSubItem()) {
  627.                 continue;
  628.             }
  629.             if ($salesItem->isPciItem() && null === $salesItem->getPurchasePrice()) {
  630.                 $purchasePricePci $salesItem->getPurchasePrice();
  631.                 if (null === $purchasePricePci) {
  632.                     // Calculate PCI purchase price from sub-items
  633.                     $purchasePricePci 0;
  634.                     foreach ($this->salesItems as $itemTemp) {
  635.                         if ($itemTemp->isPciSubItem()) {
  636.                             if ($itemTemp->getPciPosition() === $salesItem->getPosition()) {
  637.                                 if (null !== $purchasePriceSub $itemTemp->getPurchasePrice()) {
  638.                                     $purchasePricePci += $itemTemp->getQuantity() * $purchasePriceSub;
  639.                                 } else {
  640.                                     $purchasePricePci null;
  641.                                     break;
  642.                                 }
  643.                             }
  644.                         }
  645.                     }
  646.                     $salesItem->setPurchasePrice($purchasePricePci);
  647.                 }
  648.             }
  649.             if (null === $purchasePrice $salesItem->getPurchasePrice()) {
  650.                 $noPurchasePrice[] = $salesItem->getPositionDisplay();
  651.             } else {
  652.                 $total += $purchasePrice $salesItem->getQuantity();
  653.             }
  654.         }
  655.         if (count($noPurchasePrice) > 0) {
  656.             throw new Exception('Positions ' implode(', '$noPurchasePrice) . ' have no purchase price.');
  657.         }
  658.         return $total;
  659.     }
  660.     /**
  661.      * @return null|string
  662.      */
  663.     public function getTransportation(): ?string
  664.     {
  665.         return $this->transportation;
  666.     }
  667.     /**
  668.      * @return DateTime|null
  669.      */
  670.     public function getValidUntil(): ?DateTime
  671.     {
  672.         return $this->validUntil;
  673.     }
  674.     /**
  675.      * @return null|string
  676.      */
  677.     public function getWarranty(): ?string
  678.     {
  679.         return $this->warranty;
  680.     }
  681.     /**
  682.      * @return bool
  683.      */
  684.     public function isContainsCoverSheet(): bool
  685.     {
  686.         return $this->containsCoverSheet;
  687.     }
  688.     /**
  689.      * @return bool
  690.      */
  691.     public function isCurrencyExchangeRateLocked(): bool
  692.     {
  693.         return !in_array(
  694.             $this->status,
  695.             [
  696.                 self::STATUS_DRAFT,
  697.             ]
  698.         );
  699.     }
  700.     /**
  701.      * @return bool
  702.      */
  703.     public function isDraft(): bool
  704.     {
  705.         return $this->getStatus() == self::STATUS_DRAFT;
  706.     }
  707.     /**
  708.      * @return bool
  709.      */
  710.     public function isExpired(): bool
  711.     {
  712.         return $this->validUntil < new DateTime();
  713.     }
  714.     /**
  715.      * @return bool
  716.      */
  717.     public function isRejected(): bool
  718.     {
  719.         return $this->getStatus() == self::STATUS_REJECTED;
  720.     }
  721.     /**
  722.      * @return bool
  723.      */
  724.     public function isSent(): bool
  725.     {
  726.         return $this->getStatus() == self::STATUS_SENT;
  727.     }
  728.     /**
  729.      * @ORM\PostLoad
  730.      */
  731.     public function postLoad(): void
  732.     {
  733.         foreach ($this->salesItems as $salesItem) {
  734.             if (!$salesItem->isPciItem()) {
  735.                 continue;
  736.             }
  737.             // Calculate PCI purchase price from sub-items
  738.             $purchasePrice 0;
  739.             foreach ($this->salesItems as $itemTemp) {
  740.                 if ($itemTemp->isPciSubItem()) {
  741.                     if ($itemTemp->getPciPosition() === $salesItem->getPosition()) {
  742.                         if (null !== $purchasePriceSub $itemTemp->getPurchasePrice()) {
  743.                             $purchasePrice += $itemTemp->getQuantity() * $purchasePriceSub;
  744.                         } else {
  745.                             $purchasePrice null;
  746.                             break;
  747.                         }
  748.                     }
  749.                 }
  750.             }
  751.             $salesItem->setPurchasePrice($purchasePrice);
  752.         }
  753.     }
  754.     /**
  755.      * @param CoverSheetRow $coverSheetRow
  756.      */
  757.     public function removeCoverSheetRow(CoverSheetRow $coverSheetRow): void
  758.     {
  759.         $this->coverSheetRows->removeElement($coverSheetRow);
  760.     }
  761.     /**
  762.      * @param TermsOfPaymentRow $row
  763.      */
  764.     public function removeTermsOfPaymentRow(TermsOfPaymentRow $row): void
  765.     {
  766.         $this->termsOfPaymentRows->removeElement($row);
  767.     }
  768.     /**
  769.      * @param bool $appendGeneralTermsAndConditions
  770.      */
  771.     public function setAppendGeneralTermsAndConditions(bool $appendGeneralTermsAndConditions): void
  772.     {
  773.         $this->appendGeneralTermsAndConditions $appendGeneralTermsAndConditions;
  774.     }
  775.     /**
  776.      * @param CompanyDocumentAddress|null $buyer
  777.      */
  778.     public function setBuyer(?CompanyDocumentAddress $buyer): void
  779.     {
  780.         $this->buyer $buyer;
  781.     }
  782.     /**
  783.      * @param bool $containsCoverSheet
  784.      */
  785.     public function setContainsCoverSheet(bool $containsCoverSheet): void
  786.     {
  787.         $this->containsCoverSheet $containsCoverSheet;
  788.     }
  789.     /**
  790.      * @param CompanyDocumentAddress|null $deliveryAddress
  791.      */
  792.     public function setDeliveryAddress(?CompanyDocumentAddress $deliveryAddress): void
  793.     {
  794.         $this->deliveryAddress $deliveryAddress;
  795.     }
  796.     /**
  797.      * @param int|null $estimatedLeadTime
  798.      */
  799.     public function setEstimatedLeadTime(?int $estimatedLeadTime): void
  800.     {
  801.         $this->estimatedLeadTime $estimatedLeadTime;
  802.     }
  803.     /**
  804.      * @param string|null $estimatedLeadTimeNote
  805.      */
  806.     public function setEstimatedLeadTimeNote(?string $estimatedLeadTimeNote): void
  807.     {
  808.         $this->estimatedLeadTimeNote $estimatedLeadTimeNote;
  809.     }
  810.     /**
  811.      * @param string|null $notes
  812.      */
  813.     public function setNotes(?string $notes): void
  814.     {
  815.         $this->notes $notes;
  816.     }
  817.     /**
  818.      * @param DateTime|null $openingDate
  819.      */
  820.     public function setOpeningDate(?DateTime $openingDate): void
  821.     {
  822.         $this->openingDate $openingDate;
  823.     }
  824.     /**
  825.      * @param DateTime|null $signatureDate
  826.      */
  827.     public function setSignatureDate(?DateTime $signatureDate): void
  828.     {
  829.         $this->signatureDate $signatureDate;
  830.     }
  831.     /**
  832.      * @param string|null $signatureDetails
  833.      */
  834.     public function setSignatureDetails(?string $signatureDetails): void
  835.     {
  836.         $this->signatureDetails $signatureDetails;
  837.     }
  838.     /**
  839.      * @param string|null $signatureName
  840.      */
  841.     public function setSignatureName(?string $signatureName): void
  842.     {
  843.         $this->signatureName $signatureName;
  844.     }
  845.     /**
  846.      * @param string|null $signaturePlace
  847.      */
  848.     public function setSignaturePlace(?string $signaturePlace): void
  849.     {
  850.         $this->signaturePlace $signaturePlace;
  851.     }
  852.     /**
  853.      * @param string|null $termsOfDelivery
  854.      */
  855.     public function setTermsOfDelivery(?string $termsOfDelivery): void
  856.     {
  857.         $this->termsOfDelivery $termsOfDelivery;
  858.     }
  859.     /**
  860.      * @param string|null $text
  861.      */
  862.     public function setText(?string $text): void
  863.     {
  864.         $this->text $text;
  865.     }
  866.     /**
  867.      * @param string|null $transportation
  868.      */
  869.     public function setTransportation(?string $transportation): void
  870.     {
  871.         $this->transportation $transportation;
  872.     }
  873.     /**
  874.      * @param DateTime|null $validUntil
  875.      */
  876.     public function setValidUntil(?DateTime $validUntil): void
  877.     {
  878.         $this->validUntil $validUntil;
  879.     }
  880.     /**
  881.      * @param string|null $warranty
  882.      */
  883.     public function setWarranty(?string $warranty): void
  884.     {
  885.         $this->warranty $warranty;
  886.     }
  887.     /**
  888.      * @Assert\Callback
  889.      *
  890.      * @param ExecutionContextInterface $context
  891.      */
  892.     public function validate(ExecutionContextInterface $context): void
  893.     {
  894.         // Validate currency exchange rate
  895.         if ($this->getCurrency() != 'EUR' && $this->getCurrencyExchangeRate() === null) {
  896.             $context->buildViolation('You must give an exchange rate for a currency other than EUR')
  897.                 ->atPath('currencyExchangeRate')
  898.                 ->addViolation()
  899.             ;
  900.         }
  901.         // Validate cover sheet rows
  902.         if ($this->isContainsCoverSheet()) {
  903.             $salesItemPositions = [];
  904.             foreach ($this->getSalesItems() as $item) {
  905.                 if (!$item->isPciSubItem()) {
  906.                     $salesItemPositions[] = (int) $item->getPositionDisplay();
  907.                 }
  908.             }
  909.             $coverSheetPositions = [];
  910.             foreach ($this->getCoverSheetRows() as $row) {
  911.                 $coverSheetPositions array_merge($coverSheetPositions$row->parsePositions());
  912.             }
  913.             $missingPositions = [];
  914.             $extraPositions = [];
  915.             foreach ($salesItemPositions as $salesItemPosition) {
  916.                 if (!in_array($salesItemPosition$coverSheetPositions)) {
  917.                     $missingPositions[] = $salesItemPosition;
  918.                 }
  919.             }
  920.             foreach ($coverSheetPositions as $coverSheetPosition) {
  921.                 if (!in_array($coverSheetPosition$salesItemPositions)) {
  922.                     $extraPositions[] = $coverSheetPosition;
  923.                 }
  924.             }
  925.             if (count($missingPositions) > 0) {
  926.                 $context->buildViolation('The cover sheet is missing position' . (count($missingPositions) == '' 's') . ' ' implode(', '$missingPositions) . '.')
  927.                     ->atPath('containsCoverSheet')
  928.                     ->addViolation()
  929.                 ;
  930.             }
  931.             if (count($extraPositions) > 0) {
  932.                 $context->buildViolation('The cover sheet has extra position' . (count($extraPositions) == '' 's') . ' ' implode(', '$extraPositions) . '.')
  933.                     ->atPath('containsCoverSheet')
  934.                     ->addViolation()
  935.                 ;
  936.             }
  937.         }
  938.     }
  939. }