akaunting 3.0 (the last dance)

This commit is contained in:
Burak Civan
2022-06-01 10:15:55 +03:00
parent cead09f6d4
commit d9c0764572
3812 changed files with 126831 additions and 102949 deletions

View File

@ -2,16 +2,15 @@
namespace App\Models\Auth;
use Akaunting\Sortable\Traits\Sortable;
use App\Traits\Tenants;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Laratrust\Models\LaratrustPermission;
use Laratrust\Traits\LaratrustPermissionTrait;
use Kyslik\ColumnSortable\Sortable;
use Lorisleiva\LaravelSearchString\Concerns\SearchString;
class Permission extends LaratrustPermission
{
use HasFactory, LaratrustPermissionTrait, SearchString, Sortable, Tenants;
use LaratrustPermissionTrait, SearchString, Sortable, Tenants;
protected $table = 'permissions';
@ -79,14 +78,4 @@ class Permission extends LaratrustPermission
return $title;
}
/**
* Create a new factory instance for the model.
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
protected static function newFactory()
{
return \Database\Factories\Permission::new();
}
}

View File

@ -2,16 +2,15 @@
namespace App\Models\Auth;
use Akaunting\Sortable\Traits\Sortable;
use App\Traits\Tenants;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Laratrust\Models\LaratrustRole;
use Laratrust\Traits\LaratrustRoleTrait;
use Kyslik\ColumnSortable\Sortable;
use Lorisleiva\LaravelSearchString\Concerns\SearchString;
class Role extends LaratrustRole
{
use HasFactory, LaratrustRoleTrait, SearchString, Sortable, Tenants;
use LaratrustRoleTrait, SearchString, Sortable, Tenants;
protected $table = 'roles';
@ -22,6 +21,40 @@ class Role extends LaratrustRole
*/
protected $fillable = ['name', 'display_name', 'description', 'created_from', 'created_by'];
/**
* Get the line actions.
*
* @return array
*/
public function getLineActionsAttribute()
{
$actions = [];
$actions[] = [
'title' => trans('general.edit'),
'icon' => 'edit',
'url' => route('roles.roles.edit', $this->id),
'permission' => 'update-roles-roles',
];
$actions[] = [
'title' => trans('general.duplicate'),
'icon' => 'file_copy',
'url' => route('roles.roles.duplicate', $this->id),
'permission' => 'create-roles-roles',
];
$actions[] = [
'type' => 'delete',
'icon' => 'delete',
'route' => 'roles.roles.destroy',
'permission' => 'delete-roles-roles',
'model' => $this,
];
return $actions;
}
/**
* Scope to get all rows filtered, sorted and paginated.
*
@ -39,14 +72,4 @@ class Role extends LaratrustRole
return $query->usingSearchString($search)->sortable($sort)->paginate($limit);
}
/**
* Create a new factory instance for the model.
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
protected static function newFactory()
{
return \Database\Factories\Role::new();
}
}

View File

@ -2,11 +2,12 @@
namespace App\Models\Auth;
use App\Traits\Tenants;
use Akaunting\Sortable\Traits\Sortable;
use App\Notifications\Auth\Reset;
use App\Traits\Media;
use App\Traits\Owners;
use App\Traits\Sources;
use App\Traits\Tenants;
use App\Traits\Users;
use App\Utilities\Date;
use Illuminate\Contracts\Translation\HasLocalePreference;
@ -14,7 +15,6 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Kyslik\ColumnSortable\Sortable;
use Laratrust\Traits\LaratrustUserTrait;
use Lorisleiva\LaravelSearchString\Concerns\SearchString;
@ -65,11 +65,11 @@ class User extends Authenticatable implements HasLocalePreference
{
parent::boot();
static::retrieved(function($model) {
static::retrieved(function ($model) {
$model->setCompanyIds();
});
static::saving(function($model) {
static::saving(function ($model) {
$model->unsetCompanyIds();
});
}
@ -94,6 +94,10 @@ class User extends Authenticatable implements HasLocalePreference
*/
public function getNameAttribute($value)
{
if (empty($value)) {
return trans('general.na');
}
return ucfirst($value);
}
@ -106,7 +110,7 @@ class User extends Authenticatable implements HasLocalePreference
if (setting('default.use_gravatar', '0') == '1') {
try {
// Check for gravatar
$url = 'https://www.gravatar.com/avatar/' . md5(strtolower($this->getAttribute('email'))).'?size=90&d=404';
$url = 'https://www.gravatar.com/avatar/' . md5(strtolower($this->getAttribute('email'))) . '?size=90&d=404';
$client = new \GuzzleHttp\Client(['verify' => false]);
@ -141,14 +145,6 @@ class User extends Authenticatable implements HasLocalePreference
}
}
/**
* Send reset link to user via email
*/
public function sendPasswordResetNotification($token)
{
$this->notify(new Reset($token));
}
/**
* Always capitalize the name when we save it to the database
*/
@ -165,6 +161,14 @@ class User extends Authenticatable implements HasLocalePreference
$this->attributes['password'] = bcrypt($value);
}
/**
* Send reset link to user via email
*/
public function sendPasswordResetNotification($token)
{
$this->notify(new Reset($token));
}
/**
* Scope to get all rows filtered, sorted and paginated.
*
@ -294,6 +298,48 @@ class User extends Authenticatable implements HasLocalePreference
return $this->locale;
}
/**
* Get the line actions.
*
* @return array
*/
public function getLineActionsAttribute()
{
$actions = [];
if (user()->id == $this->id) {
return $actions;
}
if (! $this->hasPendingInvitation()) {
$actions[] = [
'title' => trans('general.edit'),
'icon' => 'edit',
'url' => route('users.edit', $this->id),
'permission' => 'update-auth-users',
];
}
if ($this->hasPendingInvitation()) {
$actions[] = [
'title' => trans('general.resend') . ' ' . trans_choice('general.invitations', 1),
'icon' => 'replay',
'url' => route('users.invite', $this->id),
'permission' => 'update-auth-users',
];
}
$actions[] = [
'type' => 'delete',
'icon' => 'delete',
'route' => 'users.destroy',
'permission' => 'delete-auth-users',
'model' => $this,
];
return $actions;
}
/**
* Create a new factory instance for the model.
*

View File

@ -0,0 +1,40 @@
<?php
namespace App\Models\Auth;
use App\Abstracts\Model;
class UserInvitation extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'user_invitations';
protected $tenantable = false;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = ['user_id', 'company_id', 'token'];
public function user()
{
return $this->belongsTo('App\Models\Auth\User');
}
/**
* Scope a query to only include given token value.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return void
*/
public function scopeToken($query, $token)
{
$query->where('token', $token);
}
}

View File

