Files
InvoiceShelf/app/Services/Company/CompanyService.php
Darko Gjorgjijoski 6d1816bd1b refactor: reorganize app/Services and app/Support by domain
The app/Services/ directory had grown into 22 flat files at the root plus 7 uneven subdirectories — finding anything required scrolling through an alphabetical mix of small CRUD services, infrastructure drivers, and install-time utilities. This commit groups services by domain, folds Backup into a new Storage namespace, and moves framework-infrastructure and install-time helpers out of Services and into Support where they belong.

New Services layout: Documents/ (Invoice, Estimate, RecurringInvoice, Payment, Expense, Transaction, DocumentItem, SerialNumber, Currency — matches the 'Documents' navigation group); Company/ (Company, Member, Invitation); Mail/ (MailConfiguration, CompanyMailConfig); Storage/ (FileDisk, plus Backup folded in). ExchangeRateProviderService moves next to its drivers in ExchangeRate/; FontService moves into Pdf/ where it belongs. CustomerService, ItemService, CustomFieldService stay at the Services root as standalone single-file domains.

Moves to Support/: Hashids/ (library wrapper — not business logic); Setup/ (one-shot install-time utilities — stateless helpers); Pdf/ (ImageUtils, PdfTemplateUtils, plus the existing PdfHtmlSanitizer consolidated into the same subdir). These are all framework infrastructure and stateless utilities — the 'service' label never really fit them.

Namespace declarations in 29 moved files updated to match new paths. 62 consumer files (controllers, other services, tests, database factories, seeders, routes, bootstrap/providers.php) have their use statements rewritten via a literal-string replacement script — no regex meant no risk of half-matching. Three Documents services needed an explicit 'use App\Services\Mail\CompanyMailConfigService' added because the same-namespace short reference they relied on no longer resolves after the split.

Verified: composer dump-autoload, 350 tests pass (850 assertions), vendor/bin/pint clean, npm run build succeeds.
2026-04-11 10:00:00 +02:00

237 lines
9.1 KiB
PHP

