CluPilotCloud/app/Support/CompanyProfile.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));
}
}