build version 400

This commit is contained in:
Mohit Panjwani
2020-12-02 17:54:08 +05:30
parent 326508e567
commit 89ee58590c
963 changed files with 62887 additions and 48868 deletions

39
app/Models/Address.php Normal file
View File

@ -0,0 +1,39 @@
<?php
namespace Crater\Models;
use Crater\Models\Company;
use Illuminate\Database\Eloquent\Model;
use Crater\Models\User;
use Crater\Models\Country;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Address extends Model
{
use HasFactory;
const BILLING_TYPE = 'billing';
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 company()
{
return $this->belongsTo(Company::class);
}
public function country()
{
return $this->belongsTo(Country::class);
}
}

45
app/Models/Company.php Normal file
View File

@ -0,0 +1,45 @@
<?php
namespace Crater\Models;
use Crater\Models\Address;
use Illuminate\Database\Eloquent\Model;
use Crater\Models\User;
use Crater\Models\CompanySetting;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Company extends Model implements HasMedia
{
use InteractsWithMedia;
use HasFactory;
protected $fillable = ['name', 'logo', 'unique_hash'];
protected $appends=['logo'];
public function getLogoAttribute()
{
$logo = $this->getMedia('logo')->first();
if ($logo) {
return asset($logo->getUrl());
}
return ;
}
public function user()
{
return $this->hasOne(User::class);
}
public function settings()
{
return $this->hasMany(CompanySetting::class);
}
public function address()
{
return $this->hasOne(Address::class);
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace Crater\Models;
use Crater\Models\Company;
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 getSettings($settings, $company_id)
{
$settings = static::whereIn('option', $settings)->whereCompany($company_id)->get();
$companySettings = [];
foreach ($settings as $setting) {
$companySettings[$setting->option] = $setting->value;
}
return $companySettings;
}
public static function getSetting($key, $company_id)
{
$setting = static::whereOption($key)->whereCompany($company_id)->first();
if ($setting) {
return $setting->value;
} else {
return null;
}
}
}

16
app/Models/Country.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace Crater\Models;
use Crater\Models\Address;
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);
}
}

20
app/Models/Currency.php Normal file
View File

@ -0,0 +1,20 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Currency extends Model
{
use HasFactory;
protected $fillable = [
'name',
'code',
'symbol',
'precision',
'thousand_separator',
'decimal_separator',
'position'
];
}

152
app/Models/CustomField.php Normal file
View File

