Moved to _dev

This commit is contained in:
2025-09-20 16:11:47 +02:00
parent fb1a8753b7
commit b2ba11fcd3
1670 changed files with 224899 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Address extends Model
{
use HasFactory;
public const BILLING_TYPE = 'billing';
public const SHIPPING_TYPE = 'shipping';
protected $guarded = ['id'];
public function getCountryNameAttribute()
{
$name = $this->country ? $this->country->name : null;
return $name;
}
public function user()
{
return $this->belongsTo(User::class);
}
public function customer()
{
return $this->belongsTo(Customer::class);
}
public function company()
{
return $this->belongsTo(Company::class);
}
public function country()
{
return $this->belongsTo(Country::class);
}
}

View File

@@ -0,0 +1,400 @@
<?php
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;
class Company extends Model implements HasMedia
{
use InteractsWithMedia;
use HasFactory;
protected $guarded = [
'id'
];
public const COMPANY_LEVEL = 'company_level';
public const CUSTOMER_LEVEL = 'customer_level';
protected $appends = ['logo', 'logo_path'];
public function getRolesAttribute()
{
return Role::where('scope', $this->id)
->get();
}
public function getLogoPathAttribute()
{
$logo = $this->getMedia('logo')->first();
$isSystem = FileDisk::whereSetAsDefault(true)->first()->isSystem();
if ($logo) {
if ($isSystem) {
return $logo->getPath();
} else {
return $logo->getFullUrl();
}
}
return null;
}
public function getLogoAttribute()
{
$logo = $this->getMedia('logo')->first();
if ($logo) {
return $logo->getFullUrl();
}
return null;
}
public function customers()
{
return $this->hasMany(Customer::class);
}
public function owner()
{
return $this->belongsTo(User::class, 'owner_id');
}
public function settings()
{
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', 'company_id', 'user_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 ?? 13,
'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',
'automatically_expire_public_links' => 'YES',
'link_expiry_days' => 7,
];
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);
if ($invoice->transactions()->exists()) {
$invoice->transactions()->delete();
}
});
$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();
}
}
public function hasTransactions()
{
if (
$this->customers()->exists() ||
$this->items()->exists() ||
$this->invoices()->exists() ||
$this->estimates()->exists() ||
$this->expenses()->exists() ||
$this->payments()->exists() ||
$this->recurringInvoices()->exists()
) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class CompanySetting extends Model
{
use HasFactory;
protected $fillable = ['company_id', 'option', 'value'];
public function company()
{
return $this->belongsTo(Company::class);
}
public function scopeWhereCompany($query, $company_id)
{
$query->where('company_id', $company_id);
}
public static function setSettings($settings, $company_id)
{
foreach ($settings as $key => $value) {
self::updateOrCreate(
[
'option' => $key,
'company_id' => $company_id,
],
[
'option' => $key,
'company_id' => $company_id,
'value' => $value,
]
);
}
}
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)
{
return static::whereIn('option', $settings)->whereCompany($company_id)
->get()->mapWithKeys(function ($item) {
return [$item['option'] => $item['value']];
});
}
public static function getSetting($key, $company_id)
{
$setting = static::whereOption($key)->whereCompany($company_id)->first();
if ($setting) {
return $setting->value;
} else {
return null;
}
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Country extends Model
{
use HasFactory;
public function address()
{
return $this->hasMany(Address::class);
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Currency extends Model
{
use HasFactory;
protected $guarded = [
'id'
];
}

View File

@@ -0,0 +1,121 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class CustomField extends Model
{
use HasFactory;
protected $guarded = [
'id',
];
protected $dates = [
'date_answer',
'date_time_answer'
];
protected $appends = [
'defaultAnswer',
];
protected $casts = [
'options' => 'array',
];
public function setTimeAnswerAttribute($value)
{
if ($value && $value != null) {
$this->attributes['time_answer'] = date("H:i:s", strtotime($value));
}
}
public function setOptionsAttribute($value)
{
$this->attributes['options'] = json_encode($value);
}
public function getDefaultAnswerAttribute()
{
$value_type = getCustomFieldValueKey($this->type);
return $this->$value_type;
}
public function getInUseAttribute()
{
return $this->customFieldValues()->exists();
}
public function company()
{
return $this->belongsTo(Company::class);
}
public function customFieldValues()
{
return $this->hasMany(CustomFieldValue::class);
}
public function scopeWhereCompany($query)
{
return $query->where('custom_fields.company_id', request()->header('company'));
}
public function scopeWhereSearch($query, $search)
{
$query->where(function ($query) use ($search) {
$query->where('label', 'LIKE', '%'.$search.'%')
->orWhere('name', 'LIKE', '%'.$search.'%');
});
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return $query->get();
}
return $query->paginate($limit);
}
public function scopeApplyFilters($query, array $filters)
{
$filters = collect($filters);
if ($filters->get('type')) {
$query->whereType($filters->get('type'));
}
if ($filters->get('search')) {
$query->whereSearch($filters->get('search'));
}
}
public function scopeWhereType($query, $type)
{
$query->where('custom_fields.model_type', $type);
}
public static function createCustomField($request)
{
$data = $request->validated();
$data[getCustomFieldValueKey($request->type)] = $request->default_answer;
$data['company_id'] = $request->header('company');
$data['slug'] = clean_slug($request->model_type, $request->name);
return CustomField::create($data);
}
public function updateCustomField($request)
{
$data = $request->validated();
$data[getCustomFieldValueKey($request->type)] = $request->default_answer;
$this->update($data);
return $this;
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class CustomFieldValue extends Model
{
use HasFactory;
protected $dates = [
'date_answer',
'date_time_answer'
];
protected $guarded = [
'id',
];
protected $appends = [
'defaultAnswer',
];
public function setTimeAnswerAttribute($value)
{
if ($value && $value != null) {
$this->attributes['time_answer'] = date("H:i:s", strtotime($value));
} else {
$this->attributes['time_answer'] = null;
}
}
public function getDefaultAnswerAttribute()
{
$value_type = getCustomFieldValueKey($this->type);
return $this->$value_type;
}
public function company()
{
return $this->belongsTo(Company::class);
}
public function customField()
{
return $this->belongsTo(CustomField::class);
}
public function customFieldValuable()
{
return $this->morphTo();
}
}

View File

@@ -0,0 +1,341 @@
<?php
namespace Crater\Models;
use Carbon\Carbon;
use Crater\Notifications\CustomerMailResetPasswordNotification;
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'
];
protected $casts = [
'enable_portal' => 'boolean',
];
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 sendPasswordResetNotification($token)
{
$this->notify(new CustomerMailResetPasswordNotification($token));
}
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->map(function ($invoice) {
if ($invoice->transactions()->exists()) {
$invoice->transactions()->delete();
}
$invoice->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,33 @@
<?php
namespace Crater\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class EmailLog extends Model
{
use HasFactory;
protected $guarded = ['id'];
public function mailable()
{
return $this->morphTo();
}
public function isExpired()
{
$linkexpiryDays = CompanySetting::getSetting('link_expiry_days', $this->mailable()->get()->toArray()[0]['company_id']);
$checkExpiryLinks = CompanySetting::getSetting('automatically_expire_public_links', $this->mailable()->get()->toArray()[0]['company_id']);
$expiryDate = $this->created_at->addDays($linkexpiryDays);
if ($checkExpiryLinks == 'YES' && Carbon::now()->format('Y-m-d') > $expiryDate->format('Y-m-d')) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,539 @@
<?php
namespace Crater\Models;
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\Storage;
use Illuminate\Support\Str;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Vinkla\Hashids\Facades\Hashids;
class Estimate extends Model implements HasMedia
{
use HasFactory;
use InteractsWithMedia;
use GeneratesPdfTrait;
use HasCustomFieldsTrait;
public const STATUS_DRAFT = 'DRAFT';
public const STATUS_SENT = 'SENT';
public const STATUS_VIEWED = 'VIEWED';
public const STATUS_EXPIRED = 'EXPIRED';
public const STATUS_ACCEPTED = 'ACCEPTED';
public const STATUS_REJECTED = 'REJECTED';
protected $dates = [
'created_at',
'updated_at',
'deleted_at',
'estimate_date',
'expiry_date'
];
protected $appends = [
'formattedExpiryDate',
'formattedEstimateDate',
'estimatePdfUrl',
];
protected $guarded = ['id'];
protected $casts = [
'total' => 'integer',
'tax' => 'integer',
'sub_total' => 'integer',
'discount' => 'float',
'discount_val' => 'integer',
'exchange_rate' => 'float'
];
public function getEstimatePdfUrlAttribute()
{
return url('/estimates/pdf/'.$this->unique_hash);
}
public function emailLogs()
{
return $this->morphMany('App\Models\EmailLog', 'mailable');
}
public function items()
{
return $this->hasMany('Crater\Models\EstimateItem');
}
public function customer()
{
return $this->belongsTo(Customer::class, 'customer_id');
}
public function creator()
{
return $this->belongsTo('Crater\Models\User', 'creator_id');
}
public function company()
{
return $this->belongsTo('Crater\Models\Company');
}
public function currency()
{
return $this->belongsTo(Currency::class);
}
public function taxes()
{
return $this->hasMany(Tax::class);
}
public function getFormattedExpiryDateAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->expiry_date)->format($dateFormat);
}
public function getFormattedEstimateDateAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->estimate_date)->format($dateFormat);
}
public function scopeEstimatesBetween($query, $start, $end)
{
return $query->whereBetween(
'estimates.estimate_date',
[$start->format('Y-m-d'), $end->format('Y-m-d')]
);
}
public function scopeWhereStatus($query, $status)
{
return $query->where('estimates.status', $status);
}
public function scopeWhereEstimateNumber($query, $estimateNumber)
{
return $query->where('estimates.estimate_number', 'LIKE', '%'.$estimateNumber.'%');
}
public function scopeWhereEstimate($query, $estimate_id)
{
$query->orWhere('id', $estimate_id);
}
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('search')) {
$query->whereSearch($filters->get('search'));
}
if ($filters->get('estimate_number')) {
$query->whereEstimateNumber($filters->get('estimate_number'));
}
if ($filters->get('status')) {
$query->whereStatus($filters->get('status'));
}
if ($filters->get('estimate_id')) {
$query->whereEstimate($filters->get('estimate_id'));
}
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->estimatesBetween($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') : 'sequence_number';
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'desc';
$query->whereOrder($field, $orderBy);
}
}
public function scopeWhereOrder($query, $orderByField, $orderBy)
{
$query->orderBy($orderByField, $orderBy);
}
public function scopeWhereCompany($query)
{
$query->where('estimates.company_id', request()->header('company'));
}
public function scopeWhereCustomer($query, $customer_id)
{
$query->where('estimates.customer_id', $customer_id);
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return $query->get();
}
return $query->paginate($limit);
}
public static function createEstimate($request)
{
$data = $request->getEstimatePayload();
if ($request->has('estimateSend')) {
$data['status'] = self::STATUS_SENT;
}
$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();
$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, $estimate->exchange_rate);
}
$customFields = $request->customFields;
if ($customFields) {
$estimate->addCustomFields($customFields);
}
return $estimate;
}
public function updateEstimate($request)
{
$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, $this->exchange_rate);
if ($request->has('taxes') && (! empty($request->taxes))) {
self::createTaxes($this, $request, $this->exchange_rate);
}
if ($request->customFields) {
$this->updateCustomFields($request->customFields);
}
return Estimate::with([
'items.taxes',
'items.fields',
'items.fields.customField',
'customer',
'taxes'
])
->find($this->id);
}
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']) {
foreach ($estimateItem['taxes'] as $tax) {
if (gettype($tax['amount']) !== "NULL") {
$tax['company_id'] = $request->header('company');
$item->taxes()->create($tax);
}
}
}
if (array_key_exists('custom_fields', $estimateItem) && $estimateItem['custom_fields']) {
$item->addCustomFields($estimateItem['custom_fields']);
}
}
}
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 sendEstimateData($data)
{
$data['estimate'] = $this->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;
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',
];
}
public function getPDFData()
{
$taxes = collect();
if ($this->tax_per_item === 'YES') {
foreach ($this->items as $item) {
foreach ($item->taxes as $tax) {
$found = $taxes->filter(function ($item) use ($tax) {
return $item->tax_type_id == $tax->tax_type_id;
})->first();
if ($found) {
$found->amount += $tax->amount;
} else {
$taxes->push($tax);
}
}
}
}
$estimateTemplate = self::find($this->id)->template_name;
$company = Company::find($this->company_id);
$locale = CompanySetting::getSetting('language', $company->id);
$customFields = CustomField::where('model_type', 'Item')->get();
App::setLocale($locale);
$logo = $company->logo_path;
view()->share([
'estimate' => $this,
'customFields' => $customFields,
'logo' => $logo ?? null,
'company_address' => $this->getCompanyAddress(),
'shipping_address' => $this->getCustomerShippingAddress(),
'billing_address' => $this->getCustomerBillingAddress(),
'notes' => $this->getNotes(),
'taxes' => $taxes,
]);
if (request()->has('preview')) {
return view('app.pdf.estimate.'.$estimateTemplate);
}
return PDF::loadView('app.pdf.estimate.'.$estimateTemplate);
}
public function getCompanyAddress()
{
if ($this->company && (! $this->company->address()->exists())) {
return false;
}
$format = CompanySetting::getSetting('estimate_company_address_format', $this->company_id);
return $this->getFormattedString($format);
}
public function getCustomerShippingAddress()
{
if ($this->customer && (! $this->customer->shippingAddress()->exists())) {
return false;
}
$format = CompanySetting::getSetting('estimate_shipping_address_format', $this->company_id);
return $this->getFormattedString($format);
}
public function getCustomerBillingAddress()
{
if ($this->customer && (! $this->customer->billingAddress()->exists())) {
return false;
}
$format = CompanySetting::getSetting('estimate_billing_address_format', $this->company_id);
return $this->getFormattedString($format);
}
public function getNotes()
{
return $this->getFormattedString($this->notes);
}
public function getEmailAttachmentSetting()
{
$estimateAsAttachment = CompanySetting::getSetting('estimate_email_attachment', $this->company_id);
if ($estimateAsAttachment == 'NO') {
return false;
}
return true;
}
public function getEmailBody($body)
{
$values = array_merge($this->getFieldsArray(), $this->getExtraFields());
$body = strtr($body, $values);
return preg_replace('/{(.*?)}/', '', $body);
}
public function getExtraFields()
{
return [
'{ESTIMATE_DATE}' => $this->formattedEstimateDate,
'{ESTIMATE_EXPIRY_DATE}' => $this->formattedExpiryDate,
'{ESTIMATE_NUMBER}' => $this->estimate_number,
'{ESTIMATE_REF_NUMBER}' => $this->reference_number,
];
}
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

