src/Entity/OrderConfirmation.php line 26

Open in your IDE?
  1. <?php
  2. namespace App\Entity;
  3. use App\Exception\CurrencyException;
  4. use App\Exception\InvalidArgumentException;
  5. use App\Model\LicenseFileArchive;
  6. use DateTime;
  7. use Doctrine\Common\Collections\ArrayCollection;
  8. use Doctrine\Common\Collections\Collection;
  9. use Doctrine\ORM\Mapping as ORM;
  10. use Exception;
  11. use Symfony\Component\Validator\Constraints as Assert;
  12. use Symfony\Component\Validator\Context\ExecutionContextInterface;
  13. /**
  14.  * @ORM\Entity(
  15.  *     repositoryClass="App\Repository\OrderConfirmationRepository"
  16.  * )
  17.  * @ORM\Table(
  18.  *     name="order_confirmation",
  19.  *     options={"collate"="utf8_swedish_ci"}
  20.  * )
  21.  * @ORM\HasLifecycleCallbacks
  22.  */
  23. class OrderConfirmation implements EntityInterfaceLoggableInterfaceSalesDocumentInterfaceSalesDocumentDiscountInterface
  24. {
  25.     use DocumentTrait;
  26.     use LoggableTrait;
  27.     use SalesItemListTrait;
  28.     use VatJustificationTrait;
  29.     const NUMBER_PREFIX 'OC';
  30.     const ORDER_NUMBER_PREFIX 0;
  31.     const STATUS_DRAFT 'draft';
  32.     const STATUS_NOT_CONFIRMED 'not_confirmed';
  33.     const STATUS_PRELIMINARY_CONFIRMED 'preliminary_confirmed';
  34.     const STATUS_CONFIRMED 'confirmed';
  35.     const STATUS_PARTIALLY_DELIVERED 'partially_delivered';
  36.     const STATUS_EQUIPMENT_DELIVERED 'equipment_delivered';
  37.     const STATUS_DELIVERED 'delivered';
  38.     const STATUS_COMPLETED 'completed';
  39.     const STATUS_RISK_ORDER 'risk_order';
  40.     const STATUS_REJECTED 'rejected';
  41.     /**
  42.      * @ORM\Id
  43.      * @ORM\Column(
  44.      *     type="integer"
  45.      * )
  46.      * @ORM\GeneratedValue(
  47.      *     strategy="AUTO"
  48.      * )
  49.      *
  50.      * @var int|null
  51.      */
  52.     protected ?int $id null;
  53.     /**
  54.      * @ORM\Column(
  55.      *     name="append_general_terms_and_conditions",
  56.      *     type="boolean"
  57.      * )
  58.      *
  59.      * @var bool
  60.      */
  61.     protected bool $appendGeneralTermsAndConditions true;
  62.     /**
  63.      * @ORM\OneToOne(
  64.      *     targetEntity="CompanyDocumentAddress",
  65.      *     cascade={"persist","remove"},
  66.      *     orphanRemoval=true
  67.      * )
  68.      * @ORM\JoinColumn(
  69.      *     name="buyer_id"
  70.      * )
  71.      * @Assert\Valid
  72.      *
  73.      * @var CompanyDocumentAddress|null
  74.      */
  75.     protected ?CompanyDocumentAddress $buyer null;
  76.     /**
  77.      * @ORM\OneToOne(
  78.      *     targetEntity="CompanyDocumentAddress",
  79.      *     cascade={"persist","remove"},
  80.      *     orphanRemoval=true
  81.      * )
  82.      * @ORM\JoinColumn(
  83.      *     name="consignee_id"
  84.      * )
  85.      * @Assert\Valid
  86.      *
  87.      * @var CompanyDocumentAddress|null
  88.      */
  89.     protected ?CompanyDocumentAddress $consignee null;
  90.     /**
  91.      * @ORM\OneToOne(
  92.      *     targetEntity="CompanyDocumentAddress",
  93.      *     cascade={"persist","remove"},
  94.      *     orphanRemoval=true
  95.      * )
  96.      * @ORM\JoinColumn(
  97.      *     name="delivery_address_id"
  98.      * )
  99.      * @Assert\Valid
  100.      *
  101.      * @var CompanyDocumentAddress|null
  102.      */
  103.     protected ?CompanyDocumentAddress $deliveryAddress null;
  104.     /**
  105.      * @ORM\Column(
  106.      *     name="delivery_date",
  107.      *     type="date",
  108.      *     nullable=true
  109.      * )
  110.      *
  111.      * @var DateTime|null
  112.      */
  113.     protected ?DateTime $deliveryDate null;
  114.     /**
  115.      * @ORM\OneToMany(
  116.      *     targetEntity="DocumentVersionOrderConfirmation",
  117.      *     mappedBy="orderConfirmation",
  118.      *     cascade={"persist","remove"}
  119.      * )
  120.      * @ORM\OrderBy({"created" = "DESC"})
  121.      *
  122.      * @var Collection<DocumentVersionOrderConfirmation>
  123.      */
  124.     protected $documentVersions;
  125.     /**
  126.      * @ORM\OneToOne(
  127.      *     targetEntity="DocumentEndCustomer",
  128.      *     cascade={"persist","remove"},
  129.      *     orphanRemoval=true
  130.      * )
  131.      * @ORM\JoinColumn(
  132.      *     name="end_customer_id"
  133.      * )
  134.      * @Assert\Valid
  135.      *
  136.      * @var DocumentEndCustomer|null
  137.      */
  138.     protected ?DocumentEndCustomer $endCustomer null;
  139.     /**
  140.      * @ORM\OneToOne(
  141.      *     targetEntity="CompanyDocumentAddress",
  142.      *     cascade={"persist","remove"},
  143.      *     orphanRemoval=true
  144.      * )
  145.      * @ORM\JoinColumn(
  146.      *     name="invoicing_address_id"
  147.      * )
  148.      * @Assert\Valid
  149.      *
  150.      * @var CompanyDocumentAddress|null
  151.      */
  152.     protected ?CompanyDocumentAddress $invoicingAddress null;
  153.     /**
  154.      * @ORM\ManyToMany(
  155.      *     targetEntity="LogEntry",
  156.      *     cascade={"persist","remove"},
  157.      *     orphanRemoval=true
  158.      * )
  159.      * @ORM\JoinTable(
  160.      *     name="order_confirmation_log_entry",
  161.      *     joinColumns={
  162.      *         @ORM\JoinColumn(
  163.      *             name="order_confirmation_id",
  164.      *             referencedColumnName="id",
  165.      *             onDelete="cascade"
  166.      *         )
  167.      *     },
  168.      *     inverseJoinColumns={
  169.      *         @ORM\JoinColumn(
  170.      *             name="log_entry_id",
  171.      *             referencedColumnName="id",
  172.      *             unique=true,
  173.      *             onDelete="cascade"
  174.      *         )
  175.      *     }
  176.      * )
  177.      * @ORM\OrderBy({"time" = "ASC"})
  178.      * @Assert\Valid
  179.      *
  180.      * @var Collection<LogEntry>
  181.      */
  182.     protected $logEntries;
  183.     /**
  184.      * @ORM\Column(
  185.      *     type="string",
  186.      *     length=5000,
  187.      *     nullable=true
  188.      * )
  189.      *
  190.      * @var string|null
  191.      */
  192.     protected ?string $notes null;
  193.     /**
  194.      * @ORM\Column(
  195.      *     name="order_number",
  196.      *     type="integer",
  197.      *     nullable=true
  198.      * )
  199.      *
  200.      * @var int|null
  201.      */
  202.     protected ?int $orderNumber null;
  203.     /**
  204.      * @ORM\Column(
  205.      *     name="other_instructions",
  206.      *     type="string",
  207.      *     length=1000,
  208.      *     nullable=true
  209.      * )
  210.      *
  211.      * @var string|null
  212.      */
  213.     protected ?string $otherInstructions null;
  214.     /**
  215.      * @ORM\OneToMany(
  216.      *     targetEntity="DeliveryDate",
  217.      *     mappedBy="orderConfirmation",
  218.      *     cascade={"persist","remove"},
  219.      *     orphanRemoval=true
  220.      * )
  221.      * @ORM\OrderBy({"date" = "ASC"})
  222.      *
  223.      * @var Collection<DeliveryDate>
  224.      */
  225.     protected $plannedDeliveryDates;
  226.     /**
  227.      * @ORM\Column(
  228.      *     name="preliminary_confirmation",
  229.      *     type="boolean"
  230.      * )
  231.      *
  232.      * @var bool
  233.      */
  234.     protected bool $preliminaryConfirmation false;
  235.     /**
  236.      * @ORM\OneToOne(
  237.      *     targetEntity="SalesCase",
  238.      *     inversedBy="orderConfirmation"
  239.      * )
  240.      * @ORM\JoinColumn(
  241.      *     name="sales_case_id",
  242.      *     referencedColumnName="id",
  243.      *     onDelete="cascade"
  244.      * )
  245.      * @Assert\NotNull
  246.      *
  247.      * @var SalesCase
  248.      */
  249.     protected SalesCase $salesCase;
  250.     /**
  251.      * @ORM\ManyToMany(
  252.      *     targetEntity="SalesItem",
  253.      *     cascade={"persist","remove"},
  254.      *     orphanRemoval=true,
  255.      *     fetch="EXTRA_LAZY"
  256.      * )
  257.      * @ORM\JoinTable(
  258.      *     name="order_confirmation_sales_item",
  259.      *     joinColumns={
  260.      *         @ORM\JoinColumn(
  261.      *             name="order_confirmation_id",
  262.      *             referencedColumnName="id",
  263.      *             onDelete="cascade"
  264.      *         )
  265.      *     },
  266.      *     inverseJoinColumns={
  267.      *         @ORM\JoinColumn(
  268.      *             name="sales_item_id",
  269.      *             referencedColumnName="id",
  270.      *             unique=true,
  271.      *             onDelete="cascade"
  272.      *         )
  273.      *     }
  274.      * )
  275.      * @ORM\OrderBy({"position" = "ASC"})
  276.      * @Assert\Valid
  277.      *
  278.      * @var Collection<SalesItem>
  279.      */
  280.     protected $salesItems;
  281.     /**
  282.      * @var bool
  283.      */
  284.     protected bool $salesItemsPopulated false;
  285.     /**
  286.      * @ORM\Column(
  287.      *     name="shipping_marks",
  288.      *     type="string",
  289.      *     length=500,
  290.      *     nullable=true
  291.      * )
  292.      *
  293.      * @var string|null
  294.      */
  295.     protected ?string $shippingMarks null;
  296.     /**
  297.      * @ORM\Column(
  298.      *     name="terms_of_delivery",
  299.      *     type="string",
  300.      *     length=500,
  301.      *     nullable=true
  302.      * )
  303.      *
  304.      * @var string|null
  305.      */
  306.     protected ?string $termsOfDelivery null;
  307.     /**
  308.      * @ORM\ManyToMany(
  309.      *     targetEntity="TermsOfPaymentRow",
  310.      *     cascade={"persist","remove"},
  311.      *     orphanRemoval=true
  312.      * )
  313.      * @ORM\JoinTable(
  314.      *     name="order_confirmation_terms_of_payment_row",
  315.      *     joinColumns={
  316.      *         @ORM\JoinColumn(
  317.      *             name="order_confirmation_id",
  318.      *             referencedColumnName="id",
  319.      *             onDelete="cascade"
  320.      *         )
  321.      *     },
  322.      *     inverseJoinColumns={
  323.      *         @ORM\JoinColumn(
  324.      *             name="terms_of_payment_row_id",
  325.      *             referencedColumnName="id",
  326.      *             unique=true,
  327.      *             onDelete="cascade"
  328.      *         )
  329.      *     }
  330.      * )
  331.      * @Assert\Valid
  332.      *
  333.      * @var Collection<TermsOfPaymentRow>
  334.      */
  335.     protected $termsOfPaymentRows;
  336.     /**
  337.      * @ORM\Column(
  338.      *     type="string",
  339.      *     length=2000,
  340.      *     nullable=true
  341.      * )
  342.      *
  343.      * @var string|null
  344.      */
  345.     protected ?string $text null;
  346.     /**
  347.      * @ORM\Column(
  348.      *     type="string",
  349.      *     length=500,
  350.      *     nullable=true
  351.      * )
  352.      *
  353.      * @var string|null
  354.      */
  355.     protected ?string $transportation null;
  356.     /**
  357.      * @param SalesCase $salesCase
  358.      */
  359.     public function __construct(SalesCase $salesCase)
  360.     {
  361.         $this->salesCase $salesCase;
  362.         $this->documentVersions = new ArrayCollection();
  363.         $this->logEntries = new ArrayCollection();
  364.         $this->plannedDeliveryDates = new ArrayCollection();
  365.         $this->salesItems = new ArrayCollection();
  366.         $this->termsOfPaymentRows = new ArrayCollection();
  367.     }
  368.     /**
  369.      * @return string
  370.      */
  371.     public function __toString(): string
  372.     {
  373.         return $this->getNumber() . ($this->salesCase->getCompany() !== null ' (' $this->salesCase->getCompany()->getName() . ')' '');
  374.     }
  375.     /**
  376.      * @param DocumentVersionOrderConfirmation $documentVersion
  377.      *
  378.      * @throws InvalidArgumentException
  379.      */
  380.     public function addDocumentVersion(DocumentVersionOrderConfirmation $documentVersion)
  381.     {
  382.         if ($documentVersion->getOrderConfirmation() !== $this) {
  383.             throw new InvalidArgumentException('Document version has mismatching order confirmation document.');
  384.         }
  385.         $this->documentVersions->add($documentVersion);
  386.     }
  387.     /**
  388.      * @param DeliveryDate $deliveryDate
  389.      *
  390.      * @throws InvalidArgumentException
  391.      */
  392.     public function addPlannedDeliveryDate(DeliveryDate $deliveryDate)
  393.     {
  394.         if ($deliveryDate->getOrderConfirmation() !== $this) {
  395.             throw new InvalidArgumentException('Delivery date has mismatching order confirmation document.');
  396.         }
  397.         $this->plannedDeliveryDates->add($deliveryDate);
  398.     }
  399.     /**
  400.      * @param TermsOfPaymentRow $row
  401.      */
  402.     public function addTermsOfPaymentRow(TermsOfPaymentRow $row)
  403.     {
  404.         if ($this->termsOfPaymentRows === null) {
  405.             $this->termsOfPaymentRows = new ArrayCollection();
  406.         }
  407.         $this->termsOfPaymentRows->add($row);
  408.     }
  409.     /**
  410.      * @return bool
  411.      */
  412.     public function getAppendGeneralTermsAndConditions(): bool
  413.     {
  414.         return $this->appendGeneralTermsAndConditions;
  415.     }
  416.     /**
  417.      * @return CompanyDocumentAddress|null
  418.      */
  419.     public function getBuyer(): ?CompanyDocumentAddress
  420.     {
  421.         return $this->buyer;
  422.     }
  423.     /**
  424.      * @return CompanyDocumentAddress|null
  425.      */
  426.     public function getConsignee(): ?CompanyDocumentAddress
  427.     {
  428.         return $this->consignee;
  429.     }
  430.     /**
  431.      * @return CompanyDocumentAddress|null
  432.      */
  433.     public function getDeliveryAddress(): ?CompanyDocumentAddress
  434.     {
  435.         return $this->deliveryAddress;
  436.     }
  437.     /**
  438.      * @return DateTime|null
  439.      */
  440.     public function getDeliveryDate(): ?DateTime
  441.     {
  442.         return $this->deliveryDate;
  443.     }
  444.     /**
  445.      * @return Collection<DocumentVersionOrderConfirmation>
  446.      */
  447.     public function getDocumentVersions(): Collection
  448.     {
  449.         return $this->documentVersions;
  450.     }
  451.     /**
  452.      * @return DateTime|null
  453.      *
  454.      * @throws Exception
  455.      */
  456.     public function getDueDate(): ?DateTime
  457.     {
  458.         if (null !== $deliveryDate $this->getDeliveryDate()) {
  459.             if (count($this->getTermsOfPaymentRows()) > 0) {
  460.                 /* @var TermsOfPaymentRow $first */
  461.                 $first $this->termsOfPaymentRows->first();
  462.                 if (null !== $days $first->getDays()) {
  463.                     $dueDate = clone $deliveryDate;
  464.                     $dueDate->add(new \DateInterval('P' $days 'D'));
  465.                     return $dueDate;
  466.                 }
  467.             }
  468.         }
  469.         return null;
  470.     }
  471.     /**
  472.      * @return DocumentEndCustomer|null
  473.      */
  474.     public function getEndCustomer(): ?DocumentEndCustomer
  475.     {
  476.         return $this->endCustomer;
  477.     }
  478.     /**
  479.      * @return float
  480.      *
  481.      * @throws CurrencyException
  482.      * @throws Exception
  483.      */
  484.     public function getGrossMargin(): float
  485.     {
  486.         if (($totalSales $this->getValueNet(true)) <= 0) {
  487.             throw new Exception('The OC has no total sales value.');
  488.         }
  489.         $totalCostOfSales 0;
  490.         foreach ($this->salesCase->getPurchaseOrders() as $purchaseOrder) {
  491.             if ($purchaseOrder->isSent()) {
  492.                 $totalCostOfSales += $purchaseOrder->getTotalPurchaseValue(true);
  493.             }
  494.         }
  495.         return ($totalSales $totalCostOfSales) / $totalSales;
  496.     }
  497.     /**
  498.      * @return array
  499.      */
  500.     public function getGrossMarginDisplay(): array
  501.     {
  502.         $value null;
  503.         $error null;
  504.         try {
  505.             $value $this->getGrossMargin();
  506.         } catch (Exception $e) {
  507.             $error $e->getMessage();
  508.         }
  509.         return [$value$error];
  510.     }
  511.     /**
  512.      * @return int|null
  513.      */
  514.     public function getId(): ?int
  515.     {
  516.         return $this->id;
  517.     }
  518.     /**
  519.      * @return LicenseFileArchive|null
  520.      */
  521.     public function getLicenseFileArchive(): ?LicenseFileArchive
  522.     {
  523.         if ($this->hasLicenseFiles()) {
  524.             return new LicenseFileArchive($this->salesCase);
  525.         }
  526.         return null;
  527.     }
  528.     /**
  529.      * @return CompanyDocumentAddress|null
  530.      */
  531.     public function getInvoicingAddress(): ?CompanyDocumentAddress
  532.     {
  533.         return $this->invoicingAddress;
  534.     }
  535.     /**
  536.      * @return null|string
  537.      */
  538.     public function getNotes(): ?string
  539.     {
  540.         return $this->notes;
  541.     }
  542.     /**
  543.      * @return string
  544.      */
  545.     public function getNumber(): string
  546.     {
  547.         return self::NUMBER_PREFIX str_pad($this->salesCase->getId(), 6'0'STR_PAD_LEFT)
  548.         . '-'
  549.         . ($this->isDraft() ? 'DRAFT' self::ORDER_NUMBER_PREFIX str_pad($this->getOrderNumber(), 5'0'STR_PAD_LEFT));
  550.     }
  551.     /**
  552.      * @return int|null
  553.      */
  554.     public function getOrderNumber(): ?int
  555.     {
  556.         return $this->orderNumber;
  557.     }
  558.     /**
  559.      * @return null|string
  560.      */
  561.     public function getOtherInstructions(): ?string
  562.     {
  563.         return $this->otherInstructions;
  564.     }
  565.     /**
  566.      * @return Collection<DeliveryDate>
  567.      */
  568.     public function getPlannedDeliveryDates(): Collection
  569.     {
  570.         return $this->plannedDeliveryDates;
  571.     }
  572.     /**
  573.      * @return string
  574.      */
  575.     public function getPreviewFileName(): string
  576.     {
  577.         return 'Order Confirmation - ' $this->getNumber() . ' - Preview ' date('Y-m-d') . '.pdf';
  578.     }
  579.     /**
  580.      * @return SalesCase
  581.      */
  582.     public function getSalesCase(): SalesCase
  583.     {
  584.         return $this->salesCase;
  585.     }
  586.     /**
  587.      * @return array|null
  588.      *
  589.      * @throws CurrencyException
  590.      */
  591.     public function getSalesCaseCosts(): ?array
  592.     {
  593.         $purchasePrices = [];
  594.         foreach ($this->salesCase->getPurchaseOrders() as $purchaseOrder) {
  595.             if ($purchaseOrder->isSent()) {
  596.                 foreach ($purchaseOrder->getSalesItems() as $salesItem) {
  597.                     if (null !== $purchasePrice $salesItem->getPurchasePrice(true)) {
  598.                         $purchasePrices[$salesItem->getCode()] = $purchasePrice;
  599.                     }
  600.                 }
  601.             }
  602.         }
  603.         if (=== count($purchasePrices)) {
  604.             return null;
  605.         }
  606.         $salesQuantities = [];
  607.         foreach ($this->salesCase->getInvoices() as $invoice) {
  608.             if ($invoice instanceof Invoice || $invoice instanceof CreditInvoice) {
  609.                 if ($invoice->isInvoiced()) {
  610.                     foreach ($invoice->getSalesItems() as $salesItem) {
  611.                         if (!array_key_exists($salesItem->getCode(), $salesQuantities)) {
  612.                             $salesQuantities[$salesItem->getCode()] = 0;
  613.                         }
  614.                         $quantity $salesItem->getQuantity();
  615.                         if ($invoice instanceof CreditInvoice) {
  616.                             $quantity = -$quantity;
  617.                         }
  618.                         $salesQuantities[$salesItem->getCode()] += $quantity;
  619.                     }
  620.                 }
  621.             }
  622.         }
  623.         $costs = [];
  624.         foreach ($this->salesItems as $salesItem) {
  625.             $code $salesItem->getCode();
  626.             $quantity array_key_exists($code$salesQuantities) ? $salesQuantities[$code] : null;
  627.             $unitPrice array_key_exists($code$purchasePrices) ? $purchasePrices[$code] : null;
  628.             $cost null;
  629.             if (null !== $quantity && null !== $unitPrice) {
  630.                 $cost $quantity $unitPrice;
  631.             }
  632.             $costs[$salesItem->getCode()] = [
  633.                 'position' => $salesItem->getPositionDisplay(),
  634.                 'code' => $code,
  635.                 'quantity' => $quantity,
  636.                 'unitPrice' => $unitPrice,
  637.                 'cost' => $cost,
  638.             ];
  639.         }
  640.         return $costs;
  641.     }
  642.     /**
  643.      * @param bool $includeDrafts
  644.      *
  645.      * @return int
  646.      */
  647.     public function getSalesItemInvoicedQuantity(bool $includeDrafts true): int
  648.     {
  649.         $quantity 0;
  650.         foreach ($this->getSalesCase()->getInvoicesOfTypeInvoice() as $invoice) {
  651.             if (!$includeDrafts && $invoice->isDraft()) {
  652.                 continue;
  653.             }
  654.             foreach ($invoice->getSalesItems() as $item) {
  655.                 $quantity += $item->getQuantity();
  656.             }
  657.         }
  658.         foreach ($this->getSalesCase()->getInvoicesOfTypeCreditInvoice() as $invoice) {
  659.             if (!$includeDrafts && $invoice->isDraft()) {
  660.                 continue;
  661.             }
  662.             foreach ($invoice->getSalesItems() as $item) {
  663.                 $quantity -= $item->getQuantity();
  664.             }
  665.         }
  666.         return $quantity;
  667.     }
  668.     /**
  669.      * @param bool $includeDrafts
  670.      *
  671.      * @return int
  672.      */
  673.     public function getSalesItemPurchasedQuantity(bool $includeDrafts true): int
  674.     {
  675.         $quantity 0;
  676.         foreach ($this->getSalesCase()->getPurchaseOrders() as $purchaseOrder) {
  677.             if ($purchaseOrder->isRejected() || (!$includeDrafts && $purchaseOrder->isDraft())) {
  678.                 continue;
  679.             }
  680.             foreach ($purchaseOrder->getSalesItems() as $item) {
  681.                 $quantity += $item->getQuantity();
  682.             }
  683.         }
  684.         return $quantity;
  685.     }
  686.     /**
  687.      * @return Collection<SalesItem>
  688.      */
  689.     public function getSalesItems(): Collection
  690.     {
  691.         if (!$this->salesItemsPopulated) {
  692.             $currency $this->getCurrency();
  693.             foreach ($this->salesItems as $salesItem) {
  694.                 $salesItem->setCurrency($currency);
  695.                 $salesItem->setCurrencyExchangeRate($this->currencyExchangeRate);
  696.             }
  697.             $this->salesItemsPopulated true;
  698.         }
  699.         return $this->salesItems;
  700.     }
  701.     /**
  702.      * @return null|string
  703.      */
  704.     public function getShippingMarks(): ?string
  705.     {
  706.         return $this->shippingMarks;
  707.     }
  708.     /**
  709.      * @return array<string>
  710.      */
  711.     public static function getStatusChoices(): array
  712.     {
  713.         return [
  714.             self::STATUS_DRAFT,
  715.             self::STATUS_NOT_CONFIRMED,
  716.             self::STATUS_PRELIMINARY_CONFIRMED,
  717.             self::STATUS_CONFIRMED,
  718.             self::STATUS_PARTIALLY_DELIVERED,
  719.             self::STATUS_EQUIPMENT_DELIVERED,
  720.             self::STATUS_DELIVERED,
  721.             self::STATUS_COMPLETED,
  722.             self::STATUS_RISK_ORDER,
  723.             self::STATUS_REJECTED,
  724.         ];
  725.     }
  726.     /**
  727.      * @return array<Supplier>
  728.      *
  729.      * @throws Exception
  730.      */
  731.     public function getSuppliers(): array
  732.     {
  733.         $list = new ArrayCollection();
  734.         foreach ($this->getSalesItems() as $item) {
  735.             if (null !== $supplier $item->getSupplier()) {
  736.                 if (!$list->contains($supplier)) {
  737.                     $list->add($supplier);
  738.                 }
  739.             }
  740.         }
  741.         $iterator $list->getIterator();
  742.         $iterator->uasort(
  743.             function (Supplier $aSupplier $b) {
  744.                 if ($a->getName() == $b->getName()) {
  745.                     return 0;
  746.                 }
  747.                 return ($a->getName() < $b->getName()) ? -1;
  748.             }
  749.         );
  750.         return $iterator->getArrayCopy();
  751.     }
  752.     /**
  753.      * @return Collection<SalesItem>
  754.      */
  755.     public function getSupportItems(): Collection
  756.     {
  757.         return $this->getSalesItems()->filter(
  758.             function (SalesItem $salesItem) {
  759.                 return $salesItem->isSupport();
  760.             }
  761.         );
  762.     }
  763.     /**
  764.      * @return null|string
  765.      */
  766.     public function getTermsOfDelivery(): ?string
  767.     {
  768.         return $this->termsOfDelivery;
  769.     }
  770.     /**
  771.      * @return Collection<TermsOfPaymentRow>
  772.      */
  773.     public function getTermsOfPaymentRows(): Collection
  774.     {
  775.         return $this->termsOfPaymentRows;
  776.     }
  777.     /**
  778.      * @return null|string
  779.      */
  780.     public function getText(): ?string
  781.     {
  782.         return $this->text;
  783.     }
  784.     /**
  785.      * @return null|string
  786.      */
  787.     public function getTransportation(): ?string
  788.     {
  789.         return $this->transportation;
  790.     }
  791.     /**
  792.      * @param bool $includeDrafts
  793.      *
  794.      * @return array<SalesItem>
  795.      *
  796.      * @throws Exception
  797.      */
  798.     public function getUninvoicedSalesItems(bool $includeDrafts true): array
  799.     {
  800.         /* @var SalesItem[] $list */
  801.         $list = [];
  802.         foreach ($this->getSalesItems() as $ocItem) {
  803.             $list[$ocItem->getPositionNormalized()] = clone $ocItem;
  804.         }
  805.         foreach ($this->salesCase->getInvoicesOfTypeInvoice() as $invoice) {
  806.             if (!$includeDrafts && $invoice->isDraft()) {
  807.                 continue;
  808.             }
  809.             foreach ($invoice->getSalesItems() as $invItem) {
  810.                 $position $invItem->getPositionNormalized();
  811.                 if (isset($list[$position])) {
  812.                     $itemQuantity $invItem->getQuantity();
  813.                     $list[$position]->setQuantity(
  814.                         $list[$position]->getQuantity() - $itemQuantity
  815.                     );
  816.                     if ($invItem->isPciItem() && !$invItem->isPciExpandItem()) {
  817.                         // Subtract PCI sub-items from the list of uninvoiced because they are not listed in the invoice
  818.                         foreach ($list as $itemTemp) {
  819.                             if ($itemTemp->isPciSubItem() && $itemTemp->getPciPosition() === $position) {
  820.                                 $itemTemp->setQuantity(
  821.                                     $itemTemp->getQuantity() - $itemQuantity $itemTemp->getQuantityPerPci()
  822.                                 );
  823.                             }
  824.                         }
  825.                     }
  826.                 }
  827.             }
  828.         }
  829.         foreach ($this->salesCase->getInvoicesOfTypeCreditInvoice() as $invoice) {
  830.             if (!$includeDrafts && $invoice->isDraft()) {
  831.                 continue;
  832.             }
  833.             foreach ($invoice->getSalesItems() as $invItem) {
  834.                 $position $invItem->getPositionNormalized();
  835.                 if (isset($list[$position])) {
  836.                     $itemQuantity $invItem->getQuantity();
  837.                     $list[$position]->setQuantity(
  838.                         $list[$position]->getQuantity() + $itemQuantity
  839.                     );
  840.                 }
  841.             }
  842.         }
  843.         // Adjust PCI item quantity based on uninvoiced sub-item quantities
  844.         $pciMaxQuantities = [];
  845.         foreach ($list as $item) {
  846.             if ($item->isPciSubItem()) {
  847.                 if (empty($item->getQuantityPerPci())) {
  848.                     throw new Exception('Missing quantity per PCI for OC position ' $item->getPositionDisplay() . '.');
  849.                 }
  850.                 $pciPosition $item->getPciPosition();
  851.                 $pciMaxQuantity floor($item->getQuantity() / $item->getQuantityPerPci());
  852.                 if (array_key_exists($pciPosition$pciMaxQuantities)) {
  853.                     $pciMaxQuantities[$pciPosition] = min($pciMaxQuantities[$pciPosition], $pciMaxQuantity);
  854.                 } else {
  855.                     $pciMaxQuantities[$pciPosition] = $pciMaxQuantity;
  856.                 }
  857.             }
  858.         }
  859.         foreach ($pciMaxQuantities as $position => $maxQuantity) {
  860.             if (array_key_exists($position$list)) {
  861.                 $list[$position]->setQuantity($maxQuantity);
  862.             }
  863.         }
  864.         // Remove items with no uninvoiced quantities
  865.         foreach ($list as $key => $item) {
  866.             if ($item->getQuantity() <= 0) {
  867.                 unset($list[$key]);
  868.             }
  869.         }
  870.         return $list;
  871.     }
  872.     /**
  873.      * @return array<SalesItem>
  874.      */
  875.     public function getUnpurchasedSalesItems(): array
  876.     {
  877.         /* @var SalesItem[] $list */
  878.         $list = [];
  879.         foreach ($this->getSalesItems() as $ocItem) {
  880.             $supplier $ocItem->getSupplier();
  881.             $key $ocItem->getCode() . ':' . ($supplier !== null $supplier->getId() : '');
  882.             $list[$key] = clone $ocItem;
  883.         }
  884.         foreach ($this->getSalesCase()->getPurchaseOrders() as $purchaseOrder) {
  885.             if (! $purchaseOrder->isRejected()) {
  886.                 foreach ($purchaseOrder->getSalesItems() as $item) {
  887.                     $supplier $purchaseOrder->getSupplier();
  888.                     $key $item->getCode() . ':' . ($supplier !== null $supplier->getId() : '');
  889.                     if (isset($list[$key])) {
  890.                         $list[$key]->setQuantity(
  891.                             $list[$key]->getQuantity() - $item->getQuantity()
  892.                         );
  893.                     }
  894.                 }
  895.             }
  896.         }
  897.         foreach ($list as $key => $item) {
  898.             if ($item->getQuantity() == 0) {
  899.                 unset($list[$key]);
  900.             }
  901.         }
  902.         return $list;
  903.     }
  904.     /**
  905.      * @return bool
  906.      */
  907.     public function hasLicenseFiles(): bool
  908.     {
  909.         foreach ($this->getSalesItems() as $salesItem) {
  910.             if ($salesItem->isLicenseFile()) {
  911.                 return true;
  912.             }
  913.         }
  914.         return false;
  915.     }
  916.     /**
  917.      * @return bool
  918.      */
  919.     public function isCompleted(): bool
  920.     {
  921.         return $this->getStatus() == self::STATUS_COMPLETED;
  922.     }
  923.     /**
  924.      * @return bool
  925.      */
  926.     public function isCurrencyExchangeRateLocked(): bool
  927.     {
  928.         return !in_array(
  929.             $this->status,
  930.             [
  931.                 self::STATUS_DRAFT,
  932.                 self::STATUS_NOT_CONFIRMED,
  933.                 self::STATUS_RISK_ORDER,
  934.                 self::STATUS_PARTIALLY_DELIVERED,
  935.                 self::STATUS_EQUIPMENT_DELIVERED,
  936.             ]
  937.         );
  938.     }
  939.     /**
  940.      * @return bool
  941.      */
  942.     public function isDraft(): bool
  943.     {
  944.         return $this->getStatus() == self::STATUS_DRAFT;
  945.     }
  946.     /**
  947.      * @return bool
  948.      */
  949.     public function isNotConfirmed(): bool
  950.     {
  951.         return $this->getStatus() == self::STATUS_NOT_CONFIRMED;
  952.     }
  953.     /**
  954.      * @return bool
  955.      */
  956.     public function isPreliminaryConfirmation(): bool
  957.     {
  958.         return $this->preliminaryConfirmation;
  959.     }
  960.     /**
  961.      * @return bool
  962.      */
  963.     public function isRejected(): bool
  964.     {
  965.         return $this->getStatus() == self::STATUS_REJECTED;
  966.     }
  967.     /**
  968.      * @return bool
  969.      */
  970.     public function isRiskOrder(): bool
  971.     {
  972.         return $this->getStatus() == self::STATUS_RISK_ORDER;
  973.     }
  974.     /**
  975.      * @ORM\PreUpdate
  976.      */
  977.     public function preUpdateOrderConfirmation(): void
  978.     {
  979.         if (count($this->getPlannedDeliveryDates()) == && $this->getDeliveryDate() !== null) {
  980.             $deliveryDate = new DeliveryDate($this$this->getDeliveryDate(), 100);
  981.             $this->addPlannedDeliveryDate($deliveryDate);
  982.         }
  983.     }
  984.     /**
  985.      * @param DeliveryDate $deliveryDate
  986.      */
  987.     public function removePlannedDeliveryDate(DeliveryDate $deliveryDate): void
  988.     {
  989.         if ($this->plannedDeliveryDates !== null) {
  990.             $this->plannedDeliveryDates->removeElement($deliveryDate);
  991.         }
  992.     }
  993.     /**
  994.      * @param TermsOfPaymentRow $row
  995.      */
  996.     public function removeTermsOfPaymentRow(TermsOfPaymentRow $row): void
  997.     {
  998.         if ($this->termsOfPaymentRows !== null) {
  999.             $this->termsOfPaymentRows->removeElement($row);
  1000.         }
  1001.     }
  1002.     /**
  1003.      * @param bool $appendGeneralTermsAndConditions
  1004.      */
  1005.     public function setAppendGeneralTermsAndConditions(bool $appendGeneralTermsAndConditions): void
  1006.     {
  1007.         $this->appendGeneralTermsAndConditions $appendGeneralTermsAndConditions;
  1008.     }
  1009.     /**
  1010.      * @param CompanyDocumentAddress|null $buyer
  1011.      */
  1012.     public function setBuyer(?CompanyDocumentAddress $buyer): void
  1013.     {
  1014.         $this->buyer $buyer;
  1015.     }
  1016.     /**
  1017.      * @param CompanyDocumentAddress|null $consignee
  1018.      */
  1019.     public function setConsignee(?CompanyDocumentAddress $consignee): void
  1020.     {
  1021.         $this->consignee $consignee;
  1022.     }
  1023.     /**
  1024.      * @param CompanyDocumentAddress|null $deliveryAddress
  1025.      */
  1026.     public function setDeliveryAddress(?CompanyDocumentAddress $deliveryAddress): void
  1027.     {
  1028.         $this->deliveryAddress $deliveryAddress;
  1029.     }
  1030.     /**
  1031.      * @param DateTime|null $deliveryDate
  1032.      */
  1033.     public function setDeliveryDate(?DateTime $deliveryDate)
  1034.     {
  1035.         $this->deliveryDate $deliveryDate;
  1036.     }
  1037.     /**
  1038.      * @param DocumentEndCustomer|null $endCustomer
  1039.      */
  1040.     public function setEndCustomer(?DocumentEndCustomer $endCustomer)
  1041.     {
  1042.         $this->endCustomer $endCustomer;
  1043.     }
  1044.     /**
  1045.      * @param CompanyDocumentAddress|null $invoicingAddress
  1046.      */
  1047.     public function setInvoicingAddress(?CompanyDocumentAddress $invoicingAddress): void
  1048.     {
  1049.         $this->invoicingAddress $invoicingAddress;
  1050.     }
  1051.     /**
  1052.      * @param string|null $notes
  1053.      */
  1054.     public function setNotes(?string $notes)
  1055.     {
  1056.         $this->notes $notes;
  1057.     }
  1058.     /**
  1059.      * @param int|null $orderNumber
  1060.      */
  1061.     public function setOrderNumber(?int $orderNumber)
  1062.     {
  1063.         $this->orderNumber $orderNumber;
  1064.     }
  1065.     /**
  1066.      * @param string|null $otherInstructions
  1067.      */
  1068.     public function setOtherInstructions(?string $otherInstructions)
  1069.     {
  1070.         $this->otherInstructions $otherInstructions;
  1071.     }
  1072.     /**
  1073.      * @param bool $preliminaryConfirmation
  1074.      */
  1075.     public function setPreliminaryConfirmation(bool $preliminaryConfirmation)
  1076.     {
  1077.         $this->preliminaryConfirmation $preliminaryConfirmation;
  1078.     }
  1079.     /**
  1080.      * @param string|null $shippingMarks
  1081.      */
  1082.     public function setShippingMarks(?string $shippingMarks)
  1083.     {
  1084.         $this->shippingMarks $shippingMarks;
  1085.     }
  1086.     /**
  1087.      * @param string|null $termsOfDelivery
  1088.      */
  1089.     public function setTermsOfDelivery(?string $termsOfDelivery)
  1090.     {
  1091.         $this->termsOfDelivery $termsOfDelivery;
  1092.     }
  1093.     /**
  1094.      * @param string|null $text
  1095.      */
  1096.     public function setText(?string $text)
  1097.     {
  1098.         $this->text $text;
  1099.     }
  1100.     /**
  1101.      * @param string|null $transportation
  1102.      */
  1103.     public function setTransportation(?string $transportation)
  1104.     {
  1105.         $this->transportation $transportation;
  1106.     }
  1107.     /**
  1108.      * @Assert\Callback
  1109.      *
  1110.      * @param ExecutionContextInterface $context
  1111.      */
  1112.     public function validate(ExecutionContextInterface $context)
  1113.     {
  1114.         $total 0;
  1115.         foreach ($this->getPlannedDeliveryDates() as $deliveryDate) {
  1116.             $total += $deliveryDate->getPercentage();
  1117.         }
  1118.         if ($total 100) {
  1119.             $context->buildViolation('validation.plannedDeliveryDates.percentageOutOfBounds')
  1120.                 ->setTranslationDomain('OrderConfirmation')
  1121.                 ->atPath('plannedDeliveryDates')
  1122.                 ->addViolation();
  1123.         }
  1124.     }
  1125. }