diff --git a/app/Constants.php b/app/Constants.php
index b1634af99de4..7ca6382aab75 100644
--- a/app/Constants.php
+++ b/app/Constants.php
@@ -451,6 +451,9 @@ if (! defined('APP_NAME')) {
define('FILTER_INVOICE_DATE', 'invoice_date');
define('FILTER_PAYMENT_DATE', 'payment_date');
+ define('ADDRESS_BILLING', 'billing_address');
+ define('ADDRESS_SHIPPING', 'shipping_address');
+
define('SOCIAL_GOOGLE', 'Google');
define('SOCIAL_FACEBOOK', 'Facebook');
define('SOCIAL_GITHUB', 'GitHub');
diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php
index 65cccabfcfc6..4ca491d7bab5 100644
--- a/app/Http/Controllers/ExportController.php
+++ b/app/Http/Controllers/ExportController.php
@@ -170,7 +170,7 @@ class ExportController extends BaseController
if ($request->input('include') === 'all' || $request->input('clients')) {
$data['clients'] = Client::scope()
- ->with('user', 'contacts', 'country', 'currency')
+ ->with('user', 'contacts', 'country', 'currency', 'shipping_country')
->withArchived()
->get();
}
diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php
index 0a48bd6fcdfd..7df9cb498e32 100644
--- a/app/Http/Controllers/ReportController.php
+++ b/app/Http/Controllers/ReportController.php
@@ -100,6 +100,7 @@ class ReportController extends BaseController
'invoice_status' => request()->invoice_status,
'group_dates_by' => request()->group_dates_by,
'document_filter' => request()->document_filter,
+ 'currency_type' => request()->currency_type,
'export_format' => $format,
];
$report = new $reportClass($startDate, $endDate, $isExport, $options);
diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php
index b0468d9f836d..10173e56ab78 100644
--- a/app/Libraries/Utils.php
+++ b/app/Libraries/Utils.php
@@ -459,6 +459,11 @@ class Utils
public static function parseFloat($value)
{
+ // check for comma as decimal separator
+ if (preg_match('/,[\d]{1,2}$/', $value)) {
+ $value = str_replace(',', '.', $value);
+ }
+
$value = preg_replace('/[^0-9\.\-]/', '', $value);
return floatval($value);
diff --git a/app/Models/Client.php b/app/Models/Client.php
index 3213e3d17026..104325b080a9 100644
--- a/app/Models/Client.php
+++ b/app/Models/Client.php
@@ -53,6 +53,12 @@ class Client extends EntityModel
'quote_number_counter',
'public_notes',
'task_rate',
+ 'shipping_address1',
+ 'shipping_address2',
+ 'shipping_city',
+ 'shipping_state',
+ 'shipping_postal_code',
+ 'shipping_country_id',
];
@@ -179,6 +185,14 @@ class Client extends EntityModel
return $this->belongsTo('App\Models\Country');
}
+ /**
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function shipping_country()
+ {
+ return $this->belongsTo('App\Models\Country');
+ }
+
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
diff --git a/app/Ninja/Datatables/PaymentDatatable.php b/app/Ninja/Datatables/PaymentDatatable.php
index 435b610f92c7..193207f9d82a 100644
--- a/app/Ninja/Datatables/PaymentDatatable.php
+++ b/app/Ninja/Datatables/PaymentDatatable.php
@@ -91,7 +91,13 @@ class PaymentDatatable extends EntityDatatable
[
'amount',
function ($model) {
- return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id);
+ $amount = Utils::formatMoney($model->amount, $model->currency_id, $model->country_id);
+
+ if ($model->exchange_currency_id && $model->exchange_rate != 1) {
+ $amount .= ' | ' . Utils::formatMoney($model->amount * $model->exchange_rate, $model->exchange_currency_id, $model->country_id);
+ }
+
+ return $amount;
},
],
[
diff --git a/app/Ninja/Presenters/ClientPresenter.php b/app/Ninja/Presenters/ClientPresenter.php
index 6bc87ebb46c1..d66c490b28b2 100644
--- a/app/Ninja/Presenters/ClientPresenter.php
+++ b/app/Ninja/Presenters/ClientPresenter.php
@@ -11,6 +11,11 @@ class ClientPresenter extends EntityPresenter
return $this->entity->country ? $this->entity->country->name : '';
}
+ public function shipping_country()
+ {
+ return $this->entity->shipping_country ? $this->entity->shipping_country->name : '';
+ }
+
public function balance()
{
$client = $this->entity;
@@ -51,6 +56,49 @@ class ClientPresenter extends EntityPresenter
return sprintf('%s: %s %s', trans('texts.payment_terms'), trans('texts.payment_terms_net'), $client->defaultDaysDue());
}
+ public function address($addressType = ADDRESS_BILLING)
+ {
+ $str = '';
+ $prefix = $addressType == ADDRESS_BILLING ? '' : 'shipping_';
+ $client = $this->entity;
+
+ if ($address1 = $client->{$prefix . 'address1'}) {
+ $str .= e($address1) . '
';
+ }
+ if ($address2 = $client->{$prefix . 'address2'}) {
+ $str .= e($address2) . '
';
+ }
+ if ($cityState = $this->getCityState($addressType)) {
+ $str .= e($cityState) . '
';
+ }
+ if ($country = $client->{$prefix . 'country'}) {
+ $str .= e($country->name) . '
';
+ }
+
+ if ($str) {
+ $str = '' . trans('texts.' . $addressType) . '
' . $str;
+ }
+
+ return $str;
+ }
+
+ /**
+ * @return string
+ */
+ public function getCityState($addressType = ADDRESS_BILLING)
+ {
+ $client = $this->entity;
+ $prefix = $addressType == ADDRESS_BILLING ? '' : 'shipping_';
+ $swap = $client->{$prefix . 'country'} && $client->{$prefix . 'country'}->swap_postal_code;
+
+ $city = e($client->{$prefix . 'city'});
+ $state = e($client->{$prefix . 'state'});
+ $postalCode = e($client->{$prefix . 'post_code'});
+
+ return Utils::cityStateZip($city, $state, $postalCode, $swap);
+ }
+
+
/**
* @return string
*/
diff --git a/app/Ninja/Reports/ExpenseReport.php b/app/Ninja/Reports/ExpenseReport.php
index 505db8e7f170..8457d7079b06 100644
--- a/app/Ninja/Reports/ExpenseReport.php
+++ b/app/Ninja/Reports/ExpenseReport.php
@@ -38,7 +38,8 @@ class ExpenseReport extends AbstractReport
$zip = Archive::instance_by_useragent(date('Y-m-d') . '_' . str_replace(' ', '_', trans('texts.expense_documents')));
foreach ($expenses->get() as $expense) {
foreach ($expense->documents as $document) {
- $name = sprintf('%s_%s_%s_%s', date('Y-m-d'), trans('texts.expense'), $expense->public_id, $document->name);
+ $expenseId = str_pad($expense->public_id, $account->invoice_number_padding, '0', STR_PAD_LEFT);
+ $name = sprintf('%s_%s_%s_%s', date('Y-m-d'), trans('texts.expense'), $expenseId, $document->name);
$name = str_replace(' ', '_', $name);
$zip->add_file($name, $document->getRaw());
}
diff --git a/app/Ninja/Reports/PaymentReport.php b/app/Ninja/Reports/PaymentReport.php
index f3589ca00360..53d201f1880f 100644
--- a/app/Ninja/Reports/PaymentReport.php
+++ b/app/Ninja/Reports/PaymentReport.php
@@ -4,6 +4,7 @@ namespace App\Ninja\Reports;
use App\Models\Payment;
use Auth;
+use Utils;
class PaymentReport extends AbstractReport
{
@@ -20,6 +21,7 @@ class PaymentReport extends AbstractReport
public function run()
{
$account = Auth::user()->account;
+ $currencyType = $this->options['currency_type'];
$invoiceMap = [];
$payments = Payment::scope()
@@ -39,22 +41,36 @@ class PaymentReport extends AbstractReport
foreach ($payments->get() as $payment) {
$invoice = $payment->invoice;
$client = $payment->client;
+ $amount = $payment->getCompletedAmount();
+
+ if ($currencyType == 'converted') {
+ $amount *= $payment->exchange_rate;
+ $this->addToTotals($payment->exchange_currency_id, 'paid', $amount);
+ $amount = Utils::formatMoney($amount, $payment->exchange_currency_id);
+ } else {
+ $this->addToTotals($client->currency_id, 'paid', $amount);
+ $amount = $account->formatMoney($amount, $client);
+ }
+
$this->data[] = [
$this->isExport ? $client->getDisplayName() : $client->present()->link,
$this->isExport ? $invoice->invoice_number : $invoice->present()->link,
$invoice->present()->invoice_date,
$account->formatMoney($invoice->amount, $client),
$payment->present()->payment_date,
- $account->formatMoney($payment->getCompletedAmount(), $client),
+ $amount,
$payment->present()->method,
];
if (! isset($invoiceMap[$invoice->id])) {
- $this->addToTotals($client->currency_id, 'amount', $invoice->amount);
$invoiceMap[$invoice->id] = true;
- }
- $this->addToTotals($client->currency_id, 'paid', $payment->getCompletedAmount());
+ if ($currencyType == 'converted') {
+ $this->addToTotals($payment->exchange_currency_id, 'amount', $invoice->amount * $payment->exchange_rate);
+ } else {
+ $this->addToTotals($client->currency_id, 'amount', $invoice->amount);
+ }
+ }
}
}
}
diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php
index 38d8e2988def..b9bb3b8a06d1 100644
--- a/app/Ninja/Repositories/InvoiceRepository.php
+++ b/app/Ninja/Repositories/InvoiceRepository.php
@@ -511,6 +511,8 @@ class InvoiceRepository extends BaseRepository
$invoice->invoice_footer = '';
}
+ $invoice->public_notes = isset($data['public_notes']) ? trim($data['public_notes']) : '';
+
// process date variables if not recurring
if (! $invoice->is_recurring) {
$invoice->terms = Utils::processVariables($invoice->terms);
diff --git a/app/Ninja/Repositories/PaymentRepository.php b/app/Ninja/Repositories/PaymentRepository.php
index 06f019a143ac..1aa790774947 100644
--- a/app/Ninja/Repositories/PaymentRepository.php
+++ b/app/Ninja/Repositories/PaymentRepository.php
@@ -64,6 +64,8 @@ class PaymentRepository extends BaseRepository
'payments.routing_number',
'payments.bank_name',
'payments.private_notes',
+ 'payments.exchange_rate',
+ 'payments.exchange_currency_id',
'invoices.is_deleted as invoice_is_deleted',
'gateways.name as gateway_name',
'gateways.id as gateway_id',
@@ -187,7 +189,7 @@ class PaymentRepository extends BaseRepository
$payment->payment_date = date('Y-m-d');
}
- $payment->fill(request()->all());
+ $payment->fill($input);
if (! $publicId) {
$clientId = $input['client_id'];
diff --git a/app/Ninja/Transformers/ClientTransformer.php b/app/Ninja/Transformers/ClientTransformer.php
index e6814a287f5e..a4277045a9b5 100644
--- a/app/Ninja/Transformers/ClientTransformer.php
+++ b/app/Ninja/Transformers/ClientTransformer.php
@@ -38,6 +38,12 @@ class ClientTransformer extends EntityTransformer
* @SWG\Property(property="id_number", type="string", example="123456")
* @SWG\Property(property="language_id", type="integer", example=1)
* @SWG\Property(property="task_rate", type="number", format="float", example=10)
+ * @SWG\Property(property="shipping_address1", type="string", example="10 Main St.")
+ * @SWG\Property(property="shipping_address2", type="string", example="1st Floor")
+ * @SWG\Property(property="shipping_city", type="string", example="New York")
+ * @SWG\Property(property="shipping_state", type="string", example="NY")
+ * @SWG\Property(property="shipping_postal_code", type="string", example=10010)
+ * @SWG\Property(property="shipping_country_id", type="integer", example=840)
*/
protected $defaultIncludes = [
'contacts',
@@ -137,6 +143,12 @@ class ClientTransformer extends EntityTransformer
'invoice_number_counter' => (int) $client->invoice_number_counter,
'quote_number_counter' => (int) $client->quote_number_counter,
'task_rate' => (float) $client->task_rate,
+ 'shipping_address1' => $client->shipping_address1,
+ 'shipping_address2' => $client->shipping_address2,
+ 'shipping_city' => $client->shipping_city,
+ 'shipping_state' => $client->shipping_state,
+ 'shipping_postal_code' => $client->shipping_postal_code,
+ 'shipping_country_id' => (int) $client->shipping_country_id,
]);
}
}
diff --git a/app/Ninja/Transformers/PaymentTransformer.php b/app/Ninja/Transformers/PaymentTransformer.php
index ff78eff829b1..23906c3029ea 100644
--- a/app/Ninja/Transformers/PaymentTransformer.php
+++ b/app/Ninja/Transformers/PaymentTransformer.php
@@ -60,6 +60,8 @@ class PaymentTransformer extends EntityTransformer
'invoice_id' => (int) ($this->invoice ? $this->invoice->public_id : $payment->invoice->public_id),
'invoice_number' => $this->invoice ? $this->invoice->invoice_number : $payment->invoice->invoice_number,
'private_notes' => $payment->private_notes,
+ 'exchange_rate' => (float) $payment->exchange_rate,
+ 'exchange_currency_id' => (int) $payment->exchange_currency_id,
]);
}
}
diff --git a/database/migrations/2017_11_15_114422_add_subdomain_to_lookups.php b/database/migrations/2017_11_15_114422_add_subdomain_to_lookups.php
index 773e4c2981db..23522d925241 100644
--- a/database/migrations/2017_11_15_114422_add_subdomain_to_lookups.php
+++ b/database/migrations/2017_11_15_114422_add_subdomain_to_lookups.php
@@ -26,6 +26,18 @@ class AddSubdomainToLookups extends Migration
$table->decimal('exchange_rate', 13, 4)->default(1)->change();
});
+ Schema::table('clients', function ($table) {
+ $table->string('shipping_address1')->nullable();
+ $table->string('shipping_address2')->nullable();
+ $table->string('shipping_city')->nullable();
+ $table->string('shipping_state')->nullable();
+ $table->string('shipping_postal_code')->nullable();
+ $table->unsignedInteger('shipping_country_id')->nullable();
+ });
+
+ Schema::table('clients', function ($table) {
+ $table->foreign('shipping_country_id')->references('id')->on('currencies');
+ });
}
/**
@@ -43,5 +55,15 @@ class AddSubdomainToLookups extends Migration
$table->dropColumn('exchange_rate');
$table->dropColumn('exchange_currency_id');
});
+
+ Schema::table('clients', function ($table) {
+ $table->dropForeign('clients_shipping_country_id_foreign');
+ $table->dropColumn('shipping_address1');
+ $table->dropColumn('shipping_address2');
+ $table->dropColumn('shipping_city');
+ $table->dropColumn('shipping_state');
+ $table->dropColumn('shipping_postal_code');
+ $table->dropColumn('shipping_country_id');
+ });
}
}
diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php
index f00c4d867bb7..bf0962a59a65 100644
--- a/resources/lang/en/texts.php
+++ b/resources/lang/en/texts.php
@@ -2524,6 +2524,22 @@ $LANG = array(
'your_password_reset_link' => 'Your Password Reset Link',
'subdomain_taken' => 'The subdomain is already in use',
'client_login' => 'Client Login',
+ 'converted_amount' => 'Converted Amount',
+ 'default' => 'Default',
+ 'shipping_address' => 'Shipping Address',
+ 'bllling_address' => 'Billing Address',
+ 'billing_address1' => 'Billing Street',
+ 'billing_address2' => 'Billing Apt/Suite',
+ 'billing_city' => 'Billing City',
+ 'billing_state' => 'Billing State/Province',
+ 'billing_postal_code' => 'Billing Postal Code',
+ 'billing_country' => 'Billing Country',
+ 'shipping_address1' => 'Shipping Street',
+ 'shipping_address2' => 'Shipping Apt/Suite',
+ 'shipping_city' => 'Shipping City',
+ 'shipping_state' => 'Shipping State/Province',
+ 'shipping_postal_code' => 'Shipping Postal Code',
+ 'shipping_country' => 'Shipping Country',
);
diff --git a/resources/views/clients/edit.blade.php b/resources/views/clients/edit.blade.php
index 32c091fbb19b..38fd0fbae51b 100644
--- a/resources/views/clients/edit.blade.php
+++ b/resources/views/clients/edit.blade.php
@@ -68,19 +68,42 @@
-