@@ -0,0 +1,46 @@
<?php
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 $guarded = [
'id'
];
protected $casts = [
'price' => 'integer',
'total' => 'integer',
'discount' => 'float',
'quantity' => 'float',
'discount_val' => 'integer',
'tax' => 'integer',
];
public function estimate()
{
return $this->belongsTo(Estimate::class);
}
public function item()
{
return $this->belongsTo(Item::class);
}
public function taxes()
{
return $this->hasMany(Tax::class);
}
public function scopeWhereCompany($query, $company_id)
{
$query->where('company_id', $company_id);
}
}

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

@@ -0,0 +1,279 @@
<?php
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;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class Expense extends Model implements HasMedia
{
use HasFactory;
use InteractsWithMedia;
use HasCustomFieldsTrait;
protected $dates = [
'expense_date',
];
protected $guarded = ['id'];
protected $appends = [
'formattedExpenseDate',
'formattedCreatedAt',
'receipt',
'receiptMeta'
];
protected $casts = [
'notes' => 'string',
'exchange_rate' => 'float'
];
public function category()
{
return $this->belongsTo(ExpenseCategory::class, 'expense_category_id');
}
public function customer()
{
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()
{
return $this->belongsTo('Crater\Models\User', 'creator_id');
}
public function getFormattedExpenseDateAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->expense_date)->format($dateFormat);
}
public function getFormattedCreatedAtAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
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();
}
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(
'expenses.expense_date',
[$start->format('Y-m-d'), $end->format('Y-m-d')]
);
}
public function scopeWhereCategoryName($query, $search)
{
foreach (explode(' ', $search) as $term) {
$query->whereHas('category', function ($query) use ($term) {
$query->where('name', 'LIKE', '%'.$term.'%');
});
}
}
public function scopeWhereNotes($query, $search)
{
$query->where('notes', 'LIKE', '%'.$search.'%');
}
public function scopeWhereCategory($query, $categoryId)
{
return $query->where('expenses.expense_category_id', $categoryId);
}
public function scopeWhereUser($query, $customer_id)
{
return $query->where('expenses.customer_id', $customer_id);
}
public function scopeApplyFilters($query, array $filters)
{
$filters = collect($filters);
if ($filters->get('expense_category_id')) {
$query->whereCategory($filters->get('expense_category_id'));
}
if ($filters->get('customer_id')) {
$query->whereUser($filters->get('customer_id'));
}
if ($filters->get('expense_id')) {
$query->whereExpense($filters->get('expense_id'));
}
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->expensesBetween($start, $end);
}
if ($filters->get('orderByField') || $filters->get('orderBy')) {
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'expense_date';
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'asc';
$query->whereOrder($field, $orderBy);
}
if ($filters->get('search')) {
$query->whereSearch($filters->get('search'));
}
}
public function scopeWhereExpense($query, $expense_id)
{
$query->orWhere('id', $expense_id);
}
public function scopeWhereSearch($query, $search)
{
foreach (explode(' ', $search) as $term) {
$query->whereHas('category', function ($query) use ($term) {
$query->where('name', 'LIKE', '%'.$term.'%');
})
->orWhere('notes', 'LIKE', '%'.$term.'%');
}
}
public function scopeWhereOrder($query, $orderByField, $orderBy)
{
$query->orderBy($orderByField, $orderBy);
}
public function scopeWhereCompany($query)
{
$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 $query->get();
}
return $query->paginate($limit);
}
public function scopeExpensesAttributes($query)
{
$query->select(
DB::raw('
count(*) as expenses_count,
sum(base_amount) as total_amount,
expense_category_id')
)
->groupBy('expense_category_id');
}
public static function createExpense($request)
{
$expense = self::create($request->getExpensePayload());
$company_currency = CompanySetting::getSetting('currency', $request->header('company'));
if ((string)$expense['currency_id'] !== $company_currency) {
ExchangeRateLog::addExchangeRateLog($expense);
}
if ($request->hasFile('attachment_receipt')) {
$expense->addMediaFromRequest('attachment_receipt')->toMediaCollection('receipts');
}
if ($request->customFields) {
$expense->addCustomFields(json_decode($request->customFields));
}
return $expense;
}
public function updateExpense($request)
{
$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 (isset($request->is_attachment_receipt_removed) && (bool) $request->is_attachment_receipt_removed) {
$this->clearMediaCollection('receipts');
}
if ($request->hasFile('attachment_receipt')) {
$this->clearMediaCollection('receipts');
$this->addMediaFromRequest('attachment_receipt')->toMediaCollection('receipts');
}
if ($request->customFields) {
$this->updateCustomFields(json_decode($request->customFields));
}
return true;
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace Crater\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ExpenseCategory extends Model
{
use HasFactory;
protected $fillable = ['name', 'company_id', 'description'];
/**
* The accessors to append to the model's array form.
*
* @var array
*/
protected $appends = ['amount', 'formattedCreatedAt'];
public function expenses()
{
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);
return Carbon::parse($this->created_at)->format($dateFormat);
}
public function getAmountAttribute()
{
return $this->expenses()->sum('amount');
}
public function scopeWhereCompany($query)
{
$query->where('company_id', request()->header('company'));
}
public function scopeWhereCategory($query, $category_id)
{
$query->orWhere('id', $category_id);
}
public function scopeWhereSearch($query, $search)
{
$query->where('name', 'LIKE', '%'.$search.'%');
}
public function scopeApplyFilters($query, array $filters)
{
$filters = collect($filters);
if ($filters->get('category_id')) {
$query->whereCategory($filters->get('category_id'));
}
if ($filters->get('company_id')) {
$query->whereCompany($filters->get('company_id'));
}
if ($filters->get('search')) {
$query->whereSearch($filters->get('search'));
}
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return $query->get();
}
return $query->paginate($limit);
}
}