<?php
namespace App\Services\Company;
use App\Models\Company;
use App\Models\CompanySetting;
use App\Models\PaymentMethod;
use App\Models\Unit;
use App\Models\User;
use Silber\Bouncer\BouncerFacade;
use Silber\Bouncer\Database\Role;
class CompanyService
{
public function setupDefaults(Company $company): bool
{
$this->setupRoles($company);
$this->setupDefaultPaymentMethods($company);
$this->setupDefaultUnits($company);
$this->setupDefaultSettings($company);
return true;
}
public function setupRoles(Company $company): void
{
BouncerFacade::scope()->to($company->id);
$owner = BouncerFacade::role()->firstOrCreate([
'name' => 'owner',
'title' => 'Owner',
'scope' => $company->id,
]);
foreach (config('abilities.abilities') as $ability) {
BouncerFacade::allow($owner)->to($ability['ability'], $ability['model']);
}
}
public function delete(Company $company, User $user): bool
{
if ($company->exchangeRateLogs()->exists()) {
$company->exchangeRateLogs()->delete();
}
if ($company->exchangeRateProviders()->exists()) {
$company->exchangeRateProviders()->delete();
}
if ($company->expenses()->exists()) {
$company->expenses()->delete();
}
if ($company->expenseCategories()->exists()) {
$company->expenseCategories()->delete();
}
if ($company->payments()->exists()) {
$company->payments()->delete();
}
if ($company->paymentMethods()->exists()) {
$company->paymentMethods()->delete();
}
if ($company->customFieldValues()->exists()) {
$company->customFieldValues()->delete();
}
if ($company->customFields()->exists()) {
$company->customFields()->delete();
}
if ($company->invoices()->exists()) {
$company->invoices->map(function ($invoice) {
$this->checkModelData($invoice);
if ($invoice->transactions()->exists()) {
$invoice->transactions()->delete();
}
});
$company->invoices()->delete();
}
if ($company->recurringInvoices()->exists()) {
$company->recurringInvoices->map(function ($recurringInvoice) {
$this->checkModelData($recurringInvoice);
});
$company->recurringInvoices()->delete();
}
if ($company->estimates()->exists()) {
$company->estimates->map(function ($estimate) {
$this->checkModelData($estimate);
});
$company->estimates()->delete();
}
if ($company->items()->exists()) {
$company->items()->delete();
}
if ($company->taxTypes()->exists()) {
$company->taxTypes()->delete();
}
if ($company->customers()->exists()) {
$company->customers->map(function ($customer) {
if ($customer->addresses()->exists()) {
$customer->addresses()->delete();
}
$customer->delete();
});
}
$roles = Role::when($company->id, function ($query) use ($company) {
return $query->where('scope', $company->id);
})->get();
if ($roles) {
$roles->map(function ($role) {
$role->delete();
});
}
if ($company->users()->exists()) {
$user->companies()->detach($company->id);
}
$company->settings()->delete();
$company->address()->delete();
$company->delete();
return true;
}
private function setupDefaultPaymentMethods(Company $company): void
{
PaymentMethod::create(['name' => 'Cash', 'company_id' => $company->id]);
PaymentMethod::create(['name' => 'Check', 'company_id' => $company->id]);
PaymentMethod::create(['name' => 'Credit Card', 'company_id' => $company->id]);
PaymentMethod::create(['name' => 'Bank Transfer', 'company_id' => $company->id]);
}
private function setupDefaultUnits(Company $company): void
{
Unit::create(['name' => 'box', 'company_id' => $company->id]);
Unit::create(['name' => 'cm', 'company_id' => $company->id]);
Unit::create(['name' => 'dz', 'company_id' => $company->id]);
Unit::create(['name' => 'ft', 'company_id' => $company->id]);
Unit::create(['name' => 'g', 'company_id' => $company->id]);
Unit::create(['name' => 'in', 'company_id' => $company->id]);
Unit::create(['name' => 'kg', 'company_id' => $company->id]);
Unit::create(['name' => 'km', 'company_id' => $company->id]);
Unit::create(['name' => 'lb', 'company_id' => $company->id]);
Unit::create(['name' => 'mg', 'company_id' => $company->id]);
Unit::create(['name' => 'pc', 'company_id' => $company->id]);
}
private function setupDefaultSettings(Company $company): void
{
$defaultInvoiceEmailBody = 'You have received a new invoice from <b>{COMPANY_NAME}</b>.</br> Please download using the button below:';
$defaultEstimateEmailBody = 'You have received a new estimate from <b>{COMPANY_NAME}</b>.</br> Please download using the button below:';
$defaultPaymentEmailBody = 'Thank you for the payment.</b></br> Please download your payment receipt using the button below:';
$billingAddressFormat = '<h3>{BILLING_ADDRESS_NAME}</h3><p>{BILLING_ADDRESS_STREET_1}</p><p>{BILLING_ADDRESS_STREET_2}</p><p>{BILLING_CITY} {BILLING_STATE}</p><p>{BILLING_COUNTRY} {BILLING_ZIP_CODE}</p><p>{BILLING_PHONE}</p>';
$shippingAddressFormat = '<h3>{SHIPPING_ADDRESS_NAME}</h3><p>{SHIPPING_ADDRESS_STREET_1}</p><p>{SHIPPING_ADDRESS_STREET_2}</p><p>{SHIPPING_CITY} {SHIPPING_STATE}</p><p>{SHIPPING_COUNTRY} {SHIPPING_ZIP_CODE}</p><p>{SHIPPING_PHONE}</p>';
$companyAddressFormat = '<h3><strong>{COMPANY_NAME}</strong></h3><p>{COMPANY_ADDRESS_STREET_1}</p><p>{COMPANY_ADDRESS_STREET_2}</p><p>{COMPANY_CITY} {COMPANY_STATE}</p><p>{COMPANY_COUNTRY} {COMPANY_ZIP_CODE}</p><p>{COMPANY_PHONE}</p>';
$paymentFromCustomerAddress = '<h3>{BILLING_ADDRESS_NAME}</h3><p>{BILLING_ADDRESS_STREET_1}</p><p>{BILLING_ADDRESS_STREET_2}</p><p>{BILLING_CITY} {BILLING_STATE} {BILLING_ZIP_CODE}</p><p>{BILLING_COUNTRY}</p><p>{BILLING_PHONE}</p>';
$settings = [
'invoice_mail_body' => $defaultInvoiceEmailBody,
'estimate_mail_body' => $defaultEstimateEmailBody,
'payment_mail_body' => $defaultPaymentEmailBody,
'invoice_company_address_format' => $companyAddressFormat,
'invoice_shipping_address_format' => $shippingAddressFormat,
'invoice_billing_address_format' => $billingAddressFormat,
'estimate_company_address_format' => $companyAddressFormat,
'estimate_shipping_address_format' => $shippingAddressFormat,
'estimate_billing_address_format' => $billingAddressFormat,
'payment_company_address_format' => $companyAddressFormat,
'payment_from_customer_address_format' => $paymentFromCustomerAddress,
'currency' => request()->currency ?? 13,
'time_zone' => 'Asia/Kolkata',
'language' => 'en',
'fiscal_year' => '1-12',
'carbon_date_format' => 'Y/m/d',
'moment_date_format' => 'YYYY/MM/DD',
'carbon_time_format' => 'H:i',
'moment_time_format' => 'HH:mm',
'invoice_use_time' => 'NO',
'notification_email' => 'noreply@invoiceshelf.com',
'notify_invoice_viewed' => 'NO',
'notify_estimate_viewed' => 'NO',
'tax_per_item' => 'NO',
'discount_per_item' => 'NO',
'invoice_email_attachment' => 'NO',
'estimate_email_attachment' => 'NO',
'payment_email_attachment' => 'NO',
'retrospective_edits' => 'allow',
'invoice_number_format' => '{{SERIES:INV}}{{DELIMITER:-}}{{SEQUENCE:6}}',
'estimate_number_format' => '{{SERIES:EST}}{{DELIMITER:-}}{{SEQUENCE:6}}',
'payment_number_format' => '{{SERIES:PAY}}{{DELIMITER:-}}{{SEQUENCE:6}}',
'estimate_set_expiry_date_automatically' => 'YES',
'estimate_expiry_date_days' => 7,
'invoice_set_due_date_automatically' => 'YES',
'invoice_due_date_days' => 7,
'bulk_exchange_rate_configured' => 'YES',
'estimate_convert_action' => 'no_action',
'automatically_expire_public_links' => 'YES',
'link_expiry_days' => 7,
];
CompanySetting::setSettings($settings, $company->id);
}
private function checkModelData($model): void
{
$model->items->map(function ($item) {
if ($item->taxes()->exists()) {
$item->taxes()->delete();
}
$item->delete();
});
if ($model->taxes()->exists()) {
$model->taxes()->delete();
}
}
}