@ -0,0 +1,152 @@
<?php
namespace Crater\Models;
use Carbon\Carbon;
use Crater\Models\CustomFieldValue;
use Crater\Models\CompanySetting;
use Illuminate\Database\Eloquent\Model;
use Crater\Models\Company;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class CustomField extends Model
{
use HasFactory;
protected $guarded = [
'id'
];
protected $appends = [
'defaultAnswer'
];
protected $casts = [
'options' => 'array'
];
public function setDateAnswerAttribute($value)
{
if ($value && $value != null) {
$this->attributes['date_answer'] = Carbon::createFromFormat('Y-m-d', $value);
}
}
public function setTimeAnswerAttribute($value)
{
if ($value && $value != null) {
$this->attributes['time_answer'] = date("H:i:s", strtotime($value));
}
}
public function setDateTimeAnswerAttribute($value)
{
if ($value && $value != null) {
$this->attributes['date_time_answer'] = Carbon::createFromFormat('Y-m-d H:i', $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 company()
{
return $this->belongsTo(Company::class);
}
public function customFieldValue()
{
return $this->hasMany(CustomFieldValue::class);
}
public function scopeWhereCompany($query, $company_id)
{
$query->where('custom_fields.company_id', $company_id);
}
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 collect(['data' => $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->label);
return CustomField::create($data);
}
public function updateCustomField($request)
{
$oldSlug = $this->slug;
$data = $request->validated();
$data[getCustomFieldValueKey($request->type)] = $request->default_answer;
$data['slug'] = clean_slug($request->model_type, $request->label, $this->id);
$this->update($data);
if ($oldSlug !== $data['slug']) {
$settings = [
'invoice_company_address_format',
'invoice_shipping_address_format',
'invoice_billing_address_format',
'estimate_company_address_format',
'estimate_shipping_address_format',
'estimate_billing_address_format',
'payment_company_address_format',
'payment_from_customer_address_format'
];
$settings = CompanySetting::getSettings($settings, $this->company_id);
foreach ($settings as $key => $value) {
$settings[$key] = str_replace($oldSlug, $data['slug'], $value);
}
CompanySetting::setSettings($settings, $this->company_id);
}
return $this;
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace Crater\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Crater\Models\Company;
use Crater\Models\CustomField;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class CustomFieldValue extends Model
{
use HasFactory;
protected $guarded = [
'id'
];
protected $appends = [
'defaultAnswer'
];
public function setDateAnswerAttribute($value)
{
$this->attributes['date_answer'] = Carbon::createFromFormat('Y-m-d', $value);
}
public function setTimeAnswerAttribute($value)
{
$this->attributes['time_answer'] = date("H:i:s", strtotime($value));
}
public function setDateTimeAnswerAttribute($value)
{
$this->attributes['date_time_answer'] = Carbon::createFromFormat('Y-m-d H:i', $value);
}
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();
}
}

18
app/Models/EmailLog.php Normal file
View File

@ -0,0 +1,18 @@
<?php
namespace Crater\Models;
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();
}
}

496
app/Models/Estimate.php Normal file
View File

@ -0,0 +1,496 @@
<?php
namespace Crater\Models;
use Crater\Models\EstimateTemplate;
use Crater\Models\Company;
use Crater\Models\Tax;
use Illuminate\Database\Eloquent\Model;
use Crater\Models\CompanySetting;
use Carbon\Carbon;
use Crater\Mail\SendEstimateMail;
use Crater\Traits\HasCustomFieldsTrait;
use Vinkla\Hashids\Facades\Hashids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Crater\Traits\GeneratesPdfTrait;
use Barryvdh\DomPDF\Facade as PDF;
use Illuminate\Support\Facades\Auth;
class Estimate extends Model implements HasMedia
{
use HasFactory, InteractsWithMedia, GeneratesPdfTrait;
use HasCustomFieldsTrait;
const STATUS_DRAFT = 'DRAFT';
const STATUS_SENT = 'SENT';
const STATUS_VIEWED = 'VIEWED';
const STATUS_EXPIRED = 'EXPIRED';
const STATUS_ACCEPTED = 'ACCEPTED';
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',
];
public function setEstimateDateAttribute($value)
{
if ($value) {
$this->attributes['estimate_date'] = Carbon::createFromFormat('Y-m-d', $value);
}
}
public function setExpiryDateAttribute($value)
{
if ($value) {
$this->attributes['expiry_date'] = Carbon::createFromFormat('Y-m-d', $value);
}
}
public function getEstimatePdfUrlAttribute()
{
return url('/estimates/pdf/' . $this->unique_hash);
}
public static function getNextEstimateNumber($value)
{
// Get the last created order
$lastOrder = Estimate::where('estimate_number', 'LIKE', $value . '-%')
->orderBy('created_at', 'desc')
->first();
if (!$lastOrder) {
// We get here if there is no order at all
// If there is no number set it to 0, which will be 1 at the end.
$number = 0;
} else {
$number = explode("-", $lastOrder->estimate_number);
$number = $number[1];
}
// If we have ORD000001 in the database then we only want the number
// So the substr returns this 000001
// Add the string in front and higher up the number.
// the %05d part makes sure that there are always 6 numbers in the string.
// so it adds the missing zero's when needed.
return sprintf('%06d', intval($number) + 1);
}
public function emailLogs()
{
return $this->morphMany('App\Models\EmailLog', 'mailable');
}
public function items()
{
return $this->hasMany('Crater\Models\EstimateItem');
}
public function user()
{
return $this->belongsTo('Crater\Models\User', 'user_id');
}
public function creator()
{
return $this->belongsTo('Crater\Models\User', 'creator_id');
}
public function company()
{
return $this->belongsTo('Crater\Models\Company');
}
public function taxes()
{
return $this->hasMany(Tax::class);
}
public function estimateTemplate()
{
return $this->belongsTo('Crater\Models\EstimateTemplate');
}
public function getEstimateNumAttribute()
{
$position = $this->strposX($this->estimate_number, "-", 1) + 1;
return substr($this->estimate_number, $position);
}
public function getEstimatePrefixAttribute()
{
$prefix = explode("-", $this->estimate_number)[0];
return $prefix;
}
private function strposX($haystack, $needle, $number)
{
if ($number == '1') {
return strpos($haystack, $needle);
} elseif ($number > '1') {
return strpos(
$haystack,
$needle,
$this->strposX($haystack, $needle, $number - 1) + strlen($needle)
);
} else {
return error_log('Error: Value for parameter $number is out of range');
}
}
public function getFormattedExpiryDateAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
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', $estimateNumber);
}
public function scopeWhereEstimate($query, $estimate_id)
{
$query->orWhere('id', $estimate_id);
}
public function scopeWhereSearch($query, $search)
{
foreach (explode(' ', $search) as $term) {
$query->whereHas('user', 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') : 'estimate_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 scopeWhereCompany($query, $company_id)
{
$query->where('estimates.company_id', $company_id);
}
public function scopeWhereCustomer($query, $customer_id)
{
$query->where('estimates.user_id', $customer_id);
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return collect(['data' => $query->get()]);
}
return $query->paginate($limit);
}
public static function createEstimate($request)
{
$data = $request->except(['items', 'taxes']);
$data['creator_id'] = Auth::id();
$data['status'] = self::STATUS_DRAFT;
$data['unique_hash'] = str_random(60);
$data['company_id'] = $request->header('company');
$data['tax_per_item'] = CompanySetting::getSetting(
'tax_per_item',
$request->header('company')
) ?? 'NO';
$data['discount_per_item'] = CompanySetting::getSetting(
'discount_per_item',
$request->header('company')
) ?? 'NO';
if ($request->has('estimateSend')) {
$data['status'] = self::STATUS_SENT;
}
$estimate = self::create($data);
$estimate->unique_hash = Hashids::connection(Estimate::class)->encode($estimate->id);
$estimate->save();
self::createItems($estimate, $request);
if ($request->has('taxes') && (!empty($request->taxes))) {
self::createTaxes($estimate, $request);
}
$customFields = $request->customFields;
if ($customFields) {
$estimate->addCustomFields($customFields);
}
return Estimate::with([
'items.taxes',
'user',
'estimateTemplate',
'taxes'
])
->find($estimate->id);
}
public function updateEstimate($request)
{
$data = $request->except(['items', 'taxes']);
$this->update($data);
$this->items()->delete();
$this->taxes()->delete();
self::createItems($this, $request);
if ($request->has('taxes') && (!empty($request->taxes))) {
self::createTaxes($this, $request);
}
if ($request->customFields) {
$this->updateCustomFields($request->customFields);
}
return Estimate::with([
'items.taxes',
'user',
'estimateTemplate',
'taxes'
])
->find($this->id);
}
public static function createItems($estimate, $request)
{
$estimateItems = $request->items;
foreach ($estimateItems as $estimateItem) {
$estimateItem['company_id'] = $request->header('company');
$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);
}
}
}
}
}
public static function createTaxes($estimate, $request)
{
$estimateTaxes = $request->taxes;
foreach ($estimateTaxes as $tax) {
if (gettype($tax['amount']) !== "NULL") {
$tax['company_id'] = $request->header('company');
$estimate->taxes()->create($tax);
}
}
}
public function send($data)
{
$data['estimate'] = $this->toArray();
$data['user'] = $this->user->toArray();
$data['company'] = $this->company->toArray();
$data['body'] = $this->getEmailBody($data['body']);
\Mail::to($data['to'])->send(new SendEstimateMail($data));
if ($this->status == Estimate::STATUS_DRAFT) {
$this->status = Estimate::STATUS_SENT;
$this->save();
}
return [
'success' => true
];
}
public function getPDFData()
{
$taxTypes = [];
$taxes = [];
$labels = [];
if ($this->tax_per_item === 'YES') {
foreach ($this->items as $item) {
foreach ($item->taxes as $tax) {
if (!in_array($tax->name, $taxTypes)) {
array_push($taxTypes, $tax->name);
array_push($labels, $tax->name . ' (' . $tax->percent . '%)');
}
}
}
foreach ($taxTypes as $taxType) {
$total = 0;
foreach ($this->items as $item) {
foreach ($item->taxes as $tax) {
if ($tax->name == $taxType) {
$total += $tax->amount;
}
}
}
array_push($taxes, $total);
}
}
$estimateTemplate = EstimateTemplate::find($this->estimate_template_id);
$company = Company::find($this->company_id);
$logo = $company->getMedia('logo')->first();
if ($logo) {
$logo = $logo->getFullUrl();
}
view()->share([
'estimate' => $this,
'logo' => $logo ?? null,
'company_address' => $this->getCompanyAddress(),
'shipping_address' => $this->getCustomerShippingAddress(),
'billing_address' => $this->getCustomerBillingAddress(),
'notes' => $this->getNotes(),
'labels' => $labels,
'taxes' => $taxes
]);
return PDF::loadView('app.pdf.estimate.' . $estimateTemplate->view);
}
public function getCompanyAddress()
{
$format = CompanySetting::getSetting('estimate_company_address_format', $this->company_id);
return $this->getFormattedString($format);
}
public function getCustomerShippingAddress()
{
$format = CompanySetting::getSetting('estimate_shipping_address_format', $this->company_id);
return $this->getFormattedString($format);
}
public function getCustomerBillingAddress()
{
$format = CompanySetting::getSetting('estimate_billing_address_format', $this->company_id);
return $this->getFormattedString($format);
}
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 [
'{ESTIMATE_DATE}' => $this->formattedEstimateDate,
'{ESTIMATE_EXPIRY_DATE}' => $this->formattedExpiryDate,
'{ESTIMATE_NUMBER}' => $this->estimate_number,
'{ESTIMATE_REF_NUMBER}' => $this->reference_number,
'{ESTIMATE_LINK}' => url('/customer/estimates/pdf/' . $this->unique_hash)
];
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Crater\Models;
use Crater\Models\Item;
use Crater\Models\Estimate;
use Crater\Models\Tax;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class EstimateItem extends Model
{
use HasFactory;
protected $fillable = [
'estimate_id',
'name',
'item_id',
'description',
'quantity',
'company_id',
'price',
'discount_type',
'discount_val',
'tax',
'total',
'discount'
];
protected $casts = [
'price' => 'integer',
'total' => 'integer',
'discount' => 'float',
'quantity' => 'float',
'discount_val' => 'integer',
'tax' => 'integer'
];
public function estimate()
{
return $this->belongsTo(Estimate::class);
}
public function item()
{
return $this->belongsTo(Item::class);
}
public function taxes()
{
return $this->hasMany(Tax::class);
}
public function scopeWhereCompany($query, $company_id)
{
$query->where('company_id', $company_id);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Model;
use Crater\Models\Estimate;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class EstimateTemplate extends Model
{
use HasFactory;
protected $fillable = ['path', 'view', 'name'];
public function estimates()
{
return $this->hasMany(Estimate::class);
}
public function getPathAttribute($value)
{
return url($value);
}
}

222
app/Models/Expense.php Normal file
View File

@ -0,0 +1,222 @@
<?php
namespace Crater\Models;
use Crater\Models\CompanySetting;
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Crater\Models\ExpenseCategory;
use Crater\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Crater\Traits\HasCustomFieldsTrait;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Facades\Auth;
class Expense extends Model implements HasMedia
{
use HasFactory;
use InteractsWithMedia, HasCustomFieldsTrait;
protected $guarded = ['id'];
protected $appends = [
'formattedExpenseDate',
'formattedCreatedAt',
'receipt'
];
public function setExpenseDateAttribute($value)
{
if ($value) {
$this->attributes['expense_date'] = Carbon::createFromFormat('Y-m-d', $value);
}
}
public function category()
{
return $this->belongsTo(ExpenseCategory::class, 'expense_category_id');
}
public function user()
{
return $this->belongsTo(User::class, 'user_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 getReceiptAttribute($value)
{
$media = $this->getFirstMedia('receipts');
if ($media) {
return $media->getPath();
}
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, $user_id)
{
return $query->where('expenses.user_id', $user_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('user_id')) {
$query->whereUser($filters->get('user_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, $company_id)
{
$query->where('expenses.company_id', $company_id);
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return collect(['data' => $query->get()]);
}
return $query->paginate($limit);
}
public function scopeExpensesAttributes($query)
{
$query->select(
DB::raw('
count(*) as expenses_count,
sum(amount) as total_amount,
expense_category_id')
)
->groupBy('expense_category_id');
}
public static function createExpense($request)
{
$data = $request->validated();
$data['creator_id'] = Auth::id();
$data['company_id'] = $request->header('company');
$expense = self::create($data);
if ($request->hasFile('attachment_receipt')) {
$expense->addMediaFromRequest('attachment_receipt')->toMediaCollection('receipts', 'local');
}
$customFields = json_decode($request->customFields, true);
if ($customFields) {
$expense->addCustomFields($customFields);
}
return $expense;
}
public function updateExpense($request)
{
$this->update($request->validated());
if ($request->hasFile('attachment_receipt')) {
$this->clearMediaCollection('receipts');
$this->addMediaFromRequest('attachment_receipt')->toMediaCollection('receipts', 'local');
}
$customFields = json_decode($request->customFields, true);
if ($customFields) {
$this->updateCustomFields($customFields);
}
return true;
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace Crater\Models;
use Crater\Models\CompanySetting;
use Illuminate\Database\Eloquent\Model;
use Crater\Models\Expense;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
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 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, $company_id)
{
$query->where('company_id', $company_id);
}
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 collect(['data' => $query->get()]);
}
return $query->paginate($limit);
}
}

187
app/Models/FileDisk.php Normal file
View File

@ -0,0 +1,187 @@
<?php
namespace Crater\Models;
use Crater\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class FileDisk extends Model
{
use HasFactory;
protected $guarded = [
'id'
];
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 collect(['data' => $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') : 'invoice_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,
]);
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;
}
}

580
app/Models/Invoice.php Normal file
View File

@ -0,0 +1,580 @@
<?php
namespace Crater\Models;
use Crater\Models\Company;
use Crater\Models\CompanySetting;
use Crater\Models\Currency;
use Crater\Models\Tax;
use Illuminate\Database\Eloquent\Model;
use Crater\Models\InvoiceTemplate;
use Crater\Models\Payment;
use Carbon\Carbon;
use Crater\Traits\HasCustomFieldsTrait;
use Crater\Mail\SendInvoiceMail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Vinkla\Hashids\Facades\Hashids;
use Crater\Traits\GeneratesPdfTrait;
use Barryvdh\DomPDF\Facade as PDF;
use Illuminate\Support\Facades\Auth;
class Invoice extends Model implements HasMedia
{
use HasFactory, InteractsWithMedia, GeneratesPdfTrait;
use HasCustomFieldsTrait;
const STATUS_DRAFT = 'DRAFT';
const STATUS_SENT = 'SENT';
const STATUS_VIEWED = 'VIEWED';
const STATUS_OVERDUE = 'OVERDUE';
const STATUS_COMPLETED = 'COMPLETED';
const STATUS_DUE = 'DUE';
const STATUS_UNPAID = 'UNPAID';
const STATUS_PARTIALLY_PAID = 'PARTIALLY_PAID';
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',
];
protected $guarded = [
'id'
];
protected $appends = [
'formattedCreatedAt',
'formattedInvoiceDate',
'formattedDueDate',
'invoicePdfUrl'
];
public function setInvoiceDateAttribute($value)
{
if ($value) {
$this->attributes['invoice_date'] = Carbon::createFromFormat('Y-m-d', $value);
}
}
public function setDueDateAttribute($value)
{
if ($value) {
$this->attributes['due_date'] = Carbon::createFromFormat('Y-m-d', $value);
}
}
public static function getNextInvoiceNumber($value)
{
// Get the last created order
$lastOrder = Invoice::where('invoice_number', 'LIKE', $value . '-%')
->orderBy('created_at', 'desc')
->first();
if (!$lastOrder) {
// We get here if there is no order at all
// If there is no number set it to 0, which will be 1 at the end.
$number = 0;
} else {
$number = explode("-", $lastOrder->invoice_number);
$number = $number[1];
}
// If we have ORD000001 in the database then we only want the number
// So the substr returns this 000001
// Add the string in front and higher up the number.
// the %06d part makes sure that there are always 6 numbers in the string.
// so it adds the missing zero's when needed.
return sprintf('%06d', intval($number) + 1);
}
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 user()
{
return $this->belongsTo('Crater\Models\User', 'user_id');
}
public function creator()
{
return $this->belongsTo('Crater\Models\User', 'creator_id');
}
public function invoiceTemplate()
{
return $this->belongsTo(InvoiceTemplate::class);
}
public function getInvoicePdfUrlAttribute()
{
return url('/invoices/pdf/' . $this->unique_hash);
}
public function getPreviousStatus()
{
if ($this->due_date < Carbon::now()) {
return self::STATUS_OVERDUE;
} elseif ($this->viewed) {
return self::STATUS_VIEWED;
} elseif ($this->sent) {
return self::STATUS_SENT;
} else {
return self::STATUS_DRAFT;
}
}
private function strposX($haystack, $needle, $number)
{
if ($number == '1') {
return strpos($haystack, $needle);
} elseif ($number > '1') {
return strpos(
$haystack,
$needle,
$this->strposX($haystack, $needle, $number - 1) + strlen($needle)
);
} else {
return error_log('Error: Value for parameter $number is out of range');
}
}
public function getInvoiceNumAttribute()
{
$position = $this->strposX($this->invoice_number, "-", 1) + 1;
return substr($this->invoice_number, $position);
}
public function getInvoicePrefixAttribute()
{
$prefix = explode("-", $this->invoice_number)[0];
return $prefix;
}
public function getFormattedCreatedAtAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
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('user', 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') == self::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') : 'invoice_number';
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'asc';
$query->whereOrder($field, $orderBy);
}
}
public function scopeWhereInvoice($query, $invoice_id)
{
$query->orWhere('id', $invoice_id);
}
public function scopeWhereCompany($query, $company_id)
{
$query->where('invoices.company_id', $company_id);
}
public function scopeWhereCustomer($query, $customer_id)
{
$query->where('invoices.user_id', $customer_id);
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return collect(['data' => $query->get()]);
}
return $query->paginate($limit);
}
public static function createInvoice($request)
{
$data = $request->except('items', 'taxes');
$data['creator_id'] = Auth::id();
$data['status'] = Invoice::STATUS_DRAFT;
$data['company_id'] = $request->header('company');
$data['paid_status'] = Invoice::STATUS_UNPAID;
$data['tax_per_item'] = CompanySetting::getSetting('tax_per_item', $request->header('company')) ?? 'NO ';
$data['discount_per_item'] = CompanySetting::getSetting('discount_per_item', $request->header('company')) ?? 'NO';
$data['due_amount'] = $request->total;
if ($request->has('invoiceSend')) {
$data['status'] = Invoice::STATUS_SENT;
}
$invoice = Invoice::create($data);
$invoice->unique_hash = Hashids::connection(Invoice::class)->encode($invoice->id);
$invoice->save();
self::createItems($invoice, $request);
if ($request->has('taxes') && (!empty($request->taxes))) {
self::createTaxes($invoice, $request);
}
if ($request->customFields) {
$invoice->addCustomFields($request->customFields);
}
$invoice = Invoice::with([
'items',
'user',
'invoiceTemplate',
'taxes'
])
->find($invoice->id);
return $invoice;
}
public function updateInvoice($request)
{
$data = $request->except('items');
$oldAmount = $this->total;
if ($oldAmount != $request->total) {
$oldAmount = (int) round($request->total) - (int) $oldAmount;
} else {
$oldAmount = 0;
}
$data['due_amount'] = ($this->due_amount + $oldAmount);
if ($data['due_amount'] == 0 && $this->paid_status != Invoice::STATUS_PAID) {
$data['status'] = Invoice::STATUS_COMPLETED;
$data['paid_status'] = Invoice::STATUS_PAID;
} elseif ($this->due_amount < 0 && $this->paid_status != Invoice::STATUS_UNPAID) {
return response()->json([
'error' => 'invalid_due_amount'
]);
} elseif ($data['due_amount'] != 0 && $this->paid_status == Invoice::STATUS_PAID) {
$data['status'] = $this->getPreviousStatus();
$data['paid_status'] = Invoice::STATUS_PARTIALLY_PAID;
}
$this->update($data);
$this->items()->delete();
$this->taxes()->delete();
self::createItems($this, $request);
if ($request->has('taxes') && (!empty($request->taxes))) {
self::createTaxes($this, $request);
}
if ($request->customFields) {
$this->updateCustomFields($request->customFields);
}
$invoice = Invoice::with([
'items',
'user',
'invoiceTemplate',
'taxes'
])
->find($this->id);
return $invoice;
}
public function send($data)
{
$data['invoice'] = $this->toArray();
$data['user'] = $this->user->toArray();
$data['company'] = Company::find($this->company_id);
$data['body'] = $this->getEmailBody($data['body']);
if ($this->status == Invoice::STATUS_DRAFT) {
$this->status = Invoice::STATUS_SENT;
$this->sent = true;
$this->save();
}
\Mail::to($data['to'])->send(new SendInvoiceMail($data));
return [
'success' => true
];
}
public static function createItems($invoice, $request)
{
$invoiceItems = $request->items;
foreach ($invoiceItems as $invoiceItem) {
$invoiceItem['company_id'] = $request->header('company');
$item = $invoice->items()->create($invoiceItem);
if (array_key_exists('taxes', $invoiceItem) && $invoiceItem['taxes']) {
foreach ($invoiceItem['taxes'] as $tax) {
$tax['company_id'] = $request->header('company');
if (gettype($tax['amount']) !== "NULL") {
$item->taxes()->create($tax);
}
}
}
}
}
public static function createTaxes($invoice, $request)
{
if ($request->has('taxes') && (!empty($request->taxes))) {
foreach ($request->taxes as $tax) {
$tax['company_id'] = $request->header('company');
if (gettype($tax['amount']) !== "NULL") {
$invoice->taxes()->create($tax);
}
}
}
}
public function getPDFData()
{
$taxTypes = [];
$taxes = [];
$labels = [];
if ($this->tax_per_item === 'YES') {
foreach ($this->items as $item) {
foreach ($item->taxes as $tax) {
if (!in_array($tax->name, $taxTypes)) {
array_push($taxTypes, $tax->name);
array_push($labels, $tax->name . ' (' . $tax->percent . '%)');
}
}
}
foreach ($taxTypes as $taxType) {
$total = 0;
foreach ($this->items as $item) {
foreach ($item->taxes as $tax) {
if ($tax->name == $taxType) {
$total += $tax->amount;
}
}
}
array_push($taxes, $total);
}
}
$invoiceTemplate = InvoiceTemplate::find($this->invoice_template_id);
$company = Company::find($this->company_id);
$logo = $company->getMedia('logo')->first();
if ($logo) {
$logo = $logo->getFullUrl();
}
view()->share([
'invoice' => $this,
'company_address' => $this->getCompanyAddress(),
'shipping_address' => $this->getCustomerShippingAddress(),
'billing_address' => $this->getCustomerBillingAddress(),
'notes' => $this->getNotes(),
'logo' => $logo ?? null,
'labels' => $labels,
'taxes' => $taxes
]);
return PDF::loadView('app.pdf.invoice.' . $invoiceTemplate->view);
}
public function getCompanyAddress()
{
$format = CompanySetting::getSetting('invoice_company_address_format', $this->company_id);
return $this->getFormattedString($format);
}
public function getCustomerShippingAddress()
{
$format = CompanySetting::getSetting('invoice_shipping_address_format', $this->company_id);
return $this->getFormattedString($format);
}
public function getCustomerBillingAddress()
{
$format = CompanySetting::getSetting('invoice_billing_address_format', $this->company_id);
return $this->getFormattedString($format);
}
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 [
'{INVOICE_DATE}' => $this->formattedInvoiceDate,
'{INVOICE_DUE_DATE}' => $this->formattedDueDate,
'{INVOICE_NUMBER}' => $this->invoice_number,
'{INVOICE_REF_NUMBER}' => $this->reference_number,
'{INVOICE_LINK}' => url('/customer/invoices/pdf/' . $this->unique_hash)
];
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Model;
use Crater\Models\Invoice;
use Crater\Models\Tax;
use Crater\Models\Item;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Facades\DB;
class InvoiceItem extends Model
{
use HasFactory;
protected $fillable = [
'invoice_id',
'name',
'item_id',
'description',
'company_id',
'quantity',
'price',
'discount_type',
'discount_val',
'total',
'tax',
'discount'
];
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 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(total) as total_amount, invoice_items.name')
)->groupBy('invoice_items.name');
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Model;
use Crater\Models\Invoice;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class InvoiceTemplate extends Model
{
use HasFactory;
protected $fillable = ['path', 'view', 'name'];
public function invoices()
{
return $this->hasMany(Invoice::class);
}
public function getPathAttribute($value)
{
return url($value);
}
}

161
app/Models/Item.php Normal file
View File

@ -0,0 +1,161 @@
<?php
namespace Crater\Models;
use Crater\Models\CompanySetting;
use Crater\Models\Tax;
use Crater\Models\Unit;
use Illuminate\Database\Eloquent\Model;
use Crater\Models\InvoiceItem;
use Crater\Models\EstimateItem;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
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);
}
public function creator()
{
return $this->belongsTo('Crater\Models\User', 'creator_id');
}
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 collect(['data' => $query->get()]);
}
return $query->paginate($limit);
}
public function getFormattedCreatedAtAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
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, $company_id)
{
$query->where('items.company_id', $company_id);
}
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();
$item = self::create($data);
if ($request->has('taxes')) {
foreach ($request->taxes as $tax) {
$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) {
$tax['company_id'] = $request->header('company');
$this->taxes()->create($tax);
}
}
return Item::with('taxes')->find($this->id);
}
}

