<?php
namespace App\Billing\Entity;
use App\Billing\Model\BillablePartyInterface;
use App\Repository\InvoiceRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: InvoiceRepository::class)]
#[ORM\HasLifecycleCallbacks]
class Invoice
{
const STATUS_DRAFT = 'DRAFT';
const STATUS_VALIDATED = 'VALIDATED';
const STATUS_PAID = 'PAID';
const STATUS_CANCELLED = 'CANCELLED';
const TYPE_INVOICE = 'INVOICE';
const TYPE_CREDIT_NOTE = 'CREDIT_NOTE'; // Avoir
const TYPE_DOWN_PAYMENT = 'DOWN_PAYMENT'; // Facture d'acompte
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 20)]
private ?string $type = self::TYPE_INVOICE;
#[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')]
private ?self $parentInvoice = null;
#[ORM\OneToMany(mappedBy: 'parentInvoice', targetEntity: self::class)]
private Collection $children;
#[ORM\Column(length: 255, unique: true, nullable: true)]
private ?string $reference = null;
#[ORM\Column(length: 50)]
private ?string $status = self::STATUS_DRAFT;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeInterface $issuedAt = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
private ?\DateTimeInterface $dueDate = null;
#[ORM\Column(type: Types::DECIMAL, precision: 10, scale: 2)]
private ?string $totalAmountHt = '0.00';
#[ORM\Column(type: Types::DECIMAL, precision: 10, scale: 2)]
private ?string $totalTaxAmount = '0.00';
#[ORM\Column(type: Types::DECIMAL, precision: 10, scale: 2)]
private ?string $totalAmountTtc = '0.00';
#[ORM\Column(type: Types::DECIMAL, precision: 10, scale: 3)]
private ?string $stampDuty = '0.000'; // Droit de timbre
#[ORM\OneToMany(mappedBy: 'invoice', targetEntity: InvoiceLine::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
private Collection $lines;
#[ORM\Column(length: 255)]
private ?string $customerName = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $customerAddress = null;
#[ORM\Column(length: 100, nullable: true)]
private ?string $customerTaxId = null;
// We store the serialized data of the customer at the moment of billing to keep history
// even if the customer entity changes later.
#[ORM\Column(type: Types::JSON, nullable: true)]
private array $customerSnapshot = [];
#[ORM\OneToOne(mappedBy: 'invoice', targetEntity: PaymentSchedule::class, cascade: ['persist', 'remove'])]
private ?PaymentSchedule $paymentSchedule = null;
#[ORM\OneToMany(mappedBy: 'invoice', targetEntity: Payment::class, cascade: ['persist'])]
private Collection $payments;
public function __construct()
{
$this->lines = new ArrayCollection();
$this->payments = new ArrayCollection();
$this->children = new ArrayCollection();
$this->issuedAt = new \DateTime();
}
public function getPaymentSchedule(): ?PaymentSchedule
{
return $this->paymentSchedule;
}
public function setPaymentSchedule(PaymentSchedule $paymentSchedule): static
{
// set the owning side of the relation if necessary
if ($paymentSchedule->getInvoice() !== $this) {
$paymentSchedule->setInvoice($this);
}
$this->paymentSchedule = $paymentSchedule;
return $this;
}
/**
* @return Collection<int, Payment>
*/
public function getPayments(): Collection
{
return $this->payments;
}
public function addPayment(Payment $payment): static
{
if (!$this->payments->contains($payment)) {
$this->payments->add($payment);
$payment->setInvoice($this);
}
return $this;
}
public function removePayment(Payment $payment): static
{
if ($this->payments->removeElement($payment)) {
// set the owning side to null (unless already changed)
if ($payment->getInvoice() === $this) {
$payment->setInvoice(null);
}
}
return $this;
}
public function getType(): ?string
{
return $this->type;
}
public function setType(string $type): static
{
$this->type = $type;
return $this;
}
public function getParentInvoice(): ?self
{
return $this->parentInvoice;
}
public function setParentInvoice(?self $parentInvoice): static
{
$this->parentInvoice = $parentInvoice;
return $this;
}
/**
* @return Collection<int, self>
*/
public function getChildren(): Collection
{
return $this->children;
}
public function getId(): ?int
{
return $this->id;
}
public function getReference(): ?string
{
return $this->reference;
}
public function setReference(?string $reference): static
{
$this->reference = $reference;
return $this;
}
public function getStatus(): ?string
{
return $this->status;
}
public function setStatus(string $status): static
{
$this->status = $status;
return $this;
}
public function getIssuedAt(): ?\DateTimeInterface
{
return $this->issuedAt;
}
public function setIssuedAt(\DateTimeInterface $issuedAt): static
{
$this->issuedAt = $issuedAt;
return $this;
}
public function getDueDate(): ?\DateTimeInterface
{
return $this->dueDate;
}
public function setDueDate(?\DateTimeInterface $dueDate): static
{
$this->dueDate = $dueDate;
return $this;
}
public function getTotalAmountHt(): ?string
{
return $this->totalAmountHt;
}
public function setTotalAmountHt(string $totalAmountHt): static
{
$this->totalAmountHt = $totalAmountHt;
return $this;
}
public function getTotalTaxAmount(): ?string
{
return $this->totalTaxAmount;
}
public function setTotalTaxAmount(string $totalTaxAmount): static
{
$this->totalTaxAmount = $totalTaxAmount;
return $this;
}
public function getTotalAmountTtc(): ?string
{
return $this->totalAmountTtc;
}
public function setTotalAmountTtc(string $totalAmountTtc): static
{
$this->totalAmountTtc = $totalAmountTtc;
return $this;
}
public function getStampDuty(): ?string
{
return $this->stampDuty;
}
public function setStampDuty(string $stampDuty): static
{
$this->stampDuty = $stampDuty;
return $this;
}
/**
* @return Collection<int, InvoiceLine>
*/
public function getLines(): Collection
{
return $this->lines;
}
public function addLine(InvoiceLine $line): static
{
if (!$this->lines->contains($line)) {
$this->lines->add($line);
$line->setInvoice($this);
}
return $this;
}
public function removeLine(InvoiceLine $line): static
{
if ($this->lines->removeElement($line)) {
// set the owning side to null (unless already changed)
if ($line->getInvoice() === $this) {
$line->setInvoice(null);
}
}
return $this;
}
public function getCustomerName(): ?string
{
return $this->customerName;
}
public function setCustomerName(string $customerName): static
{
$this->customerName = $customerName;
return $this;
}
public function getCustomerAddress(): ?string
{
return $this->customerAddress;
}
public function setCustomerAddress(?string $customerAddress): static
{
$this->customerAddress = $customerAddress;
return $this;
}
public function getCustomerTaxId(): ?string
{
return $this->customerTaxId;
}
public function setCustomerTaxId(?string $customerTaxId): static
{
$this->customerTaxId = $customerTaxId;
return $this;
}
public function getCustomerSnapshot(): array
{
return $this->customerSnapshot;
}
public function setCustomerSnapshot(array $customerSnapshot): static
{
$this->customerSnapshot = $customerSnapshot;
return $this;
}
public function getCustomerType(): ?string
{
return $this->customerSnapshot['type'] ?? null;
}
public function getCustomerId(): ?int
{
return $this->customerSnapshot['id'] ?? null;
}
public function setCustomer(BillablePartyInterface $customer): static
{
$this->customerName = $customer->getBillingName();
$this->customerAddress = $customer->getBillingAddress();
$this->customerTaxId = $customer->getTaxId();
$type = null;
if ($customer instanceof \App\Entity\User) {
$type = 'User';
} elseif ($customer instanceof \App\Entity\Organization) {
$type = 'Organization';
}
// Store snapshot for history
$this->customerSnapshot = [
'id' => method_exists($customer, 'getId') ? $customer->getId() : null,
'type' => $type,
'name' => $customer->getBillingName(),
'address' => $customer->getBillingAddress(),
'email' => $customer->getBillingEmail(),
'tax_id' => $customer->getTaxId(),
];
return $this;
}
}