diff --git a/app/Services/Billing/TaxTreatment.php b/app/Services/Billing/TaxTreatment.php index ca0b9ae..3703c4f 100644 --- a/app/Services/Billing/TaxTreatment.php +++ b/app/Services/Billing/TaxTreatment.php @@ -3,6 +3,7 @@ namespace App\Services\Billing; use App\Models\Customer; +use App\Support\CompanyProfile; /** * Which VAT applies to a customer, and why. @@ -37,7 +38,12 @@ final readonly class TaxTreatment public static function for(?Customer $customer): self { - $domestic = (float) config('provisioning.tax.rate_percent', 0) / 100; + // From the Finance page, not from config: an operator can change the + // rate there and cannot change a config value, and two sources for one + // number is how an invoice ends up disagreeing with the checkout that + // produced it. CompanyProfile falls back to the config value until a + // rate has been saved. + $domestic = CompanyProfile::taxRate() / 100; // Unverified is the normal state, and it must cost the customer nothing // more than the domestic rate they would pay anyway — but it must not diff --git a/app/Support/CompanyProfile.php b/app/Support/CompanyProfile.php index 3cf12ee..710812a 100644 --- a/app/Support/CompanyProfile.php +++ b/app/Support/CompanyProfile.php @@ -95,9 +95,20 @@ final class CompanyProfile )); } - /** The VAT rate as a percentage, e.g. 20.0. */ + /** + * 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', 20.0); + return (float) Settings::get('company.tax_rate', (float) config('provisioning.tax.rate_percent', 20)); } } diff --git a/tests/Feature/Billing/FinanceSettingsTest.php b/tests/Feature/Billing/FinanceSettingsTest.php index badef37..fb5c274 100644 --- a/tests/Feature/Billing/FinanceSettingsTest.php +++ b/tests/Feature/Billing/FinanceSettingsTest.php @@ -132,3 +132,18 @@ function issueOne(InvoiceSeries $series): Invoice 'currency' => 'EUR', ]); } + +it('decides VAT in one place, so the invoice cannot disagree with the checkout', function () { + // Two sources for one number is how a document ends up contradicting the + // page that produced it. The Finance tab is the source; config is only the + // fallback until a rate has been saved. + config(['provisioning.tax.rate_percent' => 20]); + + expect(App\Support\CompanyProfile::taxRate())->toBe(20.0) + ->and(App\Services\Billing\TaxTreatment::for(null)->rate)->toBe(0.2); + + App\Support\Settings::set('company.tax_rate', 10.0); + + expect(App\Support\CompanyProfile::taxRate())->toBe(10.0) + ->and(App\Services\Billing\TaxTreatment::for(null)->rate)->toBe(0.1); +});