Moved to _dev
This commit is contained in:
42
crater/app/Models/Address.php
Normal file
42
crater/app/Models/Address.php
Normal 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);
|
||||
}
|
||||
}
|
||||
400
crater/app/Models/Company.php
Normal file
400
crater/app/Models/Company.php
Normal 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;
|
||||
}
|
||||
}
|
||||
66
crater/app/Models/CompanySetting.php
Normal file
66
crater/app/Models/CompanySetting.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
crater/app/Models/Country.php
Normal file
16
crater/app/Models/Country.php
Normal 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);
|
||||
}
|
||||
}
|
||||
15
crater/app/Models/Currency.php
Normal file
15
crater/app/Models/Currency.php
Normal 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'
|
||||
];
|
||||
}
|
||||
121
crater/app/Models/CustomField.php
Normal file
121
crater/app/Models/CustomField.php
Normal 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;
|
||||
}
|
||||
}
|
||||
55
crater/app/Models/CustomFieldValue.php
Normal file
55
crater/app/Models/CustomFieldValue.php
Normal 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();
|
||||
}
|
||||
}
|
||||
341
crater/app/Models/Customer.php
Normal file
341
crater/app/Models/Customer.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
33
crater/app/Models/EmailLog.php
Normal file
33
crater/app/Models/EmailLog.php
Normal 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;
|
||||
}
|
||||
}
|
||||
539
crater/app/Models/Estimate.php
Normal file
539
crater/app/Models/Estimate.php
Normal 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;
|
||||
}
|
||||
}
|
||||
46
crater/app/Models/EstimateItem.php
Normal file
46
crater/app/Models/EstimateItem.php
Normal 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);
|
||||
}
|
||||
}
|
||||
41
crater/app/Models/ExchangeRateLog.php
Normal file
41
crater/app/Models/ExchangeRateLog.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Crater\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ExchangeRateLog extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [
|
||||
'id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'exchange_rate' => 'float'
|
||||
];
|
||||
|
||||
public function currency()
|
||||
{
|
||||
return $this->belongsTo(Currency::class);
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public static function addExchangeRateLog($model)
|
||||
{
|
||||
$data = [
|
||||
'exchange_rate' => $model->exchange_rate,
|
||||
'company_id' => $model->company_id,
|
||||
'base_currency_id' => $model->currency_id,
|
||||
'currency_id' => CompanySetting::getSetting('currency', $model->company_id),
|
||||
];
|
||||
|
||||
return self::create($data);
|
||||
}
|
||||
}
|
||||
166
crater/app/Models/ExchangeRateProvider.php
Normal file
166
crater/app/Models/ExchangeRateProvider.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace Crater\Models;
|
||||
|
||||
use Crater\Http\Requests\ExchangeRateProviderRequest;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class ExchangeRateProvider extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [
|
||||
'id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'currencies' => 'array',
|
||||
'driver_config' => 'array',
|
||||
'active' => 'boolean'
|
||||
];
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function setCurrenciesAttribute($value)
|
||||
{
|
||||
$this->attributes['currencies'] = json_encode($value);
|
||||
}
|
||||
|
||||
public function setDriverConfigAttribute($value)
|
||||
{
|
||||
$this->attributes['driver_config'] = json_encode($value);
|
||||
}
|
||||
|
||||
public function scopeWhereCompany($query)
|
||||
{
|
||||
$query->where('exchange_rate_providers.company_id', request()->header('company'));
|
||||
}
|
||||
|
||||
public static function createFromRequest(ExchangeRateProviderRequest $request)
|
||||
{
|
||||
$exchangeRateProvider = self::create($request->getExchangeRateProviderPayload());
|
||||
|
||||
return $exchangeRateProvider;
|
||||
}
|
||||
|
||||
public function updateFromRequest(ExchangeRateProviderRequest $request)
|
||||
{
|
||||
$this->update($request->getExchangeRateProviderPayload());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public static function checkActiveCurrencies($request)
|
||||
{
|
||||
$query = ExchangeRateProvider::whereJsonContains('currencies', $request->currencies)
|
||||
->where('active', true)
|
||||
->get();
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function checkUpdateActiveCurrencies($request)
|
||||
{
|
||||
$query = ExchangeRateProvider::where('active', $request->active)
|
||||
->where('id', '<>', $this->id)
|
||||
->whereJsonContains('currencies', $request->currencies)
|
||||
->get();
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public static function checkExchangeRateProviderStatus($request)
|
||||
{
|
||||
switch ($request['driver']) {
|
||||
case 'currency_freak':
|
||||
$url = "https://api.currencyfreaks.com/latest?apikey=".$request['key']."&symbols=INR&base=USD";
|
||||
$response = Http::get($url)->json();
|
||||
|
||||
if (array_key_exists('success', $response)) {
|
||||
if ($response["success"] == false) {
|
||||
return respondJson($response["error"]["message"], $response["error"]["message"]);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'exchangeRate' => array_values($response["rates"]),
|
||||
], 200);
|
||||
|
||||
break;
|
||||
|
||||
case 'currency_layer':
|
||||
$url = "http://api.currencylayer.com/live?access_key=".$request['key']."&source=INR¤cies=USD";
|
||||
$response = Http::get($url)->json();
|
||||
|
||||
if (array_key_exists('success', $response)) {
|
||||
if ($response["success"] == false) {
|
||||
return respondJson($response["error"]["info"], $response["error"]["info"]);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'exchangeRate' => array_values($response['quotes']),
|
||||
], 200);
|
||||
|
||||
break;
|
||||
|
||||
case 'open_exchange_rate':
|
||||
$url = "https://openexchangerates.org/api/latest.json?app_id=".$request['key']."&base=INR&symbols=USD";
|
||||
$response = Http::get($url)->json();
|
||||
|
||||
if (array_key_exists("error", $response)) {
|
||||
return respondJson($response['message'], $response["description"]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'exchangeRate' => array_values($response["rates"]),
|
||||
], 200);
|
||||
|
||||
break;
|
||||
|
||||
case 'currency_converter':
|
||||
$url = self::getCurrencyConverterUrl($request['driver_config']);
|
||||
$url = $url."/api/v7/convert?apiKey=".$request['key'];
|
||||
|
||||
$query = "INR_USD";
|
||||
$url = $url."&q={$query}"."&compact=y";
|
||||
$response = Http::get($url)->json();
|
||||
|
||||
return response()->json([
|
||||
'exchangeRate' => array_values($response[$query]),
|
||||
], 200);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getCurrencyConverterUrl($data)
|
||||
{
|
||||
switch ($data['type']) {
|
||||
case 'PREMIUM':
|
||||
return "https://api.currconv.com";
|
||||
|
||||
break;
|
||||
|
||||
case 'PREPAID':
|
||||
return "https://prepaid.currconv.com";
|
||||
|
||||
break;
|
||||
|
||||
case 'FREE':
|
||||
return "https://free.currconv.com";
|
||||
|
||||
break;
|
||||
|
||||
case 'DEDICATED':
|
||||
return $data['url'];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
279
crater/app/Models/Expense.php
Normal file
279
crater/app/Models/Expense.php
Normal 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;
|
||||
}
|
||||
}
|
||||
84
crater/app/Models/ExpenseCategory.php
Normal file
84
crater/app/Models/ExpenseCategory.php
Normal 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);
|
||||
}
|
||||
}
|
||||
204
crater/app/Models/FileDisk.php
Normal file
204
crater/app/Models/FileDisk.php
Normal 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;
|
||||
}
|
||||
}
|
||||
725
crater/app/Models/Invoice.php
Normal file
725
crater/app/Models/Invoice.php
Normal 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;
|
||||
}
|
||||
}
|
||||
81
crater/app/Models/InvoiceItem.php
Normal file
81
crater/app/Models/InvoiceItem.php
Normal 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
174
crater/app/Models/Item.php
Normal 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);
|
||||
}
|
||||
}
|
||||
13
crater/app/Models/Module.php
Normal file
13
crater/app/Models/Module.php
Normal 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'];
|
||||
}
|
||||
46
crater/app/Models/Note.php
Normal file
46
crater/app/Models/Note.php
Normal 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'));
|
||||
}
|
||||
}
|
||||
474
crater/app/Models/Payment.php
Normal file
474
crater/app/Models/Payment.php
Normal 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;
|
||||
}
|
||||
}
|
||||
106
crater/app/Models/PaymentMethod.php
Normal file
106
crater/app/Models/PaymentMethod.php
Normal 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;
|
||||
}
|
||||
}
|
||||
430
crater/app/Models/RecurringInvoice.php
Normal file
430
crater/app/Models/RecurringInvoice.php
Normal 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;
|
||||
}
|
||||
}
|
||||
64
crater/app/Models/Setting.php
Normal file
64
crater/app/Models/Setting.php
Normal 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
104
crater/app/Models/Tax.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
85
crater/app/Models/TaxType.php
Normal file
85
crater/app/Models/TaxType.php
Normal 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);
|
||||
}
|
||||
}
|
||||
75
crater/app/Models/Transaction.php
Normal file
75
crater/app/Models/Transaction.php
Normal 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;
|
||||
}
|
||||
}
|
||||
66
crater/app/Models/Unit.php
Normal file
66
crater/app/Models/Unit.php
Normal 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
432
crater/app/Models/User.php
Normal 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;
|
||||
}
|
||||
}
|
||||
18
crater/app/Models/UserSetting.php
Normal file
18
crater/app/Models/UserSetting.php
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user