View File

@@ -0,0 +1,204 @@
<?php
namespace Crater\Models;
use Crater\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class FileDisk extends Model
{
use HasFactory;
public const DISK_TYPE_SYSTEM = 'SYSTEM';
public const DISK_TYPE_REMOTE = 'REMOTE';
protected $guarded = [
'id',
];
protected $casts = [
'set_as_default' => 'boolean',
];
public function setCredentialsAttribute($value)
{
$this->attributes['credentials'] = json_encode($value);
}
public function scopeWhereOrder($query, $orderByField, $orderBy)
{
$query->orderBy($orderByField, $orderBy);
}
public function scopeFileDisksBetween($query, $start, $end)
{
return $query->whereBetween(
'file_disks.created_at',
[$start->format('Y-m-d'), $end->format('Y-m-d')]
);
}
public function scopeWhereSearch($query, $search)
{
foreach (explode(' ', $search) as $term) {
$query->where('name', 'LIKE', '%'.$term.'%')
->orWhere('driver', 'LIKE', '%'.$term.'%');
}
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return $query->get();
}
return $query->paginate($limit);
}
public function scopeApplyFilters($query, array $filters)
{
$filters = collect($filters);
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->fileDisksBetween($start, $end);
}
if ($filters->get('orderByField') || $filters->get('orderBy')) {
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'sequence_number';
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'asc';
$query->whereOrder($field, $orderBy);
}
}
public function setConfig()
{
$driver = $this->driver;
$credentials = collect(json_decode($this['credentials']));
self::setFilesystem($credentials, $driver);
}
public function setAsDefault()
{
return $this->set_as_default;
}
public static function setFilesystem($credentials, $driver)
{
$prefix = env('DYNAMIC_DISK_PREFIX', 'temp_');
config(['filesystems.default' => $prefix.$driver]);
$disks = config('filesystems.disks.'.$driver);
foreach ($disks as $key => $value) {
if ($credentials->has($key)) {
$disks[$key] = $credentials[$key];
}
}
config(['filesystems.disks.'.$prefix.$driver => $disks]);
}
public static function validateCredentials($credentials, $disk)
{
$exists = false;
self::setFilesystem(collect($credentials), $disk);
$prefix = env('DYNAMIC_DISK_PREFIX', 'temp_');
try {
$root = '';
if ($disk == 'dropbox') {
$root = $credentials['root'].'/';
}
\Storage::disk($prefix.$disk)->put($root.'crater_temp.text', 'Check Credentials');
if (\Storage::disk($prefix.$disk)->exists($root.'crater_temp.text')) {
$exists = true;
\Storage::disk($prefix.$disk)->delete($root.'crater_temp.text');
}
} catch (\Exception $e) {
$exists = false;
}
return $exists;
}
public static function createDisk($request)
{
if ($request->set_as_default) {
self::updateDefaultDisks();
}
$disk = self::create([
'credentials' => $request->credentials,
'name' => $request->name,
'driver' => $request->driver,
'set_as_default' => $request->set_as_default,
'company_id' => $request->header('company'),
]);
return $disk;
}
public static function updateDefaultDisks()
{
$disks = self::get();
foreach ($disks as $disk) {
$disk->set_as_default = false;
$disk->save();
}
return true;
}
public function updateDisk($request)
{
$data = [
'credentials' => $request->credentials,
'name' => $request->name,
'driver' => $request->driver,
];
if (! $this->setAsDefault()) {
if ($request->set_as_default) {
self::updateDefaultDisks();
}
$data['set_as_default'] = $request->set_as_default;
}
$this->update($data);
return $this;
}
public function setAsDefaultDisk()
{
self::updateDefaultDisks();
$this->set_as_default = true;
$this->save();
return $this;
}
public function isSystem()
{
return $this->type === self::DISK_TYPE_SYSTEM;
}
public function isRemote()
{
return $this->type === self::DISK_TYPE_REMOTE;
}
}

