$activities * @property-read int|null $activities_count * @property-read \App\Models\User|null $assigned_user * @property-read \App\Models\Company $company * @property-read \Illuminate\Database\Eloquent\Collection $company_ledger * @property-read int|null $company_ledger_count * @property-read \Illuminate\Database\Eloquent\Collection $contacts * @property-read int|null $contacts_count * @property-read \App\Models\Country|null $country * @property-read \Illuminate\Database\Eloquent\Collection $credits * @property-read int|null $credits_count * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read int|null $documents_count * @property-read \Illuminate\Database\Eloquent\Collection $expenses * @property-read int|null $expenses_count * @property-read \Illuminate\Database\Eloquent\Collection $gateway_tokens * @property-read int|null $gateway_tokens_count * @property-read mixed $hashed_id * @property-read \Illuminate\Database\Eloquent\Collection $invoices * @property-read int|null $invoices_count * @property-read \Illuminate\Database\Eloquent\Collection $ledger * @property-read int|null $ledger_count * @property-read \Illuminate\Database\Eloquent\Collection $payments * @property-read int|null $payments_count * @property-read \Illuminate\Database\Eloquent\Collection $primary_contact * @property-read int|null $primary_contact_count * @property-read \Illuminate\Database\Eloquent\Collection $projects * @property-read int|null $projects_count * @property-read \Illuminate\Database\Eloquent\Collection $quotes * @property-read int|null $quotes_count * @property-read \Illuminate\Database\Eloquent\Collection $recurring_expenses * @property-read int|null $recurring_expenses_count * @property-read \Illuminate\Database\Eloquent\Collection $recurring_invoices * @property-read int|null $recurring_invoices_count * @property-read \App\Models\Country|null $shipping_country * @property-read \Illuminate\Database\Eloquent\Collection $system_logs * @property-read int|null $system_logs_count * @property-read \Illuminate\Database\Eloquent\Collection $tasks * @property-read \Illuminate\Database\Eloquent\Collection $recurring_invoices * @property-read int|null $tasks_count * @property-read \App\Models\User $user * @method static \Illuminate\Database\Eloquent\Builder|Client exclude($columns) * @method static \Database\Factories\ClientFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|Client filter(\App\Filters\QueryFilters $filters) * @property string $payment_balance * @property mixed $tax_data * @property int $is_tax_exempt * @property int $has_valid_vat_number * @mixin \Eloquent */ class Client extends BaseModel implements HasLocalePreference { use PresentableTrait; use MakesHash; use MakesDates; use SoftDeletes; use Filterable; use GeneratesCounter; use AppSetup; use ClientGroupSettingsSaver; use Excludable; protected $presenter = ClientPresenter::class; protected $hidden = [ 'id', 'private_notes', 'user_id', 'company_id', 'last_login', ]; protected $fillable = [ 'assigned_user_id', 'name', 'website', 'private_notes', 'industry_id', 'size_id', 'address1', 'address2', 'city', 'state', 'postal_code', 'country_id', 'custom_value1', 'custom_value2', 'custom_value3', 'custom_value4', 'shipping_address1', 'shipping_address2', 'shipping_city', 'shipping_state', 'shipping_postal_code', 'shipping_country_id', 'settings', 'vat_number', 'id_number', 'group_settings_id', 'public_notes', 'phone', 'number', 'routing_id', 'is_tax_exempt', 'has_valid_vat_number', ]; protected $with = [ 'gateway_tokens', 'documents', 'contacts.company', ]; protected $casts = [ 'is_deleted' => 'boolean', 'country_id' => 'string', 'settings' => 'object', 'updated_at' => 'timestamp', 'created_at' => 'timestamp', 'deleted_at' => 'timestamp', 'last_login' => 'timestamp', 'tax_data' => 'object', ]; protected $touches = []; /** * Whitelisted fields for using from query parameters on subscriptions request. * * @var string[] */ public static $subscriptions_fillable = [ 'assigned_user_id', 'address1', 'address2', 'city', 'state', 'postal_code', 'country_id', 'custom_value1', 'custom_value2', 'custom_value3', 'custom_value4', 'shipping_address1', 'shipping_address2', 'shipping_city', 'shipping_state', 'shipping_postal_code', 'shipping_country_id', 'payment_terms', 'vat_number', 'id_number', 'public_notes', 'phone', 'routing_id', ]; // public function scopeExclude($query) // { // $query->makeHidden(['balance','paid_to_date']); // return $query; // } public function getEntityType() { return self::class; } public function ledger() { return $this->hasMany(CompanyLedger::class)->orderBy('id', 'desc'); } public function company_ledger() { return $this->morphMany(CompanyLedger::class, 'company_ledgerable'); } public function gateway_tokens() { return $this->hasMany(ClientGatewayToken::class)->orderBy('is_default', 'DESC'); } public function expenses() { return $this->hasMany(Expense::class)->withTrashed(); } public function projects() { return $this->hasMany(Project::class)->withTrashed(); } /** * Retrieves the specific payment token per * gateway - per payment method. * * Allows the storage of multiple tokens * per client per gateway per payment_method * * @param int $company_gateway_id The company gateway ID * @param int $payment_method_id The payment method ID * @return ClientGatewayToken The client token record */ public function gateway_token($company_gateway_id, $payment_method_id) { return $this->gateway_tokens() ->whereCompanyGatewayId($company_gateway_id) ->whereGatewayTypeId($payment_method_id) ->first(); } public function credits() { return $this->hasMany(Credit::class)->withTrashed(); } public function purgeable_activities() { return $this->hasMany(Activity::class); } public function activities() { return $this->hasMany(Activity::class)->take(50)->orderBy('id', 'desc'); } public function contacts() :HasMany { return $this->hasMany(ClientContact::class)->orderBy('is_primary', 'desc'); } public function primary_contact():HasMany { return $this->hasMany(ClientContact::class)->where('is_primary', true); } public function company() :BelongsTo { return $this->belongsTo(Company::class); } public function user() :BelongsTo { return $this->belongsTo(User::class)->withTrashed(); } public function assigned_user() :BelongsTo { return $this->belongsTo(User::class, 'assigned_user_id', 'id')->withTrashed(); } public function country() :BelongsTo { return $this->belongsTo(Country::class); } public function invoices() :HasMany { return $this->hasMany(Invoice::class)->withTrashed(); } public function quotes() :HasMany { return $this->hasMany(Quote::class)->withTrashed(); } public function tasks() :HasMany { return $this->hasMany(Task::class)->withTrashed(); } public function payments() :HasMany { return $this->hasMany(Payment::class)->withTrashed(); } public function recurring_invoices() :HasMany { return $this->hasMany(RecurringInvoice::class)->withTrashed(); } public function recurring_expenses() :HasMany { return $this->hasMany(RecurringExpense::class)->withTrashed(); } public function shipping_country() { return $this->belongsTo(Country::class, 'shipping_country_id', 'id'); } public function system_logs() :HasMany { return $this->hasMany(SystemLog::class)->take(50)->orderBy('id', 'desc'); } public function timezone() :Timezone { return Timezone::find($this->getSetting('timezone_id')); } public function language() { $languages = Cache::get('languages'); if (! $languages) { $this->buildCache(true); } return $languages->filter(function ($item) { return $item->id == $this->getSetting('language_id'); })->first(); } public function industry() :BelongsTo { return $this->belongsTo(Industry::class); } public function size() :BelongsTo { return $this->belongsTo(Size::class); } public function locale() :string { if (! $this->language()) { return 'en'; } return $this->language()->locale ?: 'en'; } public function date_format() { $date_formats = Cache::get('date_formats'); if (! $date_formats) { $this->buildCache(true); } return $date_formats->filter(function ($item) { return $item->id == $this->getSetting('date_format_id'); })->first()->format; } public function currency() { $currencies = Cache::get('currencies'); if (! $currencies) { $this->buildCache(true); } return $currencies->filter(function ($item) { return $item->id == $this->getSetting('currency_id'); })->first(); } public function service() :ClientService { return new ClientService($this); } public function updateBalance($amount) :ClientService { return $this->service()->updateBalance($amount); } /** * Returns the entire filtered set * of settings which have been merged from * Client > Group > Company levels. * * @return \stdClass stdClass object of settings */ public function getMergedSettings() :object { if ($this->group_settings !== null) { $group_settings = ClientSettings::buildClientSettings($this->group_settings->settings, $this->settings); return ClientSettings::buildClientSettings($this->company->settings, $group_settings); } return CompanySettings::setProperties(ClientSettings::buildClientSettings($this->company->settings, $this->settings)); } /** * Returns a single setting * which cascades from * Client > Group > Company. * * @param string $setting The Setting parameter * @return mixed The setting requested */ public function getSetting($setting) :mixed { /*Client Settings*/ if ($this->settings && property_exists($this->settings, $setting) && isset($this->settings->{$setting})) { /*need to catch empty string here*/ if (is_string($this->settings->{$setting}) && (iconv_strlen($this->settings->{$setting}) >= 1)) { return $this->settings->{$setting}; } elseif (is_bool($this->settings->{$setting})) { return $this->settings->{$setting}; } elseif (is_int($this->settings->{$setting})) { return $this->settings->{$setting}; } elseif(is_float($this->settings->{$setting})) { return $this->settings->{$setting}; } } /*Group Settings*/ if ($this->group_settings && (property_exists($this->group_settings->settings, $setting) !== false) && (isset($this->group_settings->settings->{$setting}) !== false)) { return $this->group_settings->settings->{$setting}; } /*Company Settings*/ elseif ((property_exists($this->company->settings, $setting) != false) && (isset($this->company->settings->{$setting}) !== false)) { return $this->company->settings->{$setting}; } elseif (property_exists(CompanySettings::defaults(), $setting)) { return CompanySettings::defaults()->{$setting}; } return ''; } public function getSettingEntity($setting) { /*Client Settings*/ if ($this->settings && (property_exists($this->settings, $setting) !== false) && (isset($this->settings->{$setting}) !== false)) { /*need to catch empty string here*/ if (is_string($this->settings->{$setting}) && (iconv_strlen($this->settings->{$setting}) >= 1)) { return $this; } } /*Group Settings*/ if ($this->group_settings && (property_exists($this->group_settings->settings, $setting) !== false) && (isset($this->group_settings->settings->{$setting}) !== false)) { return $this->group_settings; } /*Company Settings*/ if ((property_exists($this->company->settings, $setting) != false) && (isset($this->company->settings->{$setting}) !== false)) { return $this->company; } throw new \Exception('Could not find a settings object', 1); } public function documents() :MorphMany { return $this->morphMany(Document::class, 'documentable'); } public function group_settings() :BelongsTo { return $this->belongsTo(GroupSetting::class); } /** * Returns the first Credit Card Gateway. * * @return null|CompanyGateway The Priority Credit Card gateway */ public function getCreditCardGateway() :?CompanyGateway { $pms = $this->service()->getPaymentMethods(-1); foreach ($pms as $pm) { if ($pm['gateway_type_id'] == GatewayType::CREDIT_CARD) { $cg = CompanyGateway::find($pm['company_gateway_id']); if ($cg && ! property_exists($cg->fees_and_limits, strval(GatewayType::CREDIT_CARD))) { $fees_and_limits = $cg->fees_and_limits; $fees_and_limits->{GatewayType::CREDIT_CARD} = new FeesAndLimits; $cg->fees_and_limits = $fees_and_limits; $cg->save(); } if ($cg && $cg->fees_and_limits->{GatewayType::CREDIT_CARD}->is_enabled) { return $cg; } } } return null; } public function getBACSGateway() :?CompanyGateway { $pms = $this->service()->getPaymentMethods(-1); foreach ($pms as $pm) { if ($pm['gateway_type_id'] == GatewayType::BACS) { $cg = CompanyGateway::find($pm['company_gateway_id']); if ($cg && ! property_exists($cg->fees_and_limits, GatewayType::BACS)) { $fees_and_limits = $cg->fees_and_limits; $fees_and_limits->{GatewayType::BACS} = new FeesAndLimits; $cg->fees_and_limits = $fees_and_limits; $cg->save(); } if ($cg && $cg->fees_and_limits->{GatewayType::BACS}->is_enabled) { return $cg; } } } return null; } //todo refactor this - it is only searching for existing tokens public function getBankTransferGateway() :?CompanyGateway { $pms = $this->service()->getPaymentMethods(-1); if ($this->currency()->code == 'USD' && in_array(GatewayType::BANK_TRANSFER, array_column($pms, 'gateway_type_id'))) { foreach ($pms as $pm) { if ($pm['gateway_type_id'] == GatewayType::BANK_TRANSFER) { $cg = CompanyGateway::find($pm['company_gateway_id']); if ($cg && ! property_exists($cg->fees_and_limits, GatewayType::BANK_TRANSFER)) { $fees_and_limits = $cg->fees_and_limits; $fees_and_limits->{GatewayType::BANK_TRANSFER} = new FeesAndLimits; $cg->fees_and_limits = $fees_and_limits; $cg->save(); } if ($cg && $cg->fees_and_limits->{GatewayType::BANK_TRANSFER}->is_enabled) { return $cg; } } } } if ($this->currency()->code == 'EUR' && (in_array(GatewayType::BANK_TRANSFER, array_column($pms, 'gateway_type_id')) || in_array(GatewayType::SEPA, array_column($pms, 'gateway_type_id')))) { foreach ($pms as $pm) { if ($pm['gateway_type_id'] == GatewayType::SEPA) { $cg = CompanyGateway::find($pm['company_gateway_id']); if ($cg && $cg->fees_and_limits->{GatewayType::SEPA}->is_enabled) { return $cg; } } } } if (in_array(GatewayType::DIRECT_DEBIT, array_column($pms, 'gateway_type_id'))) { foreach ($pms as $pm) { if ($pm['gateway_type_id'] == GatewayType::DIRECT_DEBIT) { $cg = CompanyGateway::find($pm['company_gateway_id']); if ($cg && $cg->fees_and_limits->{GatewayType::DIRECT_DEBIT}->is_enabled) { return $cg; } } } } return null; } public function getBankTransferMethodType() { if ($this->currency()->code == 'USD') { return GatewayType::BANK_TRANSFER; } if ($this->currency()->code == 'EUR') { return GatewayType::SEPA; } if (in_array($this->currency()->code, ['EUR', 'GBP','DKK','SEK','AUD','NZD','USD'])) { return GatewayType::DIRECT_DEBIT; } } public function getCurrencyCode(): string { if ($this->currency()) { return $this->currency()->code; } return 'USD'; } public function validGatewayForAmount($fees_and_limits_for_payment_type, $amount) :bool { if (isset($fees_and_limits_for_payment_type)) { $fees_and_limits = $fees_and_limits_for_payment_type; } else { return true; } if ((property_exists($fees_and_limits, 'min_limit')) && $fees_and_limits->min_limit !== null && $fees_and_limits->min_limit != -1 && $amount < $fees_and_limits->min_limit) { return false; } if ((property_exists($fees_and_limits, 'max_limit')) && $fees_and_limits->max_limit !== null && $fees_and_limits->max_limit != -1 && $amount > $fees_and_limits->max_limit) { return false; } return true; } public function preferredLocale() { $languages = Cache::get('languages'); if (! $languages) { $this->buildCache(true); } return $languages->filter(function ($item) { return $item->id == $this->getSetting('language_id'); })->first()->locale; } public function backup_path() :string { return $this->company->company_key.'/'.$this->client_hash.'/backups'; } public function invoice_filepath($invitation) :string { $contact_key = $invitation->contact->contact_key; return $this->company->company_key.'/'.$this->client_hash.'/'.$contact_key.'/invoices/'; } public function e_invoice_filepath($invitation) :string { $contact_key = $invitation->contact->contact_key; return $this->company->company_key.'/'.$this->client_hash.'/'.$contact_key.'/e_invoice/'; } public function quote_filepath($invitation) :string { $contact_key = $invitation->contact->contact_key; return $this->company->company_key.'/'.$this->client_hash.'/'.$contact_key.'/quotes/'; } public function credit_filepath($invitation) :string { $contact_key = $invitation->contact->contact_key; return $this->company->company_key.'/'.$this->client_hash.'/'.$contact_key.'/credits/'; } public function recurring_invoice_filepath($invitation) :string { $contact_key = $invitation->contact->contact_key; return $this->company->company_key.'/'.$this->client_hash.'/'.$contact_key.'/recurring_invoices/'; } public function company_filepath() :string { return $this->company->company_key.'/'; } public function document_filepath() :string { return $this->company->company_key.'/documents/'; } public function setCompanyDefaults($data, $entity_name) :array { $defaults = []; if (! (array_key_exists('terms', $data) && is_string($data['terms']) && strlen($data['terms']) > 1)) { $defaults['terms'] = $this->getSetting($entity_name.'_terms'); } elseif (array_key_exists('terms', $data)) { $defaults['terms'] = $data['terms']; } if (! (array_key_exists('footer', $data) && is_string($data['footer']) && strlen($data['footer']) > 1)) { $defaults['footer'] = $this->getSetting($entity_name.'_footer'); } elseif (array_key_exists('footer', $data)) { $defaults['footer'] = $data['footer']; } if (is_string($this->public_notes) && strlen($this->public_notes) >= 1) { $defaults['public_notes'] = $this->public_notes; } $exchange_rate = new CurrencyApi(); $defaults['exchange_rate'] = 1/$exchange_rate->exchangeRate($this->getSetting('currency_id'), $this->company->settings->currency_id); return $defaults; } public function timezone_offset() :int { $offset = 0; $entity_send_time = $this->getSetting('entity_send_time'); if ($entity_send_time == 0) { return 0; } $timezone = $this->company->timezone(); $offset -= $timezone->utc_offset; $offset += ($entity_send_time * 3600); return $offset; } public function transaction_event() { $client = $this->fresh(); return [ 'client_id' => $client->id, 'client_balance' => $client->balance ?: 0, 'client_paid_to_date' => $client->paid_to_date ?: 0, 'client_credit_balance' => $client->credit_balance ?: 0, ]; } public function translate_entity() :string { return ctrans('texts.client'); } public function portalUrl(bool $use_react_url): string { return $use_react_url ? config('ninja.react_url'). "/#/clients/{$this->hashed_id}": config('ninja.app_url'); } }