diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9b60b5669fc3..bcbb19f476f2 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -40,8 +40,8 @@ jobs:
- name: Cleanup Builds
run: |
- #sudo rm -rf nodule_modules/
sudo rm -rf bootstrap/cache/*
+ sudo rm public/index.html
- name: Build project # This would actually build your project, using zip for an example artifact
run: |
diff --git a/VERSION.txt b/VERSION.txt
index f3eb74e2bae1..be51f9de1b88 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -1 +1 @@
-5.0.19
+5.0.20
diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php
index c1fa0a6e050a..e8fc16fe69c5 100644
--- a/app/DataMapper/CompanySettings.php
+++ b/app/DataMapper/CompanySettings.php
@@ -105,6 +105,9 @@ class CompanySettings extends BaseSettings
public $payment_number_pattern = '';
public $payment_number_counter = 1;
+ public $project_number_pattern = '';
+ public $project_number_counter = 1;
+
public $shared_invoice_quote_counter = false;
public $recurring_number_prefix = 'R';
public $reset_counter_frequency_id = '0';
@@ -313,6 +316,8 @@ class CompanySettings extends BaseSettings
'embed_documents' => 'bool',
'all_pages_header' => 'bool',
'all_pages_footer' => 'bool',
+ 'project_number_pattern' => 'string',
+ 'project_number_counter' => 'int',
'task_number_pattern' => 'string',
'task_number_counter' => 'int',
'expense_number_pattern' => 'string',
diff --git a/app/DataMapper/EmailTemplateDefaults.php b/app/DataMapper/EmailTemplateDefaults.php
index 39b053b4f800..8d502c64f39a 100644
--- a/app/DataMapper/EmailTemplateDefaults.php
+++ b/app/DataMapper/EmailTemplateDefaults.php
@@ -198,50 +198,47 @@ class EmailTemplateDefaults
public static function emailReminder1Template()
{
- // return Parsedown::instance()->line('First Email Reminder Text');
+ return '';
}
public static function emailReminder2Subject()
{
return ctrans('texts.reminder_subject', ['invoice'=>'$invoice.number', 'account'=>'$company.name']);
-// return Parsedown::instance()->line(self::transformText('reminder_subject'));
}
public static function emailReminder2Template()
{
- // return Parsedown::instance()->line('Second Email Reminder Text');
+ return '';
}
public static function emailReminder3Subject()
{
return ctrans('texts.reminder_subject', ['invoice'=>'$invoice.number', 'account'=>'$company.name']);
-// return Parsedown::instance()->line(self::transformText('reminder_subject'));
}
public static function emailReminder3Template()
{
- // return Parsedown::instance()->line('Third Email Reminder Text');
+ return '';
}
public static function emailReminderEndlessSubject()
{
return ctrans('texts.reminder_subject', ['invoice'=>'$invoice.number', 'account'=>'$company.name']);
-// return Parsedown::instance()->line(self::transformText('reminder_subject'));
}
public static function emailReminderEndlessTemplate()
{
- return ctrans('Endless Email Reminder Text');
+ return '';
}
public static function emailStatementSubject()
{
- return ctrans('Statement Subject needs texts record!');
+ return '';
}
public static function emailStatementTemplate()
{
- return ctrans('Statement Templates needs texts record!');
+ return '';
}
private static function transformText($string)
diff --git a/app/Factory/ExpenseFactory.php b/app/Factory/ExpenseFactory.php
index ddade6b1c18a..0272d8f3c288 100644
--- a/app/Factory/ExpenseFactory.php
+++ b/app/Factory/ExpenseFactory.php
@@ -33,7 +33,16 @@ class ExpenseFactory
$expense->tax_rate3 = 0;
$expense->date = null;
$expense->payment_date = null;
-
+ $expense->amount = 0;
+ $expense->foreign_amount = 0;
+ $expense->private_notes = '';
+ $expense->public_notes = '';
+ $expense->transaction_reference = '';
+ $expense->custom_value1 = '';
+ $expense->custom_value2 = '';
+ $expense->custom_value3 = '';
+ $expense->custom_value4 = '';
+
return $expense;
}
}
diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php
index ba7111ef860d..3c8a49610a63 100644
--- a/app/Http/Controllers/BaseController.php
+++ b/app/Http/Controllers/BaseController.php
@@ -60,10 +60,9 @@ class BaseController extends Controller
private $first_load = [
'account',
- 'user.company_user',
'token.company_user',
'company.activities',
- 'company.users.company_user',
+ 'company.users.company_users',
'company.tax_rates',
'company.groups',
'company.company_gateways.gateway',
@@ -203,60 +202,60 @@ class BaseController extends Controller
$updated_at = date('Y-m-d H:i:s', $updated_at);
$query->with(
- [
+ [ 'user.company_users',
'company' => function ($query) use ($updated_at) {
$query->whereNotNull('updated_at')->with('documents');
},
'company.clients' => function ($query) use ($updated_at) {
- $query->where('clients.updated_at', '>=', $updated_at)->with('contacts', 'gateway_tokens','documents');
- },
- 'company.tax_rates' => function ($query) use ($updated_at) {
- $query->where('updated_at', '>=', $updated_at);
- },
- 'company.groups' => function ($query) use ($updated_at) {
- $query->where('updated_at', '>=', $updated_at);
+ $query->where('clients.updated_at', '>=', $updated_at)->with('contacts.company', 'gateway_tokens','documents','company');
},
'company.company_gateways' => function ($query) {
$query->whereNotNull('updated_at');
},
- 'company.products' => function ($query) use ($updated_at) {
- $query->where('updated_at', '>=', $updated_at)->with('documents');
+ 'company.credits'=> function ($query) use ($updated_at) {
+ $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents',);
},
- 'company.recurring_invoices'=> function ($query) use ($updated_at) {
+ 'company.designs'=> function ($query) use ($updated_at) {
$query->where('updated_at', '>=', $updated_at)->with('company');
},
- 'company.invoices'=> function ($query) use ($updated_at) {
- $query->where('updated_at', '>=', $updated_at)->with('invitations', 'company', 'documents');
+ 'company.documents'=> function ($query) use ($updated_at) {
+ $query->where('updated_at', '>=', $updated_at);
},
- 'company.recurring_invoices'=> function ($query) use ($updated_at) {
- $query->where('updated_at', '>=', $updated_at)->with('invitations', 'company', 'documents');
+ 'company.expenses'=> function ($query) use ($updated_at) {
+ $query->where('updated_at', '>=', $updated_at)->with('documents' );
+ },
+ 'company.groups' => function ($query) use ($updated_at) {
+ $query->where('updated_at', '>=', $updated_at);
+ },
+ 'company.invoices'=> function ($query) use ($updated_at) {
+ $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents');
},
'company.payments'=> function ($query) use ($updated_at) {
- $query->where('updated_at', '>=', $updated_at)->with('paymentables','documents');
- },
- 'company.quotes'=> function ($query) use ($updated_at) {
- $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents');
- },
- 'company.credits'=> function ($query) use ($updated_at) {
- $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents');
+ $query->where('updated_at', '>=', $updated_at)->with('paymentables','documents', );
},
'company.payment_terms'=> function ($query) use ($updated_at) {
$query->where('updated_at', '>=', $updated_at);
},
- 'company.vendors'=> function ($query) use ($updated_at) {
- $query->where('updated_at', '>=', $updated_at)->with('contacts');
- },
- 'company.expenses'=> function ($query) use ($updated_at) {
- $query->where('updated_at', '>=', $updated_at);
- },
- 'company.tasks'=> function ($query) use ($updated_at) {
- $query->where('updated_at', '>=', $updated_at);
+ 'company.products' => function ($query) use ($updated_at) {
+ $query->where('updated_at', '>=', $updated_at)->with('documents');
},
'company.projects'=> function ($query) use ($updated_at) {
+ $query->where('updated_at', '>=', $updated_at)->with('documents' );
+ },
+ 'company.quotes'=> function ($query) use ($updated_at) {
+ $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents',);
+ },
+ 'company.recurring_invoices'=> function ($query) use ($updated_at) {
+ $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents');
+ },
+ 'company.tasks'=> function ($query) use ($updated_at) {
+ $query->where('updated_at', '>=', $updated_at)->with('documents' );
+ },
+ 'company.tax_rates' => function ($query) use ($updated_at) {
$query->where('updated_at', '>=', $updated_at);
},
- 'company.designs'=> function ($query) use ($updated_at) {
- $query->where('updated_at', '>=', $updated_at)->with('company');
+ 'company.vendors'=> function ($query) use ($updated_at) {
+ $query->where('updated_at', '>=', $updated_at)->with('contacts','documents' );
},
]
);
diff --git a/app/Http/Middleware/QueryLogging.php b/app/Http/Middleware/QueryLogging.php
index 32d854d17b3b..d0cc13c98f65 100644
--- a/app/Http/Middleware/QueryLogging.php
+++ b/app/Http/Middleware/QueryLogging.php
@@ -53,7 +53,7 @@ class QueryLogging
Log::info($request->method().' - '.$request->url().": $count queries - ".$time);
// if($count > 50)
- // Log::info($queries);
+ // Log::info($queries);
}
}
diff --git a/app/Http/Requests/Expense/StoreExpenseRequest.php b/app/Http/Requests/Expense/StoreExpenseRequest.php
index 22aee8ca7c0a..741cb6fabbde 100644
--- a/app/Http/Requests/Expense/StoreExpenseRequest.php
+++ b/app/Http/Requests/Expense/StoreExpenseRequest.php
@@ -37,23 +37,13 @@ class StoreExpenseRequest extends Request
public function rules()
{
- /* Ensure we have a client name, and that all emails are unique*/
- //$rules['name'] = 'required|min:1';
+
$rules['id_number'] = 'unique:expenses,id_number,'.$this->id.',id,company_id,'.$this->company_id;
- //$rules['settings'] = new ValidExpenseGroupSettingsRule();
+
$rules['contacts.*.email'] = 'nullable|distinct';
$rules['number'] = new UniqueExpenseNumberRule($this->all());
- // $contacts = request('contacts');
-
- // if (is_array($contacts)) {
- // for ($i = 0; $i < count($contacts); $i++) {
-
- // //$rules['contacts.' . $i . '.email'] = 'nullable|email|distinct';
- // }
- // }
-
return $rules;
}
diff --git a/app/Http/ValidationRules/Expense/UniqueExpenseNumberRule.php b/app/Http/ValidationRules/Expense/UniqueExpenseNumberRule.php
index 4dfbde5e2298..4ae379757df3 100644
--- a/app/Http/ValidationRules/Expense/UniqueExpenseNumberRule.php
+++ b/app/Http/ValidationRules/Expense/UniqueExpenseNumberRule.php
@@ -55,15 +55,27 @@ class UniqueExpenseNumberRule implements Rule
*/
private function checkIfExpenseNumberUnique() : bool
{
- $expense = Expense::where('client_id', $this->input['client_id'])
- ->where('number', $this->input['number'])
- ->withTrashed()
- ->exists();
+ if(empty($this->input['number']))
+ return true;
- if ($expense) {
- return false;
- }
+ $expense = Expense::query()
+ ->where('number', $this->input['number'])
+ ->withTrashed();
- return true;
+ // if(isset($this->input['client_id']))
+ // $expense->where('client_id', $this->input['client_id']);
+
+ return $expense->exists();
+
+ // $expense = Expense::where('client_id', $this->input['client_id'])
+ // ->where('number', $this->input['number'])
+ // ->withTrashed()
+ // ->exists();
+
+ // if ($expense) {
+ // return false;
+ // }
+
+ // return true;
}
}
diff --git a/app/Http/ValidationRules/Project/ValidProjectForClient.php b/app/Http/ValidationRules/Project/ValidProjectForClient.php
index a23e421de398..e33d5366a2bd 100644
--- a/app/Http/ValidationRules/Project/ValidProjectForClient.php
+++ b/app/Http/ValidationRules/Project/ValidProjectForClient.php
@@ -35,6 +35,9 @@ class ValidProjectForClient implements Rule
*/
public function passes($attribute, $value)
{
+ if(empty($this->input['project_id']))
+ return true;
+
if(is_string($this->input['project_id']))
$this->input['project_id'] = $this->decodePrimaryKey($this->input['project_id']);
diff --git a/app/Libraries/MultiDB.php b/app/Libraries/MultiDB.php
index ad997d15c22c..cd7a0baf3ce7 100644
--- a/app/Libraries/MultiDB.php
+++ b/app/Libraries/MultiDB.php
@@ -16,6 +16,7 @@ use App\Models\ClientContact;
use App\Models\Company;
use App\Models\CompanyToken;
use App\Models\User;
+use Illuminate\Support\Str;
/**
* Class MultiDB.
@@ -237,7 +238,7 @@ class MultiDB
public static function findAndSetDbByInvitation($entity, $invitation_key)
{
- $class = 'App\Models\\'.ucfirst($entity).'Invitation';
+ $class = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation';
foreach (self::$dbs as $db) {
if ($invite = $class::on($db)->whereRaw('BINARY `key`= ?', [$invitation_key])->first()) {
diff --git a/app/Models/InvoiceInvitation.php b/app/Models/InvoiceInvitation.php
index 60669e07483b..4b27681c2ca5 100644
--- a/app/Models/InvoiceInvitation.php
+++ b/app/Models/InvoiceInvitation.php
@@ -28,7 +28,6 @@ class InvoiceInvitation extends BaseModel
use Inviteable;
protected $fillable = [
- //'key',
'client_contact_id',
];
diff --git a/app/Models/Presenters/CompanyPresenter.php b/app/Models/Presenters/CompanyPresenter.php
index aac2ff141ce7..dfe272e13518 100644
--- a/app/Models/Presenters/CompanyPresenter.php
+++ b/app/Models/Presenters/CompanyPresenter.php
@@ -76,7 +76,7 @@ class CompanyPresenter extends EntityPresenter
$settings = $this->entity->settings;
}
- $country = Country::find($settings->country_id)->first();
+ $country = Country::find($settings->country_id);
$swap = $country && $country->swap_postal_code;
diff --git a/app/Models/Presenters/EntityPresenter.php b/app/Models/Presenters/EntityPresenter.php
index 27534220fc3b..99bd5392f57a 100644
--- a/app/Models/Presenters/EntityPresenter.php
+++ b/app/Models/Presenters/EntityPresenter.php
@@ -14,7 +14,6 @@ namespace App\Models\Presenters;
use App\Utils\Traits\MakesHash;
use Hashids\Hashids;
use Laracasts\Presenter\Presenter;
-use stdClass;
use URL;
use Utils;
diff --git a/app/Models/Presenters/VendorPresenter.php b/app/Models/Presenters/VendorPresenter.php
new file mode 100644
index 000000000000..b74212cf67a8
--- /dev/null
+++ b/app/Models/Presenters/VendorPresenter.php
@@ -0,0 +1,160 @@
+entity->name) {
+ return $this->entity->name;
+ }
+
+ $contact = $this->entity->primary_contact->first();
+
+ $contact_name = 'No Contact Set';
+
+ if ($contact && (strlen($contact->first_name) >= 1 || strlen($contact->last_name) >= 1)) {
+ $contact_name = $contact->first_name.' '.$contact->last_name;
+ } elseif ($contact && (strlen($contact->email))) {
+ $contact_name = $contact->email;
+ }
+
+ return $contact_name;
+ }
+
+ public function primary_contact_name()
+ {
+ return $this->entity->primary_contact->first() !== null ? $this->entity->primary_contact->first()->first_name.' '.$this->entity->primary_contact->first()->last_name : 'No primary contact set';
+ }
+
+ public function email()
+ {
+ return $this->entity->primary_contact->first() !== null ? $this->entity->primary_contact->first()->email : 'No Email Set';
+ }
+
+ public function address()
+ {
+ $str = '';
+ $vendor = $this->entity;
+
+ if ($address1 = $vendor->address1) {
+ $str .= e($address1).'
';
+ }
+ if ($address2 = $vendor->address2) {
+ $str .= e($address2).'
';
+ }
+ if ($cityState = $this->getCityState()) {
+ $str .= e($cityState).'
';
+ }
+ if ($country = $vendor->country) {
+ $str .= e($country->name).'
';
+ }
+
+ return $str;
+ }
+
+ public function shipping_address()
+ {
+ $str = '';
+ $vendor = $this->entity;
+
+ if ($address1 = $vendor->shipping_address1) {
+ $str .= e($address1).'
';
+ }
+ if ($address2 = $vendor->shipping_address2) {
+ $str .= e($address2).'
';
+ }
+ if ($cityState = $this->getCityState()) {
+ $str .= e($cityState).'
';
+ }
+ if ($country = $vendor->country) {
+ $str .= e($country->name).'
';
+ }
+
+ return $str;
+ }
+
+ public function phone()
+ {
+ return $this->entity->phone ?: '';
+ }
+
+ public function website()
+ {
+ return $this->entity->website ?: '';
+ }
+
+ /**
+ * Calculated company data fields
+ * using settings.
+ */
+ public function company_name()
+ {
+ $settings = $this->entity->company->settings;;
+
+ return $settings->name ?: ctrans('texts.untitled_account');
+ }
+
+ public function company_address()
+ {
+ $settings = $this->entity->company->settings;;
+
+ $str = '';
+
+ if ($settings->address1) {
+ $str .= e($settings->address1).'
';
+ }
+ if ($settings->address2) {
+ $str .= e($settings->address2).'
';
+ }
+ if ($cityState = $this->getCityState()) {
+ $str .= e($cityState).'
';
+ }
+ if ($country = Country::find($settings->country_id)) {
+ $str .= e($country->name).'
';
+ }
+
+ return $str;
+ }
+
+ public function getCityState()
+ {
+ $settings = $this->entity->company->settings;;
+
+ $country = false;
+
+ if ($settings->country_id) {
+ $country = Country::find($settings->country_id);
+ }
+
+ $swap = $country && $country->swap_postal_code;
+
+ $city = e($settings->city ?: '');
+ $state = e($settings->state ?: '');
+ $postalCode = e($settings->postal_code ?: '');
+
+ if ($city || $state || $postalCode) {
+ return $this->cityStateZip($city, $state, $postalCode, $swap);
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/app/Models/RecurringInvoiceInvitation.php b/app/Models/RecurringInvoiceInvitation.php
index d958f6b28a1b..fe4e6c4b7b0c 100644
--- a/app/Models/RecurringInvoiceInvitation.php
+++ b/app/Models/RecurringInvoiceInvitation.php
@@ -69,5 +69,16 @@ class RecurringInvoiceInvitation extends BaseModel
return $this->belongsTo(Company::class);
}
+ public function markViewed()
+ {
+ $this->viewed_date = Carbon::now();
+ $this->save();
+ }
+
+ public function markOpened()
+ {
+ $this->opened_date = Carbon::now();
+ $this->save();
+ }
}
diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php
index 3cb8b05265ff..9ab0d05dfa00 100644
--- a/app/Models/Vendor.php
+++ b/app/Models/Vendor.php
@@ -17,13 +17,15 @@ use App\Utils\Traits\GeneratesCounter;
use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
+use Laracasts\Presenter\PresentableTrait;
class Vendor extends BaseModel
{
use SoftDeletes;
use Filterable;
use GeneratesCounter;
-
+ use PresentableTrait;
+
protected $fillable = [
'name',
'assigned_user_id',
diff --git a/app/Transformers/ExpenseTransformer.php b/app/Transformers/ExpenseTransformer.php
index 6795ebb52832..ac2f0eac9b6f 100644
--- a/app/Transformers/ExpenseTransformer.php
+++ b/app/Transformers/ExpenseTransformer.php
@@ -23,6 +23,7 @@ class ExpenseTransformer extends EntityTransformer
{
use MakesHash;
use SoftDeletes;
+
protected $defaultIncludes = [
'documents',
];
diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php
index 42700c8a6b79..2075ace404bc 100644
--- a/app/Utils/HtmlEngine.php
+++ b/app/Utils/HtmlEngine.php
@@ -427,7 +427,7 @@ class HtmlEngine
private function getCountryName() :string
{
- $country = Country::find($this->settings->country_id)->first();
+ $country = Country::find($this->settings->country_id);
if ($country) {
return $country->name;
diff --git a/config/ninja.php b/config/ninja.php
index 1356bc393528..9ab231a660d5 100644
--- a/config/ninja.php
+++ b/config/ninja.php
@@ -12,7 +12,7 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/').'/',
'app_domain' => env('APP_DOMAIN', ''),
- 'app_version' => '5.0.19',
+ 'app_version' => '5.0.20',
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),
diff --git a/database/migrations/2020_10_19_101823_project_name_unique_removal.php b/database/migrations/2020_10_19_101823_project_name_unique_removal.php
new file mode 100644
index 000000000000..970e6a14bb89
--- /dev/null
+++ b/database/migrations/2020_10_19_101823_project_name_unique_removal.php
@@ -0,0 +1,44 @@
+dropUnique('projects_company_id_name_unique');
+ });
+
+ Schema::table('expenses', function (Blueprint $table) {
+ $table->unsignedInteger('invoice_currency_id')->nullable()->change();
+ $table->unsignedInteger('expense_currency_id')->nullable()->change();
+ $table->text('private_notes')->nullable()->change();
+ $table->text('public_notes')->nullable()->change();
+ $table->text('transaction_reference')->nullable()->change();
+ });
+
+ Schema::table('companies', function (Blueprint $table) {
+ $table->boolean('invoice_expense_documents')->default(false);
+ });
+
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ //
+ }
+}