115 lines
3.9 KiB
PHP
115 lines
3.9 KiB
PHP
<?php
|
|
|
|
namespace App\Support;
|
|
|
|
/**
|
|
* Who is issuing the invoice.
|
|
*
|
|
* Every field a printed invoice needs, kept in settings rather than in config,
|
|
* because an operator has to be able to change an address without a deployment
|
|
* — and because the values are copied into each invoice at issue anyway, so
|
|
* changing one here never rewrites a document that already exists.
|
|
*
|
|
* A typed front door onto Settings rather than string keys scattered through
|
|
* views and PDF code: there are fifteen of them, an invoice is a legal
|
|
* document, and a typo in a key silently prints an empty line where the VAT
|
|
* number belongs.
|
|
*/
|
|
final class CompanyProfile
|
|
{
|
|
private const PREFIX = 'company.';
|
|
|
|
/** Every key, with its default. The list IS the schema. */
|
|
public const FIELDS = [
|
|
'name' => '',
|
|
'address' => '',
|
|
'postcode' => '',
|
|
'city' => '',
|
|
'country' => 'Österreich',
|
|
'phone' => '',
|
|
'email' => '',
|
|
'website' => '',
|
|
// Firmenbuch / commercial register, and the court that keeps it.
|
|
'register_number' => '',
|
|
'register_court' => '',
|
|
// UID / VAT identification number — the one field an invoice is
|
|
// legally worthless without.
|
|
'vat_id' => '',
|
|
'bank_name' => '',
|
|
'iban' => '',
|
|
'bic' => '',
|
|
'logo_path' => '',
|
|
// Payment terms in days, and the sentence printed under the total.
|
|
'payment_days' => 14,
|
|
'payment_terms' => '',
|
|
];
|
|
|
|
/** @return array<string, mixed> */
|
|
public static function all(): array
|
|
{
|
|
$values = [];
|
|
|
|
foreach (self::FIELDS as $key => $default) {
|
|
$values[$key] = Settings::get(self::PREFIX.$key, $default);
|
|
}
|
|
|
|
return $values;
|
|
}
|
|
|
|
public static function get(string $field, mixed $default = null): mixed
|
|
{
|
|
return Settings::get(self::PREFIX.$field, $default ?? (self::FIELDS[$field] ?? null));
|
|
}
|
|
|
|
/** @param array<string, mixed> $values */
|
|
public static function put(array $values): void
|
|
{
|
|
foreach ($values as $key => $value) {
|
|
// Unknown keys are dropped rather than stored. This is fed from a
|
|
// form, and a forged field must not be able to write an arbitrary
|
|
// setting into the same table the site-visibility switch lives in.
|
|
if (! array_key_exists($key, self::FIELDS)) {
|
|
continue;
|
|
}
|
|
|
|
Settings::set(self::PREFIX.$key, $value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Is there enough here to put on an invoice?
|
|
*
|
|
* Not a nicety: an invoice without a company name, an address or a VAT
|
|
* number is not a valid invoice in Austria, and issuing one is worse than
|
|
* refusing to. The console asks this before it offers the button.
|
|
*
|
|
* @return array<int, string> the fields that are missing, empty when ready
|
|
*/
|
|
public static function missingForInvoicing(): array
|
|
{
|
|
$required = ['name', 'address', 'postcode', 'city', 'vat_id'];
|
|
|
|
return array_values(array_filter(
|
|
$required,
|
|
fn (string $field) => trim((string) self::get($field)) === '',
|
|
));
|
|
}
|
|
|
|
/**
|
|
* The domestic VAT rate as a percentage, e.g. 20.0.
|
|
*
|
|
* The ONE place it is decided. App\Services\Billing\TaxTreatment reads it
|
|
* from here rather than from config, because this page can change it and a
|
|
* config value cannot — and two sources for one number is how an invoice
|
|
* ends up disagreeing with the checkout that produced it.
|
|
*
|
|
* Falls back to the .env-derived config until an operator has saved a rate,
|
|
* so an installation that already carries CLUPILOT_TAX_PERCENT keeps
|
|
* working unchanged. Same shape as App\Support\ProvisioningSettings.
|
|
*/
|
|
public static function taxRate(): float
|
|
{
|
|
return (float) Settings::get('company.tax_rate', (float) config('provisioning.tax.rate_percent', 20));
|
|
}
|
|
}
|