mirror of
https://github.com/crater-invoice/crater.git
synced 2025-12-15 09:52:55 -05:00
v5.0.0 update
This commit is contained in:
@@ -25,6 +25,11 @@ class Address extends Model
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function customer()
|
||||
{
|
||||
return $this->belongsTo(Customer::class);
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace Crater\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Silber\Bouncer\BouncerFacade;
|
||||
use Silber\Bouncer\Database\Role;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||
|
||||
@@ -13,10 +15,18 @@ class Company extends Model implements HasMedia
|
||||
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['name', 'logo', 'unique_hash'];
|
||||
protected $guarded = [
|
||||
'id'
|
||||
];
|
||||
|
||||
protected $appends = ['logo', 'logo_path'];
|
||||
|
||||
public function getRolesAttribute()
|
||||
{
|
||||
return Role::where('scope', $this->id)
|
||||
->get();
|
||||
}
|
||||
|
||||
public function getLogoPathAttribute()
|
||||
{
|
||||
$logo = $this->getMedia('logo')->first();
|
||||
@@ -45,9 +55,14 @@ class Company extends Model implements HasMedia
|
||||
return null;
|
||||
}
|
||||
|
||||
public function user()
|
||||
public function customers()
|
||||
{
|
||||
return $this->hasOne(User::class);
|
||||
return $this->hasMany(Customer::class);
|
||||
}
|
||||
|
||||
public function owner()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'owner_id');
|
||||
}
|
||||
|
||||
public function settings()
|
||||
@@ -55,8 +70,305 @@ class Company extends Model implements HasMedia
|
||||
return $this->hasMany(CompanySetting::class);
|
||||
}
|
||||
|
||||
public function recurringInvoices()
|
||||
{
|
||||
return $this->hasMany(RecurringInvoice::class);
|
||||
}
|
||||
|
||||
public function customFields()
|
||||
{
|
||||
return $this->hasMany(CustomField::class);
|
||||
}
|
||||
|
||||
public function customFieldValues()
|
||||
{
|
||||
return $this->hasMany(CustomFieldValue::class);
|
||||
}
|
||||
|
||||
public function exchangeRateLogs()
|
||||
{
|
||||
return $this->hasMany(ExchangeRateLog::class);
|
||||
}
|
||||
|
||||
public function exchangeRateProviders()
|
||||
{
|
||||
return $this->hasMany(ExchangeRateProvider::class);
|
||||
}
|
||||
|
||||
public function invoices()
|
||||
{
|
||||
return $this->hasMany(Invoice::class);
|
||||
}
|
||||
|
||||
public function expenses()
|
||||
{
|
||||
return $this->hasMany(Expense::class);
|
||||
}
|
||||
|
||||
public function units()
|
||||
{
|
||||
return $this->hasMany(Unit::class);
|
||||
}
|
||||
|
||||
public function expenseCategories()
|
||||
{
|
||||
return $this->hasMany(ExpenseCategory::class);
|
||||
}
|
||||
|
||||
public function taxTypes()
|
||||
{
|
||||
return $this->hasMany(TaxType::class);
|
||||
}
|
||||
|
||||
public function items()
|
||||
{
|
||||
return $this->hasMany(Item::class);
|
||||
}
|
||||
|
||||
public function payments()
|
||||
{
|
||||
return $this->hasMany(Payment::class);
|
||||
}
|
||||
|
||||
public function paymentMethods()
|
||||
{
|
||||
return $this->hasMany(PaymentMethod::class);
|
||||
}
|
||||
|
||||
public function estimates()
|
||||
{
|
||||
return $this->hasMany(Estimate::class);
|
||||
}
|
||||
|
||||
public function address()
|
||||
{
|
||||
return $this->hasOne(Address::class);
|
||||
}
|
||||
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'user_company', 'user_id', 'company_id');
|
||||
}
|
||||
|
||||
public function setupRoles()
|
||||
{
|
||||
BouncerFacade::scope()->to($this->id);
|
||||
|
||||
$super_admin = BouncerFacade::role()->firstOrCreate([
|
||||
'name' => 'super admin',
|
||||
'title' => 'Super Admin',
|
||||
'scope' => $this->id
|
||||
]);
|
||||
|
||||
foreach (config('abilities.abilities') as $ability) {
|
||||
BouncerFacade::allow($super_admin)->to($ability['ability'], $ability['model']);
|
||||
}
|
||||
}
|
||||
|
||||
public function setupDefaultPaymentMethods()
|
||||
{
|
||||
PaymentMethod::create(['name' => 'Cash', 'company_id' => $this->id]);
|
||||
PaymentMethod::create(['name' => 'Check', 'company_id' => $this->id]);
|
||||
PaymentMethod::create(['name' => 'Credit Card', 'company_id' => $this->id]);
|
||||
PaymentMethod::create(['name' => 'Bank Transfer', 'company_id' => $this->id]);
|
||||
}
|
||||
|
||||
public function setupDefaultUnits()
|
||||
{
|
||||
Unit::create(['name' => 'box', 'company_id' => $this->id]);
|
||||
Unit::create(['name' => 'cm', 'company_id' => $this->id]);
|
||||
Unit::create(['name' => 'dz', 'company_id' => $this->id]);
|
||||
Unit::create(['name' => 'ft', 'company_id' => $this->id]);
|
||||
Unit::create(['name' => 'g', 'company_id' => $this->id]);
|
||||
Unit::create(['name' => 'in', 'company_id' => $this->id]);
|
||||
Unit::create(['name' => 'kg', 'company_id' => $this->id]);
|
||||
Unit::create(['name' => 'km', 'company_id' => $this->id]);
|
||||
Unit::create(['name' => 'lb', 'company_id' => $this->id]);
|
||||
Unit::create(['name' => 'mg', 'company_id' => $this->id]);
|
||||
Unit::create(['name' => 'pc', 'company_id' => $this->id]);
|
||||
}
|
||||
|
||||
public function setupDefaultSettings()
|
||||
{
|
||||
$defaultInvoiceEmailBody = 'You have received a new invoice from <b>{COMPANY_NAME}</b>.</br> Please download using the button below:';
|
||||
$defaultEstimateEmailBody = 'You have received a new estimate from <b>{COMPANY_NAME}</b>.</br> Please download using the button below:';
|
||||
$defaultPaymentEmailBody = 'Thank you for the payment.</b></br> Please download your payment receipt using the button below:';
|
||||
$billingAddressFormat = '<h3>{BILLING_ADDRESS_NAME}</h3><p>{BILLING_ADDRESS_STREET_1}</p><p>{BILLING_ADDRESS_STREET_2}</p><p>{BILLING_CITY} {BILLING_STATE}</p><p>{BILLING_COUNTRY} {BILLING_ZIP_CODE}</p><p>{BILLING_PHONE}</p>';
|
||||
$shippingAddressFormat = '<h3>{SHIPPING_ADDRESS_NAME}</h3><p>{SHIPPING_ADDRESS_STREET_1}</p><p>{SHIPPING_ADDRESS_STREET_2}</p><p>{SHIPPING_CITY} {SHIPPING_STATE}</p><p>{SHIPPING_COUNTRY} {SHIPPING_ZIP_CODE}</p><p>{SHIPPING_PHONE}</p>';
|
||||
$companyAddressFormat = '<h3><strong>{COMPANY_NAME}</strong></h3><p>{COMPANY_ADDRESS_STREET_1}</p><p>{COMPANY_ADDRESS_STREET_2}</p><p>{COMPANY_CITY} {COMPANY_STATE}</p><p>{COMPANY_COUNTRY} {COMPANY_ZIP_CODE}</p><p>{COMPANY_PHONE}</p>';
|
||||
$paymentFromCustomerAddress = '<h3>{BILLING_ADDRESS_NAME}</h3><p>{BILLING_ADDRESS_STREET_1}</p><p>{BILLING_ADDRESS_STREET_2}</p><p>{BILLING_CITY} {BILLING_STATE} {BILLING_ZIP_CODE}</p><p>{BILLING_COUNTRY}</p><p>{BILLING_PHONE}</p>';
|
||||
|
||||
$settings = [
|
||||
'invoice_auto_generate' => 'YES',
|
||||
'payment_auto_generate' => 'YES',
|
||||
'estimate_auto_generate' => 'YES',
|
||||
'save_pdf_to_disk' => 'NO',
|
||||
'invoice_mail_body' => $defaultInvoiceEmailBody,
|
||||
'estimate_mail_body' => $defaultEstimateEmailBody,
|
||||
'payment_mail_body' => $defaultPaymentEmailBody,
|
||||
'invoice_company_address_format' => $companyAddressFormat,
|
||||
'invoice_shipping_address_format' => $shippingAddressFormat,
|
||||
'invoice_billing_address_format' => $billingAddressFormat,
|
||||
'estimate_company_address_format' => $companyAddressFormat,
|
||||
'estimate_shipping_address_format' => $shippingAddressFormat,
|
||||
'estimate_billing_address_format' => $billingAddressFormat,
|
||||
'payment_company_address_format' => $companyAddressFormat,
|
||||
'payment_from_customer_address_format' => $paymentFromCustomerAddress,
|
||||
'currency' => request()->currency ?? 12,
|
||||
'time_zone' => 'Asia/Kolkata',
|
||||
'language' => 'en',
|
||||
'fiscal_year' => '1-12',
|
||||
'carbon_date_format' => 'Y/m/d',
|
||||
'moment_date_format' => 'YYYY/MM/DD',
|
||||
'notification_email' => 'noreply@crater.in',
|
||||
'notify_invoice_viewed' => 'NO',
|
||||
'notify_estimate_viewed' => 'NO',
|
||||
'tax_per_item' => 'NO',
|
||||
'discount_per_item' => 'NO',
|
||||
'invoice_auto_generate' => 'YES',
|
||||
'invoice_email_attachment' => 'NO',
|
||||
'estimate_auto_generate' => 'YES',
|
||||
'estimate_email_attachment' => 'NO',
|
||||
'payment_auto_generate' => 'YES',
|
||||
'payment_email_attachment' => 'NO',
|
||||
'save_pdf_to_disk' => 'NO',
|
||||
'retrospective_edits' => 'allow',
|
||||
'invoice_number_format' => '{{SERIES:INV}}{{DELIMITER:-}}{{SEQUENCE:6}}',
|
||||
'estimate_number_format' => '{{SERIES:EST}}{{DELIMITER:-}}{{SEQUENCE:6}}',
|
||||
'payment_number_format' => '{{SERIES:PAY}}{{DELIMITER:-}}{{SEQUENCE:6}}',
|
||||
'estimate_set_expiry_date_automatically' => 'YES',
|
||||
'estimate_expiry_date_days' => 7,
|
||||
'invoice_set_due_date_automatically' => 'YES',
|
||||
'invoice_due_date_days' => 7,
|
||||
'bulk_exchange_rate_configured' => 'YES',
|
||||
'estimate_convert_action' => 'no_action'
|
||||
];
|
||||
|
||||
CompanySetting::setSettings($settings, $this->id);
|
||||
}
|
||||
|
||||
public function setupDefaultData()
|
||||
{
|
||||
$this->setupRoles();
|
||||
$this->setupDefaultPaymentMethods();
|
||||
$this->setupDefaultUnits();
|
||||
$this->setupDefaultSettings();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function deleteCompany($user)
|
||||
{
|
||||
if ($this->exchangeRateLogs()->exists()) {
|
||||
$this->exchangeRateLogs()->delete();
|
||||
}
|
||||
|
||||
if ($this->exchangeRateProviders()->exists()) {
|
||||
$this->exchangeRateProviders()->delete();
|
||||
}
|
||||
|
||||
if ($this->expenses()->exists()) {
|
||||
$this->expenses()->delete();
|
||||
}
|
||||
|
||||
if ($this->expenseCategories()->exists()) {
|
||||
$this->expenseCategories()->delete();
|
||||
}
|
||||
|
||||
if ($this->payments()->exists()) {
|
||||
$this->payments()->delete();
|
||||
}
|
||||
|
||||
if ($this->paymentMethods()->exists()) {
|
||||
$this->paymentMethods()->delete();
|
||||
}
|
||||
|
||||
if ($this->customFieldValues()->exists()) {
|
||||
$this->customFieldValues()->delete();
|
||||
}
|
||||
|
||||
|
||||
if ($this->customFields()->exists()) {
|
||||
$this->customFields()->delete();
|
||||
}
|
||||
|
||||
if ($this->invoices()->exists()) {
|
||||
$this->invoices->map(function ($invoice) {
|
||||
$this->checkModelData($invoice);
|
||||
});
|
||||
|
||||
$this->invoices()->delete();
|
||||
}
|
||||
|
||||
if ($this->recurringInvoices()->exists()) {
|
||||
$this->recurringInvoices->map(function ($recurringInvoice) {
|
||||
$this->checkModelData($recurringInvoice);
|
||||
});
|
||||
|
||||
$this->recurringInvoices()->delete();
|
||||
}
|
||||
|
||||
if ($this->estimates()->exists()) {
|
||||
$this->estimates->map(function ($estimate) {
|
||||
$this->checkModelData($estimate);
|
||||
});
|
||||
|
||||
$this->estimates()->delete();
|
||||
}
|
||||
|
||||
if ($this->items()->exists()) {
|
||||
$this->items()->delete();
|
||||
}
|
||||
|
||||
if ($this->taxTypes()->exists()) {
|
||||
$this->taxTypes()->delete();
|
||||
}
|
||||
|
||||
if ($this->customers()->exists()) {
|
||||
$this->customers->map(function ($customer) {
|
||||
if ($customer->addresses()->exists()) {
|
||||
$customer->addresses()->delete();
|
||||
}
|
||||
|
||||
$customer->delete();
|
||||
});
|
||||
}
|
||||
|
||||
$roles = Role::when($this->id, function ($query) {
|
||||
return $query->where('scope', $this->id);
|
||||
})->get();
|
||||
|
||||
if ($roles) {
|
||||
$roles->map(function ($role) {
|
||||
$role->delete();
|
||||
});
|
||||
}
|
||||
|
||||
if ($this->users()->exists()) {
|
||||
$user->companies()->detach($this->id);
|
||||
}
|
||||
|
||||
$this->settings()->delete();
|
||||
|
||||
$this->address()->delete();
|
||||
|
||||
$this->delete();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function checkModelData($model)
|
||||
{
|
||||
$model->items->map(function ($item) {
|
||||
if ($item->taxes()->exists()) {
|
||||
$item->taxes()->delete();
|
||||
}
|
||||
|
||||
$item->delete();
|
||||
});
|
||||
|
||||
if ($model->taxes()->exists()) {
|
||||
$model->taxes()->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,16 +38,19 @@ class CompanySetting extends Model
|
||||
}
|
||||
}
|
||||
|
||||
public static function getAllSettings($company_id)
|
||||
{
|
||||
return static::whereCompany($company_id)->get()->mapWithKeys(function ($item) {
|
||||
return [$item['option'] => $item['value']];
|
||||
});
|
||||
}
|
||||
|
||||
public static function getSettings($settings, $company_id)
|
||||
{
|
||||
$settings = static::whereIn('option', $settings)->whereCompany($company_id)->get();
|
||||
$companySettings = [];
|
||||
|
||||
foreach ($settings as $setting) {
|
||||
$companySettings[$setting->option] = $setting->value;
|
||||
}
|
||||
|
||||
return $companySettings;
|
||||
return static::whereIn('option', $settings)->whereCompany($company_id)
|
||||
->get()->mapWithKeys(function ($item) {
|
||||
return [$item['option'] => $item['value']];
|
||||
});
|
||||
}
|
||||
|
||||
public static function getSetting($key, $company_id)
|
||||
|
||||
@@ -9,13 +9,7 @@ class Currency extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'code',
|
||||
'symbol',
|
||||
'precision',
|
||||
'thousand_separator',
|
||||
'decimal_separator',
|
||||
'position',
|
||||
protected $guarded = [
|
||||
'id'
|
||||
];
|
||||
}
|
||||
|
||||
@@ -55,19 +55,24 @@ class CustomField extends Model
|
||||
return $this->$value_type;
|
||||
}
|
||||
|
||||
public function getInUseAttribute()
|
||||
{
|
||||
return $this->customFieldValues()->exists();
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function customFieldValue()
|
||||
public function customFieldValues()
|
||||
{
|
||||
return $this->hasMany(CustomFieldValue::class);
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query, $company_id)
|
||||
public function scopeWhereCompany($query)
|
||||
{
|
||||
$query->where('custom_fields.company_id', $company_id);
|
||||
return $query->where('custom_fields.company_id', request()->header('company'));
|
||||
}
|
||||
|
||||
public function scopeWhereSearch($query, $search)
|
||||
@@ -81,7 +86,7 @@ class CustomField extends Model
|
||||
public function scopePaginateData($query, $limit)
|
||||
{
|
||||
if ($limit == 'all') {
|
||||
return collect(['data' => $query->get()]);
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
return $query->paginate($limit);
|
||||
@@ -110,40 +115,17 @@ class CustomField extends Model
|
||||
$data = $request->validated();
|
||||
$data[getCustomFieldValueKey($request->type)] = $request->default_answer;
|
||||
$data['company_id'] = $request->header('company');
|
||||
$data['slug'] = clean_slug($request->model_type, $request->label);
|
||||
$data['slug'] = clean_slug($request->model_type, $request->name);
|
||||
|
||||
return CustomField::create($data);
|
||||
}
|
||||
|
||||
public function updateCustomField($request)
|
||||
{
|
||||
$oldSlug = $this->slug;
|
||||
$data = $request->validated();
|
||||
$data[getCustomFieldValueKey($request->type)] = $request->default_answer;
|
||||
$data['slug'] = clean_slug($request->model_type, $request->label, $this->id);
|
||||
$this->update($data);
|
||||
|
||||
if ($oldSlug !== $data['slug']) {
|
||||
$settings = [
|
||||
'invoice_company_address_format',
|
||||
'invoice_shipping_address_format',
|
||||
'invoice_billing_address_format',
|
||||
'estimate_company_address_format',
|
||||
'estimate_shipping_address_format',
|
||||
'estimate_billing_address_format',
|
||||
'payment_company_address_format',
|
||||
'payment_from_customer_address_format',
|
||||
];
|
||||
|
||||
$settings = CompanySetting::getSettings($settings, $this->company_id);
|
||||
|
||||
foreach ($settings as $key => $value) {
|
||||
$settings[$key] = str_replace($oldSlug, $data['slug'], $value);
|
||||
}
|
||||
|
||||
CompanySetting::setSettings($settings, $this->company_id);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,15 +23,15 @@ class CustomFieldValue extends Model
|
||||
if ($value && $value != null) {
|
||||
$this->attributes['date_answer'] = Carbon::createFromFormat('Y-m-d', $value);
|
||||
}
|
||||
$this->attributes['date_answer'] = null;
|
||||
}
|
||||
|
||||
public function setTimeAnswerAttribute($value)
|
||||
{
|
||||
if ($value && $value != null) {
|
||||
$this->attributes['time_answer'] = date("H:i:s", strtotime($value));
|
||||
} else {
|
||||
$this->attributes['time_answer'] = null;
|
||||
}
|
||||
$this->attributes['time_answer'] = null;
|
||||
}
|
||||
|
||||
public function setDateTimeAnswerAttribute($value)
|
||||
|
||||
326
app/Models/Customer.php
Normal file
326
app/Models/Customer.php
Normal file
@@ -0,0 +1,326 @@
|
||||
<?php
|
||||
|
||||
namespace Crater\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Crater\Traits\HasCustomFieldsTrait;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use Silber\Bouncer\Database\HasRolesAndAbilities;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||
|
||||
class Customer extends Authenticatable implements HasMedia
|
||||
{
|
||||
use HasApiTokens;
|
||||
use Notifiable;
|
||||
use InteractsWithMedia;
|
||||
use HasCustomFieldsTrait;
|
||||
use HasFactory;
|
||||
use HasRolesAndAbilities;
|
||||
|
||||
protected $guarded = [
|
||||
'id'
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
protected $with = [
|
||||
'currency',
|
||||
];
|
||||
|
||||
protected $appends = [
|
||||
'formattedCreatedAt',
|
||||
'avatar'
|
||||
];
|
||||
|
||||
public function getFormattedCreatedAtAttribute($value)
|
||||
{
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
|
||||
|
||||
return Carbon::parse($this->created_at)->format($dateFormat);
|
||||
}
|
||||
|
||||
public function setPasswordAttribute($value)
|
||||
{
|
||||
if ($value != null) {
|
||||
$this->attributes['password'] = bcrypt($value);
|
||||
}
|
||||
}
|
||||
|
||||
public function estimates()
|
||||
{
|
||||
return $this->hasMany(Estimate::class);
|
||||
}
|
||||
|
||||
public function expenses()
|
||||
{
|
||||
return $this->hasMany(Expense::class);
|
||||
}
|
||||
|
||||
public function invoices()
|
||||
{
|
||||
return $this->hasMany(Invoice::class);
|
||||
}
|
||||
|
||||
public function payments()
|
||||
{
|
||||
return $this->hasMany(Payment::class);
|
||||
}
|
||||
|
||||
public function addresses()
|
||||
{
|
||||
return $this->hasMany(Address::class);
|
||||
}
|
||||
|
||||
public function recurringInvoices()
|
||||
{
|
||||
return $this->hasMany(RecurringInvoice::class);
|
||||
}
|
||||
|
||||
public function currency()
|
||||
{
|
||||
return $this->belongsTo(Currency::class);
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(Customer::class, 'creator_id');
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function billingAddress()
|
||||
{
|
||||
return $this->hasOne(Address::class)->where('type', Address::BILLING_TYPE);
|
||||
}
|
||||
|
||||
public function shippingAddress()
|
||||
{
|
||||
return $this->hasOne(Address::class)->where('type', Address::SHIPPING_TYPE);
|
||||
}
|
||||
|
||||
public function getAvatarAttribute()
|
||||
{
|
||||
$avatar = $this->getMedia('customer_avatar')->first();
|
||||
|
||||
if ($avatar) {
|
||||
return asset($avatar->getUrl());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static function deleteCustomers($ids)
|
||||
{
|
||||
foreach ($ids as $id) {
|
||||
$customer = self::find($id);
|
||||
|
||||
if ($customer->estimates()->exists()) {
|
||||
$customer->estimates()->delete();
|
||||
}
|
||||
|
||||
if ($customer->invoices()->exists()) {
|
||||
$customer->invoices()->delete();
|
||||
}
|
||||
|
||||
if ($customer->payments()->exists()) {
|
||||
$customer->payments()->delete();
|
||||
}
|
||||
|
||||
if ($customer->addresses()->exists()) {
|
||||
$customer->addresses()->delete();
|
||||
}
|
||||
|
||||
if ($customer->expenses()->exists()) {
|
||||
$customer->expenses()->delete();
|
||||
}
|
||||
|
||||
if ($customer->recurringInvoices()->exists()) {
|
||||
foreach ($customer->recurringInvoices as $recurringInvoice) {
|
||||
if ($recurringInvoice->items()->exists()) {
|
||||
$recurringInvoice->items()->delete();
|
||||
}
|
||||
|
||||
$recurringInvoice->delete();
|
||||
}
|
||||
}
|
||||
|
||||
$customer->delete();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function createCustomer($request)
|
||||
{
|
||||
$customer = Customer::create($request->getCustomerPayload());
|
||||
|
||||
if ($request->shipping) {
|
||||
if ($request->hasAddress($request->shipping)) {
|
||||
$customer->addresses()->create($request->getShippingAddress());
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->billing) {
|
||||
if ($request->hasAddress($request->billing)) {
|
||||
$customer->addresses()->create($request->getBillingAddress());
|
||||
}
|
||||
}
|
||||
|
||||
$customFields = $request->customFields;
|
||||
|
||||
if ($customFields) {
|
||||
$customer->addCustomFields($customFields);
|
||||
}
|
||||
|
||||
$customer = Customer::with('billingAddress', 'shippingAddress', 'fields')->find($customer->id);
|
||||
|
||||
return $customer;
|
||||
}
|
||||
|
||||
public static function updateCustomer($request, $customer)
|
||||
{
|
||||
$condition = $customer->estimates()->exists() || $customer->invoices()->exists() || $customer->payments()->exists() || $customer->recurringInvoices()->exists();
|
||||
|
||||
if (($customer->currency_id !== $request->currency_id) && $condition) {
|
||||
return 'you_cannot_edit_currency';
|
||||
}
|
||||
|
||||
$customer->update($request->getCustomerPayload());
|
||||
|
||||
$customer->addresses()->delete();
|
||||
|
||||
if ($request->shipping) {
|
||||
if ($request->hasAddress($request->shipping)) {
|
||||
$customer->addresses()->create($request->getShippingAddress());
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->billing) {
|
||||
if ($request->hasAddress($request->billing)) {
|
||||
$customer->addresses()->create($request->getBillingAddress());
|
||||
}
|
||||
}
|
||||
|
||||
$customFields = $request->customFields;
|
||||
|
||||
if ($customFields) {
|
||||
$customer->updateCustomFields($customFields);
|
||||
}
|
||||
|
||||
$customer = Customer::with('billingAddress', 'shippingAddress', 'fields')->find($customer->id);
|
||||
|
||||
return $customer;
|
||||
}
|
||||
|
||||
public function scopePaginateData($query, $limit)
|
||||
{
|
||||
if ($limit == 'all') {
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
return $query->paginate($limit);
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query)
|
||||
{
|
||||
return $query->where('customers.company_id', request()->header('company'));
|
||||
}
|
||||
|
||||
public function scopeWhereContactName($query, $contactName)
|
||||
{
|
||||
return $query->where('contact_name', 'LIKE', '%'.$contactName.'%');
|
||||
}
|
||||
|
||||
public function scopeWhereDisplayName($query, $displayName)
|
||||
{
|
||||
return $query->where('name', 'LIKE', '%'.$displayName.'%');
|
||||
}
|
||||
|
||||
public function scopeWhereOrder($query, $orderByField, $orderBy)
|
||||
{
|
||||
$query->orderBy($orderByField, $orderBy);
|
||||
}
|
||||
|
||||
public function scopeWhereSearch($query, $search)
|
||||
{
|
||||
foreach (explode(' ', $search) as $term) {
|
||||
$query->where(function ($query) use ($term) {
|
||||
$query->where('name', 'LIKE', '%'.$term.'%')
|
||||
->orWhere('email', 'LIKE', '%'.$term.'%')
|
||||
->orWhere('phone', 'LIKE', '%'.$term.'%');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function scopeWherePhone($query, $phone)
|
||||
{
|
||||
return $query->where('phone', 'LIKE', '%'.$phone.'%');
|
||||
}
|
||||
|
||||
public function scopeWhereCustomer($query, $customer_id)
|
||||
{
|
||||
$query->orWhere('customers.id', $customer_id);
|
||||
}
|
||||
|
||||
public function scopeApplyInvoiceFilters($query, array $filters)
|
||||
{
|
||||
$filters = collect($filters);
|
||||
|
||||
if ($filters->get('from_date') && $filters->get('to_date')) {
|
||||
$start = Carbon::createFromFormat('Y-m-d', $filters->get('from_date'));
|
||||
$end = Carbon::createFromFormat('Y-m-d', $filters->get('to_date'));
|
||||
$query->invoicesBetween($start, $end);
|
||||
}
|
||||
}
|
||||
|
||||
public function scopeInvoicesBetween($query, $start, $end)
|
||||
{
|
||||
$query->whereHas('invoices', function ($query) use ($start, $end) {
|
||||
$query->whereBetween(
|
||||
'invoice_date',
|
||||
[$start->format('Y-m-d'), $end->format('Y-m-d')]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public function scopeApplyFilters($query, array $filters)
|
||||
{
|
||||
$filters = collect($filters);
|
||||
|
||||
if ($filters->get('search')) {
|
||||
$query->whereSearch($filters->get('search'));
|
||||
}
|
||||
|
||||
if ($filters->get('contact_name')) {
|
||||
$query->whereContactName($filters->get('contact_name'));
|
||||
}
|
||||
|
||||
if ($filters->get('display_name')) {
|
||||
$query->whereDisplayName($filters->get('display_name'));
|
||||
}
|
||||
|
||||
if ($filters->get('customer_id')) {
|
||||
$query->whereCustomer($filters->get('customer_id'));
|
||||
}
|
||||
|
||||
if ($filters->get('phone')) {
|
||||
$query->wherePhone($filters->get('phone'));
|
||||
}
|
||||
|
||||
if ($filters->get('orderByField') || $filters->get('orderBy')) {
|
||||
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'name';
|
||||
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'asc';
|
||||
$query->whereOrder($field, $orderBy);
|
||||
}
|
||||
}
|
||||
}
|
||||
40
app/Models/CustomerFactory.php
Normal file
40
app/Models/CustomerFactory.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Crater\Models\Currency;
|
||||
use Crater\Models\Customer;
|
||||
use Crater\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class CustomerFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = Customer::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->name,
|
||||
'company_name' => $this->faker->company,
|
||||
'contact_name' => $this->faker->name,
|
||||
'website' => $this->faker->url,
|
||||
'enable_portal' => true,
|
||||
'email' => $this->faker->unique()->safeEmail,
|
||||
'phone' => $this->faker->phoneNumber,
|
||||
'company_id' => User::find(1)->companies()->first()->id,
|
||||
'password' => Hash::make('secret'),
|
||||
'currency_id' => Currency::find(1)->id,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,13 @@ use App;
|
||||
use Barryvdh\DomPDF\Facade as PDF;
|
||||
use Carbon\Carbon;
|
||||
use Crater\Mail\SendEstimateMail;
|
||||
use Crater\Services\SerialNumberFormatter;
|
||||
use Crater\Traits\GeneratesPdfTrait;
|
||||
use Crater\Traits\HasCustomFieldsTrait;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||
use Vinkla\Hashids\Facades\Hashids;
|
||||
@@ -49,6 +51,7 @@ class Estimate extends Model implements HasMedia
|
||||
'sub_total' => 'integer',
|
||||
'discount' => 'float',
|
||||
'discount_val' => 'integer',
|
||||
'exchange_rate' => 'float'
|
||||
];
|
||||
|
||||
public function setEstimateDateAttribute($value)
|
||||
@@ -70,36 +73,6 @@ class Estimate extends Model implements HasMedia
|
||||
return url('/estimates/pdf/'.$this->unique_hash);
|
||||
}
|
||||
|
||||
public static function getNextEstimateNumber($value)
|
||||
{
|
||||
// Get the last created order
|
||||
$lastOrder = Estimate::where('estimate_number', 'LIKE', $value.'-%')
|
||||
->orderBy('estimate_number', 'desc')
|
||||
->first();
|
||||
|
||||
// Get number length config
|
||||
$numberLength = CompanySetting::getSetting('estimate_number_length', request()->header('company'));
|
||||
$numberLengthText = "%0{$numberLength}d";
|
||||
|
||||
if (! $lastOrder) {
|
||||
// We get here if there is no order at all
|
||||
// If there is no number set it to 0, which will be 1 at the end.
|
||||
$number = 0;
|
||||
} else {
|
||||
$number = explode("-", $lastOrder->estimate_number);
|
||||
$number = $number[1];
|
||||
}
|
||||
|
||||
// If we have ORD000001 in the database then we only want the number
|
||||
// So the substr returns this 000001
|
||||
|
||||
// Add the string in front and higher up the number.
|
||||
// the %05d part makes sure that there are always 6 numbers in the string.
|
||||
// so it adds the missing zero's when needed.
|
||||
|
||||
return sprintf($numberLengthText, intval($number) + 1);
|
||||
}
|
||||
|
||||
public function emailLogs()
|
||||
{
|
||||
return $this->morphMany('App\Models\EmailLog', 'mailable');
|
||||
@@ -110,9 +83,9 @@ class Estimate extends Model implements HasMedia
|
||||
return $this->hasMany('Crater\Models\EstimateItem');
|
||||
}
|
||||
|
||||
public function user()
|
||||
public function customer()
|
||||
{
|
||||
return $this->belongsTo('Crater\Models\User', 'user_id');
|
||||
return $this->belongsTo(Customer::class, 'customer_id');
|
||||
}
|
||||
|
||||
public function creator()
|
||||
@@ -125,40 +98,16 @@ class Estimate extends Model implements HasMedia
|
||||
return $this->belongsTo('Crater\Models\Company');
|
||||
}
|
||||
|
||||
public function currency()
|
||||
{
|
||||
return $this->belongsTo(Currency::class);
|
||||
}
|
||||
|
||||
public function taxes()
|
||||
{
|
||||
return $this->hasMany(Tax::class);
|
||||
}
|
||||
|
||||
public function getEstimateNumAttribute()
|
||||
{
|
||||
$position = $this->strposX($this->estimate_number, "-", 1) + 1;
|
||||
|
||||
return substr($this->estimate_number, $position);
|
||||
}
|
||||
|
||||
public function getEstimatePrefixAttribute()
|
||||
{
|
||||
$prefix = explode("-", $this->estimate_number)[0];
|
||||
|
||||
return $prefix;
|
||||
}
|
||||
|
||||
private function strposX($haystack, $needle, $number)
|
||||
{
|
||||
if ($number == '1') {
|
||||
return strpos($haystack, $needle);
|
||||
} elseif ($number > '1') {
|
||||
return strpos(
|
||||
$haystack,
|
||||
$needle,
|
||||
$this->strposX($haystack, $needle, $number - 1) + strlen($needle)
|
||||
);
|
||||
} else {
|
||||
return error_log('Error: Value for parameter $number is out of range');
|
||||
}
|
||||
}
|
||||
|
||||
public function getFormattedExpiryDateAttribute($value)
|
||||
{
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
|
||||
@@ -188,7 +137,7 @@ class Estimate extends Model implements HasMedia
|
||||
|
||||
public function scopeWhereEstimateNumber($query, $estimateNumber)
|
||||
{
|
||||
return $query->where('estimates.estimate_number', $estimateNumber);
|
||||
return $query->where('estimates.estimate_number', 'LIKE', '%'.$estimateNumber.'%');
|
||||
}
|
||||
|
||||
public function scopeWhereEstimate($query, $estimate_id)
|
||||
@@ -199,7 +148,7 @@ class Estimate extends Model implements HasMedia
|
||||
public function scopeWhereSearch($query, $search)
|
||||
{
|
||||
foreach (explode(' ', $search) as $term) {
|
||||
$query->whereHas('user', function ($query) use ($term) {
|
||||
$query->whereHas('customer', function ($query) use ($term) {
|
||||
$query->where('name', 'LIKE', '%'.$term.'%')
|
||||
->orWhere('contact_name', 'LIKE', '%'.$term.'%')
|
||||
->orWhere('company_name', 'LIKE', '%'.$term.'%');
|
||||
@@ -238,8 +187,8 @@ class Estimate extends Model implements HasMedia
|
||||
}
|
||||
|
||||
if ($filters->get('orderByField') || $filters->get('orderBy')) {
|
||||
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'estimate_number';
|
||||
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'asc';
|
||||
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'sequence_number';
|
||||
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'desc';
|
||||
$query->whereOrder($field, $orderBy);
|
||||
}
|
||||
}
|
||||
@@ -249,20 +198,20 @@ class Estimate extends Model implements HasMedia
|
||||
$query->orderBy($orderByField, $orderBy);
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query, $company_id)
|
||||
public function scopeWhereCompany($query)
|
||||
{
|
||||
$query->where('estimates.company_id', $company_id);
|
||||
$query->where('estimates.company_id', request()->header('company'));
|
||||
}
|
||||
|
||||
public function scopeWhereCustomer($query, $customer_id)
|
||||
{
|
||||
$query->where('estimates.user_id', $customer_id);
|
||||
$query->where('estimates.customer_id', $customer_id);
|
||||
}
|
||||
|
||||
public function scopePaginateData($query, $limit)
|
||||
{
|
||||
if ($limit == 'all') {
|
||||
return collect(['data' => $query->get()]);
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
return $query->paginate($limit);
|
||||
@@ -270,22 +219,7 @@ class Estimate extends Model implements HasMedia
|
||||
|
||||
public static function createEstimate($request)
|
||||
{
|
||||
$data = $request->except(['items', 'taxes']);
|
||||
|
||||
$data['creator_id'] = Auth::id();
|
||||
$data['status'] = self::STATUS_DRAFT;
|
||||
$data['unique_hash'] = str_random(60);
|
||||
$data['company_id'] = $request->header('company');
|
||||
|
||||
$data['tax_per_item'] = CompanySetting::getSetting(
|
||||
'tax_per_item',
|
||||
$request->header('company')
|
||||
) ?? 'NO';
|
||||
|
||||
$data['discount_per_item'] = CompanySetting::getSetting(
|
||||
'discount_per_item',
|
||||
$request->header('company')
|
||||
) ?? 'NO';
|
||||
$data = $request->getEstimatePayload();
|
||||
|
||||
if ($request->has('estimateSend')) {
|
||||
$data['status'] = self::STATUS_SENT;
|
||||
@@ -293,12 +227,26 @@ class Estimate extends Model implements HasMedia
|
||||
|
||||
$estimate = self::create($data);
|
||||
$estimate->unique_hash = Hashids::connection(Estimate::class)->encode($estimate->id);
|
||||
$serial = (new SerialNumberFormatter())
|
||||
->setModel($estimate)
|
||||
->setCompany($estimate->company_id)
|
||||
->setCustomer($estimate->customer_id)
|
||||
->setNextNumbers();
|
||||
|
||||
$estimate->sequence_number = $serial->nextSequenceNumber;
|
||||
$estimate->customer_sequence_number = $serial->nextCustomerSequenceNumber;
|
||||
$estimate->save();
|
||||
|
||||
self::createItems($estimate, $request);
|
||||
$company_currency = CompanySetting::getSetting('currency', $request->header('company'));
|
||||
|
||||
if ((string)$data['currency_id'] !== $company_currency) {
|
||||
ExchangeRateLog::addExchangeRateLog($estimate);
|
||||
}
|
||||
|
||||
self::createItems($estimate, $request, $estimate->exchange_rate);
|
||||
|
||||
if ($request->has('taxes') && (! empty($request->taxes))) {
|
||||
self::createTaxes($estimate, $request);
|
||||
self::createTaxes($estimate, $request, $estimate->exchange_rate);
|
||||
}
|
||||
|
||||
$customFields = $request->customFields;
|
||||
@@ -307,27 +255,45 @@ class Estimate extends Model implements HasMedia
|
||||
$estimate->addCustomFields($customFields);
|
||||
}
|
||||
|
||||
return Estimate::with([
|
||||
'items.taxes',
|
||||
'user',
|
||||
'taxes'
|
||||
])
|
||||
->find($estimate->id);
|
||||
return $estimate;
|
||||
}
|
||||
|
||||
public function updateEstimate($request)
|
||||
{
|
||||
$data = $request->except(['items', 'taxes']);
|
||||
$data = $request->getEstimatePayload();
|
||||
|
||||
$serial = (new SerialNumberFormatter())
|
||||
->setModel($this)
|
||||
->setCompany($this->company_id)
|
||||
->setCustomer($request->customer_id)
|
||||
->setModelObject($this->id)
|
||||
->setNextNumbers();
|
||||
|
||||
$data['customer_sequence_number'] = $serial->nextCustomerSequenceNumber;
|
||||
|
||||
$this->update($data);
|
||||
|
||||
$company_currency = CompanySetting::getSetting('currency', $request->header('company'));
|
||||
|
||||
if ((string)$data['currency_id'] !== $company_currency) {
|
||||
ExchangeRateLog::addExchangeRateLog($this);
|
||||
}
|
||||
|
||||
$this->items->map(function ($item) {
|
||||
$fields = $item->fields()->get();
|
||||
|
||||
$fields->map(function ($field) {
|
||||
$field->delete();
|
||||
});
|
||||
});
|
||||
|
||||
$this->items()->delete();
|
||||
$this->taxes()->delete();
|
||||
|
||||
self::createItems($this, $request);
|
||||
self::createItems($this, $request, $this->exchange_rate);
|
||||
|
||||
if ($request->has('taxes') && (! empty($request->taxes))) {
|
||||
self::createTaxes($this, $request);
|
||||
self::createTaxes($this, $request, $this->exchange_rate);
|
||||
}
|
||||
|
||||
if ($request->customFields) {
|
||||
@@ -336,18 +302,26 @@ class Estimate extends Model implements HasMedia
|
||||
|
||||
return Estimate::with([
|
||||
'items.taxes',
|
||||
'user',
|
||||
'items.fields',
|
||||
'items.fields.customField',
|
||||
'customer',
|
||||
'taxes'
|
||||
])
|
||||
->find($this->id);
|
||||
}
|
||||
|
||||
public static function createItems($estimate, $request)
|
||||
public static function createItems($estimate, $request, $exchange_rate)
|
||||
{
|
||||
$estimateItems = $request->items;
|
||||
|
||||
foreach ($estimateItems as $estimateItem) {
|
||||
$estimateItem['company_id'] = $request->header('company');
|
||||
$estimateItem['exchange_rate'] = $exchange_rate;
|
||||
$estimateItem['base_price'] = $estimateItem['price'] * $exchange_rate;
|
||||
$estimateItem['base_discount_val'] = $estimateItem['discount_val'] * $exchange_rate;
|
||||
$estimateItem['base_tax'] = $estimate['tax'] * $exchange_rate;
|
||||
$estimateItem['base_total'] = $estimateItem['total'] * $exchange_rate;
|
||||
|
||||
$item = $estimate->items()->create($estimateItem);
|
||||
|
||||
if (array_key_exists('taxes', $estimateItem) && $estimateItem['taxes']) {
|
||||
@@ -358,38 +332,54 @@ class Estimate extends Model implements HasMedia
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('custom_fields', $estimateItem) && $estimateItem['custom_fields']) {
|
||||
$item->addCustomFields($estimateItem['custom_fields']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function createTaxes($estimate, $request)
|
||||
public static function createTaxes($estimate, $request, $exchange_rate)
|
||||
{
|
||||
$estimateTaxes = $request->taxes;
|
||||
|
||||
foreach ($estimateTaxes as $tax) {
|
||||
if (gettype($tax['amount']) !== "NULL") {
|
||||
$tax['company_id'] = $request->header('company');
|
||||
$tax['exchange_rate'] = $exchange_rate;
|
||||
$tax['base_amount'] = $tax['amount'] * $exchange_rate;
|
||||
$tax['currency_id'] = $estimate->currency_id;
|
||||
|
||||
$estimate->taxes()->create($tax);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function send($data)
|
||||
public function sendEstimateData($data)
|
||||
{
|
||||
$data['estimate'] = $this->toArray();
|
||||
$data['user'] = $this->user->toArray();
|
||||
$data['user'] = $this->customer->toArray();
|
||||
$data['company'] = $this->company->toArray();
|
||||
$data['body'] = $this->getEmailBody($data['body']);
|
||||
$data['attach']['data'] = ($this->getEmailAttachmentSetting()) ? $this->getPDFData() : null;
|
||||
|
||||
\Mail::to($data['to'])->send(new SendEstimateMail($data));
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function send($data)
|
||||
{
|
||||
$data = $this->sendEstimateData($data);
|
||||
|
||||
if ($this->status == Estimate::STATUS_DRAFT) {
|
||||
$this->status = Estimate::STATUS_SENT;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
\Mail::to($data['to'])->send(new SendEstimateMail($data));
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'type' => 'send',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -417,6 +407,7 @@ class Estimate extends Model implements HasMedia
|
||||
|
||||
$company = Company::find($this->company_id);
|
||||
$locale = CompanySetting::getSetting('language', $company->id);
|
||||
$customFields = CustomField::where('model_type', 'Item')->get();
|
||||
|
||||
App::setLocale($locale);
|
||||
|
||||
@@ -424,6 +415,7 @@ class Estimate extends Model implements HasMedia
|
||||
|
||||
view()->share([
|
||||
'estimate' => $this,
|
||||
'customFields' => $customFields,
|
||||
'logo' => $logo ?? null,
|
||||
'company_address' => $this->getCompanyAddress(),
|
||||
'shipping_address' => $this->getCustomerShippingAddress(),
|
||||
@@ -448,7 +440,7 @@ class Estimate extends Model implements HasMedia
|
||||
|
||||
public function getCustomerShippingAddress()
|
||||
{
|
||||
if ($this->user && (! $this->user->shippingAddress()->exists())) {
|
||||
if ($this->customer && (! $this->customer->shippingAddress()->exists())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -459,7 +451,7 @@ class Estimate extends Model implements HasMedia
|
||||
|
||||
public function getCustomerBillingAddress()
|
||||
{
|
||||
if ($this->user && (! $this->user->billingAddress()->exists())) {
|
||||
if ($this->customer && (! $this->customer->billingAddress()->exists())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -503,4 +495,54 @@ class Estimate extends Model implements HasMedia
|
||||
'{ESTIMATE_LINK}' => url('/customer/estimates/pdf/'.$this->unique_hash),
|
||||
];
|
||||
}
|
||||
|
||||
public static function estimateTemplates()
|
||||
{
|
||||
$templates = Storage::disk('views')->files('/app/pdf/estimate');
|
||||
$estimateTemplates = [];
|
||||
|
||||
foreach ($templates as $key => $template) {
|
||||
$templateName = Str::before(basename($template), '.blade.php');
|
||||
$estimateTemplates[$key]['name'] = $templateName;
|
||||
$estimateTemplates[$key]['path'] = vite_asset('/img/PDF/'.$templateName.'.png');
|
||||
}
|
||||
|
||||
return $estimateTemplates;
|
||||
}
|
||||
|
||||
public function getInvoiceTemplateName()
|
||||
{
|
||||
$templateName = Str::replace('estimate', 'invoice', $this->template_name);
|
||||
|
||||
$name = [];
|
||||
|
||||
foreach (Invoice::invoiceTemplates() as $template) {
|
||||
$name[] = $template['name'];
|
||||
}
|
||||
|
||||
if (in_array($templateName, $name) == false) {
|
||||
$templateName = 'invoice1';
|
||||
}
|
||||
|
||||
return $templateName;
|
||||
}
|
||||
|
||||
public function checkForEstimateConvertAction()
|
||||
{
|
||||
$convertEstimateAction = CompanySetting::getSetting(
|
||||
'estimate_convert_action',
|
||||
$this->company_id
|
||||
);
|
||||
|
||||
if ($convertEstimateAction === 'delete_estimate') {
|
||||
$this->delete();
|
||||
}
|
||||
|
||||
if ($convertEstimateAction === 'mark_estimate_as_accepted') {
|
||||
$this->status = self::STATUS_ACCEPTED;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,27 +2,17 @@
|
||||
|
||||
namespace Crater\Models;
|
||||
|
||||
use Crater\Traits\HasCustomFieldsTrait;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class EstimateItem extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use HasCustomFieldsTrait;
|
||||
|
||||
protected $fillable = [
|
||||
'estimate_id',
|
||||
'name',
|
||||
'item_id',
|
||||
'description',
|
||||
'quantity',
|
||||
'company_id',
|
||||
'price',
|
||||
'discount_type',
|
||||
'discount_val',
|
||||
'tax',
|
||||
'total',
|
||||
'discount',
|
||||
'unit_name',
|
||||
protected $guarded = [
|
||||
'id'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
||||
41
app/Models/ExchangeRateLog.php
Normal file
41
app/Models/ExchangeRateLog.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Crater\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ExchangeRateLog extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [
|
||||
'id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'exchange_rate' => 'float'
|
||||
];
|
||||
|
||||
public function currency()
|
||||
{
|
||||
return $this->belongsTo(Currency::class);
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public static function addExchangeRateLog($model)
|
||||
{
|
||||
$data = [
|
||||
'exchange_rate' => $model->exchange_rate,
|
||||
'company_id' => $model->company_id,
|
||||
'base_currency_id' => $model->currency_id,
|
||||
'currency_id' => CompanySetting::getSetting('currency', $model->company_id),
|
||||
];
|
||||
|
||||
return self::create($data);
|
||||
}
|
||||
}
|
||||
166
app/Models/ExchangeRateProvider.php
Normal file
166
app/Models/ExchangeRateProvider.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace Crater\Models;
|
||||
|
||||
use Crater\Http\Requests\ExchangeRateProviderRequest;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class ExchangeRateProvider extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [
|
||||
'id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'currencies' => 'array',
|
||||
'driver_config' => 'array',
|
||||
'active' => 'boolean'
|
||||
];
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function setCurrenciesAttribute($value)
|
||||
{
|
||||
$this->attributes['currencies'] = json_encode($value);
|
||||
}
|
||||
|
||||
public function setDriverConfigAttribute($value)
|
||||
{
|
||||
$this->attributes['driver_config'] = json_encode($value);
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query)
|
||||
{
|
||||
$query->where('exchange_rate_providers.company_id', request()->header('company'));
|
||||
}
|
||||
|
||||
public static function createFromRequest(ExchangeRateProviderRequest $request)
|
||||
{
|
||||
$exchangeRateProvider = self::create($request->getExchangeRateProviderPayload());
|
||||
|
||||
return $exchangeRateProvider;
|
||||
}
|
||||
|
||||
public function updateFromRequest(ExchangeRateProviderRequest $request)
|
||||
{
|
||||
$this->update($request->getExchangeRateProviderPayload());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public static function checkActiveCurrencies($request)
|
||||
{
|
||||
$query = ExchangeRateProvider::whereJsonContains('currencies', $request->currencies)
|
||||
->where('active', true)
|
||||
->get();
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function checkUpdateActiveCurrencies($request)
|
||||
{
|
||||
$query = ExchangeRateProvider::where('active', $request->active)
|
||||
->where('id', '<>', $this->id)
|
||||
->whereJsonContains('currencies', $request->currencies)
|
||||
->get();
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public static function checkExchangeRateProviderStatus($request)
|
||||
{
|
||||
switch ($request['driver']) {
|
||||
case 'currency_freak':
|
||||
$url = "https://api.currencyfreaks.com/latest?apikey=".$request['key']."&symbols=INR&base=USD";
|
||||
$response = Http::get($url)->json();
|
||||
|
||||
if (array_key_exists('success', $response)) {
|
||||
if ($response["success"] == false) {
|
||||
return respondJson($response["error"]["message"], $response["error"]["message"]);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'exchangeRate' => array_values($response["rates"]),
|
||||
], 200);
|
||||
|
||||
break;
|
||||
|
||||
case 'currency_layer':
|
||||
$url = "http://api.currencylayer.com/live?access_key=".$request['key']."&source=INR¤cies=USD";
|
||||
$response = Http::get($url)->json();
|
||||
|
||||
if (array_key_exists('success', $response)) {
|
||||
if ($response["success"] == false) {
|
||||
return respondJson($response["error"]["info"], $response["error"]["info"]);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'exchangeRate' => array_values($response['quotes']),
|
||||
], 200);
|
||||
|
||||
break;
|
||||
|
||||
case 'open_exchange_rate':
|
||||
$url = "https://openexchangerates.org/api/latest.json?app_id=".$request['key']."&base=INR&symbols=USD";
|
||||
$response = Http::get($url)->json();
|
||||
|
||||
if (array_key_exists("error", $response)) {
|
||||
return respondJson($response['message'], $response["description"]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'exchangeRate' => array_values($response["rates"]),
|
||||
], 200);
|
||||
|
||||
break;
|
||||
|
||||
case 'currency_converter':
|
||||
$url = self::getCurrencyConverterUrl($request['driver_config']);
|
||||
$url = $url."/api/v7/convert?apiKey=".$request['key'];
|
||||
|
||||
$query = "INR_USD";
|
||||
$url = $url."&q={$query}"."&compact=y";
|
||||
$response = Http::get($url)->json();
|
||||
|
||||
return response()->json([
|
||||
'exchangeRate' => array_values($response[$query]),
|
||||
], 200);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getCurrencyConverterUrl($data)
|
||||
{
|
||||
switch ($data['type']) {
|
||||
case 'PREMIUM':
|
||||
return "https://api.currconv.com";
|
||||
|
||||
break;
|
||||
|
||||
case 'PREPAID':
|
||||
return "https://prepaid.currconv.com";
|
||||
|
||||
break;
|
||||
|
||||
case 'FREE':
|
||||
return "https://free.currconv.com";
|
||||
|
||||
break;
|
||||
|
||||
case 'DEDICATED':
|
||||
return $data['url'];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ use Carbon\Carbon;
|
||||
use Crater\Traits\HasCustomFieldsTrait;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||
@@ -23,6 +22,12 @@ class Expense extends Model implements HasMedia
|
||||
'formattedExpenseDate',
|
||||
'formattedCreatedAt',
|
||||
'receipt',
|
||||
'receiptMeta'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'notes' => 'string',
|
||||
'exchange_rate' => 'float'
|
||||
];
|
||||
|
||||
public function setExpenseDateAttribute($value)
|
||||
@@ -37,9 +42,24 @@ class Expense extends Model implements HasMedia
|
||||
return $this->belongsTo(ExpenseCategory::class, 'expense_category_id');
|
||||
}
|
||||
|
||||
public function user()
|
||||
public function customer()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
return $this->belongsTo(Customer::class, 'customer_id');
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class, 'company_id');
|
||||
}
|
||||
|
||||
public function paymentMethod()
|
||||
{
|
||||
return $this->belongsTo(PaymentMethod::class);
|
||||
}
|
||||
|
||||
public function currency()
|
||||
{
|
||||
return $this->belongsTo(Currency::class, 'currency_id');
|
||||
}
|
||||
|
||||
public function creator()
|
||||
@@ -61,9 +81,24 @@ class Expense extends Model implements HasMedia
|
||||
return Carbon::parse($this->created_at)->format($dateFormat);
|
||||
}
|
||||
|
||||
public function getReceiptUrlAttribute($value)
|
||||
{
|
||||
$media = $this->getFirstMedia('receipts');
|
||||
|
||||
if ($media) {
|
||||
return [
|
||||
'url' => $media->getFullUrl(),
|
||||
'type' => $media->type
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getReceiptAttribute($value)
|
||||
{
|
||||
$media = $this->getFirstMedia('receipts');
|
||||
|
||||
if ($media) {
|
||||
return $media->getPath();
|
||||
}
|
||||
@@ -71,6 +106,17 @@ class Expense extends Model implements HasMedia
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getReceiptMetaAttribute($value)
|
||||
{
|
||||
$media = $this->getFirstMedia('receipts');
|
||||
|
||||
if ($media) {
|
||||
return $media;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function scopeExpensesBetween($query, $start, $end)
|
||||
{
|
||||
return $query->whereBetween(
|
||||
@@ -98,9 +144,9 @@ class Expense extends Model implements HasMedia
|
||||
return $query->where('expenses.expense_category_id', $categoryId);
|
||||
}
|
||||
|
||||
public function scopeWhereUser($query, $user_id)
|
||||
public function scopeWhereUser($query, $customer_id)
|
||||
{
|
||||
return $query->where('expenses.user_id', $user_id);
|
||||
return $query->where('expenses.customer_id', $customer_id);
|
||||
}
|
||||
|
||||
public function scopeApplyFilters($query, array $filters)
|
||||
@@ -111,8 +157,8 @@ class Expense extends Model implements HasMedia
|
||||
$query->whereCategory($filters->get('expense_category_id'));
|
||||
}
|
||||
|
||||
if ($filters->get('user_id')) {
|
||||
$query->whereUser($filters->get('user_id'));
|
||||
if ($filters->get('customer_id')) {
|
||||
$query->whereUser($filters->get('customer_id'));
|
||||
}
|
||||
|
||||
if ($filters->get('expense_id')) {
|
||||
@@ -156,15 +202,20 @@ class Expense extends Model implements HasMedia
|
||||
$query->orderBy($orderByField, $orderBy);
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query, $company_id)
|
||||
public function scopeWhereCompany($query)
|
||||
{
|
||||
$query->where('expenses.company_id', $company_id);
|
||||
$query->where('expenses.company_id', request()->header('company'));
|
||||
}
|
||||
|
||||
public function scopeWhereCompanyId($query, $company)
|
||||
{
|
||||
$query->where('expenses.company_id', $company);
|
||||
}
|
||||
|
||||
public function scopePaginateData($query, $limit)
|
||||
{
|
||||
if ($limit == 'all') {
|
||||
return collect(['data' => $query->get()]);
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
return $query->paginate($limit);
|
||||
@@ -183,20 +234,20 @@ class Expense extends Model implements HasMedia
|
||||
|
||||
public static function createExpense($request)
|
||||
{
|
||||
$data = $request->validated();
|
||||
$data['creator_id'] = Auth::id();
|
||||
$data['company_id'] = $request->header('company');
|
||||
$expense = self::create($request->getExpensePayload());
|
||||
|
||||
$expense = self::create($data);
|
||||
$company_currency = CompanySetting::getSetting('currency', $request->header('company'));
|
||||
|
||||
if ($request->hasFile('attachment_receipt')) {
|
||||
$expense->addMediaFromRequest('attachment_receipt')->toMediaCollection('receipts', 'local');
|
||||
if ((string)$expense['currency_id'] !== $company_currency) {
|
||||
ExchangeRateLog::addExchangeRateLog($expense);
|
||||
}
|
||||
|
||||
$customFields = json_decode($request->customFields, true);
|
||||
if ($request->hasFile('attachment_receipt')) {
|
||||
$expense->addMediaFromRequest('attachment_receipt')->toMediaCollection('receipts');
|
||||
}
|
||||
|
||||
if ($customFields) {
|
||||
$expense->addCustomFields($customFields);
|
||||
if ($request->customFields && empty($request->customFields)) {
|
||||
$expense->addCustomFields($request->customFields);
|
||||
}
|
||||
|
||||
return $expense;
|
||||
@@ -204,17 +255,23 @@ class Expense extends Model implements HasMedia
|
||||
|
||||
public function updateExpense($request)
|
||||
{
|
||||
$this->update($request->validated());
|
||||
$data = $request->getExpensePayload();
|
||||
|
||||
$this->update($data);
|
||||
|
||||
$company_currency = CompanySetting::getSetting('currency', $request->header('company'));
|
||||
|
||||
if ((string)$data['currency_id'] !== $company_currency) {
|
||||
ExchangeRateLog::addExchangeRateLog($this);
|
||||
}
|
||||
|
||||
if ($request->hasFile('attachment_receipt')) {
|
||||
$this->clearMediaCollection('receipts');
|
||||
$this->addMediaFromRequest('attachment_receipt')->toMediaCollection('receipts', 'local');
|
||||
$this->addMediaFromRequest('attachment_receipt')->toMediaCollection('receipts');
|
||||
}
|
||||
|
||||
$customFields = json_decode($request->customFields, true);
|
||||
|
||||
if ($customFields) {
|
||||
$this->updateCustomFields($customFields);
|
||||
if ($request->customFields && empty($request->customFields)) {
|
||||
$this->updateCustomFields($request->customFields);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -24,6 +24,11 @@ class ExpenseCategory extends Model
|
||||
return $this->hasMany(Expense::class);
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function getFormattedCreatedAtAttribute($value)
|
||||
{
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
|
||||
@@ -36,9 +41,9 @@ class ExpenseCategory extends Model
|
||||
return $this->expenses()->sum('amount');
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query, $company_id)
|
||||
public function scopeWhereCompany($query)
|
||||
{
|
||||
$query->where('company_id', $company_id);
|
||||
$query->where('company_id', request()->header('company'));
|
||||
}
|
||||
|
||||
public function scopeWhereCategory($query, $category_id)
|
||||
@@ -71,7 +76,7 @@ class ExpenseCategory extends Model
|
||||
public function scopePaginateData($query, $limit)
|
||||
{
|
||||
if ($limit == 'all') {
|
||||
return collect(['data' => $query->get()]);
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
return $query->paginate($limit);
|
||||
|
||||
@@ -16,6 +16,10 @@ class FileDisk extends Model
|
||||
'id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'set_as_default' => 'boolean',
|
||||
];
|
||||
|
||||
public function setCredentialsAttribute($value)
|
||||
{
|
||||
$this->attributes['credentials'] = json_encode($value);
|
||||
@@ -45,7 +49,7 @@ class FileDisk extends Model
|
||||
public function scopePaginateData($query, $limit)
|
||||
{
|
||||
if ($limit == 'all') {
|
||||
return collect(['data' => $query->get()]);
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
return $query->paginate($limit);
|
||||
@@ -65,7 +69,7 @@ class FileDisk extends Model
|
||||
}
|
||||
|
||||
if ($filters->get('orderByField') || $filters->get('orderBy')) {
|
||||
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'invoice_number';
|
||||
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'sequence_number';
|
||||
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'asc';
|
||||
$query->whereOrder($field, $orderBy);
|
||||
}
|
||||
@@ -139,6 +143,7 @@ class FileDisk extends Model
|
||||
'name' => $request->name,
|
||||
'driver' => $request->driver,
|
||||
'set_as_default' => $request->set_as_default,
|
||||
'company_id' => $request->header('company'),
|
||||
]);
|
||||
|
||||
return $disk;
|
||||
|
||||
@@ -6,11 +6,13 @@ use App;
|
||||
use Barryvdh\DomPDF\Facade as PDF;
|
||||
use Carbon\Carbon;
|
||||
use Crater\Mail\SendInvoiceMail;
|
||||
use Crater\Services\SerialNumberFormatter;
|
||||
use Crater\Traits\GeneratesPdfTrait;
|
||||
use Crater\Traits\HasCustomFieldsTrait;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||
use Vinkla\Hashids\Facades\Hashids;
|
||||
@@ -45,6 +47,7 @@ class Invoice extends Model implements HasMedia
|
||||
'sub_total' => 'integer',
|
||||
'discount' => 'float',
|
||||
'discount_val' => 'integer',
|
||||
'exchange_rate' => 'float'
|
||||
];
|
||||
|
||||
protected $guarded = [
|
||||
@@ -72,35 +75,6 @@ class Invoice extends Model implements HasMedia
|
||||
}
|
||||
}
|
||||
|
||||
public static function getNextInvoiceNumber($value)
|
||||
{
|
||||
// Get the last created order
|
||||
$lastOrder = Invoice::where('invoice_number', 'LIKE', $value.'-%')
|
||||
->orderBy('invoice_number', 'desc')
|
||||
->first();
|
||||
|
||||
// Get number length config
|
||||
$numberLength = CompanySetting::getSetting('invoice_number_length', request()->header('company'));
|
||||
$numberLengthText = "%0{$numberLength}d";
|
||||
|
||||
if (! $lastOrder) {
|
||||
// We get here if there is no order at all
|
||||
// If there is no number set it to 0, which will be 1 at the end.
|
||||
$number = 0;
|
||||
} else {
|
||||
$number = explode("-", $lastOrder->invoice_number);
|
||||
$number = $number[1];
|
||||
}
|
||||
// If we have ORD000001 in the database then we only want the number
|
||||
// So the substr returns this 000001
|
||||
|
||||
// Add the string in front and higher up the number.
|
||||
// the %06d part makes sure that there are always 6 numbers in the string.
|
||||
// so it adds the missing zero's when needed.
|
||||
|
||||
return sprintf($numberLengthText, intval($number) + 1);
|
||||
}
|
||||
|
||||
public function emailLogs()
|
||||
{
|
||||
return $this->morphMany('App\Models\EmailLog', 'mailable');
|
||||
@@ -131,14 +105,19 @@ class Invoice extends Model implements HasMedia
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function user()
|
||||
public function customer()
|
||||
{
|
||||
return $this->belongsTo('Crater\Models\User', 'user_id');
|
||||
return $this->belongsTo(Customer::class, 'customer_id');
|
||||
}
|
||||
|
||||
public function recurringInvoice()
|
||||
{
|
||||
return $this->belongsTo(RecurringInvoice::class);
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo('Crater\Models\User', 'creator_id');
|
||||
return $this->belongsTo(User::class, 'creator_id');
|
||||
}
|
||||
|
||||
public function getInvoicePdfUrlAttribute()
|
||||
@@ -146,6 +125,31 @@ class Invoice extends Model implements HasMedia
|
||||
return url('/invoices/pdf/'.$this->unique_hash);
|
||||
}
|
||||
|
||||
public function getAllowEditAttribute()
|
||||
{
|
||||
$retrospective_edit = CompanySetting::getSetting('retrospective_edits', $this->company_id);
|
||||
|
||||
$allowed = true;
|
||||
|
||||
$status = [
|
||||
self::STATUS_DRAFT,
|
||||
self::STATUS_SENT,
|
||||
self::STATUS_VIEWED,
|
||||
self::STATUS_OVERDUE,
|
||||
self::STATUS_COMPLETED,
|
||||
];
|
||||
|
||||
if ($retrospective_edit == 'disable_on_invoice_sent' && (in_array($this->status, $status)) && ($this->paid_status === Invoice::STATUS_PARTIALLY_PAID || $this->paid_status === Invoice::STATUS_PAID)) {
|
||||
$allowed = false;
|
||||
} elseif ($retrospective_edit == 'disable_on_invoice_partial_paid' && ($this->paid_status === Invoice::STATUS_PARTIALLY_PAID || $this->paid_status === Invoice::STATUS_PAID)) {
|
||||
$allowed = false;
|
||||
} elseif ($retrospective_edit == 'disable_on_invoice_paid' && $this->paid_status === Invoice::STATUS_PAID) {
|
||||
$allowed = false;
|
||||
}
|
||||
|
||||
return $allowed;
|
||||
}
|
||||
|
||||
public function getPreviousStatus()
|
||||
{
|
||||
if ($this->due_date < Carbon::now()) {
|
||||
@@ -159,35 +163,6 @@ class Invoice extends Model implements HasMedia
|
||||
}
|
||||
}
|
||||
|
||||
private function strposX($haystack, $needle, $number)
|
||||
{
|
||||
if ($number == '1') {
|
||||
return strpos($haystack, $needle);
|
||||
} elseif ($number > '1') {
|
||||
return strpos(
|
||||
$haystack,
|
||||
$needle,
|
||||
$this->strposX($haystack, $needle, $number - 1) + strlen($needle)
|
||||
);
|
||||
} else {
|
||||
return error_log('Error: Value for parameter $number is out of range');
|
||||
}
|
||||
}
|
||||
|
||||
public function getInvoiceNumAttribute()
|
||||
{
|
||||
$position = $this->strposX($this->invoice_number, "-", 1) + 1;
|
||||
|
||||
return substr($this->invoice_number, $position);
|
||||
}
|
||||
|
||||
public function getInvoicePrefixAttribute()
|
||||
{
|
||||
$prefix = explode("-", $this->invoice_number)[0];
|
||||
|
||||
return $prefix;
|
||||
}
|
||||
|
||||
public function getFormattedCreatedAtAttribute($value)
|
||||
{
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
|
||||
@@ -243,7 +218,7 @@ class Invoice extends Model implements HasMedia
|
||||
public function scopeWhereSearch($query, $search)
|
||||
{
|
||||
foreach (explode(' ', $search) as $term) {
|
||||
$query->whereHas('user', function ($query) use ($term) {
|
||||
$query->whereHas('customer', function ($query) use ($term) {
|
||||
$query->where('name', 'LIKE', '%'.$term.'%')
|
||||
->orWhere('contact_name', 'LIKE', '%'.$term.'%')
|
||||
->orWhere('company_name', 'LIKE', '%'.$term.'%');
|
||||
@@ -259,6 +234,7 @@ class Invoice extends Model implements HasMedia
|
||||
public function scopeApplyFilters($query, array $filters)
|
||||
{
|
||||
$filters = collect($filters);
|
||||
|
||||
if ($filters->get('search')) {
|
||||
$query->whereSearch($filters->get('search'));
|
||||
}
|
||||
@@ -300,8 +276,8 @@ class Invoice extends Model implements HasMedia
|
||||
}
|
||||
|
||||
if ($filters->get('orderByField') || $filters->get('orderBy')) {
|
||||
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'invoice_number';
|
||||
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'asc';
|
||||
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'sequence_number';
|
||||
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'desc';
|
||||
$query->whereOrder($field, $orderBy);
|
||||
}
|
||||
}
|
||||
@@ -311,20 +287,25 @@ class Invoice extends Model implements HasMedia
|
||||
$query->orWhere('id', $invoice_id);
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query, $company_id)
|
||||
public function scopeWhereCompany($query)
|
||||
{
|
||||
$query->where('invoices.company_id', $company_id);
|
||||
$query->where('invoices.company_id', request()->header('company'));
|
||||
}
|
||||
|
||||
public function scopeWhereCompanyId($query, $company)
|
||||
{
|
||||
$query->where('invoices.company_id', $company);
|
||||
}
|
||||
|
||||
public function scopeWhereCustomer($query, $customer_id)
|
||||
{
|
||||
$query->where('invoices.user_id', $customer_id);
|
||||
$query->where('invoices.customer_id', $customer_id);
|
||||
}
|
||||
|
||||
public function scopePaginateData($query, $limit)
|
||||
{
|
||||
if ($limit == 'all') {
|
||||
return collect(['data' => $query->get()]);
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
return $query->paginate($limit);
|
||||
@@ -332,28 +313,35 @@ class Invoice extends Model implements HasMedia
|
||||
|
||||
public static function createInvoice($request)
|
||||
{
|
||||
$data = $request->except('items', 'taxes');
|
||||
|
||||
$data['creator_id'] = Auth::id();
|
||||
$data['status'] = Invoice::STATUS_DRAFT;
|
||||
$data['company_id'] = $request->header('company');
|
||||
$data['paid_status'] = Invoice::STATUS_UNPAID;
|
||||
$data['tax_per_item'] = CompanySetting::getSetting('tax_per_item', $request->header('company')) ?? 'NO ';
|
||||
$data['discount_per_item'] = CompanySetting::getSetting('discount_per_item', $request->header('company')) ?? 'NO';
|
||||
$data['due_amount'] = $request->total;
|
||||
$data = $request->getInvoicePayload();
|
||||
|
||||
if ($request->has('invoiceSend')) {
|
||||
$data['status'] = Invoice::STATUS_SENT;
|
||||
}
|
||||
|
||||
$invoice = Invoice::create($data);
|
||||
|
||||
$serial = (new SerialNumberFormatter())
|
||||
->setModel($invoice)
|
||||
->setCompany($invoice->company_id)
|
||||
->setCustomer($invoice->customer_id)
|
||||
->setNextNumbers();
|
||||
|
||||
$invoice->sequence_number = $serial->nextSequenceNumber;
|
||||
$invoice->customer_sequence_number = $serial->nextCustomerSequenceNumber;
|
||||
$invoice->unique_hash = Hashids::connection(Invoice::class)->encode($invoice->id);
|
||||
$invoice->save();
|
||||
|
||||
self::createItems($invoice, $request);
|
||||
self::createItems($invoice, $request->items);
|
||||
|
||||
$company_currency = CompanySetting::getSetting('currency', $request->header('company'));
|
||||
|
||||
if ((string)$data['currency_id'] !== $company_currency) {
|
||||
ExchangeRateLog::addExchangeRateLog($invoice);
|
||||
}
|
||||
|
||||
if ($request->has('taxes') && (! empty($request->taxes))) {
|
||||
self::createTaxes($invoice, $request);
|
||||
self::createTaxes($invoice, $request->taxes);
|
||||
}
|
||||
|
||||
if ($request->customFields) {
|
||||
@@ -361,10 +349,12 @@ class Invoice extends Model implements HasMedia
|
||||
}
|
||||
|
||||
$invoice = Invoice::with([
|
||||
'items',
|
||||
'user',
|
||||
'taxes'
|
||||
])
|
||||
'items',
|
||||
'items.fields',
|
||||
'items.fields.customField',
|
||||
'customer',
|
||||
'taxes'
|
||||
])
|
||||
->find($invoice->id);
|
||||
|
||||
return $invoice;
|
||||
@@ -372,39 +362,60 @@ class Invoice extends Model implements HasMedia
|
||||
|
||||
public function updateInvoice($request)
|
||||
{
|
||||
$data = $request->except('items');
|
||||
$oldAmount = $this->total;
|
||||
$serial = (new SerialNumberFormatter())
|
||||
->setModel($this)
|
||||
->setCompany($this->company_id)
|
||||
->setCustomer($request->customer_id)
|
||||
->setModelObject($this->id)
|
||||
->setNextNumbers();
|
||||
|
||||
if ($oldAmount != $request->total) {
|
||||
$oldAmount = (int) round($request->total) - (int) $oldAmount;
|
||||
$data = $request->getInvoicePayload();
|
||||
$oldTotal = $this->total;
|
||||
|
||||
$total_paid_amount = $this->total - $this->due_amount;
|
||||
|
||||
if ($total_paid_amount > 0 && $this->customer_id !== $request->customer_id) {
|
||||
return 'customer_cannot_be_changed_after_payment_is_added';
|
||||
}
|
||||
|
||||
if ($request->total < $total_paid_amount) {
|
||||
return 'total_invoice_amount_must_be_more_than_paid_amount';
|
||||
}
|
||||
|
||||
if ($oldTotal != $request->total) {
|
||||
$oldTotal = (int) round($request->total) - (int) $oldTotal;
|
||||
} else {
|
||||
$oldAmount = 0;
|
||||
$oldTotal = 0;
|
||||
}
|
||||
|
||||
$data['due_amount'] = ($this->due_amount + $oldAmount);
|
||||
$data['due_amount'] = ($this->due_amount + $oldTotal);
|
||||
$data['customer_sequence_number'] = $serial->nextCustomerSequenceNumber;
|
||||
|
||||
if ($data['due_amount'] == 0 && $this->paid_status != Invoice::STATUS_PAID) {
|
||||
$data['status'] = Invoice::STATUS_COMPLETED;
|
||||
$data['paid_status'] = Invoice::STATUS_PAID;
|
||||
} elseif ($this->due_amount < 0 && $this->paid_status != Invoice::STATUS_UNPAID) {
|
||||
return response()->json([
|
||||
'error' => 'invalid_due_amount',
|
||||
]);
|
||||
} elseif ($data['due_amount'] != 0 && $this->paid_status == Invoice::STATUS_PAID) {
|
||||
$data['status'] = $this->getPreviousStatus();
|
||||
$data['paid_status'] = Invoice::STATUS_PARTIALLY_PAID;
|
||||
}
|
||||
$this->changeInvoiceStatus($data['due_amount']);
|
||||
|
||||
$this->update($data);
|
||||
|
||||
$company_currency = CompanySetting::getSetting('currency', $request->header('company'));
|
||||
|
||||
if ((string)$data['currency_id'] !== $company_currency) {
|
||||
ExchangeRateLog::addExchangeRateLog($this);
|
||||
}
|
||||
|
||||
$this->items->map(function ($item) {
|
||||
$fields = $item->fields()->get();
|
||||
|
||||
$fields->map(function ($field) {
|
||||
$field->delete();
|
||||
});
|
||||
});
|
||||
|
||||
$this->items()->delete();
|
||||
$this->taxes()->delete();
|
||||
|
||||
self::createItems($this, $request);
|
||||
self::createItems($this, $request->items);
|
||||
|
||||
if ($request->has('taxes') && (! empty($request->taxes))) {
|
||||
self::createTaxes($this, $request);
|
||||
self::createTaxes($this, $request->taxes);
|
||||
}
|
||||
|
||||
if ($request->customFields) {
|
||||
@@ -412,23 +423,42 @@ class Invoice extends Model implements HasMedia
|
||||
}
|
||||
|
||||
$invoice = Invoice::with([
|
||||
'items',
|
||||
'user',
|
||||
'taxes'
|
||||
])
|
||||
'items',
|
||||
'items.fields',
|
||||
'items.fields.customField',
|
||||
'customer',
|
||||
'taxes'
|
||||
])
|
||||
->find($this->id);
|
||||
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
public function send($data)
|
||||
public function sendInvoiceData($data)
|
||||
{
|
||||
$data['invoice'] = $this->toArray();
|
||||
$data['user'] = $this->user->toArray();
|
||||
$data['customer'] = $this->customer->toArray();
|
||||
$data['company'] = Company::find($this->company_id);
|
||||
$data['body'] = $this->getEmailBody($data['body']);
|
||||
$data['attach']['data'] = ($this->getEmailAttachmentSetting()) ? $this->getPDFData() : null;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function preview($data)
|
||||
{
|
||||
$data = $this->sendInvoiceData($data);
|
||||
|
||||
return [
|
||||
'type' => 'preview',
|
||||
'view' => new SendInvoiceMail($data)
|
||||
];
|
||||
}
|
||||
|
||||
public function send($data)
|
||||
{
|
||||
$data = $this->sendInvoiceData($data);
|
||||
|
||||
if ($this->status == Invoice::STATUS_DRAFT) {
|
||||
$this->status = Invoice::STATUS_SENT;
|
||||
$this->sent = true;
|
||||
@@ -439,37 +469,63 @@ class Invoice extends Model implements HasMedia
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'type' => 'send',
|
||||
];
|
||||
}
|
||||
|
||||
public static function createItems($invoice, $request)
|
||||
public static function createItems($invoice, $invoiceItems)
|
||||
{
|
||||
$invoiceItems = $request->items;
|
||||
$exchange_rate = $invoice->exchange_rate;
|
||||
|
||||
foreach ($invoiceItems as $invoiceItem) {
|
||||
$invoiceItem['company_id'] = $request->header('company');
|
||||
$invoiceItem['company_id'] = $invoice->company_id;
|
||||
$invoiceItem['exchange_rate'] = $exchange_rate;
|
||||
$invoiceItem['base_price'] = $invoiceItem['price'] * $exchange_rate;
|
||||
$invoiceItem['base_discount_val'] = $invoiceItem['discount_val'] * $exchange_rate;
|
||||
$invoiceItem['base_tax'] = $invoiceItem['tax'] * $exchange_rate;
|
||||
$invoiceItem['base_total'] = $invoiceItem['total'] * $exchange_rate;
|
||||
|
||||
if (array_key_exists('recurring_invoice_id', $invoiceItem)) {
|
||||
unset($invoiceItem['recurring_invoice_id']);
|
||||
}
|
||||
|
||||
$item = $invoice->items()->create($invoiceItem);
|
||||
|
||||
if (array_key_exists('taxes', $invoiceItem) && $invoiceItem['taxes']) {
|
||||
foreach ($invoiceItem['taxes'] as $tax) {
|
||||
$tax['company_id'] = $request->header('company');
|
||||
$tax['company_id'] = $invoice->company_id;
|
||||
if (gettype($tax['amount']) !== "NULL") {
|
||||
if (array_key_exists('recurring_invoice_id', $invoiceItem)) {
|
||||
unset($invoiceItem['recurring_invoice_id']);
|
||||
}
|
||||
|
||||
$item->taxes()->create($tax);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('custom_fields', $invoiceItem) && $invoiceItem['custom_fields']) {
|
||||
$item->addCustomFields($invoiceItem['custom_fields']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function createTaxes($invoice, $request)
|
||||
public static function createTaxes($invoice, $taxes)
|
||||
{
|
||||
if ($request->has('taxes') && (! empty($request->taxes))) {
|
||||
foreach ($request->taxes as $tax) {
|
||||
$tax['company_id'] = $request->header('company');
|
||||
$exchange_rate = $invoice->exchange_rate;
|
||||
|
||||
if (gettype($tax['amount']) !== "NULL") {
|
||||
$invoice->taxes()->create($tax);
|
||||
foreach ($taxes as $tax) {
|
||||
$tax['company_id'] = $invoice->company_id;
|
||||
$tax['exchnage_rate'] = $invoice->exchange_rate;
|
||||
$tax['base_amount'] = $tax['amount'] * $exchange_rate;
|
||||
$tax['currency_id'] = $invoice->currency_id;
|
||||
|
||||
if (gettype($tax['amount']) !== "NULL") {
|
||||
if (array_key_exists('recurring_invoice_id', $tax)) {
|
||||
unset($tax['recurring_invoice_id']);
|
||||
}
|
||||
|
||||
$invoice->taxes()->create($tax);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -498,6 +554,7 @@ class Invoice extends Model implements HasMedia
|
||||
|
||||
$company = Company::find($this->company_id);
|
||||
$locale = CompanySetting::getSetting('language', $company->id);
|
||||
$customFields = CustomField::where('model_type', 'Item')->get();
|
||||
|
||||
App::setLocale($locale);
|
||||
|
||||
@@ -505,6 +562,7 @@ class Invoice extends Model implements HasMedia
|
||||
|
||||
view()->share([
|
||||
'invoice' => $this,
|
||||
'customFields' => $customFields,
|
||||
'company_address' => $this->getCompanyAddress(),
|
||||
'shipping_address' => $this->getCustomerShippingAddress(),
|
||||
'billing_address' => $this->getCustomerBillingAddress(),
|
||||
@@ -540,7 +598,7 @@ class Invoice extends Model implements HasMedia
|
||||
|
||||
public function getCustomerShippingAddress()
|
||||
{
|
||||
if ($this->user && (! $this->user->shippingAddress()->exists())) {
|
||||
if ($this->customer && (! $this->customer->shippingAddress()->exists())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -551,7 +609,7 @@ class Invoice extends Model implements HasMedia
|
||||
|
||||
public function getCustomerBillingAddress()
|
||||
{
|
||||
if ($this->user && (! $this->user->billingAddress()->exists())) {
|
||||
if ($this->customer && (! $this->customer->billingAddress()->exists())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -584,4 +642,54 @@ class Invoice extends Model implements HasMedia
|
||||
'{INVOICE_LINK}' => url('/customer/invoices/pdf/'.$this->unique_hash),
|
||||
];
|
||||
}
|
||||
|
||||
public static function invoiceTemplates()
|
||||
{
|
||||
$templates = Storage::disk('views')->files('/app/pdf/invoice');
|
||||
$invoiceTemplates = [];
|
||||
|
||||
foreach ($templates as $key => $template) {
|
||||
$templateName = Str::before(basename($template), '.blade.php');
|
||||
$invoiceTemplates[$key]['name'] = $templateName;
|
||||
$invoiceTemplates[$key]['path'] = vite_asset('img/PDF/'.$templateName.'.png');
|
||||
}
|
||||
|
||||
return $invoiceTemplates;
|
||||
}
|
||||
|
||||
public function addInvoicePayment($amount)
|
||||
{
|
||||
$this->due_amount += $amount;
|
||||
|
||||
$this->changeInvoiceStatus($this->due_amount);
|
||||
}
|
||||
|
||||
public function subtractInvoicePayment($amount)
|
||||
{
|
||||
$this->due_amount -= $amount;
|
||||
|
||||
$this->changeInvoiceStatus($this->due_amount);
|
||||
}
|
||||
|
||||
public function changeInvoiceStatus($amount)
|
||||
{
|
||||
if ($amount < 0) {
|
||||
return [
|
||||
'error' => 'invalid_amount',
|
||||
];
|
||||
}
|
||||
|
||||
if ($amount == 0) {
|
||||
$this->status = Invoice::STATUS_COMPLETED;
|
||||
$this->paid_status = Invoice::STATUS_PAID;
|
||||
} elseif ($amount == $this->total) {
|
||||
$this->status = $this->getPreviousStatus();
|
||||
$this->paid_status = Invoice::STATUS_UNPAID;
|
||||
} else {
|
||||
$this->status = $this->getPreviousStatus();
|
||||
$this->paid_status = Invoice::STATUS_PARTIALLY_PAID;
|
||||
}
|
||||
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Crater\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Crater\Traits\HasCustomFieldsTrait;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -10,21 +11,10 @@ use Illuminate\Support\Facades\DB;
|
||||
class InvoiceItem extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use HasCustomFieldsTrait;
|
||||
|
||||
protected $fillable = [
|
||||
'invoice_id',
|
||||
'name',
|
||||
'item_id',
|
||||
'description',
|
||||
'company_id',
|
||||
'quantity',
|
||||
'price',
|
||||
'discount_type',
|
||||
'discount_val',
|
||||
'total',
|
||||
'tax',
|
||||
'discount',
|
||||
'unit_name',
|
||||
protected $guarded = [
|
||||
'id'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@@ -51,6 +41,11 @@ class InvoiceItem extends Model
|
||||
return $this->hasMany(Tax::class);
|
||||
}
|
||||
|
||||
public function recurringInvoice()
|
||||
{
|
||||
return $this->belongsTo(RecurringInvoice::class);
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query, $company_id)
|
||||
{
|
||||
$query->where('company_id', $company_id);
|
||||
|
||||
@@ -23,7 +23,12 @@ class Item extends Model
|
||||
|
||||
public function unit()
|
||||
{
|
||||
return $this->belongsTo(Unit::class);
|
||||
return $this->belongsTo(Unit::class, 'unit_id');
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function creator()
|
||||
@@ -31,6 +36,11 @@ class Item extends Model
|
||||
return $this->belongsTo('Crater\Models\User', 'creator_id');
|
||||
}
|
||||
|
||||
public function currency()
|
||||
{
|
||||
return $this->belongsTo(Currency::class);
|
||||
}
|
||||
|
||||
public function scopeWhereSearch($query, $search)
|
||||
{
|
||||
return $query->where('items.name', 'LIKE', '%'.$search.'%');
|
||||
@@ -86,7 +96,7 @@ class Item extends Model
|
||||
public function scopePaginateData($query, $limit)
|
||||
{
|
||||
if ($limit == 'all') {
|
||||
return collect(['data' => $query->get()]);
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
return $query->paginate($limit);
|
||||
@@ -94,7 +104,7 @@ class Item extends Model
|
||||
|
||||
public function getFormattedCreatedAtAttribute($value)
|
||||
{
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', request()->header('company'));
|
||||
|
||||
return Carbon::parse($this->created_at)->format($dateFormat);
|
||||
}
|
||||
@@ -106,9 +116,9 @@ class Item extends Model
|
||||
->where('estimate_item_id', null);
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query, $company_id)
|
||||
public function scopeWhereCompany($query)
|
||||
{
|
||||
$query->where('items.company_id', $company_id);
|
||||
$query->where('items.company_id', request()->header('company'));
|
||||
}
|
||||
|
||||
public function invoiceItems()
|
||||
@@ -126,10 +136,14 @@ class Item extends Model
|
||||
$data = $request->validated();
|
||||
$data['company_id'] = $request->header('company');
|
||||
$data['creator_id'] = Auth::id();
|
||||
$company_currency = CompanySetting::getSetting('currency', $request->header('company'));
|
||||
$data['currency_id'] = $company_currency;
|
||||
$item = self::create($data);
|
||||
|
||||
if ($request->has('taxes')) {
|
||||
foreach ($request->taxes as $tax) {
|
||||
$item->tax_per_item = true;
|
||||
$item->save();
|
||||
$tax['company_id'] = $request->header('company');
|
||||
$item->taxes()->create($tax);
|
||||
}
|
||||
@@ -148,6 +162,8 @@ class Item extends Model
|
||||
|
||||
if ($request->has('taxes')) {
|
||||
foreach ($request->taxes as $tax) {
|
||||
$this->tax_per_item = true;
|
||||
$this->save();
|
||||
$tax['company_id'] = $request->header('company');
|
||||
$this->taxes()->create($tax);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@ class Note extends Model
|
||||
|
||||
protected $guarded = ['id'];
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function scopeApplyFilters($query, array $filters)
|
||||
{
|
||||
$filters = collect($filters);
|
||||
@@ -33,4 +38,9 @@ class Note extends Model
|
||||
{
|
||||
return $query->where('type', $type);
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query)
|
||||
{
|
||||
$query->where('notes.company_id', request()->header('company'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ use Barryvdh\DomPDF\Facade as PDF;
|
||||
use Carbon\Carbon;
|
||||
use Crater\Jobs\GeneratePaymentPdfJob;
|
||||
use Crater\Mail\SendPaymentMail;
|
||||
use Crater\Services\SerialNumberFormatter;
|
||||
use Crater\Traits\GeneratesPdfTrait;
|
||||
use Crater\Traits\HasCustomFieldsTrait;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||
use Vinkla\Hashids\Facades\Hashids;
|
||||
@@ -38,6 +38,11 @@ class Payment extends Model implements HasMedia
|
||||
'paymentPdfUrl',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'notes' => 'string',
|
||||
'exchange_rate' => 'float'
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function ($payment) {
|
||||
@@ -56,13 +61,6 @@ class Payment extends Model implements HasMedia
|
||||
}
|
||||
}
|
||||
|
||||
public function getPaymentPrefixAttribute()
|
||||
{
|
||||
$prefix = explode("-", $this->payment_number)[0];
|
||||
|
||||
return $prefix;
|
||||
}
|
||||
|
||||
public function getFormattedCreatedAtAttribute($value)
|
||||
{
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
|
||||
@@ -82,18 +80,16 @@ class Payment extends Model implements HasMedia
|
||||
return url('/payments/pdf/'.$this->unique_hash);
|
||||
}
|
||||
|
||||
public function getPaymentNumAttribute()
|
||||
{
|
||||
$position = $this->strposX($this->payment_number, "-", 1) + 1;
|
||||
|
||||
return substr($this->payment_number, $position);
|
||||
}
|
||||
|
||||
public function emailLogs()
|
||||
{
|
||||
return $this->morphMany('App\Models\EmailLog', 'mailable');
|
||||
}
|
||||
|
||||
public function customer()
|
||||
{
|
||||
return $this->belongsTo(Customer::class, 'customer_id');
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
@@ -104,29 +100,36 @@ class Payment extends Model implements HasMedia
|
||||
return $this->belongsTo(Invoice::class);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo('Crater\Models\User', 'creator_id');
|
||||
}
|
||||
|
||||
public function currency()
|
||||
{
|
||||
return $this->belongsTo(Currency::class);
|
||||
}
|
||||
|
||||
public function paymentMethod()
|
||||
{
|
||||
return $this->belongsTo(PaymentMethod::class);
|
||||
}
|
||||
|
||||
public function send($data)
|
||||
public function sendPaymentData($data)
|
||||
{
|
||||
$data['payment'] = $this->toArray();
|
||||
$data['user'] = $this->user->toArray();
|
||||
$data['user'] = $this->customer->toArray();
|
||||
$data['company'] = Company::find($this->company_id);
|
||||
$data['body'] = $this->getEmailBody($data['body']);
|
||||
$data['attach']['data'] = ($this->getEmailAttachmentSetting()) ? $this->getPDFData() : null;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function send($data)
|
||||
{
|
||||
$data = $this->sendPaymentData($data);
|
||||
|
||||
\Mail::to($data['to'])->send(new SendPaymentMail($data));
|
||||
|
||||
return [
|
||||
@@ -136,36 +139,32 @@ class Payment extends Model implements HasMedia
|
||||
|
||||
public static function createPayment($request)
|
||||
{
|
||||
$data = $request->validated();
|
||||
$data = $request->getPaymentPayload();
|
||||
|
||||
$data['company_id'] = $request->header('company');
|
||||
$data['creator_id'] = Auth::id();
|
||||
|
||||
if ($request->has('invoice_id') && $request->invoice_id != null) {
|
||||
if ($request->invoice_id) {
|
||||
$invoice = Invoice::find($request->invoice_id);
|
||||
if ($invoice && $invoice->due_amount == $request->amount) {
|
||||
$invoice->status = Invoice::STATUS_COMPLETED;
|
||||
$invoice->paid_status = Invoice::STATUS_PAID;
|
||||
$invoice->due_amount = 0;
|
||||
} elseif ($invoice && $invoice->due_amount != $request->amount) {
|
||||
$invoice->due_amount = (int)$invoice->due_amount - (int)$request->amount;
|
||||
if ($invoice->due_amount < 0) {
|
||||
return [
|
||||
'error' => 'invalid_amount',
|
||||
];
|
||||
}
|
||||
$invoice->paid_status = Invoice::STATUS_PARTIALLY_PAID;
|
||||
}
|
||||
$invoice->save();
|
||||
$invoice->subtractInvoicePayment($request->amount);
|
||||
}
|
||||
|
||||
|
||||
$payment = Payment::create($data);
|
||||
|
||||
$payment->unique_hash = Hashids::connection(Payment::class)->encode($payment->id);
|
||||
|
||||
$serial = (new SerialNumberFormatter())
|
||||
->setModel($payment)
|
||||
->setCompany($payment->company_id)
|
||||
->setCustomer($payment->customer_id)
|
||||
->setNextNumbers();
|
||||
|
||||
$payment->sequence_number = $serial->nextSequenceNumber;
|
||||
$payment->customer_sequence_number = $serial->nextCustomerSequenceNumber;
|
||||
$payment->save();
|
||||
|
||||
$company_currency = CompanySetting::getSetting('currency', $request->header('company'));
|
||||
|
||||
if ((string)$payment['currency_id'] !== $company_currency) {
|
||||
ExchangeRateLog::addExchangeRateLog($payment);
|
||||
}
|
||||
|
||||
$customFields = $request->customFields;
|
||||
|
||||
if ($customFields) {
|
||||
@@ -173,9 +172,10 @@ class Payment extends Model implements HasMedia
|
||||
}
|
||||
|
||||
$payment = Payment::with([
|
||||
'user',
|
||||
'customer',
|
||||
'invoice',
|
||||
'paymentMethod',
|
||||
'fields'
|
||||
])->find($payment->id);
|
||||
|
||||
return $payment;
|
||||
@@ -183,34 +183,40 @@ class Payment extends Model implements HasMedia
|
||||
|
||||
public function updatePayment($request)
|
||||
{
|
||||
$oldAmount = $this->amount;
|
||||
|
||||
if ($request->has('invoice_id') && $request->invoice_id && ($oldAmount != $request->amount)) {
|
||||
$amount = (int)$request->amount - (int)$oldAmount;
|
||||
$invoice = Invoice::find($request->invoice_id);
|
||||
$invoice->due_amount = (int)$invoice->due_amount - (int)$amount;
|
||||
|
||||
if ($invoice->due_amount < 0) {
|
||||
return [
|
||||
'error' => 'invalid_amount',
|
||||
];
|
||||
}
|
||||
|
||||
if ($invoice->due_amount == 0) {
|
||||
$invoice->status = Invoice::STATUS_COMPLETED;
|
||||
$invoice->paid_status = Invoice::STATUS_PAID;
|
||||
} else {
|
||||
$invoice->status = $invoice->getPreviousStatus();
|
||||
$invoice->paid_status = Invoice::STATUS_PARTIALLY_PAID;
|
||||
}
|
||||
|
||||
$invoice->save();
|
||||
}
|
||||
|
||||
$data = $request->all();
|
||||
|
||||
if ($request->invoice_id && (! $this->invoice_id || $this->invoice_id !== $request->invoice_id)) {
|
||||
$invoice = Invoice::find($request->invoice_id);
|
||||
$invoice->subtractInvoicePayment($request->amount);
|
||||
}
|
||||
|
||||
if ($this->invoice_id && (! $request->invoice_id || $this->invoice_id !== $request->invoice_id)) {
|
||||
$invoice = Invoice::find($this->invoice_id);
|
||||
$invoice->addInvoicePayment($this->amount);
|
||||
}
|
||||
|
||||
if ($this->invoice_id === $request->invoice_id && $request->amount !== $this->amount) {
|
||||
$invoice = Invoice::find($this->invoice_id);
|
||||
$invoice->addInvoicePayment($this->amount);
|
||||
$invoice->subtractInvoicePayment($request->amount);
|
||||
}
|
||||
|
||||
$serial = (new SerialNumberFormatter())
|
||||
->setModel($this)
|
||||
->setCompany($this->company_id)
|
||||
->setCustomer($request->customer_id)
|
||||
->setModelObject($this->id)
|
||||
->setNextNumbers();
|
||||
|
||||
$data['customer_sequence_number'] = $serial->nextCustomerSequenceNumber;
|
||||
$this->update($data);
|
||||
|
||||
$company_currency = CompanySetting::getSetting('currency', $request->header('company'));
|
||||
|
||||
if ((string)$data['currency_id'] !== $company_currency) {
|
||||
ExchangeRateLog::addExchangeRateLog($this);
|
||||
}
|
||||
|
||||
$customFields = $request->customFields;
|
||||
|
||||
if ($customFields) {
|
||||
@@ -218,7 +224,7 @@ class Payment extends Model implements HasMedia
|
||||
}
|
||||
|
||||
$payment = Payment::with([
|
||||
'user',
|
||||
'customer',
|
||||
'invoice',
|
||||
'paymentMethod',
|
||||
])
|
||||
@@ -252,54 +258,10 @@ class Payment extends Model implements HasMedia
|
||||
return true;
|
||||
}
|
||||
|
||||
private function strposX($haystack, $needle, $number)
|
||||
{
|
||||
if ($number == '1') {
|
||||
return strpos($haystack, $needle);
|
||||
} elseif ($number > '1') {
|
||||
return strpos(
|
||||
$haystack,
|
||||
$needle,
|
||||
$this->strposX($haystack, $needle, $number - 1) + strlen($needle)
|
||||
);
|
||||
} else {
|
||||
return error_log('Error: Value for parameter $number is out of range');
|
||||
}
|
||||
}
|
||||
|
||||
public static function getNextPaymentNumber($value)
|
||||
{
|
||||
// Get the last created order
|
||||
$payment = Payment::where('payment_number', 'LIKE', $value.'-%')
|
||||
->orderBy('payment_number', 'desc')
|
||||
->first();
|
||||
|
||||
// Get number length config
|
||||
$numberLength = CompanySetting::getSetting('payment_number_length', request()->header('company'));
|
||||
$numberLengthText = "%0{$numberLength}d";
|
||||
|
||||
if (! $payment) {
|
||||
// We get here if there is no order at all
|
||||
// If there is no number set it to 0, which will be 1 at the end.
|
||||
$number = 0;
|
||||
} else {
|
||||
$number = explode("-", $payment->payment_number);
|
||||
$number = $number[1];
|
||||
}
|
||||
// If we have ORD000001 in the database then we only want the number
|
||||
// So the substr returns this 000001
|
||||
|
||||
// Add the string in front and higher up the number.
|
||||
// the %05d part makes sure that there are always 6 numbers in the string.
|
||||
// so it adds the missing zero's when needed.
|
||||
|
||||
return sprintf($numberLengthText, intval($number) + 1);
|
||||
}
|
||||
|
||||
public function scopeWhereSearch($query, $search)
|
||||
{
|
||||
foreach (explode(' ', $search) as $term) {
|
||||
$query->whereHas('user', function ($query) use ($term) {
|
||||
$query->whereHas('customer', function ($query) use ($term) {
|
||||
$query->where('name', 'LIKE', '%'.$term.'%')
|
||||
->orWhere('contact_name', 'LIKE', '%'.$term.'%')
|
||||
->orWhere('company_name', 'LIKE', '%'.$term.'%');
|
||||
@@ -320,7 +282,7 @@ class Payment extends Model implements HasMedia
|
||||
public function scopePaginateData($query, $limit)
|
||||
{
|
||||
if ($limit == 'all') {
|
||||
return collect(['data' => $query->get()]);
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
return $query->paginate($limit);
|
||||
@@ -351,8 +313,8 @@ class Payment extends Model implements HasMedia
|
||||
}
|
||||
|
||||
if ($filters->get('orderByField') || $filters->get('orderBy')) {
|
||||
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'payment_number';
|
||||
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'asc';
|
||||
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'sequence_number';
|
||||
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'desc';
|
||||
$query->whereOrder($field, $orderBy);
|
||||
}
|
||||
}
|
||||
@@ -367,14 +329,14 @@ class Payment extends Model implements HasMedia
|
||||
$query->orWhere('id', $payment_id);
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query, $company_id)
|
||||
public function scopeWhereCompany($query)
|
||||
{
|
||||
$query->where('payments.company_id', $company_id);
|
||||
$query->where('payments.company_id', request()->header('company'));
|
||||
}
|
||||
|
||||
public function scopeWhereCustomer($query, $customer_id)
|
||||
{
|
||||
$query->where('payments.user_id', $customer_id);
|
||||
$query->where('payments.customer_id', $customer_id);
|
||||
}
|
||||
|
||||
public function getPDFData()
|
||||
@@ -410,7 +372,7 @@ class Payment extends Model implements HasMedia
|
||||
|
||||
public function getCustomerBillingAddress()
|
||||
{
|
||||
if ($this->user && (! $this->user->billingAddress()->exists())) {
|
||||
if ($this->customer && (! $this->customer->billingAddress()->exists())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ class PaymentMethod extends Model
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query, $company_id)
|
||||
public function scopeWhereCompany($query)
|
||||
{
|
||||
$query->where('company_id', $company_id);
|
||||
$query->where('company_id', request()->header('company'));
|
||||
}
|
||||
|
||||
public function scopeWherePaymentMethod($query, $payment_id)
|
||||
@@ -56,7 +56,7 @@ class PaymentMethod extends Model
|
||||
public function scopePaginateData($query, $limit)
|
||||
{
|
||||
if ($limit == 'all') {
|
||||
return collect(['data' => $query->get()]);
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
return $query->paginate($limit);
|
||||
|
||||
413
app/Models/RecurringInvoice.php
Normal file
413
app/Models/RecurringInvoice.php
Normal file
@@ -0,0 +1,413 @@
|
||||
<?php
|
||||
|
||||
namespace Crater\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Crater\Http\Requests\RecurringInvoiceRequest;
|
||||
use Crater\Services\SerialNumberFormatter;
|
||||
use Crater\Traits\HasCustomFieldsTrait;
|
||||
use Cron;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Vinkla\Hashids\Facades\Hashids;
|
||||
|
||||
class RecurringInvoice extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use HasCustomFieldsTrait;
|
||||
|
||||
protected $guarded = [
|
||||
'id',
|
||||
];
|
||||
|
||||
public const NONE = 'NONE';
|
||||
public const COUNT = 'COUNT';
|
||||
public const DATE = 'DATE';
|
||||
|
||||
public const COMPLETED = 'COMPLETED';
|
||||
public const ON_HOLD = 'ON_HOLD';
|
||||
public const ACTIVE = 'ACTIVE';
|
||||
|
||||
protected $appends = [
|
||||
'formattedCreatedAt',
|
||||
'formattedStartsAt',
|
||||
'formattedNextInvoiceAt',
|
||||
'formattedLimitDate'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'exchange_rate' => 'float',
|
||||
'send_automatically' => 'boolean'
|
||||
];
|
||||
|
||||
public function getFormattedStartsAtAttribute()
|
||||
{
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
|
||||
|
||||
return Carbon::parse($this->starts_at)->format($dateFormat);
|
||||
}
|
||||
|
||||
public function getFormattedNextInvoiceAtAttribute()
|
||||
{
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
|
||||
|
||||
return Carbon::parse($this->next_invoice_at)->format($dateFormat);
|
||||
}
|
||||
|
||||
public function getFormattedLimitDateAttribute()
|
||||
{
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
|
||||
|
||||
return Carbon::parse($this->limit_date)->format($dateFormat);
|
||||
}
|
||||
|
||||
public function getFormattedCreatedAtAttribute()
|
||||
{
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
|
||||
|
||||
return Carbon::parse($this->created_at)->format($dateFormat);
|
||||
}
|
||||
|
||||
public function invoices()
|
||||
{
|
||||
return $this->hasMany(Invoice::class);
|
||||
}
|
||||
|
||||
public function taxes()
|
||||
{
|
||||
return $this->hasMany(Tax::class);
|
||||
}
|
||||
|
||||
public function items()
|
||||
{
|
||||
return $this->hasMany(InvoiceItem::class);
|
||||
}
|
||||
|
||||
public function customer()
|
||||
{
|
||||
return $this->belongsTo(Customer::class);
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'creator_id');
|
||||
}
|
||||
|
||||
public function currency()
|
||||
{
|
||||
return $this->belongsTo(Currency::class);
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query)
|
||||
{
|
||||
$query->where('recurring_invoices.company_id', request()->header('company'));
|
||||
}
|
||||
|
||||
public function scopePaginateData($query, $limit)
|
||||
{
|
||||
if ($limit == 'all') {
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
return $query->paginate($limit);
|
||||
}
|
||||
|
||||
public function scopeWhereOrder($query, $orderByField, $orderBy)
|
||||
{
|
||||
$query->orderBy($orderByField, $orderBy);
|
||||
}
|
||||
|
||||
public function scopeWhereStatus($query, $status)
|
||||
{
|
||||
return $query->where('recurring_invoices.status', $status);
|
||||
}
|
||||
|
||||
public function scopeWhereCustomer($query, $customer_id)
|
||||
{
|
||||
$query->where('customer_id', $customer_id);
|
||||
}
|
||||
|
||||
public function scopeRecurringInvoicesStartBetween($query, $start, $end)
|
||||
{
|
||||
return $query->whereBetween(
|
||||
'starts_at',
|
||||
[$start->format('Y-m-d'), $end->format('Y-m-d')]
|
||||
);
|
||||
}
|
||||
|
||||
public function scopeWhereSearch($query, $search)
|
||||
{
|
||||
foreach (explode(' ', $search) as $term) {
|
||||
$query->whereHas('customer', function ($query) use ($term) {
|
||||
$query->where('name', 'LIKE', '%'.$term.'%')
|
||||
->orWhere('contact_name', 'LIKE', '%'.$term.'%')
|
||||
->orWhere('company_name', 'LIKE', '%'.$term.'%');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function scopeApplyFilters($query, array $filters)
|
||||
{
|
||||
$filters = collect($filters);
|
||||
|
||||
if ($filters->get('status') && $filters->get('status') !== 'ALL') {
|
||||
$query->whereStatus($filters->get('status'));
|
||||
}
|
||||
|
||||
if ($filters->get('search')) {
|
||||
$query->whereSearch($filters->get('search'));
|
||||
}
|
||||
|
||||
if ($filters->get('from_date') && $filters->get('to_date')) {
|
||||
$start = Carbon::createFromFormat('Y-m-d', $filters->get('from_date'));
|
||||
$end = Carbon::createFromFormat('Y-m-d', $filters->get('to_date'));
|
||||
$query->recurringInvoicesStartBetween($start, $end);
|
||||
}
|
||||
|
||||
if ($filters->get('customer_id')) {
|
||||
$query->whereCustomer($filters->get('customer_id'));
|
||||
}
|
||||
|
||||
if ($filters->get('orderByField') || $filters->get('orderBy')) {
|
||||
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'created_at';
|
||||
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'asc';
|
||||
$query->whereOrder($field, $orderBy);
|
||||
}
|
||||
}
|
||||
|
||||
public static function createFromRequest(RecurringInvoiceRequest $request)
|
||||
{
|
||||
$recurringInvoice = self::create($request->getRecurringInvoicePayload());
|
||||
|
||||
$company_currency = CompanySetting::getSetting('currency', $request->header('company'));
|
||||
|
||||
if ((string)$recurringInvoice['currency_id'] !== $company_currency) {
|
||||
ExchangeRateLog::addExchangeRateLog($recurringInvoice);
|
||||
}
|
||||
|
||||
self::createItems($recurringInvoice, $request->items);
|
||||
|
||||
if ($request->has('taxes') && (! empty($request->taxes))) {
|
||||
self::createTaxes($recurringInvoice, $request->taxes);
|
||||
}
|
||||
|
||||
if ($request->customFields) {
|
||||
$recurringInvoice->addCustomFields($request->customFields);
|
||||
}
|
||||
|
||||
return $recurringInvoice;
|
||||
}
|
||||
|
||||
public function updateFromRequest(RecurringInvoiceRequest $request)
|
||||
{
|
||||
$data = $request->getRecurringInvoicePayload();
|
||||
|
||||
$this->update($data);
|
||||
|
||||
$company_currency = CompanySetting::getSetting('currency', $request->header('company'));
|
||||
|
||||
if ((string)$data['currency_id'] !== $company_currency) {
|
||||
ExchangeRateLog::addExchangeRateLog($this);
|
||||
}
|
||||
|
||||
$this->items()->delete();
|
||||
self::createItems($this, $request->items);
|
||||
|
||||
$this->taxes()->delete();
|
||||
if ($request->has('taxes') && (! empty($request->taxes))) {
|
||||
self::createTaxes($this, $request->taxes);
|
||||
}
|
||||
|
||||
if ($request->customFields) {
|
||||
$this->updateCustomFields($request->customFields);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public static function createItems($recurringInvoice, $invoiceItems)
|
||||
{
|
||||
foreach ($invoiceItems as $invoiceItem) {
|
||||
$invoiceItem['company_id'] = $recurringInvoice->company_id;
|
||||
$item = $recurringInvoice->items()->create($invoiceItem);
|
||||
if (array_key_exists('taxes', $invoiceItem) && $invoiceItem['taxes']) {
|
||||
foreach ($invoiceItem['taxes'] as $tax) {
|
||||
$tax['company_id'] = $recurringInvoice->company_id;
|
||||
if (gettype($tax['amount']) !== "NULL") {
|
||||
$item->taxes()->create($tax);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function createTaxes($recurringInvoice, $taxes)
|
||||
{
|
||||
foreach ($taxes as $tax) {
|
||||
$tax['company_id'] = $recurringInvoice->company_id;
|
||||
|
||||
if (gettype($tax['amount']) !== "NULL") {
|
||||
$recurringInvoice->taxes()->create($tax);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function generateInvoice()
|
||||
{
|
||||
if ($this->limit_by == 'DATE') {
|
||||
$startDate = Carbon::today()->format('Y-m-d');
|
||||
|
||||
$endDate = $this->limit_date;
|
||||
|
||||
if ($endDate >= $startDate) {
|
||||
$this->createInvoice();
|
||||
|
||||
$this->updateNextInvoiceDate();
|
||||
} else {
|
||||
$this->markStatusAsCompleted();
|
||||
}
|
||||
} elseif ($this->limit_by == 'COUNT') {
|
||||
$invoiceCount = Invoice::where('recurring_invoice_id', $this->id)->count();
|
||||
|
||||
if ($invoiceCount < $this->limit_count) {
|
||||
$this->createInvoice();
|
||||
|
||||
$this->updateNextInvoiceDate();
|
||||
} else {
|
||||
$this->markStatusAsCompleted();
|
||||
}
|
||||
} else {
|
||||
$this->createInvoice();
|
||||
|
||||
$this->updateNextInvoiceDate();
|
||||
}
|
||||
}
|
||||
|
||||
public function createInvoice()
|
||||
{
|
||||
//get invoice_number
|
||||
$serial = (new SerialNumberFormatter())
|
||||
->setModel(new Invoice())
|
||||
->setCompany($this->company_id)
|
||||
->setCustomer($this->customer_id)
|
||||
->setNextNumbers();
|
||||
|
||||
$newInvoice['creator_id'] = $this->creator_id;
|
||||
$newInvoice['invoice_date'] = Carbon::today()->format('Y-m-d');
|
||||
$newInvoice['due_date'] = Carbon::today()->addDays(7)->format('Y-m-d');
|
||||
$newInvoice['status'] = Invoice::STATUS_DRAFT;
|
||||
$newInvoice['company_id'] = $this->company_id;
|
||||
$newInvoice['paid_status'] = Invoice::STATUS_UNPAID;
|
||||
$newInvoice['sub_total'] = $this->sub_total;
|
||||
$newInvoice['tax_per_item'] = $this->tax_per_item;
|
||||
$newInvoice['discount_per_item'] = $this->discount_per_item;
|
||||
$newInvoice['tax'] = $this->tax;
|
||||
$newInvoice['total'] = $this->total;
|
||||
$newInvoice['customer_id'] = $this->customer_id;
|
||||
$newInvoice['currency_id'] = Customer::find($this->customer_id)->currency_id;
|
||||
$newInvoice['template_name'] = $this->template_name;
|
||||
$newInvoice['due_amount'] = $this->total;
|
||||
$newInvoice['recurring_invoice_id'] = $this->id;
|
||||
$newInvoice['discount_val'] = $this->discount_val;
|
||||
$newInvoice['discount'] = $this->discount;
|
||||
$newInvoice['discount_type'] = $this->discount_type;
|
||||
$newInvoice['notes'] = $this->notes;
|
||||
$newInvoice['exchange_rate'] = $this->exchange_rate;
|
||||
$newInvoice['invoice_number'] = $serial->getNextNumber();
|
||||
$newInvoice['sequence_number'] = $serial->nextSequenceNumber;
|
||||
$newInvoice['customer_sequence_number'] = $serial->nextCustomerSequenceNumber;
|
||||
$newInvoice['base_due_amount'] = $this->exchange_rate * $this->due_amount;
|
||||
$newInvoice['base_discount_val'] = $this->exchange_rate * $this->discount_val;
|
||||
$newInvoice['base_sub_total'] = $this->exchange_rate * $this->sub_total;
|
||||
$newInvoice['base_tax'] = $this->exchange_rate * $this->tax;
|
||||
$newInvoice['base_total'] = $this->exchange_rate * $this->total;
|
||||
$invoice = Invoice::create($newInvoice);
|
||||
$invoice->unique_hash = Hashids::connection(Invoice::class)->encode($invoice->id);
|
||||
$invoice->save();
|
||||
|
||||
$this->load('items.taxes');
|
||||
Invoice::createItems($invoice, $this->items->toArray());
|
||||
|
||||
if ($this->taxes()->exists()) {
|
||||
Invoice::createTaxes($invoice, $this->taxes->toArray());
|
||||
}
|
||||
|
||||
if ($this->fields()->exists()) {
|
||||
$customField = [];
|
||||
|
||||
foreach ($this->fields as $data) {
|
||||
$customField[] = [
|
||||
'id' => $data->custom_field_id,
|
||||
'value' => $data->defaultAnswer
|
||||
];
|
||||
}
|
||||
|
||||
$invoice->addCustomFields($customField);
|
||||
}
|
||||
|
||||
//send automatically
|
||||
if ($this->send_automatically == true) {
|
||||
$data = [
|
||||
'body' => CompanySetting::getSetting('invoice_mail_body', $this->company_id),
|
||||
'from' => config('mail.from.address'),
|
||||
'to' => $this->customer->email,
|
||||
'subject' => 'New Invoice',
|
||||
'invoice' => $invoice->toArray(),
|
||||
'customer' => $invoice->customer->toArray(),
|
||||
'company' => Company::find($invoice->company_id)
|
||||
];
|
||||
$invoice->send($data);
|
||||
}
|
||||
}
|
||||
|
||||
public function markStatusAsCompleted()
|
||||
{
|
||||
if ($this->status == $this->status) {
|
||||
$this->status = self::COMPLETED;
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
|
||||
public static function getNextInvoiceDate($frequency, $starts_at)
|
||||
{
|
||||
$cron = new Cron\CronExpression($frequency);
|
||||
|
||||
return $cron->getNextRunDate($starts_at)->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
public function updateNextInvoiceDate()
|
||||
{
|
||||
$nextInvoiceAt = self::getNextInvoiceDate($this->frequency, $this->starts_at);
|
||||
|
||||
$this->next_invoice_at = $nextInvoiceAt;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public static function deleteRecurringInvoice($ids)
|
||||
{
|
||||
foreach ($ids as $id) {
|
||||
$recurringInvoice = self::find($id);
|
||||
|
||||
if ($recurringInvoice->invoices()->exists()) {
|
||||
$recurringInvoice->invoices()->update(['recurring_invoice_id' => null]);
|
||||
}
|
||||
|
||||
if ($recurringInvoice->items()->exists()) {
|
||||
$recurringInvoice->items()->delete();
|
||||
}
|
||||
|
||||
if ($recurringInvoice->taxes()->exists()) {
|
||||
$recurringInvoice->taxes()->delete();
|
||||
}
|
||||
|
||||
$recurringInvoice->delete();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -11,16 +11,8 @@ class Tax extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'amount',
|
||||
'company_id',
|
||||
'percent',
|
||||
'tax_type_id',
|
||||
'invoice_id',
|
||||
'estimate_id',
|
||||
'item_id',
|
||||
'compound_tax',
|
||||
protected $guarded = [
|
||||
'id'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@@ -38,11 +30,21 @@ class Tax extends Model
|
||||
return $this->belongsTo(Invoice::class);
|
||||
}
|
||||
|
||||
public function recurringInvoice()
|
||||
{
|
||||
return $this->belongsTo(RecurringInvoice::class);
|
||||
}
|
||||
|
||||
public function estimate()
|
||||
{
|
||||
return $this->belongsTo(Estimate::class);
|
||||
}
|
||||
|
||||
public function currency()
|
||||
{
|
||||
return $this->belongsTo(Currency::class);
|
||||
}
|
||||
|
||||
public function invoiceItem()
|
||||
{
|
||||
return $this->belongsTo(InvoiceItem::class);
|
||||
|
||||
@@ -20,6 +20,7 @@ class TaxType extends Model
|
||||
|
||||
protected $casts = [
|
||||
'percent' => 'float',
|
||||
'compound_tax' => 'boolean'
|
||||
];
|
||||
|
||||
public function taxes()
|
||||
@@ -27,9 +28,14 @@ class TaxType extends Model
|
||||
return $this->hasMany(Tax::class);
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query, $company_id)
|
||||
public function company()
|
||||
{
|
||||
$query->where('company_id', $company_id);
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query)
|
||||
{
|
||||
$query->where('company_id', request()->header('company'));
|
||||
}
|
||||
|
||||
public function scopeWhereTaxType($query, $tax_type_id)
|
||||
@@ -73,7 +79,7 @@ class TaxType extends Model
|
||||
public function scopePaginateData($query, $limit)
|
||||
{
|
||||
if ($limit == 'all') {
|
||||
return collect(['data' => $query->get()]);
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
return $query->paginate($limit);
|
||||
|
||||
@@ -21,9 +21,9 @@ class Unit extends Model
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query, $company_id)
|
||||
public function scopeWhereCompany($query)
|
||||
{
|
||||
$query->where('company_id', $company_id);
|
||||
$query->where('company_id', request()->header('company'));
|
||||
}
|
||||
|
||||
public function scopeWhereUnit($query, $unit_id)
|
||||
@@ -31,10 +31,19 @@ class Unit extends Model
|
||||
$query->orWhere('id', $unit_id);
|
||||
}
|
||||
|
||||
public function scopeWhereSearch($query, $search)
|
||||
{
|
||||
return $query->where('name', 'LIKE', '%'.$search.'%');
|
||||
}
|
||||
|
||||
public function scopeApplyFilters($query, array $filters)
|
||||
{
|
||||
$filters = collect($filters);
|
||||
|
||||
if ($filters->get('search')) {
|
||||
$query->whereSearch($filters->get('search'));
|
||||
}
|
||||
|
||||
if ($filters->get('unit_id')) {
|
||||
$query->whereUnit($filters->get('unit_id'));
|
||||
}
|
||||
@@ -42,12 +51,14 @@ class Unit extends Model
|
||||
if ($filters->get('company_id')) {
|
||||
$query->whereCompany($filters->get('company_id'));
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function scopePaginateData($query, $limit)
|
||||
{
|
||||
if ($limit == 'all') {
|
||||
return collect(['data' => $query->get()]);
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
return $query->paginate($limit);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Crater\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Crater\Http\Requests\UserRequest;
|
||||
use Crater\Notifications\MailResetPasswordNotification;
|
||||
use Crater\Traits\HasCustomFieldsTrait;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -10,7 +11,10 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use Silber\Bouncer\BouncerFacade;
|
||||
use Silber\Bouncer\Database\HasRolesAndAbilities;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||
|
||||
@@ -21,29 +25,15 @@ class User extends Authenticatable implements HasMedia
|
||||
use InteractsWithMedia;
|
||||
use HasCustomFieldsTrait;
|
||||
use HasFactory;
|
||||
use HasRolesAndAbilities;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'company_id',
|
||||
'password',
|
||||
'facebook_id',
|
||||
'currency_id',
|
||||
'google_id',
|
||||
'github_id',
|
||||
'role',
|
||||
'group_id',
|
||||
'phone',
|
||||
'company_name',
|
||||
'contact_name',
|
||||
'website',
|
||||
'enable_portal',
|
||||
'creator_id',
|
||||
protected $guarded = [
|
||||
'id'
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -99,14 +89,19 @@ class User extends Authenticatable implements HasMedia
|
||||
|
||||
public function getFormattedCreatedAtAttribute($value)
|
||||
{
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
|
||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', request()->header('company'));
|
||||
|
||||
return Carbon::parse($this->created_at)->format($dateFormat);
|
||||
}
|
||||
|
||||
public function estimates()
|
||||
{
|
||||
return $this->hasMany(Estimate::class);
|
||||
return $this->hasMany(Estimate::class, 'creator_id');
|
||||
}
|
||||
|
||||
public function customers()
|
||||
{
|
||||
return $this->hasMany(Customer::class, 'creator_id');
|
||||
}
|
||||
|
||||
public function currency()
|
||||
@@ -119,39 +114,34 @@ class User extends Authenticatable implements HasMedia
|
||||
return $this->belongsTo('Crater\Models\User', 'creator_id');
|
||||
}
|
||||
|
||||
public function company()
|
||||
public function companies()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
return $this->belongsToMany(Company::class, 'user_company', 'user_id', 'company_id');
|
||||
}
|
||||
|
||||
public function addresses()
|
||||
public function recurringInvoices()
|
||||
{
|
||||
return $this->hasMany(Address::class);
|
||||
return $this->hasMany(RecurringInvoice::class, 'creator_id');
|
||||
}
|
||||
|
||||
public function expenses()
|
||||
{
|
||||
return $this->hasMany(Expense::class);
|
||||
}
|
||||
|
||||
public function billingAddress()
|
||||
{
|
||||
return $this->hasOne(Address::class)->where('type', Address::BILLING_TYPE);
|
||||
}
|
||||
|
||||
public function shippingAddress()
|
||||
{
|
||||
return $this->hasOne(Address::class)->where('type', Address::SHIPPING_TYPE);
|
||||
return $this->hasMany(Expense::class, 'creator_id');
|
||||
}
|
||||
|
||||
public function payments()
|
||||
{
|
||||
return $this->hasMany(Payment::class);
|
||||
return $this->hasMany(Payment::class, 'creator_id');
|
||||
}
|
||||
|
||||
public function invoices()
|
||||
{
|
||||
return $this->hasMany(Invoice::class);
|
||||
return $this->hasMany(Invoice::class, 'creator_id');
|
||||
}
|
||||
|
||||
public function items()
|
||||
{
|
||||
return $this->hasMany(Item::class, 'creator_id');
|
||||
}
|
||||
|
||||
public function settings()
|
||||
@@ -203,15 +193,10 @@ class User extends Authenticatable implements HasMedia
|
||||
return $query->where('email', 'LIKE', '%'.$email.'%');
|
||||
}
|
||||
|
||||
public function scopeCustomer($query)
|
||||
{
|
||||
return $query->where('role', 'customer');
|
||||
}
|
||||
|
||||
public function scopePaginateData($query, $limit)
|
||||
{
|
||||
if ($limit == 'all') {
|
||||
return collect(['data' => $query->get()]);
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
return $query->paginate($limit);
|
||||
@@ -225,10 +210,6 @@ class User extends Authenticatable implements HasMedia
|
||||
$query->whereSearch($filters->get('search'));
|
||||
}
|
||||
|
||||
if ($filters->get('contact_name')) {
|
||||
$query->whereContactName($filters->get('contact_name'));
|
||||
}
|
||||
|
||||
if ($filters->get('display_name')) {
|
||||
$query->whereDisplayName($filters->get('display_name'));
|
||||
}
|
||||
@@ -237,10 +218,6 @@ class User extends Authenticatable implements HasMedia
|
||||
$query->whereEmail($filters->get('email'));
|
||||
}
|
||||
|
||||
if ($filters->get('customer_id')) {
|
||||
$query->whereCustomer($filters->get('customer_id'));
|
||||
}
|
||||
|
||||
if ($filters->get('phone')) {
|
||||
$query->wherePhone($filters->get('phone'));
|
||||
}
|
||||
@@ -252,16 +229,6 @@ class User extends Authenticatable implements HasMedia
|
||||
}
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query, $company_id)
|
||||
{
|
||||
$query->where('users.company_id', $company_id);
|
||||
}
|
||||
|
||||
public function scopeWhereCustomer($query, $customer_id)
|
||||
{
|
||||
$query->orWhere('users.id', $customer_id);
|
||||
}
|
||||
|
||||
public function scopeWhereSuperAdmin($query)
|
||||
{
|
||||
$query->orWhere('role', 'super admin');
|
||||
@@ -288,33 +255,6 @@ class User extends Authenticatable implements HasMedia
|
||||
});
|
||||
}
|
||||
|
||||
public static function deleteCustomers($ids)
|
||||
{
|
||||
foreach ($ids as $id) {
|
||||
$customer = self::find($id);
|
||||
|
||||
if ($customer->estimates()->exists()) {
|
||||
$customer->estimates()->delete();
|
||||
}
|
||||
|
||||
if ($customer->invoices()->exists()) {
|
||||
$customer->invoices()->delete();
|
||||
}
|
||||
|
||||
if ($customer->payments()->exists()) {
|
||||
$customer->payments()->delete();
|
||||
}
|
||||
|
||||
if ($customer->addresses()->exists()) {
|
||||
$customer->addresses()->delete();
|
||||
}
|
||||
|
||||
$customer->delete();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getAvatarAttribute()
|
||||
{
|
||||
$avatar = $this->getMedia('admin_avatar')->first();
|
||||
@@ -336,6 +276,9 @@ class User extends Authenticatable implements HasMedia
|
||||
'contact_name',
|
||||
'website',
|
||||
'enable_portal',
|
||||
'invoice_prefix',
|
||||
'estimate_prefix',
|
||||
'payment_prefix'
|
||||
]);
|
||||
|
||||
$data['creator_id'] = Auth::id();
|
||||
@@ -375,6 +318,9 @@ class User extends Authenticatable implements HasMedia
|
||||
'contact_name',
|
||||
'website',
|
||||
'enable_portal',
|
||||
'invoice_prefix',
|
||||
'estimate_prefix',
|
||||
'payment_prefix'
|
||||
]);
|
||||
|
||||
$data['role'] = 'customer';
|
||||
@@ -416,15 +362,139 @@ class User extends Authenticatable implements HasMedia
|
||||
}
|
||||
}
|
||||
|
||||
public function hasCompany($company_id)
|
||||
{
|
||||
$companies = $this->companies()->pluck('company_id')->toArray();
|
||||
|
||||
return in_array($company_id, $companies);
|
||||
}
|
||||
|
||||
public function getAllSettings()
|
||||
{
|
||||
return $this->settings()->get()->mapWithKeys(function ($item) {
|
||||
return [$item['key'] => $item['value']];
|
||||
});
|
||||
}
|
||||
|
||||
public function getSettings($settings)
|
||||
{
|
||||
$settings = $this->settings()->whereIn('key', $settings)->get();
|
||||
$companySettings = [];
|
||||
return $this->settings()->whereIn('key', $settings)->get()->mapWithKeys(function ($item) {
|
||||
return [$item['key'] => $item['value']];
|
||||
});
|
||||
}
|
||||
|
||||
foreach ($settings as $setting) {
|
||||
$companySettings[$setting->key] = $setting->value;
|
||||
public function isOwner()
|
||||
{
|
||||
if (Schema::hasColumn('companies', 'owner_id')) {
|
||||
$company = Company::find(request()->header('company'));
|
||||
|
||||
if ($company && $this->id === $company->owner_id) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return $this->role == 'super admin' || $this->role == 'admin';
|
||||
}
|
||||
|
||||
return $companySettings;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function createFromRequest(UserRequest $request)
|
||||
{
|
||||
$user = self::create($request->getUserPayload());
|
||||
|
||||
$user->setSettings([
|
||||
'language' => CompanySetting::getSetting('language', $request->header('company')),
|
||||
]);
|
||||
|
||||
$companies = collect($request->companies);
|
||||
$user->companies()->sync($companies->pluck('id'));
|
||||
|
||||
foreach ($companies as $company) {
|
||||
BouncerFacade::scope()->to($company['id']);
|
||||
|
||||
BouncerFacade::sync($user)->roles([$company['role']]);
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function updateFromRequest(UserRequest $request)
|
||||
{
|
||||
$this->update($request->getUserPayload());
|
||||
|
||||
$companies = collect($request->companies);
|
||||
$this->companies()->sync($companies->pluck('id'));
|
||||
|
||||
foreach ($companies as $company) {
|
||||
BouncerFacade::scope()->to($company['id']);
|
||||
|
||||
BouncerFacade::sync($this)->roles([$company['role']]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function checkAccess($data)
|
||||
{
|
||||
if ($this->isOwner()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((! $data->data['owner_only']) && empty($data->data['ability'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((! $data->data['owner_only']) && (! empty($data->data['ability'])) && (! empty($data->data['model'])) && $this->can($data->data['ability'], $data->data['model'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((! $data->data['owner_only']) && $this->can($data->data['ability'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function deleteUsers($ids)
|
||||
{
|
||||
foreach ($ids as $id) {
|
||||
$user = self::find($id);
|
||||
|
||||
if ($user->invoices()->exists()) {
|
||||
$user->invoices()->update(['creator_id' => null]);
|
||||
}
|
||||
|
||||
if ($user->estimates()->exists()) {
|
||||
$user->estimates()->update(['creator_id' => null]);
|
||||
}
|
||||
|
||||
if ($user->customers()->exists()) {
|
||||
$user->customers()->update(['creator_id' => null]);
|
||||
}
|
||||
|
||||
if ($user->recurringInvoices()->exists()) {
|
||||
$user->recurringInvoices()->update(['creator_id' => null]);
|
||||
}
|
||||
|
||||
if ($user->expenses()->exists()) {
|
||||
$user->expenses()->update(['creator_id' => null]);
|
||||
}
|
||||
|
||||
if ($user->payments()->exists()) {
|
||||
$user->payments()->update(['creator_id' => null]);
|
||||
}
|
||||
|
||||
if ($user->items()->exists()) {
|
||||
$user->items()->update(['creator_id' => null]);
|
||||
}
|
||||
|
||||
if ($user->settings()->exists()) {
|
||||
$user->settings()->delete();
|
||||
}
|
||||
|
||||
$user->delete();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user