Add return types, typed parameters, and PHPDoc to all model methods

Modernize all 16 models with missing type declarations:
- Return types on ~87 methods (string, bool, void, array, mixed, etc.)
- Typed parameters where missing
- PHPDoc blocks on non-obvious methods explaining their purpose

Models updated: Invoice, Estimate, Payment, User, Company, Customer,
RecurringInvoice, Setting, CompanySetting, FileDisk, Transaction,
EmailLog, ExchangeRateLog, PaymentMethod, CustomField, CustomFieldValue.
This commit is contained in:
Darko Gjorgjijoski
2026-04-03 20:46:26 +02:00
parent c794f92932
commit 0fa1aac748
16 changed files with 157 additions and 75 deletions

View File

@@ -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() ||

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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));

View File

@@ -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));
}

View File

@@ -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']);

View File

@@ -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',

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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)) {

View File

@@ -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,

View File

@@ -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;

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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();