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 @@ -
+

{!! trans('texts.address') !!}

- {!! Former::text('address1') !!} - {!! Former::text('address2') !!} - {!! Former::text('city') !!} - {!! Former::text('state') !!} - {!! Former::text('postal_code') !!} - {!! Former::select('country_id')->addOption('','') - ->fromQuery($countries, 'name', 'id') !!} +
+ +
+
+
+ {!! Former::text('address1') !!} + {!! Former::text('address2') !!} + {!! Former::text('city') !!} + {!! Former::text('state') !!} + {!! Former::text('postal_code') !!} + {!! Former::select('country_id')->addOption('','') + ->fromQuery($countries, 'name', 'id') !!} +
+
+ {!! Former::text('shipping_address1')->label('address1') !!} + {!! Former::text('shipping_address2')->label('address2') !!} + {!! Former::text('shipping_city')->label('city') !!} + {!! Former::text('shipping_state')->label('state') !!} + {!! Former::text('shipping_postal_code')->label('postal_code') !!} + {!! Former::select('shipping_country_id')->addOption('','') + ->fromQuery($countries, 'name', 'id')->label('country_id') !!} +
+
@@ -233,7 +256,7 @@