@ -4,8 +4,8 @@ namespace App\Models\Banking;
use App\Abstracts\Model;
use App\Traits\Transactions;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Bkwld\Cloner\Cloneable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Account extends Model
{
@ -25,7 +25,7 @@ class Account extends Model
*
* @var array
*/
protected $fillable = ['company_id', 'name', 'number', 'currency_code', 'opening_balance', 'bank_name', 'bank_phone', 'bank_address', 'enabled', 'created_from', 'created_by'];
protected $fillable = ['company_id', 'type', 'name', 'number', 'currency_code', 'opening_balance', 'bank_name', 'bank_phone', 'bank_address', 'enabled', 'created_from', 'created_by'];
/**
* The attributes that should be cast.
@ -42,7 +42,7 @@ class Account extends Model
*
* @var array
*/
public $sortable = ['name', 'number', 'opening_balance', 'enabled'];
public $sortable = ['name', 'number', 'balance', 'bank_name', 'bank_phone'];
public function currency()
{
@ -74,6 +74,21 @@ class Account extends Model
return $query->where('number', '=', $number);
}
/**
* Sort by balance
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param $direction
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function balanceSortable($query, $direction)
{
return $query//->join('transactions', 'transactions.account_id', '=', 'accounts.id')
->orderBy('balance', $direction)
->select(['accounts.*', 'accounts.opening_balance as balance']);
}
/**
* Get the current balance.
*
@ -127,6 +142,39 @@ class Account extends Model
return $total;
}
/**
* Get the line actions.
*
* @return array
*/
public function getLineActionsAttribute()
{
$actions = [];
$actions[] = [
'title' => trans('general.show'),
'icon' => 'visibility',
'url' => route('accounts.show', $this->id),
'permission' => 'read-banking-accounts',
];
$actions[] = [
'title' => trans('general.edit'),
'icon' => 'edit',
'url' => route('accounts.edit', $this->id),
'permission' => 'update-banking-accounts',
];
$actions[] = [
'type' => 'delete',
'icon' => 'delete',
'route' => 'accounts.destroy',
'permission' => 'delete-banking-accounts',
'model' => $this,
];
return $actions;
}
/**
* Create a new factory instance for the model.

View File

@ -42,6 +42,33 @@ class Reconciliation extends Model
return $this->belongsTo('App\Models\Banking\Account');
}
/**
* Get the line actions.
*
* @return array
*/
public function getLineActionsAttribute()
{
$actions = [];
$actions[] = [
'title' => trans('general.edit'),
'icon' => 'edit',
'url' => route('reconciliations.edit', $this->id),
'permission' => 'update-banking-reconciliations',
];
$actions[] = [
'type' => 'delete',
'icon' => 'delete',
'route' => 'reconciliations.destroy',
'permission' => 'delete-banking-reconciliations',
'model' => $this,
];
return $actions;
}
/**
* Create a new factory instance for the model.
*

View File

@ -12,6 +12,7 @@ use App\Traits\Media;
use App\Traits\Recurring;
use App\Traits\Transactions;
use Bkwld\Cloner\Cloneable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Str;
@ -19,6 +20,13 @@ class Transaction extends Model
{
use Cloneable, Currencies, DateTime, HasFactory, Media, Recurring, Transactions;
public const INCOME_TYPE = 'income';
public const INCOME_SPLIT_TYPE = 'income-split';
public const INCOME_RECURRING_TYPE = 'income-recurring';
public const EXPENSE_TYPE = 'expense';
public const EXPENSE_SPLIT_TYPE = 'expense-split';
public const EXPENSE_RECURRING_TYPE = 'expense-recurring';
protected $table = 'transactions';
protected $dates = ['deleted_at', 'paid_at'];
@ -31,6 +39,7 @@ class Transaction extends Model
protected $fillable = [
'company_id',
'type',
'number',
'account_id',
'paid_at',
'amount',
@ -43,6 +52,7 @@ class Transaction extends Model
'payment_method',
'reference',
'parent_id',
'split_id',
'created_from',
'created_by',
];
@ -62,7 +72,7 @@ class Transaction extends Model
*
* @var array
*/
public $sortable = ['paid_at', 'amount','category.name', 'account.name'];
public $sortable = ['type', 'number', 'paid_at', 'amount','category.name', 'account.name', 'customer.name', 'invoice.document_number'];
/**
* Clonable relationships.
@ -96,6 +106,11 @@ class Transaction extends Model
return $this->belongsTo('App\Models\Setting\Category')->withDefault(['name' => trans('general.na')]);
}
public function children()
{
return $this->hasMany('App\Models\Banking\Transaction', 'parent_id');
}
public function contact()
{
return $this->belongsTo('App\Models\Common\Contact')->withDefault(['name' => trans('general.na')]);
@ -116,29 +131,27 @@ class Transaction extends Model
return $this->belongsTo('App\Models\Document\Document', 'document_id');
}
public function parent()
{
return $this->belongsTo('App\Models\Banking\Transaction', 'parent_id');
}
public function recurring()
{
return $this->morphOne('App\Models\Common\Recurring', 'recurable');
}
public function splits()
{
return $this->hasMany('App\Models\Banking\Transaction', 'split_id');
}
public function user()
{
return $this->belongsTo('App\Models\Auth\User', 'contact_id', 'id');
}
public function parent()
{
return $this->belongsTo('App\Models\Banking\Transaction', 'parent_id');
}
/**
* Scope to only include contacts of a given type.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param mixed $types
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeType($query, $types)
public function scopeType(Builder $query, $types): Builder
{
if (empty($types)) {
return $query;
@ -147,167 +160,110 @@ class Transaction extends Model
return $query->whereIn($this->qualifyColumn('type'), (array) $types);
}
/**
* Scope to include only income.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeIncome($query)
public function scopeIncome(Builder $query): Builder
{
return $query->whereIn($this->qualifyColumn('type'), (array) $this->getIncomeTypes());
}
/**
* Scope to include only expense.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeExpense($query)
public function scopeIncomeRecurring(Builder $query): Builder
{
return $query->where($this->qualifyColumn('type'), '=', self::INCOME_RECURRING_TYPE);
}
public function scopeExpense(Builder $query): Builder
{
return $query->whereIn($this->qualifyColumn('type'), (array) $this->getExpenseTypes());
}
/**
* Get only transfers.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeIsTransfer($query)
public function scopeExpenseRecurring(Builder $query): Builder
{
return $query->where($this->qualifyColumn('type'), '=', self::EXPENSE_RECURRING_TYPE);
}
public function scopeIsRecurring(Builder $query): Builder
{
return $query->where($this->qualifyColumn('type'), 'like', '%-recurring');
}
public function scopeIsNotRecurring(Builder $query): Builder
{
return $query->where($this->qualifyColumn('type'), 'not like', '%-recurring');
}
public function scopeIsTransfer(Builder $query): Builder
{
return $query->where('category_id', '=', Category::transfer());
}
/**
* Skip transfers.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeIsNotTransfer($query)
public function scopeIsNotTransfer(Builder $query): Builder
{
return $query->where('category_id', '<>', Category::transfer());
}
/**
* Get only documents (invoice/bill).
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeIsDocument($query)
public function scopeIsDocument(Builder $query): Builder
{
return $query->whereNotNull('document_id');
}
/**
* Get only transactions (revenue/payment).
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeIsNotDocument($query)
public function scopeIsNotDocument(Builder $query): Builder
{
return $query->whereNull('document_id');
}
/**
* Get by document id.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param integer $document_id
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeDocumentId($query, $document_id)
public function scopeDocumentId(Builder $query, int $document_id): Builder
{
return $query->where('document_id', '=', $document_id);
}
/**
* Get by account id.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param integer $account_id
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeAccountId($query, $account_id)
public function scopeAccountId(Builder $query, int $account_id): Builder
{
return $query->where('account_id', '=', $account_id);
}
/**
* Get by contact id.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param integer $contact_id
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeContactId($query, $contact_id)
public function scopeContactId(Builder $query, int $contact_id): Builder
{
return $query->where('contact_id', '=', $contact_id);
}
/**
* Get by category id.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param integer $category_id
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeCategoryId($query, $category_id)
public function scopeCategoryId(Builder $query, int $category_id): Builder
{
return $query->where('category_id', '=', $category_id);
}
/**
* Order by paid date.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeLatest($query)
public function scopeLatest(Builder $query): Builder
{
return $query->orderBy('paid_at', 'desc');
}
/**
* Scope paid invoice.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePaid($query)
public function scopePaid(Builder $query): Builder
{
return $query->sum('amount');
}
/**
* Get only reconciled.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeIsReconciled($query)
public function scopeIsReconciled(Builder $query): Builder
{
return $query->where('reconciled', 1);
}
/**
* Get only not reconciled.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeIsNotReconciled($query)
public function scopeIsNotReconciled(Builder $query): Builder
{
return $query->where('reconciled', 0);
}
public function onCloning($src, $child = null)
{
$this->document_id = null;
if (app()->has(\App\Console\Commands\RecurringCheck::class)) {
$suffix = '';
} else {
$suffix = $src->isRecurringTransaction() ? '-recurring' : '';
}
$this->number = $this->getNextTransactionNumber($suffix);
$this->document_id = null;
$this->split_id = null;
}
/**
@ -346,6 +302,16 @@ class Transaction extends Model
return $this->getMedia('attachment')->all();
}
/**
* Get the splittable status.
*
* @return bool
*/
public function getIsSplittableAttribute()
{
return is_null($this->split_id);
}
public function delete_attachment()
{
if ($attachments = $this->attachment) {
@ -362,7 +328,7 @@ class Transaction extends Model
*/
public function getHasTransferRelationAttribute()
{
return (bool) (optional($this->category)->id == optional($this->category)->transfer());
return (bool) ($this->category?->id == $this->category?->transfer());
}
/**
@ -372,7 +338,9 @@ class Transaction extends Model
*/
public function getTypeTitleAttribute($value)
{
return $value ?? trans_choice('general.' . Str::plural($this->type), 1);
$type = $this->getRealTypeOfRecurringTransaction($this->type);
return $value ?? trans_choice('general.' . Str::plural($type), 1);
}
/**
@ -387,18 +355,18 @@ class Transaction extends Model
}
if ($this->isIncome()) {
if (! empty($this->document) && $this->document->type != 'invoice') {
if (! empty($this->document_id) && $this->document->type != 'invoice') {
return $this->getRouteFromConfig();
} else {
return ! empty($this->document_id) ? 'invoices.show' : 'revenues.show';
return !empty($this->document_id) ? 'invoices.show' : 'transactions.show';
}
}
if ($this->isExpense()) {
if (! empty($this->document) && $this->document->type != 'bill') {
if (! empty($this->document_id) && $this->document->type != 'bill') {
return $this->getRouteFromConfig();
} else {
return ! empty($this->document_id) ? 'bills.show' : 'payments.show';
return !empty($this->document_id) ? 'bills.show' : 'transactions.show';
}
}
@ -409,8 +377,8 @@ class Transaction extends Model
{
$route = '';
$alias = config('type.' . $this->document->type . '.alias');
$prefix = config('type.' . $this->document->type . '.route.prefix');
$alias = config('type.document.' . $this->document->type . '.alias');
$prefix = config('type.document.' . $this->document->type . '.route.prefix');
// if use module set module alias
if (!empty($alias)) {
@ -438,10 +406,195 @@ class Transaction extends Model
return !empty($value) ? $value : (!empty($this->document_id) ? $this->document_id : $this->id);
}
public function getTemplatePathAttribute($value = null)
/**
* Get the line actions.
*
* @return array
*/
public function getLineActionsAttribute()
{
$type_for_theme = ($this->type == 'income') ? 'sales.revenues.print_default' : 'purchases.payments.print_default';
return $value ?: $type_for_theme;
$actions = [];
$prefix = 'transactions';
if (Str::contains($this->type, 'recurring')) {
$prefix = 'recurring-transactions';
}
try {
$actions[] = [
'title' => trans('general.show'),
'icon' => 'visibility',
'url' => route($prefix. '.show', $this->id),
'permission' => 'read-banking-transactions',
'attributes' => [
'id' => 'index-more-actions-show-' . $this->id,
],
];
} catch (\Exception $e) {}
try {
if (! $this->reconciled) {
$actions[] = [
'title' => trans('general.edit'),
'icon' => 'edit',
'url' => route($prefix. '.edit', $this->id),
'permission' => 'update-banking-transactions',
'attributes' => [
'id' => 'index-more-actions-edit-' . $this->id,
],
];
}
} catch (\Exception $e) {}
try {
if (empty($this->document_id)) {
$actions[] = [
'title' => trans('general.duplicate'),
'icon' => 'file_copy',
'url' => route($prefix. '.duplicate', $this->id),
'permission' => 'create-banking-transactions',
'attributes' => [
'id' => 'index-more-actions-duplicate-' . $this->id,
],
];
}
} catch (\Exception $e) {}
try {
if ($this->is_splittable && empty($this->document_id) && empty($this->recurring)) {
$conenct = [
'type' => 'button',
'title' => trans('general.connect'),
'icon' => 'sensors',
'permission' => 'create-banking-transactions',
'attributes' => [
'id' => 'index-transactions-more-actions-connect-' . $this->id,
],
];
$transaction = $this->load('account')->toJson();
$currency = $this->currency->toJson();
if ($this->contact->exists) {
$document = $this->contact->invoices()->notPaid()->where('currency_code', $this->currency_code)->with(['media', 'totals', 'transactions'])->get()->toJson();
$conenct['attributes']['@click'] = 'onConnect()';
} else {
$document = \App\Models\Document\Document::invoice()->notPaid()->where('currency_code', $this->currency_code)->with(['media', 'totals', 'transactions'])->get()->toJson();
$conenct['attributes']['@click'] = 'onConnect()';
}
$actions[] = $conenct;
$actions[] = [
'type' => 'divider',
];
}
} catch (\Exception $e) {}
try {
$actions[] = [
'title' => trans('general.print'),
'icon' => 'print',
'url' => route($prefix. '.print', $this->id),
'permission' => 'read-banking-transactions',
'attributes' => [
'id' => 'index-more-actions-print-' . $this->id,
'target' => '_blank',
],
];
} catch (\Exception $e) {}
try {
$actions[] = [
'title' => trans('general.download_pdf'),
'icon' => 'pdf',
'url' => route($prefix. '.pdf', $this->id),
'permission' => 'read-banking-transactions',
'attributes' => [
'id' => 'index-more-actions-pdf-' . $this->id,
'target' => '_blank',
],
];
} catch (\Exception $e) {}
if ($prefix != 'recurring-transactions') {
$actions[] = [
'type' => 'divider',
];
try {
$actions[] = [
'type' => 'button',
'title' => trans('general.share_link'),
'icon' => 'share',
'url' => route('modals.transactions.share.create', $this->id),
'permission' => 'read-banking-transactions',
'attributes' => [
'id' => 'index-more-actions-share-' . $this->id,
'@click' => 'onShareLink("' . route('modals.transactions.share.create', $this->id) . '")',
],
];
} catch (\Exception $e) {}
try {
$actions[] = [
'type' => 'button',
'title' => trans('invoices.send_mail'),
'icon' => 'email',
'url' => route('modals.transactions.emails.create', $this->id),
'permission' => 'read-banking-transactions',
'attributes' => [
'id' => 'index-more-actions-send-email-' . $this->id,
'@click' => 'onEmail("' . route('modals.transactions.emails.create', $this->id) . '")',
],
];
} catch (\Exception $e) {}
$actions[] = [
'type' => 'divider',
];
try {
if (! $this->reconciled) {
$actions[] = [
'type' => 'delete',
'icon' => 'delete',
'text' => ! empty($this->recurring) ? 'transactions' : 'recurring_template',
'route' => $prefix. '.destroy',
'permission' => 'delete-banking-transactions',
'model' => $this,
];
}
} catch (\Exception $e) {}
} else {
try {
$actions[] = [
'title' => trans('general.end'),
'icon' => 'block',
'url' => route($prefix. '.end', $this->id),
'permission' => 'update-banking-transactions',
];
} catch (\Exception $e) {}
}
return $actions;
}
/**
* Get the recurring status label.
*
* @return string
*/
public function getRecurringStatusLabelAttribute()
{
return match($this->recurring->status) {
'active' => 'status-partial',
'ended' => 'status-success',
default => 'status-success',
};
}
/**

View File

@ -224,6 +224,47 @@ class Transfer extends Model
return $value ?: $this->expense_transaction->reference;
}
/**
* Get the line actions.
*
* @return array
*/
public function getLineActionsAttribute()
{
$actions = [];
$actions[] = [
'title' => trans('general.show'),
'icon' => 'visibility',
'url' => route('transfers.show', $this->id),
'permission' => 'read-banking-transfers',
];
$actions[] = [
'title' => trans('general.edit'),
'icon' => 'edit',
'url' => route('transfers.edit', $this->id),
'permission' => 'update-banking-transfers',
];
$actions[] = [
'title' => trans('general.duplicate'),
'icon' => 'file_copy',
'url' => route('transfers.duplicate', $this->id),
'permission' => 'update-banking-transfers',
];
$actions[] = [
'type' => 'delete',
'icon' => 'delete',
'route' => 'transfers.destroy',
'permission' => 'delete-banking-transfers',
'model' => $this,
];
return $actions;
}
/**
* Create a new factory instance for the model.
*

View File

@ -2,6 +2,7 @@
namespace App\Models\Common;
use Akaunting\Sortable\Traits\Sortable;
use App\Events\Common\CompanyForgettingCurrent;
use App\Events\Common\CompanyForgotCurrent;
use App\Events\Common\CompanyMadeCurrent;
@ -14,15 +15,15 @@ use App\Traits\Sources;
use App\Traits\Tenants;
use App\Traits\Transactions;
use App\Utilities\Overrider;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Database\Eloquent\SoftDeletes;
use Kyslik\ColumnSortable\Sortable;
use Laratrust\Contracts\Ownable;
use Lorisleiva\LaravelSearchString\Concerns\SearchString;
class Company extends Eloquent implements Ownable
{
use Contacts, Media, Owners, SearchString, SoftDeletes, Sortable, Sources, Tenants, Transactions;
use Contacts, HasFactory, Media, Owners, SearchString, SoftDeletes, Sortable, Sources, Tenants, Transactions;
protected $table = 'companies';
@ -48,33 +49,21 @@ class Company extends Eloquent implements Ownable
*
* @var array
*/
public $sortable = ['name', 'domain', 'email', 'enabled', 'created_at'];
public $sortable = ['id', 'name', 'domain', 'email', 'enabled', 'created_at', 'tax_number', 'country', 'currency'];
/**
* Create a new Eloquent model instance.
* Fill the model with an array of attributes.
*
* @param array $attributes
* @return void
* @return $this
*
* @throws \Illuminate\Database\Eloquent\MassAssignmentException
*/
public function __construct(array $attributes = [])
public function fill(array $attributes)
{
$this->allAttributes = $attributes;
parent::__construct($attributes);
}
/**
* Update the model in the database.
*
* @param array $attributes
* @param array $options
* @return bool
*/
public function update(array $attributes = [], array $options = [])
{
$this->allAttributes = $attributes;
return parent::update($attributes, $options);
return parent::fill($attributes);
}
public static function boot()
@ -90,7 +79,7 @@ class Company extends Eloquent implements Ownable
$model->unsetCommonSettingsFromAttributes();
});
} catch(\Throwable $e) {
}
}
@ -176,7 +165,7 @@ class Company extends Eloquent implements Ownable
public function email_templates()
{
return $this->hasMany('App\Models\Common\EmailTemplate');
return $this->hasMany('App\Models\Setting\EmailTemplate');
}
public function expense_transactions()
@ -298,7 +287,7 @@ class Company extends Eloquent implements Ownable
list($group, $key) = explode('.', $setting->getAttribute('key'));
// Load only general settings
if (!in_array($group, $groups)) {
if (! in_array($group, $groups)) {
continue;
}
@ -315,8 +304,13 @@ class Company extends Eloquent implements Ownable
if ($this->getAttribute('logo') == '') {
$this->setAttribute('logo', 'public/img/company.png');
}
// Set default default company currency if empty
if ($this->getAttribute('currency') == '') {
$this->setAttribute('currency', config('setting.fallback.default.currency'));
}
} catch(\Throwable $e) {
}
}
@ -334,7 +328,7 @@ class Company extends Eloquent implements Ownable
list($group, $key) = explode('.', $setting->getAttribute('key'));
// Load only general settings
if (!in_array($group, $groups)) {
if (! in_array($group, $groups)) {
continue;
}
@ -343,7 +337,7 @@ class Company extends Eloquent implements Ownable
$this->offsetUnset('logo');
} catch(\Throwable $e) {
}
}
@ -430,6 +424,54 @@ class Company extends Eloquent implements Ownable
->select('companies.*');
}
/**
* Sort by company tax number
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param $direction
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function taxNumberSortable($query, $direction)
{
return $query->join('settings', 'companies.id', '=', 'settings.company_id')
->where('key', 'company.tax_number')
->orderBy('value', $direction)
->select('companies.*');
}
/**
* Sort by company country
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param $direction
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function countrySortable($query, $direction)
{
return $query->join('settings', 'companies.id', '=', 'settings.company_id')
->where('key', 'company.country')
->orderBy('value', $direction)
->select('companies.*');
}
/**
* Sort by company currency
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param $direction
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function currencySortable($query, $direction)
{
return $query->join('settings', 'companies.id', '=', 'settings.company_id')
->where('key', 'default.currency')
->orderBy('value', $direction)
->select('companies.*');
}
/**
* Scope autocomplete.
*
@ -489,6 +531,42 @@ class Company extends Eloquent implements Ownable
return implode(', ', $location);
}
/**
* Get the line actions.
*
* @return array
*/
public function getLineActionsAttribute()
{
$actions = [];
if ($this->enabled) {
$actions[] = [
'title' => trans('general.switch'),
'icon' => 'settings_ethernet',
'url' => route('companies.switch', $this->id),
'permission' => 'read-common-companies',
];
}
$actions[] = [
'title' => trans('general.edit'),
'icon' => 'edit',
'url' => route('companies.edit', $this->id),
'permission' => 'update-common-companies',
];
$actions[] = [
'type' => 'delete',
'icon' => 'delete',
'route' => 'companies.destroy',
'permission' => 'delete-common-companies',
'model' => $this,
];
return $actions;
}
public function makeCurrent($force = false)
{
if (!$force && $this->isCurrent()) {
@ -502,9 +580,6 @@ class Company extends Eloquent implements Ownable
// Bind to container
app()->instance(static::class, $this);
// Set session for backward compatibility @deprecated
//session(['company_id' => $this->id]);
// Load settings
setting()->setExtraColumns(['company_id' => $this->id]);
setting()->forgetAll();
@ -521,7 +596,7 @@ class Company extends Eloquent implements Ownable
public function isCurrent()
{
return optional(static::getCurrent())->id === $this->id;
return static::getCurrent()?->id === $this->id;
}
public function isNotCurrent()
@ -551,9 +626,6 @@ class Company extends Eloquent implements Ownable
// Remove from container
app()->forgetInstance(static::class);
// Unset session for backward compatibility @deprecated
//session()->forget('company_id');
// Remove settings
setting()->forgetAll();
@ -590,4 +662,14 @@ class Company extends Eloquent implements Ownable
return $this->created_by;
}
/**
* Create a new factory instance for the model.
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
protected static function newFactory()
{
return \Database\Factories\Company::new();
}
}

View File

@ -2,21 +2,28 @@
namespace App\Models\Common;
use App\Traits\Media;
use App\Abstracts\Model;
use App\Models\Document\Document;
use App\Scopes\Contact as Scope;
use App\Traits\Contacts;
use App\Traits\Currencies;
use App\Traits\Media;
use App\Traits\Transactions;
use App\Scopes\Contact as Scope;
use App\Models\Document\Document;
use App\Utilities\Date;
use App\Utilities\Str;
use Bkwld\Cloner\Cloneable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Contact extends Model
{
use Cloneable, Contacts, Currencies, HasFactory, Media, Notifiable, Transactions;
public const CUSTOMER_TYPE = 'customer';
public const VENDOR_TYPE = 'vendor';
public const EMPLOYEE_TYPE = 'employee';
protected $table = 'contacts';
/**
@ -169,6 +176,11 @@ class Contact extends Model
$this->user_id = null;
}
public function getInitialsAttribute($value)
{
return Str::getInitials($this->name);
}
/**
* Get the current balance.
*
@ -191,7 +203,35 @@ class Contact extends Model
$collection = $this->isCustomer() ? 'invoices' : 'bills';
$this->$collection->whereNotIn('status', ['draft', 'cancelled', 'paid'])->each(function ($item) use (&$amount) {
$this->$collection->whereIn('status', ['sent', 'received', 'viewed', 'partial'])->each(function ($item) use (&$amount) {
$amount += $this->convertToDefault($item->amount_due, $item->currency_code, $item->currency_rate);
});
return $amount;
}
public function getOpenAttribute()
{
$amount = 0;
$today = Date::today()->toDateString();
$collection = $this->isCustomer() ? 'invoices' : 'bills';
$this->$collection->whereIn('status', ['sent', 'received', 'viewed', 'partial'])->where('due_at', '>=', $today)->each(function ($item) use (&$amount) {
$amount += $this->convertToDefault($item->amount_due, $item->currency_code, $item->currency_rate);
});
return $amount;
}
public function getOverdueAttribute()
{
$amount = 0;
$today = Date::today()->toDateString();
$collection = $this->isCustomer() ? 'invoices' : 'bills';
$this->$collection->whereIn('status', ['sent', 'received', 'viewed', 'partial'])->where('due_at', '<', $today)->each(function ($item) use (&$amount) {
$amount += $this->convertToDefault($item->amount_due, $item->currency_code, $item->currency_rate);
});
@ -221,6 +261,71 @@ class Contact extends Model
return implode(', ', $location);
}
/**
* Get the line actions.
*
* @return array
*/
public function getLineActionsAttribute()
{
$actions = [];
$group = config('type.contact.' . $this->type . '.group');
$prefix = config('type.contact.' . $this->type . '.route.prefix');
$permission_prefix = config('type.contact.' . $this->type . '.permission.prefix');
$translation_prefix = config('type.contact.' . $this->type . '.translation.prefix');
if (empty($prefix)) {
if (in_array($this->type, (array) $this->getCustomerTypes())) {
$prefix = config('type.contact.customer.route.prefix');
} elseif (in_array($this->type, (array) $this->getVendorTypes())) {
$prefix = config('type.contact.vendor.route.prefix');
} else {
return $actions;
}
}
try {
$actions[] = [
'title' => trans('general.show'),
'icon' => 'visibility',
'url' => route($prefix . '.show', $this->id),
'permission' => 'read-' . $group . '-' . $permission_prefix,
];
} catch (\Exception $e) {}
try {
$actions[] = [
'title' => trans('general.edit'),
'icon' => 'edit',
'url' => route($prefix . '.edit', $this->id),
'permission' => 'update-' . $group . '-' . $permission_prefix,
];
} catch (\Exception $e) {}
try {
$actions[] = [
'title' => trans('general.duplicate'),
'icon' => 'file_copy',
'url' => route($prefix . '.duplicate', $this->id),
'permission' => 'create-' . $group . '-' . $permission_prefix,
];
} catch (\Exception $e) {}
try {
$actions[] = [
'type' => 'delete',
'icon' => 'delete',
'title' => $translation_prefix,
'route' => $prefix . '.destroy',
'permission' => 'delete-' . $group . '-' . $permission_prefix,
'model' => $this,
];
} catch (\Exception $e) {}
return $actions;
}
/**
* Create a new factory instance for the model.
*

View File

@ -114,6 +114,42 @@ class Dashboard extends Model
return $alias;
}
/**
* Get the line actions.
*
* @return array
*/
public function getLineActionsAttribute()
{
$actions = [];
if ($this->enabled) {
$actions[] = [
'title' => trans('general.switch'),
'icon' => 'settings_ethernet',
'url' => route('dashboards.switch', $this->id),
'permission' => 'read-common-dashboards',
];
}
$actions[] = [
'title' => trans('general.edit'),
'icon' => 'edit',
'url' => route('dashboards.edit', $this->id),
'permission' => 'update-common-dashboards',
];
$actions[] = [
'type' => 'delete',
'icon' => 'delete',
'route' => 'dashboards.destroy',
'permission' => 'delete-common-dashboards',
'model' => $this,
];
return $actions;
}
/**
* Create a new factory instance for the model.
*

View File

@ -27,7 +27,7 @@ class Item extends Model
*
* @var array
*/
protected $fillable = ['company_id', 'name', 'description', 'sale_price', 'purchase_price', 'category_id', 'enabled', 'created_from', 'created_by'];
protected $fillable = ['company_id', 'type', 'name', 'description', 'sale_price', 'purchase_price', 'category_id', 'enabled', 'created_from', 'created_by'];
/**
* The attributes that should be cast.
@ -45,7 +45,7 @@ class Item extends Model
*
* @var array
*/
protected $sortable = ['name', 'category', 'sale_price', 'purchase_price', 'enabled'];
protected $sortable = ['name', 'category.name', 'sale_price', 'purchase_price', 'enabled'];
/**
* @var array
@ -82,6 +82,11 @@ class Item extends Model
return $query->where('name', '=', $name);
}
public function scopeBilling($query, $billing)
{
return $query->where($billing . '_price', '=', null);
}
/**
* Get the item id.
*
@ -149,6 +154,40 @@ class Item extends Model
return $this->getMedia('picture')->last();
}
/**
* Get the line actions.
*
* @return array
*/
public function getLineActionsAttribute()
{
$actions = [];
$actions[] = [
'title' => trans('general.edit'),
'icon' => 'edit',
'url' => route('items.edit', $this->id),
'permission' => 'update-common-items',
];
$actions[] = [
'title' => trans('general.duplicate'),
'icon' => 'file_copy',
'url' => route('items.duplicate', $this->id),
'permission' => 'create-common-items',
];
$actions[] = [
'type' => 'delete',
'icon' => 'delete',
'route' => 'items.destroy',
'permission' => 'delete-common-items',
'model' => $this,
];
return $actions;
}
/**
* Create a new factory instance for the model.
*

View File

@ -4,11 +4,16 @@ namespace App\Models\Common;
use App\Abstracts\Model;
use App\Traits\Recurring as RecurringTrait;
use Illuminate\Database\Eloquent\Builder;
class Recurring extends Model
{
use RecurringTrait;
public const ACTIVE_STATUS = 'active';
public const END_STATUS = 'ended';
public const COMPLETE_STATUS = 'completed';
protected $table = 'recurring';
/**
@ -16,7 +21,30 @@ class Recurring extends Model
*
* @var array
*/
protected $fillable = ['company_id', 'recurable_id', 'recurable_type', 'frequency', 'interval', 'started_at', 'count', 'created_from', 'created_by'];
protected $fillable = [
'company_id',
'recurable_id',
'recurable_type',
'frequency',
'interval',
'started_at',
'status',
'limit_by',
'limit_count',
'limit_date',
'auto_send',
'created_from',
'created_by',
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'auto_send' => 'boolean',
];
/**
* Get all of the owning recurable models.
@ -25,4 +53,19 @@ class Recurring extends Model
{
return $this->morphTo();
}
public function scopeActive(Builder $query): Builder
{
return $query->where($this->qualifyColumn('status'), '=', static::ACTIVE_STATUS);
}
public function scopeEnded(Builder $query): Builder
{
return $query->where($this->qualifyColumn('status'), '=', static::END_STATUS);
}
public function scopeCompleted(Builder $query): Builder
{
return $query->where($this->qualifyColumn('status'), '=', static::COMPLETE_STATUS);
}
}

View File

@ -22,7 +22,9 @@ class Document extends Model
use HasFactory, Documents, Cloneable, Currencies, DateTime, Media, Recurring;
public const INVOICE_TYPE = 'invoice';
public const INVOICE_RECURRING_TYPE = 'invoice-recurring';
public const BILL_TYPE = 'bill';
public const BILL_RECURRING_TYPE = 'bill-recurring';
protected $table = 'documents';
@ -94,6 +96,11 @@ class Document extends Model
return $this->belongsTo('App\Models\Setting\Category')->withDefault(['name' => trans('general.na')]);
}
public function children()
{
return $this->hasMany('App\Models\Document\Document', 'parent_id');
}
public function contact()
{
return $this->belongsTo('App\Models\Common\Contact')->withDefault(['name' => trans('general.na')]);
@ -119,6 +126,19 @@ class Document extends Model
return $this->hasMany('App\Models\Document\DocumentHistory', 'document_id');
}
public function last_history()
{
return $this->hasOne('App\Models\Document\DocumentHistory', 'document_id')->latest()->withDefault([
'description' => trans('messages.success.added', ['type' => $this->document_number]),
'created_at' => $this->created_at
]);
}
public function parent()
{
return $this->belongsTo('App\Models\Document\Document', 'parent_id');
}
public function payments()
{
return $this->transactions();
@ -144,56 +164,66 @@ class Document extends Model
return $this->totals()->orderBy('sort_order');
}
public function parent()
{
return $this->belongsTo('App\Models\Document\Document', 'parent_id');
}
public function scopeLatest(Builder $query)
public function scopeLatest(Builder $query): Builder
{
return $query->orderBy('issued_at', 'desc');
}
public function scopeNumber(Builder $query, string $number)
public function scopeNumber(Builder $query, string $number): Builder
{
return $query->where('document_number', '=', $number);
}
public function scopeDue($query, $date)
public function scopeDue(Builder $query, $date): Builder
{
return $query->whereDate('due_at', '=', $date);
}
public function scopeAccrued($query)
public function scopeAccrued(Builder $query): Builder
{
return $query->whereNotIn('status', ['draft', 'cancelled']);
}
public function scopePaid($query)
public function scopePaid(Builder $query): Builder
{
return $query->where('status', '=', 'paid');
}
public function scopeNotPaid($query)
public function scopeNotPaid(Builder $query): Builder
{
return $query->where('status', '<>', 'paid');
}
public function scopeType(Builder $query, string $type)
public function scopeFuture(Builder $query): Builder
{
return $query->whereIn('status', $this->getDocumentStatusesForFuture());
}
public function scopeType(Builder $query, string $type): Builder
{
return $query->where($this->qualifyColumn('type'), '=', $type);
}
public function scopeInvoice(Builder $query)
public function scopeInvoice(Builder $query): Builder
{
return $query->where($this->qualifyColumn('type'), '=', self::INVOICE_TYPE);
}
public function scopeBill(Builder $query)
public function scopeInvoiceRecurring(Builder $query): Builder
{
return $query->where($this->qualifyColumn('type'), '=', self::INVOICE_RECURRING_TYPE);
}
public function scopeBill(Builder $query): Builder
{
return $query->where($this->qualifyColumn('type'), '=', self::BILL_TYPE);
}
public function scopeBillRecurring(Builder $query): Builder
{
return $query->where($this->qualifyColumn('type'), '=', self::BILL_RECURRING_TYPE);
}
/**
* @inheritDoc
*
@ -202,8 +232,14 @@ class Document extends Model
*/
public function onCloning($src, $child = null)
{
if (app()->has(\App\Console\Commands\RecurringCheck::class)) {
$type = $this->getRealTypeOfRecurringDocument($src->type);
} else {
$type = $src->type;
}
$this->status = 'draft';
$this->document_number = $this->getNextDocumentNumber($src->type);
$this->document_number = $this->getNextDocumentNumber($type);
}
public function getSentAtAttribute(string $value = null)
@ -354,29 +390,29 @@ class Document extends Model
*/
public function getStatusLabelAttribute()
{
switch ($this->status) {
case 'paid':
$label = 'success';
break;
case 'partial':
$label = 'info';
break;
case 'sent':
case 'received':
$label = 'danger';
break;
case 'viewed':
$label = 'warning';
break;
case 'cancelled':
$label = 'dark';
break;
default:
$label = 'primary';
break;
}
return match($this->status) {
'paid' => 'status-success',
'partial' => 'status-partial',
'sent' => 'status-danger',
'received' => 'status-danger',
'viewed' => 'status-sent',
'cancelled' => 'status-canceled',
default => 'status-draft',
};
}
return $label;
/**
* Get the recurring status label.
*
* @return string
*/
public function getRecurringStatusLabelAttribute()
{
return match($this->recurring->status) {
'active' => 'status-partial',
'ended' => 'status-success',
default => 'status-success',
};
}
/**
@ -429,6 +465,167 @@ class Document extends Model
return implode(', ', $location);
}
/**
* Get the line actions.
*
* @return array
*/
public function getLineActionsAttribute()
{
$actions = [];
$group = config('type.document.' . $this->type . '.group');
$prefix = config('type.document.' . $this->type . '.route.prefix');
$permission_prefix = config('type.document.' . $this->type . '.permission.prefix');
$translation_prefix = config('type.document.' . $this->type . '.translation.prefix');
if (empty($prefix)) {
return $actions;
}
try {
$actions[] = [
'title' => trans('general.show'),
'icon' => 'visibility',
'url' => route($prefix . '.show', $this->id),
'permission' => 'read-' . $group . '-' . $permission_prefix,
'attributes' => [
'id' => 'index-more-actions-show-' . $this->id,
],
];
} catch (\Exception $e) {}
try {
if (! $this->reconciled) {
$actions[] = [
'title' => trans('general.edit'),
'icon' => 'edit',
'url' => route($prefix . '.edit', $this->id),
'permission' => 'update-' . $group . '-' . $permission_prefix,
'attributes' => [
'id' => 'index-more-actions-edit-' . $this->id,
],
];
}
} catch (\Exception $e) {}
try {
$actions[] = [
'title' => trans('general.duplicate'),
'icon' => 'file_copy',
'url' => route($prefix . '.duplicate', $this->id),
'permission' => 'create-' . $group . '-' . $permission_prefix,
'attributes' => [
'id' => 'index-more-actions-duplicate-' . $this->id,
],
];
} catch (\Exception $e) {}
try {
$actions[] = [
'title' => trans('general.print'),
'icon' => 'print',
'url' => route($prefix . '.print', $this->id),
'permission' => 'read-' . $group . '-' . $permission_prefix,
'attributes' => [
'id' => 'index-more-actions-print-' . $this->id,
'target' => '_blank',
],
];
} catch (\Exception $e) {}
try {
$actions[] = [
'title' => trans('general.download_pdf'),
'icon' => 'pdf',
'url' => route($prefix . '.pdf', $this->id),
'permission' => 'read-' . $group . '-' . $permission_prefix,
'attributes' => [
'id' => 'index-more-actions-pdf-' . $this->id,
'target' => '_blank',
],
];
} catch (\Exception $e) {}
if (!str_contains($this->type, 'recurring')) {
$actions[] = [
'type' => 'divider',
];
try {
$actions[] = [
'type' => 'button',
'title' => trans('general.share_link'),
'icon' => 'share',
'url' => route('modals.'. $prefix . '.share.create', $this->id),
'permission' => 'read-' . $group . '-' . $permission_prefix,
'attributes' => [
'id' => 'index-more-actions-share-link-' . $this->id,
'@click' => 'onShareLink("' . route('modals.'. $prefix . '.share.create', $this->id) . '")',
],
];
} catch (\Exception $e) {}
try {
if ($this->type == 'invoice') {
$actions[] = [
'type' => 'button',
'title' => trans('invoices.send_mail'),
'icon' => 'email',
'url' => route('modals.'. $prefix . '.emails.create', $this->id),
'permission' => 'read-' . $group . '-' . $permission_prefix,
'attributes' => [
'id' => 'index-more-actions-send-email-' . $this->id,
'@click' => 'onEmail("' . route('modals.'. $prefix . '.emails.create', $this->id) . '")',
],
];
}
} catch (\Exception $e) {}
$actions[] = [
'type' => 'divider',
];
try {
$actions[] = [
'title' => trans('general.cancel'),
'icon' => 'cancel',
'url' => route($prefix . '.cancelled', $this->id),
'permission' => 'update-' . $group . '-' . $permission_prefix,
'attributes' => [
'id' => 'index-more-actions-cancel-' . $this->id,
],
];
} catch (\Exception $e) {}
$actions[] = [
'type' => 'divider',
];
try {
$actions[] = [
'type' => 'delete',
'icon' => 'delete',
'title' => $translation_prefix,
'route' => $prefix . '.destroy',
'permission' => 'delete-' . $group . '-' . $permission_prefix,
'model' => $this,
];
} catch (\Exception $e) {}
} else {
try {
$actions[] = [
'title' => trans('general.end'),
'icon' => 'block',
'url' => route($prefix. '.end', $this->id),
'permission' => 'update-' . $group . '-' . $permission_prefix,
];
} catch (\Exception $e) {}
}
return $actions;
}
protected static function newFactory(): Factory
{
return DocumentFactory::new();

View File

@ -3,14 +3,24 @@
namespace App\Models\Setting;
use App\Abstracts\Model;
use App\Builders\Category as Builder;
use App\Models\Document\Document;
use App\Relations\HasMany\Category as HasMany;
use App\Scopes\Category as Scope;
use App\Traits\Transactions;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model as EloquentModel;
class Category extends Model
{
use HasFactory, Transactions;
public const INCOME_TYPE = 'income';
public const EXPENSE_TYPE = 'expense';
public const ITEM_TYPE = 'item';
public const OTHER_TYPE = 'other';
protected $table = 'categories';
/**
@ -18,7 +28,7 @@ class Category extends Model
*
* @var array
*/
protected $fillable = ['company_id', 'name', 'type', 'color', 'enabled', 'created_from', 'created_by'];
protected $fillable = ['company_id', 'name', 'type', 'color', 'enabled', 'created_from', 'created_by', 'parent_id'];
/**
* The attributes that should be cast.
@ -36,6 +46,65 @@ class Category extends Model
*/
public $sortable = ['name', 'type', 'enabled'];
/**
* The "booted" method of the model.
*
* @return void
*/
protected static function booted()
{
static::addGlobalScope(new Scope);
}
/**
* Create a new Eloquent query builder for the model.
*
* @param \Illuminate\Database\Query\Builder $query
* @return \App\Builders\Category
*/
public function newEloquentBuilder($query)
{
return new Builder($query);
}
/**
* Instantiate a new HasMany relationship.
*
* @param EloquentBuilder $query
* @param EloquentModel $parent
* @param string $foreignKey
* @param string $localKey
* @return HasMany
*/
protected function newHasMany(EloquentBuilder $query, EloquentModel $parent, $foreignKey, $localKey)
{
return new HasMany($query, $parent, $foreignKey, $localKey);
}
/**
* Retrieve the model for a bound value.
*
* @param mixed $value
* @param string|null $field
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function resolveRouteBinding($value, $field = null)
{
return $this->resolveRouteBindingQuery($this, $value, $field)
->withoutGlobalScope(Scope::class)
->first();
}
public function categories()
{
return $this->hasMany(Category::class, 'parent_id')->withSubCategory();
}
public function sub_categories()
{
return $this->hasMany(Category::class, 'parent_id')->withSubCategory()->with('categories')->orderBy('name');
}
public function documents()
{
return $this->hasMany('App\Models\Document\Document');
@ -147,6 +216,50 @@ class Category extends Model
return (int) $query->other()->pluck('id')->first();
}
/**
* Scope gets only parent categories.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeWithSubCategory($query)
{
return $query->withoutGlobalScope(new Scope);
}
/**
* Get the line actions.
*
* @return array
*/
public function getLineActionsAttribute()
{
$actions = [];
$actions[] = [
'title' => trans('general.edit'),
'icon' => 'create',
'url' => route('categories.edit', $this->id),
'permission' => 'update-settings-categories',
];
$transfer_id = Category::transfer();
if ($this->id == $transfer_id) {
return $actions;
}
$actions[] = [
'type' => 'delete',
'icon' => 'delete',
'route' => 'categories.destroy',
'permission' => 'delete-settings-categories',
'model' => $this,
];
return $actions;
}
/**
* Create a new factory instance for the model.
*

View File

@ -101,6 +101,18 @@ class Currency extends Model
return $this->contacts()->whereIn('type', (array) $this->getVendorTypes());
}
/**
* Scope currency by code.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param mixed $code
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeCode($query, $code)
{
return $query->where($this->qualifyColumn('code'), $code);
}
/**
* Get the current precision.
*
@ -172,15 +184,30 @@ class Currency extends Model
}
/**
* Scope currency by code.
* Get the line actions.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param mixed $code
* @return \Illuminate\Database\Eloquent\Builder
* @return array
*/
public function scopeCode($query, $code)
public function getLineActionsAttribute()
{
return $query->where($this->qualifyColumn('code'), $code);
$actions = [];
$actions[] = [
'title' => trans('general.edit'),
'icon' => 'edit',
'url' => route('currencies.edit', $this->id),
'permission' => 'update-settings-currencies',
];
$actions[] = [
'type' => 'delete',
'icon' => 'delete',
'route' => 'currencies.destroy',
'permission' => 'delete-settings-currencies',
'model' => $this,
];
return $actions;
}
/**

View File

@ -1,6 +1,6 @@
<?php
namespace App\Models\Common;
namespace App\Models\Setting;
use App\Abstracts\Model;
use Illuminate\Support\Str;
@ -9,6 +9,13 @@ class EmailTemplate extends Model
{
protected $table = 'email_templates';
/**
* The accessors to append to the model's array form.
*
* @var array
*/
protected $appends = ['title'];
/**
* Attributes that should be mass-assignable.
*
@ -16,6 +23,26 @@ class EmailTemplate extends Model
*/
protected $fillable = ['company_id', 'alias', 'class', 'name', 'subject', 'body', 'params', 'created_from', 'created_by'];
public function getTitleAttribute()
{
return trans($this->name);
}
public function getGroupAttribute()
{
if (Str::startsWith($this->alias, 'invoice_')) {
$group = 'general.invoices';
} elseif (Str::startsWith($this->alias, 'bill_')) {
$group = 'general.bills';
} elseif (Str::startsWith($this->alias, 'payment_')) {
$group = 'general.payments';
} else {
$group = 'general.others';
}
return $group;
}
/**
* Scope to only include email templates of a given alias.
*

View File

@ -45,7 +45,7 @@ class Tax extends Model
public function items()
{
return $this->hasMany('App\Models\Common\Item');
return $this->hasMany('App\Models\Common\ItemTax');
}
public function document_items()
@ -136,6 +136,33 @@ class Tax extends Model
return $title;
}
/**
* Get the line actions.
*
* @return array
*/
public function getLineActionsAttribute()
{
$actions = [];
$actions[] = [
'title' => trans('general.edit'),
'icon' => 'edit',
'url' => route('taxes.edit', $this->id),
'permission' => 'update-settings-taxes',
];
$actions[] = [
'type' => 'delete',
'icon' => 'delete',
'route' => 'taxes.destroy',
'permission' => 'delete-settings-taxes',
'model' => $this,
];
return $actions;
}
/**
* Create a new factory instance for the model.
*