mirror of
https://github.com/Karaka-Management/oms-Billing.git
synced 2026-01-11 15:18:42 +00:00
718 lines
14 KiB
PHP
Executable File
718 lines
14 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* Jingga
|
|
*
|
|
* PHP Version 8.2
|
|
*
|
|
* @package Modules\Billing\Models
|
|
* @copyright Dennis Eichhorn
|
|
* @license OMS License 2.0
|
|
* @version 1.0.0
|
|
* @link https://jingga.app
|
|
*/
|
|
declare(strict_types=1);
|
|
|
|
namespace Modules\Billing\Models;
|
|
|
|
use Modules\Admin\Models\Account;
|
|
use Modules\Admin\Models\NullAccount;
|
|
use Modules\ClientManagement\Models\Client;
|
|
use Modules\SupplierManagement\Models\Supplier;
|
|
use phpOMS\Localization\ISO4217CharEnum;
|
|
use phpOMS\Localization\ISO639x1Enum;
|
|
use phpOMS\Stdlib\Base\FloatInt;
|
|
|
|
/**
|
|
* Bill class.
|
|
*
|
|
* @package Modules\Billing\Models
|
|
* @license OMS License 2.0
|
|
* @link https://jingga.app
|
|
* @since 1.0.0
|
|
*/
|
|
class Bill implements \JsonSerializable
|
|
{
|
|
/**
|
|
* ID.
|
|
*
|
|
* @var int
|
|
* @since 1.0.0
|
|
*/
|
|
public int $id = 0;
|
|
|
|
/**
|
|
* Sequence.
|
|
*
|
|
* Incrementing value depending on multiple columns e.g.:
|
|
* id & unit
|
|
* id & unit & type
|
|
* id & unit & year
|
|
*
|
|
* @var int
|
|
* @since 1.0.0
|
|
*/
|
|
public int $sequence = 0;
|
|
|
|
public int $unit = 0;
|
|
|
|
public int $source = 0;
|
|
|
|
/**
|
|
* Number ID.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $number = '';
|
|
|
|
public string $external = '';
|
|
|
|
/**
|
|
* Bill type.
|
|
*
|
|
* @var BillType
|
|
* @since 1.0.0
|
|
*/
|
|
public BillType $type;
|
|
|
|
public bool $isTemplate = false;
|
|
|
|
public bool $isArchived = false;
|
|
|
|
/**
|
|
* Bill status.
|
|
*
|
|
* @var int
|
|
* @since 1.0.0
|
|
*/
|
|
public int $status = BillStatus::DRAFT;
|
|
|
|
/**
|
|
* Bill payment status
|
|
*
|
|
* @var int
|
|
* @since 1.0.0
|
|
*/
|
|
public int $paymentStatus = BillPaymentStatus::UNPAID;
|
|
|
|
/**
|
|
* Bill created at.
|
|
*
|
|
* @var \DateTimeImmutable
|
|
* @since 1.0.0
|
|
*/
|
|
public \DateTimeImmutable $createdAt;
|
|
|
|
/**
|
|
* Bill created at.
|
|
*
|
|
* @var null|\DateTime
|
|
* @since 1.0.0
|
|
*/
|
|
public ?\DateTime $billDate = null;
|
|
|
|
/**
|
|
* Bill created at.
|
|
*
|
|
* @var null|\DateTime
|
|
* @since 1.0.0
|
|
*/
|
|
public ?\DateTime $performanceDate = null;
|
|
|
|
/**
|
|
* Bill send at.
|
|
*
|
|
* @var null|\DateTime
|
|
* @since 1.0.0
|
|
*/
|
|
public ?\DateTime $send = null;
|
|
|
|
/**
|
|
* Creator.
|
|
*
|
|
* @var Account
|
|
* @since 1.0.0
|
|
*/
|
|
public Account $createdBy;
|
|
|
|
public ?Client $client = null;
|
|
|
|
public ?Supplier $supplier = null;
|
|
|
|
public string $language = ISO639x1Enum::_EN;
|
|
|
|
public string $accountNumber = '';
|
|
|
|
/**
|
|
* Receiver.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $shipTo = '';
|
|
|
|
/**
|
|
* For the attention of.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $shipFAO = '';
|
|
|
|
/**
|
|
* Shipping address.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $shipAddress = '';
|
|
|
|
/**
|
|
* Shipping city.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $shipCity = '';
|
|
|
|
/**
|
|
* Shipping zip.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $shipZip = '';
|
|
|
|
/**
|
|
* Shipping country.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $shipCountry = '';
|
|
|
|
/**
|
|
* Billing.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $billTo = '';
|
|
|
|
/**
|
|
* Billing for the attention of.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $billFAO = '';
|
|
|
|
/**
|
|
* Billing address.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $billAddress = '';
|
|
|
|
/**
|
|
* Billing city.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $billCity = '';
|
|
|
|
/**
|
|
* Billing zip.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $billZip = '';
|
|
|
|
/**
|
|
* Billing country.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $billCountry = '';
|
|
|
|
public string $billEmail = '';
|
|
|
|
/**
|
|
* Person referring for this order.
|
|
*
|
|
* @var Account
|
|
* @since 1.0.0
|
|
*/
|
|
public Account $referral;
|
|
|
|
/**
|
|
* Net amount.
|
|
*
|
|
* @var FloatInt
|
|
* @since 1.0.0
|
|
*/
|
|
public FloatInt $netProfit;
|
|
|
|
/**
|
|
* Costs in net.
|
|
*
|
|
* @var FloatInt
|
|
* @since 1.0.0
|
|
*/
|
|
public FloatInt $netCosts;
|
|
|
|
/**
|
|
* Costs in net.
|
|
*
|
|
* @var FloatInt
|
|
* @since 1.0.0
|
|
*/
|
|
public FloatInt $netSales;
|
|
|
|
/**
|
|
* Profit in net.
|
|
*
|
|
* @var FloatInt
|
|
* @since 1.0.0
|
|
*/
|
|
public FloatInt $grossSales;
|
|
|
|
/**
|
|
* Costs in net.
|
|
*
|
|
* @var FloatInt
|
|
* @since 1.0.0
|
|
*/
|
|
public FloatInt $netDiscount;
|
|
|
|
/**
|
|
* Tax amount
|
|
*
|
|
* @var FloatInt
|
|
* @since 1.0.0
|
|
*/
|
|
public FloatInt $taxP;
|
|
|
|
public ?int $accTaxCode = null;
|
|
|
|
/**
|
|
* Currency.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $currency = ISO4217CharEnum::_EUR;
|
|
|
|
/**
|
|
* Info text.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $header = '';
|
|
|
|
/**
|
|
* Info text.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $footer = '';
|
|
|
|
/**
|
|
* Info text.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $info = '';
|
|
|
|
public int $payment = 0;
|
|
|
|
/**
|
|
* Payment text.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $paymentText = '';
|
|
|
|
/**
|
|
* Terms.
|
|
*
|
|
* @var int
|
|
* @since 1.0.0
|
|
*/
|
|
public int $terms = 0;
|
|
|
|
public ?int $paymentTerms = null;
|
|
|
|
public ?int $shippingTerms = null;
|
|
|
|
/**
|
|
* Terms text.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $termsText = '';
|
|
|
|
/**
|
|
* Shipping text.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $shippingText = '';
|
|
|
|
/**
|
|
* Vouchers used.
|
|
*
|
|
* @var array
|
|
* @since 1.0.0
|
|
*/
|
|
public array $vouchers = [];
|
|
|
|
/**
|
|
* Tracking ids for shipping.
|
|
*
|
|
* @var array
|
|
* @since 1.0.0
|
|
*/
|
|
public array $trackings = [];
|
|
|
|
/**
|
|
* Bill elements / bill lines.
|
|
*
|
|
* @var BillElement[]
|
|
* @since 1.0.0
|
|
*/
|
|
public array $elements = [];
|
|
|
|
/**
|
|
* Reference to other Bill (delivery note/credit note etc).
|
|
*
|
|
* @var int
|
|
* @since 1.0.0
|
|
*/
|
|
public int $reference = 0;
|
|
|
|
public ?int $accSegment = null;
|
|
|
|
public ?int $accSection = null;
|
|
|
|
public ?int $accGroup = null;
|
|
|
|
public ?int $accType = null;
|
|
|
|
public ?string $fiAccount = null;
|
|
|
|
// @todo Implement reason for bill (especially useful for credit notes, warehouse bookings)
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$this->netProfit = new FloatInt(0);
|
|
$this->netCosts = new FloatInt(0);
|
|
$this->netSales = new FloatInt(0);
|
|
$this->grossSales = new FloatInt(0);
|
|
$this->netDiscount = new FloatInt(0);
|
|
$this->taxP = new FloatInt(0);
|
|
|
|
$this->billDate = new \DateTime('now');
|
|
$this->createdAt = new \DateTimeImmutable();
|
|
$this->createdBy = new NullAccount();
|
|
$this->referral = new NullAccount();
|
|
$this->type = new NullBillType();
|
|
}
|
|
|
|
/**
|
|
* Build the invoice number.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function buildNumber() : void
|
|
{
|
|
$this->number = \str_replace(
|
|
[
|
|
'{y}',
|
|
'{m}',
|
|
'{d}',
|
|
'{id}',
|
|
'{sequence}',
|
|
'{type}',
|
|
'{unit}',
|
|
'{account}',
|
|
'{country}',
|
|
],
|
|
[
|
|
$this->createdAt->format('Y'),
|
|
$this->createdAt->format('m'),
|
|
$this->createdAt->format('d'),
|
|
$this->id,
|
|
$this->sequence,
|
|
$this->type->id,
|
|
$this->unit,
|
|
$this->accountNumber,
|
|
$this->billCountry,
|
|
],
|
|
$this->type->numberFormat
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get Bill number.
|
|
*
|
|
* @return string
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function getNumber() : string
|
|
{
|
|
if (empty($this->number)) {
|
|
$this->buildNumber();
|
|
}
|
|
|
|
return $this->number;
|
|
}
|
|
|
|
/**
|
|
* Get paymentStatus
|
|
*
|
|
* @return int
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function getPaymentStatus() : int
|
|
{
|
|
return $this->paymentStatus;
|
|
}
|
|
|
|
/**
|
|
* Set paymentStatus
|
|
*
|
|
* @param int $paymentStatus Status
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function setPaymentStatus(int $paymentStatus) : void
|
|
{
|
|
$this->paymentStatus = $paymentStatus;
|
|
}
|
|
|
|
/**
|
|
* Add Bill element.
|
|
*
|
|
* @param BillElement $element Bill element
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function addElement(BillElement $element) : void
|
|
{
|
|
$this->elements[] = $element;
|
|
|
|
$this->netProfit->value += $element->totalProfitNet->value;
|
|
$this->netCosts->value += $element->totalPurchasePriceNet->value;
|
|
$this->netSales->value += $element->totalSalesPriceNet->value;
|
|
$this->grossSales->value += $element->totalSalesPriceGross->value;
|
|
$this->netDiscount->value += $element->totalDiscountP->value;
|
|
}
|
|
|
|
/**
|
|
* Validate the correctness of the bill
|
|
*
|
|
* @return bool
|
|
*
|
|
* @todo also consider rounding similarly to recalculatePrices in elements
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function isValid() : bool
|
|
{
|
|
return $this->validateTaxAmountElements()
|
|
&& $this->validateProfit()
|
|
&& $this->validateGrossElements()
|
|
&& $this->validatePriceQuantityElements()
|
|
&& $this->validateNetElements()
|
|
&& $this->validateNetGross()
|
|
&& $this->areElementsValid();
|
|
}
|
|
|
|
/**
|
|
* Validate the correctness of the bill elements
|
|
*
|
|
* @return bool
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function areElementsValid() : bool
|
|
{
|
|
foreach ($this->elements as $element) {
|
|
if (!$element->isValid()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Validate the correctness of the net and gross values
|
|
*
|
|
* @return bool
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function validateNetGross() : bool
|
|
{
|
|
return $this->netSales->value <= $this->grossSales->value;
|
|
}
|
|
|
|
/**
|
|
* Validate the correctness of the profit
|
|
*
|
|
* @return bool
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function validateProfit() : bool
|
|
{
|
|
return $this->netSales->value - $this->netCosts->value === $this->netProfit->value;
|
|
}
|
|
|
|
/**
|
|
* Validate the correctness of the taxes
|
|
*
|
|
* @return bool
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function validateTax() : bool
|
|
{
|
|
return \abs($this->netSales->value + $this->taxP->value - $this->grossSales->value) === 0;
|
|
}
|
|
|
|
/**
|
|
* Validate the correctness of the taxes for the elements
|
|
*
|
|
* @return bool
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function validateTaxAmountElements() : bool
|
|
{
|
|
$taxes = 0;
|
|
foreach ($this->elements as $element) {
|
|
$taxes += $element->taxP->value;
|
|
}
|
|
|
|
return $taxes === $this->taxP->value;
|
|
}
|
|
|
|
/**
|
|
* Validate the correctness of the net of the elements
|
|
*
|
|
* @return bool
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function validateNetElements() : bool
|
|
{
|
|
$net = 0;
|
|
foreach ($this->elements as $element) {
|
|
$net += $element->totalSalesPriceNet->value;
|
|
}
|
|
|
|
return $net === $this->netSales->value;
|
|
}
|
|
|
|
/**
|
|
* Validate the correctness of the gross of the elements
|
|
*
|
|
* @return bool
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function validateGrossElements()
|
|
{
|
|
$gross = 0;
|
|
foreach ($this->elements as $element) {
|
|
$gross += $element->totalSalesPriceGross->value;
|
|
}
|
|
|
|
return $gross === $this->grossSales->value;
|
|
}
|
|
|
|
/**
|
|
* Validate the correctness of the quantities and total price
|
|
*
|
|
* @return bool
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function validatePriceQuantityElements()
|
|
{
|
|
foreach ($this->elements as $element) {
|
|
if ($element->discountQ->value === 0
|
|
&& $element->totalDiscountP->value === 0
|
|
&& ($element->quantity->value / FloatInt::DIVISOR) * $element->singleSalesPriceNet->value - $element->totalSalesPriceNet->value > 1.0
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function toArray() : array
|
|
{
|
|
return [
|
|
'id' => $this->id,
|
|
'number' => $this->number,
|
|
'external' => $this->external,
|
|
'type' => $this->type,
|
|
'shipTo' => $this->shipTo,
|
|
'shipFAO' => $this->shipFAO,
|
|
'shipAddress' => $this->shipAddress,
|
|
'shipCity' => $this->shipCity,
|
|
'shipZip' => $this->shipZip,
|
|
'shipCountry' => $this->shipCountry,
|
|
'billTo' => $this->billTo,
|
|
'billFAO' => $this->billFAO,
|
|
'billAddress' => $this->billAddress,
|
|
'billCity' => $this->billCity,
|
|
'billZip' => $this->billZip,
|
|
'billCountry' => $this->billCountry,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function jsonSerialize() : mixed
|
|
{
|
|
return $this->toArray();
|
|
}
|
|
|
|
use \Modules\Attribute\Models\AttributeHolderTrait;
|
|
use \Modules\Editor\Models\EditorDocListTrait;
|
|
use \Modules\Media\Models\MediaListTrait;
|
|
}
|