View File

@@ -0,0 +1,725 @@
<?php
namespace Crater\Models;
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\Storage;
use Illuminate\Support\Str;
use Nwidart\Modules\Facades\Module;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Vinkla\Hashids\Facades\Hashids;
class Invoice extends Model implements HasMedia
{
use HasFactory;
use InteractsWithMedia;
use GeneratesPdfTrait;
use HasCustomFieldsTrait;
public const STATUS_DRAFT = 'DRAFT';
public const STATUS_SENT = 'SENT';
public const STATUS_VIEWED = 'VIEWED';
public const STATUS_COMPLETED = 'COMPLETED';
public const STATUS_UNPAID = 'UNPAID';
public const STATUS_PARTIALLY_PAID = 'PARTIALLY_PAID';
public const STATUS_PAID = 'PAID';
protected $dates = [
'created_at',
'updated_at',
'deleted_at',
'invoice_date',
'due_date'
];
protected $casts = [
'total' => 'integer',
'tax' => 'integer',
'sub_total' => 'integer',
'discount' => 'float',
'discount_val' => 'integer',
'exchange_rate' => 'float'
];
protected $guarded = [
'id',
];
protected $appends = [
'formattedCreatedAt',
'formattedInvoiceDate',
'formattedDueDate',
'invoicePdfUrl',
];
public function transactions()
{
return $this->hasMany(Transaction::class);
}
public function emailLogs()
{
return $this->morphMany('App\Models\EmailLog', 'mailable');
}
public function items()
{
return $this->hasMany('Crater\Models\InvoiceItem');
}
public function taxes()
{
return $this->hasMany(Tax::class);
}
public function payments()
{
return $this->hasMany(Payment::class);
}
public function currency()
{
return $this->belongsTo(Currency::class);
}
public function company()
{
return $this->belongsTo(Company::class);
}
public function customer()
{
return $this->belongsTo(Customer::class, 'customer_id');
}
public function recurringInvoice()
{
return $this->belongsTo(RecurringInvoice::class);
}
public function creator()
{
return $this->belongsTo(User::class, 'creator_id');
}
public function getInvoicePdfUrlAttribute()
{
return url('/invoices/pdf/'.$this->unique_hash);
}
public function getPaymentModuleEnabledAttribute()
{
if (Module::has('Payments')) {
return Module::isEnabled('Payments');
}
return false;
}
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_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->viewed) {
return self::STATUS_VIEWED;
} elseif ($this->sent) {
return self::STATUS_SENT;
} else {
return self::STATUS_DRAFT;
}
}
public function getFormattedNotesAttribute($value)
{
return $this->getNotes();
}
public function getFormattedCreatedAtAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->created_at)->format($dateFormat);
}
public function getFormattedDueDateAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->due_date)->format($dateFormat);
}
public function getFormattedInvoiceDateAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->invoice_date)->format($dateFormat);
}
public function scopeWhereStatus($query, $status)
{
return $query->where('invoices.status', $status);
}
public function scopeWherePaidStatus($query, $status)
{
return $query->where('invoices.paid_status', $status);
}
public function scopeWhereDueStatus($query, $status)
{
return $query->whereIn('invoices.paid_status', [
self::STATUS_UNPAID,
self::STATUS_PARTIALLY_PAID,
]);
}
public function scopeWhereInvoiceNumber($query, $invoiceNumber)
{
return $query->where('invoices.invoice_number', 'LIKE', '%'.$invoiceNumber.'%');
}
public function scopeInvoicesBetween($query, $start, $end)
{
return $query->whereBetween(
'invoices.invoice_date',
[$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 scopeWhereOrder($query, $orderByField, $orderBy)
{
$query->orderBy($orderByField, $orderBy);
}
public function scopeApplyFilters($query, array $filters)
{
$filters = collect($filters);
if ($filters->get('search')) {
$query->whereSearch($filters->get('search'));
}
if ($filters->get('status')) {
if (
$filters->get('status') == self::STATUS_UNPAID ||
$filters->get('status') == self::STATUS_PARTIALLY_PAID ||
$filters->get('status') == self::STATUS_PAID
) {
$query->wherePaidStatus($filters->get('status'));
} elseif ($filters->get('status') == 'DUE') {
$query->whereDueStatus($filters->get('status'));
} else {
$query->whereStatus($filters->get('status'));
}
}
if ($filters->get('paid_status')) {
$query->wherePaidStatus($filters->get('status'));
}
if ($filters->get('invoice_id')) {
$query->whereInvoice($filters->get('invoice_id'));
}
if ($filters->get('invoice_number')) {
$query->whereInvoiceNumber($filters->get('invoice_number'));
}
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);
}
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') : 'sequence_number';
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'desc';
$query->whereOrder($field, $orderBy);
}
}
public function scopeWhereInvoice($query, $invoice_id)
{
$query->orWhere('id', $invoice_id);
}
public function scopeWhereCompany($query)
{
$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.customer_id', $customer_id);
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return $query->get();
}
return $query->paginate($limit);
}
public static function createInvoice($request)
{
$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->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->taxes);
}
if ($request->customFields) {
$invoice->addCustomFields($request->customFields);
}
$invoice = Invoice::with([
'items',
'items.fields',
'items.fields.customField',
'customer',
'taxes'
])
->find($invoice->id);
return $invoice;
}
public function updateInvoice($request)
{
$serial = (new SerialNumberFormatter())
->setModel($this)
->setCompany($this->company_id)
->setCustomer($request->customer_id)
->setModelObject($this->id)
->setNextNumbers();
$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 {
$oldTotal = 0;
}
$data['due_amount'] = ($this->due_amount + $oldTotal);
$data['base_due_amount'] = $data['due_amount'] * $data['exchange_rate'];
$data['customer_sequence_number'] = $serial->nextCustomerSequenceNumber;
$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->items);
if ($request->has('taxes') && (! empty($request->taxes))) {
self::createTaxes($this, $request->taxes);
}
if ($request->customFields) {
$this->updateCustomFields($request->customFields);
}
$invoice = Invoice::with([
'items',
'items.fields',
'items.fields.customField',
'customer',
'taxes'
])
->find($this->id);
return $invoice;
}
public function sendInvoiceData($data)
{
$data['invoice'] = $this->toArray();
$data['customer'] = $this->customer->toArray();
$data['company'] = Company::find($this->company_id);
$data['subject'] = $this->getEmailString($data['subject']);
$data['body'] = $this->getEmailString($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);
\Mail::to($data['to'])->send(new SendInvoiceMail($data));
if ($this->status == Invoice::STATUS_DRAFT) {
$this->status = Invoice::STATUS_SENT;
$this->sent = true;
$this->save();
}
return [
'success' => true,
'type' => 'send',
];
}
public static function createItems($invoice, $invoiceItems)
{
$exchange_rate = $invoice->exchange_rate;
foreach ($invoiceItems as $invoiceItem) {
$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'] = $invoice->company_id;
$tax['exchange_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', $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, $taxes)
{
$exchange_rate = $invoice->exchange_rate;
foreach ($taxes as $tax) {
$tax['company_id'] = $invoice->company_id;
$tax['exchange_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);
}
}
}
public function getPDFData()
{
$taxes = collect();
if ($this->tax_per_item === 'YES') {
foreach ($this->items as $item) {
foreach ($item->taxes as $tax) {
$found = $taxes->filter(function ($item) use ($tax) {
return $item->tax_type_id == $tax->tax_type_id;
})->first();
if ($found) {
$found->amount += $tax->amount;
} else {
$taxes->push($tax);
}
}
}
}
$invoiceTemplate = self::find($this->id)->template_name;
$company = Company::find($this->company_id);
$locale = CompanySetting::getSetting('language', $company->id);
$customFields = CustomField::where('model_type', 'Item')->get();
App::setLocale($locale);
$logo = $company->logo_path;
view()->share([
'invoice' => $this,
'customFields' => $customFields,
'company_address' => $this->getCompanyAddress(),
'shipping_address' => $this->getCustomerShippingAddress(),
'billing_address' => $this->getCustomerBillingAddress(),
'notes' => $this->getNotes(),
'logo' => $logo ?? null,
'taxes' => $taxes,
]);
if (request()->has('preview')) {
return view('app.pdf.invoice.'.$invoiceTemplate);
}
return PDF::loadView('app.pdf.invoice.'.$invoiceTemplate);
}
public function getEmailAttachmentSetting()
{
$invoiceAsAttachment = CompanySetting::getSetting('invoice_email_attachment', $this->company_id);
if ($invoiceAsAttachment == 'NO') {
return false;
}
return true;
}
public function getCompanyAddress()
{
if ($this->company && (! $this->company->address()->exists())) {
return false;
}
$format = CompanySetting::getSetting('invoice_company_address_format', $this->company_id);
return $this->getFormattedString($format);
}
public function getCustomerShippingAddress()
{
if ($this->customer && (! $this->customer->shippingAddress()->exists())) {
return false;
}
$format = CompanySetting::getSetting('invoice_shipping_address_format', $this->company_id);
return $this->getFormattedString($format);
}
public function getCustomerBillingAddress()
{
if ($this->customer && (! $this->customer->billingAddress()->exists())) {
return false;
}
$format = CompanySetting::getSetting('invoice_billing_address_format', $this->company_id);
return $this->getFormattedString($format);
}
public function getNotes()
{
return $this->getFormattedString($this->notes);
}
public function getEmailString($body)
{
$values = array_merge($this->getFieldsArray(), $this->getExtraFields());
$body = strtr($body, $values);
return preg_replace('/{(.*?)}/', '', $body);
}
public function getExtraFields()
{
return [
'{INVOICE_DATE}' => $this->formattedInvoiceDate,
'{INVOICE_DUE_DATE}' => $this->formattedDueDate,
'{INVOICE_NUMBER}' => $this->invoice_number,
'{INVOICE_REF_NUMBER}' => $this->reference_number,
];
}
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->base_due_amount = $this->due_amount * $this->exchange_rate;
$this->changeInvoiceStatus($this->due_amount);
}
public function subtractInvoicePayment($amount)
{
$this->due_amount -= $amount;
$this->base_due_amount = $this->due_amount * $this->exchange_rate;
$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;
$this->overdue = false;
} 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();
}
public static function deleteInvoices($ids)
{
foreach ($ids as $id) {
$invoice = self::find($id);
if ($invoice->transactions()->exists()) {
$invoice->transactions()->delete();
}
$invoice->delete();
}
return true;
}
}