36
app/Models/Note.php Normal file
View File

@ -0,0 +1,36 @@
<?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 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);
}
}

431
app/Models/Payment.php Normal file
View File

@ -0,0 +1,431 @@
<?php
namespace Crater\Models;
use Crater\Models\CompanySetting;
use Crater\Models\User;
use Crater\Models\Invoice;
use Crater\Models\Company;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Crater\Models\PaymentMethod;
use Crater\Traits\HasCustomFieldsTrait;
use Crater\Mail\SendPaymentMail;
use Vinkla\Hashids\Facades\Hashids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Crater\Traits\GeneratesPdfTrait;
use Barryvdh\DomPDF\Facade as PDF;
use Crater\Jobs\GeneratePaymentPdfJob;
use Illuminate\Support\Facades\Auth;
class Payment extends Model implements HasMedia
{
use HasFactory, InteractsWithMedia, GeneratesPdfTrait;
use HasCustomFieldsTrait;
const PAYMENT_MODE_CHECK = 'CHECK';
const PAYMENT_MODE_OTHER = 'OTHER';
const PAYMENT_MODE_CASH = 'CASH';
const PAYMENT_MODE_CREDIT_CARD = 'CREDIT_CARD';
const PAYMENT_MODE_BANK_TRANSFER = 'BANK_TRANSFER';
protected $dates = ['created_at', 'updated_at', 'payment_date'];
protected $guarded = ['id'];
protected $appends = [
'formattedCreatedAt',
'formattedPaymentDate',
'paymentPdfUrl'
];
protected static function booted()
{
static::created(function ($payment) {
GeneratePaymentPdfJob::dispatch($payment);
});
static::updated(function ($payment) {
GeneratePaymentPdfJob::dispatch($payment, true);
});
}
public function setPaymentDateAttribute($value)
{
if ($value) {
$this->attributes['payment_date'] = Carbon::createFromFormat('Y-m-d', $value);
}
}
public function getPaymentPrefixAttribute()
{
$prefix = explode("-", $this->payment_number)[0];
return $prefix;
}
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 getPaymentNumAttribute()
{
$position = $this->strposX($this->payment_number, "-", 1) + 1;
return substr($this->payment_number, $position);
}
public function emailLogs()
{
return $this->morphMany('App\Models\EmailLog', 'mailable');
}
public function company()
{
return $this->belongsTo(Company::class);
}
public function invoice()
{
return $this->belongsTo(Invoice::class);
}
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
public function creator()
{
return $this->belongsTo('Crater\Models\User', 'creator_id');
}
public function paymentMethod()
{
return $this->belongsTo(PaymentMethod::class);
}
public function send($data)
{
$data['payment'] = $this->toArray();
$data['user'] = $this->user->toArray();
$data['company'] = Company::find($this->company_id);
$data['body'] = $this->getEmailBody($data['body']);
\Mail::to($data['to'])->send(new SendPaymentMail($data));
return [
'success' => true
];
}
public static function createPayment($request)
{
$data = $request->validated();
$data['company_id'] = $request->header('company');
$data['creator_id'] = Auth::id();
if ($request->has('invoice_id') && $request->invoice_id != null) {
$invoice = Invoice::find($request->invoice_id);
if ($invoice && $invoice->due_amount == $request->amount) {
$invoice->status = Invoice::STATUS_COMPLETED;
$invoice->paid_status = Invoice::STATUS_PAID;
$invoice->due_amount = 0;
} elseif ($invoice && $invoice->due_amount != $request->amount) {
$invoice->due_amount = (int)$invoice->due_amount - (int)$request->amount;
if ($invoice->due_amount < 0) {
return [
'error' => 'invalid_amount'
];
}
$invoice->paid_status = Invoice::STATUS_PARTIALLY_PAID;
}
$invoice->save();
}
$payment = Payment::create($data);
$payment->unique_hash = Hashids::connection(Payment::class)->encode($payment->id);
$payment->save();
$customFields = $request->customFields;
if ($customFields) {
$payment->addCustomFields($customFields);
}
$payment = Payment::with([
'user',
'invoice',
'paymentMethod',
])->find($payment->id);
return $payment;
}
public function updatePayment($request)
{
$oldAmount = $this->amount;
if ($request->has('invoice_id') && $request->invoice_id && ($oldAmount != $request->amount)) {
$amount = (int)$request->amount - (int)$oldAmount;
$invoice = Invoice::find($request->invoice_id);
$invoice->due_amount = (int)$invoice->due_amount - (int)$amount;
if ($invoice->due_amount < 0) {
return [
'error' => 'invalid_amount'
];
}
if ($invoice->due_amount == 0) {
$invoice->status = Invoice::STATUS_COMPLETED;
$invoice->paid_status = Invoice::STATUS_PAID;
} else {
$invoice->status = $invoice->getPreviousStatus();
$invoice->paid_status = Invoice::STATUS_PARTIALLY_PAID;
}
$invoice->save();
}
$data = $request->all();
$this->update($data);
$customFields = $request->customFields;
if ($customFields) {
$this->updateCustomFields($customFields);
}
$payment = Payment::with([
'user',
'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;
}
private function strposX($haystack, $needle, $number)
{
if ($number == '1') {
return strpos($haystack, $needle);
} elseif ($number > '1') {
return strpos(
$haystack,
$needle,
$this->strposX($haystack, $needle, $number - 1) + strlen($needle)
);
} else {
return error_log('Error: Value for parameter $number is out of range');
}
}
public static function getNextPaymentNumber($value)
{
// Get the last created order
$payment = Payment::where('payment_number', 'LIKE', $value . '-%')
->orderBy('created_at', 'desc')
->first();
if (!$payment) {
// We get here if there is no order at all
// If there is no number set it to 0, which will be 1 at the end.
$number = 0;
} else {
$number = explode("-", $payment->payment_number);
$number = $number[1];
}
// If we have ORD000001 in the database then we only want the number
// So the substr returns this 000001
// Add the string in front and higher up the number.
// the %05d part makes sure that there are always 6 numbers in the string.
// so it adds the missing zero's when needed.
return sprintf('%06d', intval($number) + 1);
}
public function scopeWhereSearch($query, $search)
{
foreach (explode(' ', $search) as $term) {
$query->whereHas('user', 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 collect(['data' => $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('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 scopeWherePayment($query, $payment_id)
{
$query->orWhere('id', $payment_id);
}
public function scopeWhereCompany($query, $company_id)
{
$query->where('payments.company_id', $company_id);
}
public function scopeWhereCustomer($query, $customer_id)
{
$query->where('payments.user_id', $customer_id);
}
public function getPDFData()
{
$company = Company::find($this->company_id);
$logo = $company->getMedia('logo')->first();
if ($logo) {
$logo = $logo->getFullUrl();
}
view()->share([
'payment' => $this,
'company_address' => $this->getCompanyAddress(),
'billing_address' => $this->getCustomerBillingAddress(),
'notes' => $this->getNotes(),
'logo' => $logo ?? null
]);
return PDF::loadView('app.pdf.payment.payment');
}
public function getCompanyAddress()
{
$format = CompanySetting::getSetting('payment_company_address_format', $this->company_id);
return $this->getFormattedString($format);
}
public function getCustomerBillingAddress()
{
$format = CompanySetting::getSetting('payment_from_customer_address_format', $this->company_id);
return $this->getFormattedString($format);
}
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,
'{PAYMENT_LINK}' => $this->paymentPdfUrl
];
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace Crater\Models;
use Crater\Models\Company;
use Crater\Models\Payment;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PaymentMethod extends Model
{
use HasFactory;
protected $fillable = ['name', 'company_id'];
public function payments()
{
return $this->hasMany(Payment::class);
}
public function company()
{
return $this->belongsTo(Company::class);
}
public function scopeWhereCompany($query, $company_id)
{
$query->where('company_id', $company_id);
}
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 collect(['data' => $query->get()]);
}
return $query->paginate($limit);
}
public static function createPaymentMethod($request)
{
$data = $request->validated();
$data['company_id'] = $request->header('company');
$paymentMethod = self::create($data);
return $paymentMethod;
}
}

37
app/Models/Setting.php Normal file
View File

@ -0,0 +1,37 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Setting extends Model
{
use HasFactory;
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 getSetting($key)
{
$setting = static::whereOption($key)->first();
if ($setting) {
return $setting->value;
} else {
return null;
}
}
}

106
app/Models/Tax.php Normal file
View File

@ -0,0 +1,106 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Model;
use Crater\Models\TaxType;
use Crater\Models\Invoice;
use Crater\Models\Estimate;
use Crater\Models\Item;
use Crater\Models\InvoiceItem;
use Crater\Models\EstimateItem;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Facades\DB;
class Tax extends Model
{
use HasFactory;
protected $fillable = [
'name',
'amount',
'company_id',
'percent',
'tax_type_id',
'invoice_id',
'estimate_id',
'item_id',
'compound_tax'
];
protected $casts = [
'amount' => 'integer',
'percent' => 'float'
];
public function taxType()
{
return $this->belongsTo(TaxType::class);
}
public function invoice()
{
return $this->belongsTo(Invoice::class);
}
public function estimate()
{
return $this->belongsTo(Estimate::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(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);
}
}
}

81
app/Models/TaxType.php Normal file
View File

@ -0,0 +1,81 @@
<?php
namespace Crater\Models;
use Illuminate\Database\Eloquent\Model;
use Crater\Models\Tax;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class TaxType extends Model
{
use HasFactory;
protected $fillable = [
'name',
'percent',
'company_id',
'compound_tax',
'collective_tax',
'description'
];
protected $casts = [
'percent' => 'float'
];
public function taxes()
{
return $this->hasMany(Tax::class);
}
public function scopeWhereCompany($query, $company_id)
{
$query->where('company_id', $company_id);
}
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 collect(['data' => $query->get()]);
}
return $query->paginate($limit);
}
}

56
app/Models/Unit.php Normal file
View File

@ -0,0 +1,56 @@
<?php
namespace Crater\Models;
use Crater\Models\Company;
use Illuminate\Database\Eloquent\Model;
use Crater\Models\Item;
use Illuminate\Database\Eloquent\Factories\HasFactory;
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, $company_id)
{
$query->where('company_id', $company_id);
}
public function scopeWhereUnit($query, $unit_id)
{
$query->orWhere('id', $unit_id);
}
public function scopeApplyFilters($query, array $filters)
{
$filters = collect($filters);
if ($filters->get('unit_id')) {
$query->whereUnit($filters->get('unit_id'));
}
if ($filters->get('company_id')) {
$query->whereCompany($filters->get('company_id'));
}
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return collect(['data' => $query->get()]);
}
return $query->paginate($limit);
}
}

430
app/Models/User.php Normal file
View File

@ -0,0 +1,430 @@
<?php
namespace Crater\Models;
use Crater\Models\CompanySetting;
use Crater\Models\Currency;
use Crater\Models\Estimate;
use Crater\Models\Invoice;
use Crater\Models\UserSetting;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Carbon\Carbon;
use Crater\Models\Address;
use Crater\Models\Payment;
use Crater\Models\Expense;
use Crater\Models\Company;
use Crater\Traits\HasCustomFieldsTrait;
use Crater\Notifications\MailResetPasswordNotification;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Facades\Auth;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Illuminate\Support\Facades\Hash;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable implements HasMedia
{
use HasApiTokens, Notifiable, InteractsWithMedia, HasCustomFieldsTrait;
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'company_id',
'password',
'facebook_id',
'currency_id',
'google_id',
'github_id',
'role',
'group_id',
'phone',
'company_name',
'contact_name',
'website',
'enable_portal',
'creator_id'
];
/**
* 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(array('email' => $email, 'password' => $password), $remember));
}
public function getFormattedCreatedAtAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->created_at)->format($dateFormat);
}
public function estimates()
{
return $this->hasMany(Estimate::class);
}
public function currency()
{
return $this->belongsTo(Currency::class);
}
public function creator()
{
return $this->belongsTo('Crater\Models\User', 'creator_id');
}
public function company()
{
return $this->belongsTo(Company::class);
}
public function addresses()
{
return $this->hasMany(Address::class);
}
public function expenses()
{
return $this->hasMany(Expense::class);
}
public function billingAddress()
{
return $this->hasOne(Address::class)->where('type', Address::BILLING_TYPE);
}
public function shippingAddress()
{
return $this->hasOne(Address::class)->where('type', Address::SHIPPING_TYPE);
}
public function payments()
{
return $this->hasMany(Payment::class);
}
public function invoices()
{
return $this->hasMany(Invoice::class);
}
public function settings()
{
return $this->hasMany(UserSetting::class, 'user_id');
}
/**
* 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 scopeCustomer($query)
{
return $query->where('role', 'customer');
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return collect(['data' => $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('contact_name')) {
$query->whereContactName($filters->get('contact_name'));
}
if ($filters->get('display_name')) {
$query->whereDisplayName($filters->get('display_name'));
}
if ($filters->get('email')) {
$query->whereEmail($filters->get('email'));
}
if ($filters->get('customer_id')) {
$query->whereCustomer($filters->get('customer_id'));
}
if ($filters->get('phone')) {
$query->wherePhone($filters->get('phone'));
}
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 scopeWhereCompany($query, $company_id)
{
$query->where('users.company_id', $company_id);
}
public function scopeWhereCustomer($query, $customer_id)
{
$query->orWhere('users.id', $customer_id);
}
public function 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 static function deleteCustomers($ids)
{
foreach ($ids as $id) {
$customer = self::find($id);
if ($customer->estimates()->exists()) {
$customer->estimates()->delete();
}
if ($customer->invoices()->exists()) {
$customer->invoices()->delete();
}
if ($customer->payments()->exists()) {
$customer->payments()->delete();
}
if ($customer->addresses()->exists()) {
$customer->addresses()->delete();
}
$customer->delete();
}
return true;
}
public function getAvatarAttribute()
{
$avatar = $this->getMedia('admin_avatar')->first();
if ($avatar) {
return asset($avatar->getUrl());
}
return 0;
}
public static function createCustomer($request)
{
$data = $request->only([
'name',
'email',
'phone',
'company_name',
'contact_name',
'website',
'enable_portal'
]);
$data['creator_id'] = Auth::id();
$data['company_id'] = $request->header('company');
$data['role'] = 'customer';
$data['password'] = Hash::make($request->password);
$customer = User::create($data);
$customer['currency_id'] = $request->currency_id;
$customer->save();
if ($request->addresses) {
foreach ($request->addresses as $address) {
$customer->addresses()->create($address);
}
}
$customFields = $request->customFields;
if ($customFields) {
$customer->addCustomFields($customFields);
}
$customer = User::with('billingAddress', 'shippingAddress', 'fields')->find($customer->id);
return $customer;
}
public static function updateCustomer($request, $customer)
{
$data = $request->only([
'name',
'currency_id',
'email',
'phone',
'company_name',
'contact_name',
'website',
'enable_portal'
]);
$data['role'] = 'customer';
if ($request->has('password')) {
$customer->password = Hash::make($request->password);
}
$customer->update($data);
$customer->addresses()->delete();
if ($request->addresses) {
foreach ($request->addresses as $address) {
$customer->addresses()->create($address);
}
}
$customFields = $request->customFields;
if ($customFields) {
$customer->updateCustomFields($customFields);
}
$customer = User::with('billingAddress', 'shippingAddress', 'fields')->find($customer->id);
return $customer;
}
public function setSettings($settings)
{
foreach ($settings as $key => $value) {
$this->settings()->updateOrCreate(
[
'key' => $key,
],
[
'key' => $key,
'value' => $value
]
);
}
}
public function getSettings($settings)
{
$settings = $this->settings()->whereIn('key', $settings)->get();
$companySettings = [];
foreach ($settings as $setting) {
$companySettings[$setting->key] = $setting->value;
}
return $companySettings;
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Crater\Models;
use Crater\Models\User;
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);
}
}