diff --git a/app/Models/Company.php b/app/Models/Company.php index 4c748195..382cbe9b 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -152,7 +152,11 @@ class Company extends Model implements HasMedia return $this->belongsToMany(User::class, 'user_company', 'company_id', 'user_id'); } - public function hasTransactions() + /** + * Check whether the company has any business data such as customers, + * items, invoices, estimates, expenses, payments, or recurring invoices. + */ + public function hasTransactions(): bool { if ( $this->customers()->exists() || diff --git a/app/Models/CompanySetting.php b/app/Models/CompanySetting.php index 459e4bc5..5f96676f 100644 --- a/app/Models/CompanySetting.php +++ b/app/Models/CompanySetting.php @@ -5,6 +5,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Support\Collection; class CompanySetting extends Model { @@ -22,7 +23,10 @@ class CompanySetting extends Model $query->where('company_id', $company_id); } - public static function setSettings($settings, $company_id) + /** + * Bulk create or update settings for a specific company. + */ + public static function setSettings(array $settings, mixed $company_id): void { foreach ($settings as $key => $value) { self::updateOrCreate( @@ -39,14 +43,20 @@ class CompanySetting extends Model } } - public static function getAllSettings($company_id) + /** + * Retrieve all settings for a company as a key-value collection. + */ + public static function getAllSettings(mixed $company_id): Collection { return static::whereCompany($company_id)->get()->mapWithKeys(function ($item) { return [$item['option'] => $item['value']]; }); } - public static function getSettings($settings, $company_id) + /** + * Retrieve specific settings for a company as a key-value collection. + */ + public static function getSettings(array $settings, mixed $company_id): Collection { return static::whereIn('option', $settings)->whereCompany($company_id) ->get()->mapWithKeys(function ($item) { @@ -54,7 +64,10 @@ class CompanySetting extends Model }); } - public static function getSetting($key, $company_id) + /** + * Retrieve a single company setting value by key, or null if not found. + */ + public static function getSetting(string $key, mixed $company_id): mixed { $setting = static::whereOption($key)->whereCompany($company_id)->first(); diff --git a/app/Models/CustomField.php b/app/Models/CustomField.php index 454e8c7e..2e0e2fd2 100644 --- a/app/Models/CustomField.php +++ b/app/Models/CustomField.php @@ -31,14 +31,14 @@ class CustomField extends Model ]; } - public function setTimeAnswerAttribute($value) + public function setTimeAnswerAttribute(mixed $value): void { if ($value && $value != null) { $this->attributes['time_answer'] = date('H:i:s', strtotime($value)); } } - public function setOptionsAttribute($value) + public function setOptionsAttribute(mixed $value): void { $this->attributes['options'] = json_encode($value); } diff --git a/app/Models/CustomFieldValue.php b/app/Models/CustomFieldValue.php index f3f39f1c..0895470f 100644 --- a/app/Models/CustomFieldValue.php +++ b/app/Models/CustomFieldValue.php @@ -24,7 +24,7 @@ class CustomFieldValue extends Model 'defaultAnswer', ]; - public function setTimeAnswerAttribute($value) + public function setTimeAnswerAttribute(mixed $value): void { if ($value && $value != null) { $this->attributes['time_answer'] = date('H:i:s', strtotime($value)); diff --git a/app/Models/Customer.php b/app/Models/Customer.php index 50df2a05..349a688e 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -119,7 +119,7 @@ class Customer extends Authenticatable implements HasMedia return $this->hasOne(Address::class)->where('type', Address::SHIPPING_TYPE); } - public function sendPasswordResetNotification($token) + public function sendPasswordResetNotification(mixed $token): void { $this->notify(new CustomerMailResetPasswordNotification($token)); } diff --git a/app/Models/EmailLog.php b/app/Models/EmailLog.php index 4a456013..e0aef939 100644 --- a/app/Models/EmailLog.php +++ b/app/Models/EmailLog.php @@ -18,7 +18,11 @@ class EmailLog extends Model return $this->morphTo(); } - public function isExpired() + /** + * Check if the email log's public link has expired based on the owning + * company's link expiry settings (link_expiry_days and automatically_expire_public_links). + */ + public function isExpired(): bool { $linkExpiryDays = (int) CompanySetting::getSetting('link_expiry_days', $this->mailable()->get()->toArray()[0]['company_id']); $checkExpiryLinks = CompanySetting::getSetting('automatically_expire_public_links', $this->mailable()->get()->toArray()[0]['company_id']); diff --git a/app/Models/Estimate.php b/app/Models/Estimate.php index a0a78965..6702e110 100644 --- a/app/Models/Estimate.php +++ b/app/Models/Estimate.php @@ -213,12 +213,12 @@ class Estimate extends Model implements HasMedia return $query->paginate($limit); } - public function getPDFData() + public function getPDFData(): mixed { return app(EstimateService::class)->getPdfData($this); } - public function getCompanyAddress() + public function getCompanyAddress(): string|false { if ($this->company && (! $this->company->address()->exists())) { return false; @@ -229,7 +229,7 @@ class Estimate extends Model implements HasMedia return $this->getFormattedString($format); } - public function getCustomerShippingAddress() + public function getCustomerShippingAddress(): string|false { if ($this->customer && (! $this->customer->shippingAddress()->exists())) { return false; @@ -240,7 +240,7 @@ class Estimate extends Model implements HasMedia return $this->getFormattedString($format); } - public function getCustomerBillingAddress() + public function getCustomerBillingAddress(): string|false { if ($this->customer && (! $this->customer->billingAddress()->exists())) { return false; @@ -251,12 +251,12 @@ class Estimate extends Model implements HasMedia return $this->getFormattedString($format); } - public function getNotes() + public function getNotes(): string { return PdfHtmlSanitizer::sanitize($this->getFormattedString($this->notes)); } - public function getEmailAttachmentSetting() + public function getEmailAttachmentSetting(): bool { $estimateAsAttachment = CompanySetting::getSetting('estimate_email_attachment', $this->company_id); @@ -267,7 +267,7 @@ class Estimate extends Model implements HasMedia return true; } - public function getEmailBody($body) + public function getEmailBody(string $body): string { $values = array_merge($this->getFieldsArray(), $this->getExtraFields()); @@ -276,7 +276,7 @@ class Estimate extends Model implements HasMedia return preg_replace('/{(.*?)}/', '', $body); } - public function getExtraFields() + public function getExtraFields(): array { return [ '{ESTIMATE_DATE}' => $this->formattedEstimateDate, @@ -286,7 +286,12 @@ class Estimate extends Model implements HasMedia ]; } - public function getInvoiceTemplateName() + /** + * Map the estimate's template name to the corresponding invoice template name. + * + * Falls back to 'invoice1' if the mapped name does not exist in available templates. + */ + public function getInvoiceTemplateName(): string { $templateName = Str::replace('estimate', 'invoice', $this->template_name); @@ -303,7 +308,13 @@ class Estimate extends Model implements HasMedia return $templateName; } - public function checkForEstimateConvertAction() + /** + * Handle the post-conversion action for this estimate based on company settings. + * + * Either deletes the estimate or marks it as accepted, depending on the + * 'estimate_convert_action' company setting. + */ + public function checkForEstimateConvertAction(): bool { $convertEstimateAction = CompanySetting::getSetting( 'estimate_convert_action', diff --git a/app/Models/ExchangeRateLog.php b/app/Models/ExchangeRateLog.php index e716037b..c7584526 100644 --- a/app/Models/ExchangeRateLog.php +++ b/app/Models/ExchangeRateLog.php @@ -31,7 +31,11 @@ class ExchangeRateLog extends Model return $this->belongsTo(Company::class); } - public static function addExchangeRateLog($model) + /** + * Create an exchange rate log entry from a document model (invoice, estimate, etc.) + * using its exchange rate, currency, and the company's default currency setting. + */ + public static function addExchangeRateLog(mixed $model): self { $data = [ 'exchange_rate' => $model->exchange_rate, diff --git a/app/Models/FileDisk.php b/app/Models/FileDisk.php index 5d933b5f..0a8fca72 100644 --- a/app/Models/FileDisk.php +++ b/app/Models/FileDisk.php @@ -5,6 +5,7 @@ namespace App\Models; use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Collection; class FileDisk extends Model { @@ -25,7 +26,7 @@ class FileDisk extends Model ]; } - public function setCredentialsAttribute($value) + public function setCredentialsAttribute(mixed $value): void { $this->attributes['credentials'] = json_encode($value); } @@ -80,7 +81,10 @@ class FileDisk extends Model } } - public function setConfig() + /** + * Apply this disk's credentials to the filesystem configuration at runtime. + */ + public function setConfig(): void { $driver = $this->driver; @@ -89,12 +93,18 @@ class FileDisk extends Model self::setFilesystem($credentials, $driver); } - public function setAsDefault() + /** + * Determine whether this disk is configured as the default storage disk. + */ + public function setAsDefault(): bool { return $this->set_as_default; } - public static function setFilesystem($credentials, $driver) + /** + * Register a dynamic filesystem disk in the runtime configuration using the given credentials. + */ + public static function setFilesystem(Collection $credentials, string $driver): void { $prefix = env('DYNAMIC_DISK_PREFIX', 'temp_'); @@ -111,12 +121,12 @@ class FileDisk extends Model config(['filesystems.disks.'.$prefix.$driver => $disks]); } - public function isSystem() + public function isSystem(): bool { return $this->type === self::DISK_TYPE_SYSTEM; } - public function isRemote() + public function isRemote(): bool { return $this->type === self::DISK_TYPE_REMOTE; } diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 9c35a0c5..35f22c4b 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -157,7 +157,7 @@ class Invoice extends Model implements HasMedia return $allowed; } - public function getPreviousStatus() + public function getPreviousStatus(): string { if ($this->viewed) { return self::STATUS_VIEWED; @@ -319,12 +319,12 @@ class Invoice extends Model implements HasMedia return $query->paginate($limit); } - public function getPDFData() + public function getPDFData(): mixed { return app(InvoiceService::class)->getPdfData($this); } - public function getEmailAttachmentSetting() + public function getEmailAttachmentSetting(): bool { $invoiceAsAttachment = CompanySetting::getSetting('invoice_email_attachment', $this->company_id); @@ -335,7 +335,7 @@ class Invoice extends Model implements HasMedia return true; } - public function getCompanyAddress() + public function getCompanyAddress(): string|false { if ($this->company && (! $this->company->address()->exists())) { return false; @@ -346,7 +346,7 @@ class Invoice extends Model implements HasMedia return $this->getFormattedString($format); } - public function getCustomerShippingAddress() + public function getCustomerShippingAddress(): string|false { if ($this->customer && (! $this->customer->shippingAddress()->exists())) { return false; @@ -357,7 +357,7 @@ class Invoice extends Model implements HasMedia return $this->getFormattedString($format); } - public function getCustomerBillingAddress() + public function getCustomerBillingAddress(): string|false { if ($this->customer && (! $this->customer->billingAddress()->exists())) { return false; @@ -368,12 +368,12 @@ class Invoice extends Model implements HasMedia return $this->getFormattedString($format); } - public function getNotes() + public function getNotes(): string { return PdfHtmlSanitizer::sanitize($this->getFormattedString($this->notes)); } - public function getEmailString($body) + public function getEmailString(string $body): string { $values = array_merge($this->getFieldsArray(), $this->getExtraFields()); @@ -382,7 +382,7 @@ class Invoice extends Model implements HasMedia return preg_replace('/{(.*?)}/', '', $body); } - public function getExtraFields() + public function getExtraFields(): array { return [ '{INVOICE_DATE}' => $this->formattedInvoiceDate, @@ -392,7 +392,10 @@ class Invoice extends Model implements HasMedia ]; } - public function addInvoicePayment($amount) + /** + * Add an amount to the invoice's due balance and recalculate the paid status. + */ + public function addInvoicePayment(int $amount): void { $this->due_amount += $amount; $this->base_due_amount = $this->due_amount * $this->exchange_rate; @@ -400,7 +403,10 @@ class Invoice extends Model implements HasMedia $this->changeInvoiceStatus($this->due_amount); } - public function subtractInvoicePayment($amount) + /** + * Subtract an amount from the invoice's due balance and recalculate the paid status. + */ + public function subtractInvoicePayment(int $amount): void { $this->due_amount -= $amount; $this->base_due_amount = $this->due_amount * $this->exchange_rate; @@ -409,11 +415,12 @@ class Invoice extends Model implements HasMedia } /** - * Set the invoice status from amount. + * Determine the invoice status and paid_status based on the remaining due amount. * - * @return array + * Returns an empty array for negative amounts, marks as paid when zero, + * unpaid when equal to total, or partially paid otherwise. */ - public function getInvoiceStatusByAmount($amount) + public function getInvoiceStatusByAmount(int $amount): array { if ($amount < 0) { return []; @@ -441,11 +448,9 @@ class Invoice extends Model implements HasMedia } /** - * Changes the invoice status right away - * - * @return string[]|void + * Persist the invoice status change immediately based on the given due amount. */ - public function changeInvoiceStatus($amount) + public function changeInvoiceStatus(int $amount): void { $status = $this->getInvoiceStatusByAmount($amount); if (! empty($status)) { diff --git a/app/Models/Payment.php b/app/Models/Payment.php index b836419b..139d80bd 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -212,12 +212,12 @@ class Payment extends Model implements HasMedia $query->where('payments.customer_id', $customer_id); } - public function getPDFData() + public function getPDFData(): mixed { return app(PaymentService::class)->getPdfData($this); } - public function getCompanyAddress() + public function getCompanyAddress(): string|false { if ($this->company && (! $this->company->address()->exists())) { return false; @@ -228,7 +228,7 @@ class Payment extends Model implements HasMedia return $this->getFormattedString($format); } - public function getCustomerBillingAddress() + public function getCustomerBillingAddress(): string|false { if ($this->customer && (! $this->customer->billingAddress()->exists())) { return false; @@ -239,7 +239,7 @@ class Payment extends Model implements HasMedia return $this->getFormattedString($format); } - public function getEmailAttachmentSetting() + public function getEmailAttachmentSetting(): bool { $paymentAsAttachment = CompanySetting::getSetting('payment_email_attachment', $this->company_id); @@ -250,12 +250,12 @@ class Payment extends Model implements HasMedia return true; } - public function getNotes() + public function getNotes(): string { return PdfHtmlSanitizer::sanitize($this->getFormattedString($this->notes)); } - public function getEmailBody($body) + public function getEmailBody(string $body): string { $values = array_merge($this->getFieldsArray(), $this->getExtraFields()); @@ -264,7 +264,7 @@ class Payment extends Model implements HasMedia return preg_replace('/{(.*?)}/', '', $body); } - public function getExtraFields() + public function getExtraFields(): array { return [ '{PAYMENT_DATE}' => $this->formattedPaymentDate, diff --git a/app/Models/PaymentMethod.php b/app/Models/PaymentMethod.php index ab3a8a3c..ebceb50c 100644 --- a/app/Models/PaymentMethod.php +++ b/app/Models/PaymentMethod.php @@ -93,7 +93,10 @@ class PaymentMethod extends Model return $query->paginate($limit); } - public static function createPaymentMethod($request) + /** + * Create a new payment method from a validated form request. + */ + public static function createPaymentMethod(mixed $request): self { $data = $request->getPaymentMethodPayload(); @@ -102,7 +105,10 @@ class PaymentMethod extends Model return $paymentMethod; } - public static function getSettings($id) + /** + * Retrieve the settings array for a payment method by its ID. + */ + public static function getSettings(int $id): mixed { $settings = PaymentMethod::find($id) ->settings; diff --git a/app/Models/RecurringInvoice.php b/app/Models/RecurringInvoice.php index 1affb3a5..a42ed365 100644 --- a/app/Models/RecurringInvoice.php +++ b/app/Models/RecurringInvoice.php @@ -190,7 +190,7 @@ class RecurringInvoice extends Model } } - public function markStatusAsCompleted() + public function markStatusAsCompleted(): void { if ($this->status == $this->status) { $this->status = self::COMPLETED; @@ -198,14 +198,14 @@ class RecurringInvoice extends Model } } - public static function getNextInvoiceDate($frequency, $starts_at) + public static function getNextInvoiceDate(string $frequency, string $starts_at): string { $cron = new Cron\CronExpression($frequency); return $cron->getNextRunDate($starts_at)->format('Y-m-d H:i:s'); } - public function updateNextInvoiceDate() + public function updateNextInvoiceDate(): void { $nextInvoiceAt = self::getNextInvoiceDate($this->frequency, $this->starts_at); diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 203b0c56..f5581428 100644 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -4,6 +4,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Collection; class Setting extends Model { @@ -11,7 +12,10 @@ class Setting extends Model protected $fillable = ['option', 'value']; - public static function setSetting($key, $setting) + /** + * Create or update a single application setting by key. + */ + public static function setSetting(string $key, mixed $setting): void { $old = self::whereOption($key)->first(); @@ -28,7 +32,10 @@ class Setting extends Model $set->save(); } - public static function setSettings($settings) + /** + * Bulk create or update application settings from a key-value array. + */ + public static function setSettings(array $settings): void { foreach ($settings as $key => $value) { self::updateOrCreate( @@ -43,7 +50,10 @@ class Setting extends Model } } - public static function getSetting($key) + /** + * Retrieve a single setting value by key, or null if not found. + */ + public static function getSetting(string $key): mixed { $setting = static::whereOption($key)->first(); @@ -54,7 +64,10 @@ class Setting extends Model } } - public static function getSettings($settings) + /** + * Retrieve multiple settings as a key-value collection. + */ + public static function getSettings(array $settings): Collection { return static::whereIn('option', $settings) ->get()->mapWithKeys(function ($item) { diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 7ec5ac9d..0ddccdcf 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -39,7 +39,11 @@ class Transaction extends Model return $this->belongsTo(Company::class); } - public function isExpired() + /** + * Check if a completed transaction's public link has expired based on the + * company's link expiry settings (link_expiry_days and automatically_expire_public_links). + */ + public function isExpired(): bool { $linkExpiryDays = (int) CompanySetting::getSetting('link_expiry_days', $this->company_id); $checkExpiryLinks = CompanySetting::getSetting('automatically_expire_public_links', $this->company_id); diff --git a/app/Models/User.php b/app/Models/User.php index a6d9384a..12ec0b30 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -12,6 +12,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Schema; use Laravel\Sanctum\HasApiTokens; use Silber\Bouncer\Database\HasRolesAndAbilities; @@ -57,16 +58,13 @@ class User extends Authenticatable implements HasMedia /** * Find the user instance for the given username. - * - * @param string $username - * @return \App\User */ - public function findForPassport($username) + public function findForPassport(string $username): ?self { return $this->where('email', $username)->first(); } - public function setPasswordAttribute($value) + public function setPasswordAttribute(string $value): void { if ($value != null) { $this->attributes['password'] = bcrypt($value); @@ -78,12 +76,12 @@ class User extends Authenticatable implements HasMedia return $this->role === 'super admin'; } - public function isSuperAdminOrAdmin() + public function isSuperAdminOrAdmin(): bool { return ($this->role == 'super admin') || ($this->role == 'admin'); } - public static function login($request) + public static function login(object $request): bool { $remember = $request->remember; $email = $request->email; @@ -296,7 +294,10 @@ class User extends Authenticatable implements HasMedia return 0; } - public function setSettings($settings) + /** + * Bulk upsert user settings, creating or updating each key-value pair. + */ + public function setSettings(array $settings): void { foreach ($settings as $key => $value) { $this->settings()->updateOrCreate( @@ -311,28 +312,31 @@ class User extends Authenticatable implements HasMedia } } - public function hasCompany($company_id) + public function hasCompany(int $company_id): bool { $companies = $this->companies()->pluck('company_id')->toArray(); return in_array($company_id, $companies); } - public function getAllSettings() + public function getAllSettings(): Collection { return $this->settings()->get()->mapWithKeys(function ($item) { return [$item['key'] => $item['value']]; }); } - public function getSettings($settings) + public function getSettings(array $settings): Collection { return $this->settings()->whereIn('key', $settings)->get()->mapWithKeys(function ($item) { return [$item['key'] => $item['value']]; }); } - public function isOwner() + /** + * Determine whether the user is the owner of the current company. + */ + public function isOwner(): bool { if (Schema::hasColumn('companies', 'owner_id')) { $company = Company::find(request()->header('company')); @@ -347,7 +351,11 @@ class User extends Authenticatable implements HasMedia return false; } - public function checkAccess($data) + /** + * Check whether the user has the required permissions based on ability data, + * considering super-admin status, company ownership, and Bouncer abilities. + */ + public function checkAccess(object $data): bool { if (! empty($data->data['super_admin_only']) && $data->data['super_admin_only']) { return $this->isSuperAdmin();