View File

@@ -0,0 +1,81 @@
<?php
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;
class InvoiceItem extends Model
{
use HasFactory;
use HasCustomFieldsTrait;
protected $guarded = [
'id'
];
protected $casts = [
'price' => 'integer',
'total' => 'integer',
'discount' => 'float',
'quantity' => 'float',
'discount_val' => 'integer',
'tax' => 'integer',
];
public function invoice()
{
return $this->belongsTo(Invoice::class);
}
public function item()
{
return $this->belongsTo(Item::class);
}
public function taxes()
{
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);
}
public function scopeInvoicesBetween($query, $start, $end)
{
$query->whereHas('invoice', function ($query) use ($start, $end) {
$query->whereBetween(
'invoice_date',
[$start->format('Y-m-d'), $end->format('Y-m-d')]
);
});
}
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 scopeItemAttributes($query)
{
$query->select(
DB::raw('sum(quantity) as total_quantity, sum(base_total) as total_amount, invoice_items.name')
)->groupBy('invoice_items.name');
}
}

174
crater/app/Models/Item.php Normal file
View File

@@ -0,0 +1,174 @@
<?php
namespace Crater\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
class Item extends Model
{
use HasFactory;
protected $guarded = ['id'];
protected $casts = [
'price' => 'integer',
];
protected $appends = [
'formattedCreatedAt',
];
public function unit()
{
return $this->belongsTo(Unit::class, 'unit_id');
}
public function company()
{
return $this->belongsTo(Company::class);
}
public function creator()
{
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.'%');
}
public function scopeWherePrice($query, $price)
{
return $query->where('items.price', $price);
}
public function scopeWhereUnit($query, $unit_id)
{
return $query->where('items.unit_id', $unit_id);
}
public function scopeWhereOrder($query, $orderByField, $orderBy)
{
$query->orderBy($orderByField, $orderBy);
}
public function scopeWhereItem($query, $item_id)
{
$query->orWhere('id', $item_id);
}
public function scopeApplyFilters($query, array $filters)
{
$filters = collect($filters);
if ($filters->get('search')) {
$query->whereSearch($filters->get('search'));
}
if ($filters->get('price')) {
$query->wherePrice($filters->get('price'));
}
if ($filters->get('unit_id')) {
$query->whereUnit($filters->get('unit_id'));
}
if ($filters->get('item_id')) {
$query->whereItem($filters->get('item_id'));
}
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);
}
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return $query->get();
}
return $query->paginate($limit);
}
public function getFormattedCreatedAtAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', request()->header('company'));
return Carbon::parse($this->created_at)->format($dateFormat);
}
public function taxes()
{
return $this->hasMany(Tax::class)
->where('invoice_item_id', null)
->where('estimate_item_id', null);
}
public function scopeWhereCompany($query)
{
$query->where('items.company_id', request()->header('company'));
}
public function invoiceItems()
{
return $this->hasMany(InvoiceItem::class);
}
public function estimateItems()
{
return $this->hasMany(EstimateItem::class);
}
public static function createItem($request)
{
$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);
}
}
$item = self::with('taxes')->find($item->id);
return $item;
}
public function updateItem($request)
{
$this->update($request->validated());
$this->taxes()->delete();
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);
}
}
return Item::with('taxes')->find($this->id);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Module extends Model
{
use HasFactory;
protected $guarded = ['id'];
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Note extends Model
{
use HasFactory;
protected $guarded = ['id'];
public function company()
{
return $this->belongsTo(Company::class);
}
public function scopeApplyFilters($query, array $filters)
{
$filters = collect($filters);
if ($filters->get('type')) {
$query->whereType($filters->get('type'));
}
if ($filters->get('search')) {
$query->whereSearch($filters->get('search'));
}
}
public function scopeWhereSearch($query, $search)
{
$query->where('name', 'LIKE', '%'.$search.'%');
}
public function scopeWhereType($query, $type)
{
return $query->where('type', $type);
}
public function scopeWhereCompany($query)
{
$query->where('notes.company_id', request()->header('company'));
}
}

View File

@@ -0,0 +1,474 @@
<?php
namespace Crater\Models;
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 Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Vinkla\Hashids\Facades\Hashids;
class Payment extends Model implements HasMedia
{
use HasFactory;
use InteractsWithMedia;
use GeneratesPdfTrait;
use HasCustomFieldsTrait;
public const PAYMENT_MODE_CHECK = 'CHECK';
public const PAYMENT_MODE_OTHER = 'OTHER';
public const PAYMENT_MODE_CASH = 'CASH';
public const PAYMENT_MODE_CREDIT_CARD = 'CREDIT_CARD';
public const PAYMENT_MODE_BANK_TRANSFER = 'BANK_TRANSFER';
protected $dates = ['created_at', 'updated_at', 'payment_date'];
protected $guarded = ['id'];
protected $appends = [
'formattedCreatedAt',
'formattedPaymentDate',
'paymentPdfUrl',
];
protected $casts = [
'notes' => 'string',
'exchange_rate' => 'float'
];
protected static function booted()
{
static::created(function ($payment) {
GeneratePaymentPdfJob::dispatch($payment);
});
static::updated(function ($payment) {
GeneratePaymentPdfJob::dispatch($payment, true);
});
}
public function setSettingsAttribute($value)
{
if ($value) {
$this->attributes['settings'] = json_encode($value);
}
}
public function getFormattedCreatedAtAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->created_at)->format($dateFormat);
}
public function getFormattedPaymentDateAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->payment_date)->format($dateFormat);
}
public function getPaymentPdfUrlAttribute()
{
return url('/payments/pdf/'.$this->unique_hash);
}
public function transaction()
{
return $this->belongsTo(Transaction::class);
}
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);
}
public function invoice()
{
return $this->belongsTo(Invoice::class);
}
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 sendPaymentData($data)
{
$data['payment'] = $this->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 [
'success' => true,
];
}
public static function createPayment($request)
{
$data = $request->getPaymentPayload();
if ($request->invoice_id) {
$invoice = Invoice::find($request->invoice_id);
$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) {
$payment->addCustomFields($customFields);
}
$payment = Payment::with([
'customer',
'invoice',
'paymentMethod',
'fields'
])->find($payment->id);
return $payment;
}
public function updatePayment($request)
{
$data = $request->getPaymentPayload();
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 && $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) {
$this->updateCustomFields($customFields);
}
$payment = Payment::with([
'customer',
'invoice',
'paymentMethod',
])
->find($this->id);
return $payment;
}
public static function deletePayments($ids)
{
foreach ($ids as $id) {
$payment = Payment::find($id);
if ($payment->invoice_id != null) {
$invoice = Invoice::find($payment->invoice_id);
$invoice->due_amount = ((int)$invoice->due_amount + (int)$payment->amount);
if ($invoice->due_amount == $invoice->total) {
$invoice->paid_status = Invoice::STATUS_UNPAID;
} else {
$invoice->paid_status = Invoice::STATUS_PARTIALLY_PAID;
}
$invoice->status = $invoice->getPreviousStatus();
$invoice->save();
}
$payment->delete();
}
return true;
}
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 scopePaymentNumber($query, $paymentNumber)
{
return $query->where('payments.payment_number', 'LIKE', '%'.$paymentNumber.'%');
}
public function scopePaymentMethod($query, $paymentMethodId)
{
return $query->where('payments.payment_method_id', $paymentMethodId);
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return $query->get();
}
return $query->paginate($limit);
}
public function scopeApplyFilters($query, array $filters)
{
$filters = collect($filters);
if ($filters->get('search')) {
$query->whereSearch($filters->get('search'));
}
if ($filters->get('payment_number')) {
$query->paymentNumber($filters->get('payment_number'));
}
if ($filters->get('payment_id')) {
$query->wherePayment($filters->get('payment_id'));
}
if ($filters->get('payment_method_id')) {
$query->paymentMethod($filters->get('payment_method_id'));
}
if ($filters->get('customer_id')) {
$query->whereCustomer($filters->get('customer_id'));
}
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->paymentsBetween($start, $end);
}
if ($filters->get('orderByField') || $filters->get('orderBy')) {
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'sequence_number';
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'desc';
$query->whereOrder($field, $orderBy);
}
}
public function scopePaymentsBetween($query, $start, $end)
{
return $query->whereBetween(
'payments.payment_date',
[$start->format('Y-m-d'), $end->format('Y-m-d')]
);
}
public function scopeWhereOrder($query, $orderByField, $orderBy)
{
$query->orderBy($orderByField, $orderBy);
}
public function scopeWherePayment($query, $payment_id)
{
$query->orWhere('id', $payment_id);
}
public function scopeWhereCompany($query)
{
$query->where('payments.company_id', request()->header('company'));
}
public function scopeWhereCustomer($query, $customer_id)
{
$query->where('payments.customer_id', $customer_id);
}
public function getPDFData()
{
$company = Company::find($this->company_id);
$locale = CompanySetting::getSetting('language', $company->id);
\App::setLocale($locale);
$logo = $company->logo_path;
view()->share([
'payment' => $this,
'company_address' => $this->getCompanyAddress(),
'billing_address' => $this->getCustomerBillingAddress(),
'notes' => $this->getNotes(),
'logo' => $logo ?? null,
]);
if (request()->has('preview')) {
return view('app.pdf.payment.payment');
}
return PDF::loadView('app.pdf.payment.payment');
}
public function getCompanyAddress()
{
if ($this->company && (! $this->company->address()->exists())) {
return false;
}
$format = CompanySetting::getSetting('payment_company_address_format', $this->company_id);
return $this->getFormattedString($format);
}
public function getCustomerBillingAddress()
{
if ($this->customer && (! $this->customer->billingAddress()->exists())) {
return false;
}
$format = CompanySetting::getSetting('payment_from_customer_address_format', $this->company_id);
return $this->getFormattedString($format);
}
public function getEmailAttachmentSetting()
{
$paymentAsAttachment = CompanySetting::getSetting('payment_email_attachment', $this->company_id);
if ($paymentAsAttachment == 'NO') {
return false;
}
return true;
}
public function getNotes()
{
return $this->getFormattedString($this->notes);
}
public function getEmailBody($body)
{
$values = array_merge($this->getFieldsArray(), $this->getExtraFields());
$body = strtr($body, $values);
return preg_replace('/{(.*?)}/', '', $body);
}
public function getExtraFields()
{
return [
'{PAYMENT_DATE}' => $this->formattedPaymentDate,
'{PAYMENT_MODE}' => $this->paymentMethod ? $this->paymentMethod->name : null,
'{PAYMENT_NUMBER}' => $this->payment_number,
'{PAYMENT_AMOUNT}' => $this->reference_number,
];
}
public static function generatePayment($transaction)
{
$invoice = Invoice::find($transaction->invoice_id);
$serial = (new SerialNumberFormatter())
->setModel(new Payment())
->setCompany($invoice->company_id)
->setCustomer($invoice->customer_id)
->setNextNumbers();
$data['payment_number'] = $serial->getNextNumber();
$data['payment_date'] = Carbon::now()->format('y-m-d');
$data['amount'] = $invoice->total;
$data['invoice_id'] = $invoice->id;
$data['payment_method_id'] = request()->payment_method_id;
$data['customer_id'] = $invoice->customer_id;
$data['exchange_rate'] = $invoice->exchange_rate;
$data['base_amount'] = $data['amount'] * $data['exchange_rate'];
$data['currency_id'] = $invoice->currency_id;
$data['company_id'] = $invoice->company_id;
$data['transaction_id'] = $transaction->id;
$payment = Payment::create($data);
$payment->unique_hash = Hashids::connection(Payment::class)->encode($payment->id);
$payment->sequence_number = $serial->nextSequenceNumber;
$payment->customer_sequence_number = $serial->nextCustomerSequenceNumber;
$payment->save();
$invoice->subtractInvoicePayment($invoice->total);
return $payment;
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PaymentMethod extends Model
{
use HasFactory;
protected $guarded = [
'id'
];
public const TYPE_GENERAL = 'GENERAL';
public const TYPE_MODULE = 'MODULE';
protected $casts = [
'settings' => 'array',
'use_test_env' => 'boolean'
];
public function setSettingsAttribute($value)
{
$this->attributes['settings'] = json_encode($value);
}
public function payments()
{
return $this->hasMany(Payment::class);
}
public function expenses()
{
return $this->hasMany(Expense::class);
}
public function company()
{
return $this->belongsTo(Company::class);
}
public function scopeWhereCompanyId($query, $id)
{
$query->where('company_id', $id);
}
public function scopeWhereCompany($query)
{
$query->where('company_id', request()->header('company'));
}
public function scopeWherePaymentMethod($query, $payment_id)
{
$query->orWhere('id', $payment_id);
}
public function scopeWhereSearch($query, $search)
{
$query->where('name', 'LIKE', '%'.$search.'%');
}
public function scopeApplyFilters($query, array $filters)
{
$filters = collect($filters);
if ($filters->get('method_id')) {
$query->wherePaymentMethod($filters->get('method_id'));
}
if ($filters->get('company_id')) {
$query->whereCompany($filters->get('company_id'));
}
if ($filters->get('search')) {
$query->whereSearch($filters->get('search'));
}
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return $query->get();
}
return $query->paginate($limit);
}
public static function createPaymentMethod($request)
{
$data = $request->getPaymentMethodPayload();
$paymentMethod = self::create($data);
return $paymentMethod;
}
public static function getSettings($id)
{
$settings = PaymentMethod::find($id)
->settings;
return $settings;
}
}

