v5.0.0 update

This commit is contained in:
Mohit Panjwani
2021-11-30 18:58:19 +05:30
parent d332712c22
commit 082d5cacf2
1253 changed files with 88309 additions and 71741 deletions

View File

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

View File

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

View File

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

View File

@@ -9,13 +9,7 @@ class Currency extends Model
{
use HasFactory;
protected $fillable = [
'name',
'code',
'symbol',
'precision',
'thousand_separator',
'decimal_separator',
'position',
protected $guarded = [
'id'
];
}

View File

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

View File

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

View 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,
];
}
}

View File

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

View File

@@ -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 = [

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

View 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&currencies=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;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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