View File

@@ -0,0 +1,430 @@
<?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',
];
protected $dates = [
'starts_at'
];
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 (Carbon::now()->lessThan($this->starts_at)) {
return;
}
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();
$days = CompanySetting::getSetting('invoice_due_date_days', $this->company_id);
if (! $days || $days == "null") {
$days = 7;
}
$newInvoice['creator_id'] = $this->creator_id;
$newInvoice['invoice_date'] = Carbon::today()->format('Y-m-d');
$newInvoice['due_date'] = Carbon::today()->addDays($days)->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['sales_tax_type'] = $this->sales_tax_type;
$newInvoice['sales_tax_address_type'] = $this->sales_tax_address_type;
$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

@@ -0,0 +1,64 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Setting extends Model
{
use HasFactory;
protected $fillable = ['option', 'value'];
public static function setSetting($key, $setting)
{
$old = self::whereOption($key)->first();
if ($old) {
$old->value = $setting;
$old->save();
return;
}
$set = new Setting();
$set->option = $key;
$set->value = $setting;
$set->save();
}
public static function setSettings($settings)
{
foreach ($settings as $key => $value) {
self::updateOrCreate(
[
'option' => $key,
],
[
'option' => $key,
'value' => $value,
]
);
}
}
public static function getSetting($key)
{
$setting = static::whereOption($key)->first();
if ($setting) {
return $setting->value;
} else {
return null;
}
}
public static function getSettings($settings)
{
return static::whereIn('option', $settings)
->get()->mapWithKeys(function ($item) {
return [$item['option'] => $item['value']];
});
}
}

104
crater/app/Models/Tax.php Normal file
View File

@@ -0,0 +1,104 @@
<?php
namespace Crater\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
class Tax extends Model
{
use HasFactory;
protected $guarded = [
'id'
];
protected $casts = [
'amount' => 'integer',
'percent' => 'float',
];
public function taxType()
{
return $this->belongsTo(TaxType::class);
}
public function invoice()
{
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);
}
public function estimateItem()
{
return $this->belongsTo(EstimateItem::class);
}
public function item()
{
return $this->belongsTo(Item::class);
}
public function scopeWhereCompany($query, $company_id)
{
$query->where('company_id', $company_id);
}
public function scopeTaxAttributes($query)
{
$query->select(
DB::raw('sum(base_amount) as total_tax_amount, tax_type_id')
)->groupBy('tax_type_id');
}
public function scopeInvoicesBetween($query, $start, $end)
{
$query->whereHas('invoice', function ($query) use ($start, $end) {
$query->where('paid_status', Invoice::STATUS_PAID)
->whereBetween(
'invoice_date',
[$start->format('Y-m-d'), $end->format('Y-m-d')]
);
})
->orWhereHas('invoiceItem.invoice', function ($query) use ($start, $end) {
$query->where('paid_status', Invoice::STATUS_PAID)
->whereBetween(
'invoice_date',
[$start->format('Y-m-d'), $end->format('Y-m-d')]
);
});
}
public function scopeWhereInvoicesFilters($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);
}
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TaxType extends Model
{
use HasFactory;
protected $guarded = [
'id',
];
protected $casts = [
'percent' => 'float',
'compound_tax' => 'boolean'
];
public const TYPE_GENERAL = 'GENERAL';
public const TYPE_MODULE = 'MODULE';
public function taxes()
{
return $this->hasMany(Tax::class);
}
public function company()
{
return $this->belongsTo(Company::class);
}
public function scopeWhereCompany($query)
{
$query->where('company_id', request()->header('company'));
}
public function scopeWhereTaxType($query, $tax_type_id)
{
$query->orWhere('id', $tax_type_id);
}
public function scopeApplyFilters($query, array $filters)
{
$filters = collect($filters);
if ($filters->get('tax_type_id')) {
$query->whereTaxType($filters->get('tax_type_id'));
}
if ($filters->get('company_id')) {
$query->whereCompany($filters->get('company_id'));
}
if ($filters->get('search')) {
$query->whereSearch($filters->get('search'));
}
if ($filters->get('orderByField') || $filters->get('orderBy')) {
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'payment_number';
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'asc';
$query->whereOrder($field, $orderBy);
}
}
public function scopeWhereOrder($query, $orderByField, $orderBy)
{
$query->orderBy($orderByField, $orderBy);
}
public function scopeWhereSearch($query, $search)
{
$query->where('name', 'LIKE', '%'.$search.'%');
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return $query->get();
}
return $query->paginate($limit);
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace Crater\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Vinkla\Hashids\Facades\Hashids;
class Transaction extends Model
{
use HasFactory;
protected $guarded = [
'id'
];
protected $dates = [
'transaction_date'
];
public const PENDING = 'PENDING';
public const FAILED = 'FAILED';
public const SUCCESS = 'SUCCESS';
public function payments()
{
return $this->hasMany(Payment::class);
}
public function invoice()
{
return $this->belongsTo(Invoice::class);
}
public function company()
{
return $this->belongsTo(Company::class);
}
public function completeTransaction()
{
$this->status = self::SUCCESS;
$this->save();
}
public function failedTransaction()
{
$this->status = self::FAILED;
$this->save();
}
public static function createTransaction($data)
{
$transaction = self::create($data);
$transaction->unique_hash = Hashids::connection(Transaction::class)->encode($transaction->id);
$transaction->save();
return $transaction;
}
public function isExpired()
{
$linkexpiryDays = CompanySetting::getSetting('link_expiry_days', $this->company_id);
$checkExpiryLinks = CompanySetting::getSetting('automatically_expire_public_links', $this->company_id);
$expiryDate = $this->updated_at->addDays($linkexpiryDays);
if ($checkExpiryLinks == 'YES' && $this->status == self::SUCCESS && Carbon::now()->format('Y-m-d') > $expiryDate->format('Y-m-d')) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Unit extends Model
{
use HasFactory;
protected $fillable = ['name', 'company_id'];
public function items()
{
return $this->hasMany(Item::class);
}
public function company()
{
return $this->belongsTo(Company::class);
}
public function scopeWhereCompany($query)
{
$query->where('company_id', request()->header('company'));
}
public function scopeWhereUnit($query, $unit_id)
{
$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'));
}
if ($filters->get('company_id')) {
$query->whereCompany($filters->get('company_id'));
}
return $query;
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return $query->get();
}
return $query->paginate($limit);
}
}

432
crater/app/Models/User.php Normal file
View File

@@ -0,0 +1,432 @@
<?php
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;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
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;
class User extends Authenticatable implements HasMedia
{
use HasApiTokens;
use Notifiable;
use InteractsWithMedia;
use HasCustomFieldsTrait;
use HasFactory;
use HasRolesAndAbilities;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $guarded = [
'id'
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
protected $with = [
'currency',
];
protected $appends = [
'formattedCreatedAt',
'avatar',
];
/**
* Find the user instance for the given username.
*
* @param string $username
* @return \App\User
*/
public function findForPassport($username)
{
return $this->where('email', $username)->first();
}
public function setPasswordAttribute($value)
{
if ($value != null) {
$this->attributes['password'] = bcrypt($value);
}
}
public function isSuperAdminOrAdmin()
{
return ($this->role == 'super admin') || ($this->role == 'admin');
}
public static function login($request)
{
$remember = $request->remember;
$email = $request->email;
$password = $request->password;
return (\Auth::attempt(['email' => $email, 'password' => $password], $remember));
}
public function getFormattedCreatedAtAttribute($value)
{
$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, 'creator_id');
}
public function customers()
{
return $this->hasMany(Customer::class, 'creator_id');
}
public function recurringInvoices()
{
return $this->hasMany(RecurringInvoice::class, 'creator_id');
}
public function currency()
{
return $this->belongsTo(Currency::class, 'currency_id');
}
public function creator()
{
return $this->belongsTo('Crater\Models\User', 'creator_id');
}
public function companies()
{
return $this->belongsToMany(Company::class, 'user_company', 'user_id', 'company_id');
}
public function expenses()
{
return $this->hasMany(Expense::class, 'creator_id');
}
public function payments()
{
return $this->hasMany(Payment::class, 'creator_id');
}
public function invoices()
{
return $this->hasMany(Invoice::class, 'creator_id');
}
public function items()
{
return $this->hasMany(Item::class, 'creator_id');
}
public function settings()
{
return $this->hasMany(UserSetting::class, 'user_id');
}
public function addresses()
{
return $this->hasMany(Address::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);
}
/**
* Override the mail body for reset password notification mail.
*/
public function sendPasswordResetNotification($token)
{
$this->notify(new MailResetPasswordNotification($token));
}
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 scopeWhereContactName($query, $contactName)
{
return $query->where('contact_name', 'LIKE', '%'.$contactName.'%');
}
public function scopeWhereDisplayName($query, $displayName)
{
return $query->where('name', 'LIKE', '%'.$displayName.'%');
}
public function scopeWherePhone($query, $phone)
{
return $query->where('phone', 'LIKE', '%'.$phone.'%');
}
public function scopeWhereEmail($query, $email)
{
return $query->where('email', 'LIKE', '%'.$email.'%');
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return $query->get();
}
return $query->paginate($limit);
}
public function scopeApplyFilters($query, array $filters)
{
$filters = collect($filters);
if ($filters->get('search')) {
$query->whereSearch($filters->get('search'));
}
if ($filters->get('display_name')) {
$query->whereDisplayName($filters->get('display_name'));
}
if ($filters->get('email')) {
$query->whereEmail($filters->get('email'));
}
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);
}
}
public function scopeWhereSuperAdmin($query)
{
$query->orWhere('role', 'super admin');
}
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 getAvatarAttribute()
{
$avatar = $this->getMedia('admin_avatar')->first();
if ($avatar) {
return asset($avatar->getUrl());
}
return 0;
}
public function setSettings($settings)
{
foreach ($settings as $key => $value) {
$this->settings()->updateOrCreate(
[
'key' => $key,
],
[
'key' => $key,
'value' => $value,
]
);
}
}
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)
{
return $this->settings()->whereIn('key', $settings)->get()->mapWithKeys(function ($item) {
return [$item['key'] => $item['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 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;
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class UserSetting extends Model
{
use HasFactory;
protected $guarded = ['id'];
public function user()
{
return $this->belongsTo(User::class);
}
}