diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 1ef81862d069..4b55fd317543 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -1,11 +1,11 @@ -on: +on: push: branches: - v5-develop pull_request: - branches: + branches: - v5-develop - + name: phpunit jobs: run: @@ -24,7 +24,7 @@ jobs: DB_DATABASE: ninja DB_USERNAME: root DB_PASSWORD: ninja - DB_HOST: '127.0.0.1' + DB_HOST: '127.0.0.1' BROADCAST_DRIVER: log CACHE_DRIVER: file QUEUE_CONNECTION: sync @@ -47,12 +47,12 @@ jobs: MYSQL_DATABASE: ninja MYSQL_ROOT_PASSWORD: ninja options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 - + steps: - name: Start mysql service run: | sudo /etc/init.d/mysql start - + - name: Verify MariaDB connection env: DB_PORT: ${{ job.services.mariadb.ports[3306] }} @@ -62,13 +62,13 @@ jobs: while ! mysqladmin ping -h"127.0.0.1" -P"$DB_PORT" --silent; do sleep 1 done - + - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} extensions: mysql, mysqlnd, sqlite3, bcmath, gmp, gd, curl, zip, openssl, mbstring, xml - + - uses: actions/checkout@v1 with: ref: v5-develop @@ -77,7 +77,7 @@ jobs: - name: Copy .env run: | cp .env.ci .env - + - name: Install composer dependencies run: | composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} @@ -89,12 +89,12 @@ jobs: php artisan optimize php artisan cache:clear php artisan config:cache - + - name: Create DB and schemas run: | mkdir -p database touch database/database.sqlite - + - name: Migrate Database run: | php artisan migrate:fresh --seed --force && php artisan db:seed --force @@ -103,7 +103,7 @@ jobs: run: | npm i npm run production - + - name: Run Testsuite run: | cat .env @@ -111,3 +111,7 @@ jobs: env: DB_PORT: ${{ job.services.mysql.ports[3306] }} + - name: Run php-cs-fixer + run: | + vendor/bin/php-cs-fixer fix + diff --git a/VERSION.txt b/VERSION.txt index 7d908497ecd1..2baca668b6f3 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.0.43 \ No newline at end of file +5.0.44 \ No newline at end of file diff --git a/app/Console/Commands/GenerateSetupKey.php b/app/Console/Commands/GenerateSetupKey.php deleted file mode 100644 index 798d3112c930..000000000000 --- a/app/Console/Commands/GenerateSetupKey.php +++ /dev/null @@ -1,55 +0,0 @@ -info('Success! Copy the following content into your .env or docker-compose.yml:'); - $this->warn('base64:' . $randomString); - } -} diff --git a/app/Http/Controllers/OpenAPI/TaskSchema.php b/app/Http/Controllers/OpenAPI/TaskSchema.php index 23acb2649e06..ab7fd87c3a55 100644 --- a/app/Http/Controllers/OpenAPI/TaskSchema.php +++ b/app/Http/Controllers/OpenAPI/TaskSchema.php @@ -18,7 +18,7 @@ * @OA\Property(property="task_status_id", type="string", example="", description="________"), * @OA\Property(property="description", type="string", example="", description="________"), * @OA\Property(property="duration", type="integer", example="", description="________"), - * @OA\Property(property="task_status_sort_order", type="integer", example="", description="________"), + * @OA\Property(property="task_status_order", type="integer", example="", description="________"), * @OA\Property(property="custom_value1", type="string", example="", description="________"), * @OA\Property(property="custom_value2", type="string", example="", description="________"), * @OA\Property(property="custom_value3", type="string", example="", description="________"), diff --git a/app/Http/Controllers/SetupController.php b/app/Http/Controllers/SetupController.php index 97b4b4d1b506..674f163eff78 100644 --- a/app/Http/Controllers/SetupController.php +++ b/app/Http/Controllers/SetupController.php @@ -12,6 +12,7 @@ namespace App\Http\Controllers; +use \Illuminate\Support\Facades\DB; use App\Http\Requests\Setup\CheckDatabaseRequest; use App\Http\Requests\Setup\CheckMailRequest; use App\Http\Requests\Setup\StoreSetupRequest; @@ -22,7 +23,6 @@ use App\Utils\CurlUtils; use App\Utils\SystemHealth; use App\Utils\Traits\AppSetup; use Beganovich\Snappdf\Snappdf; -use DB; use Exception; use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Routing\ResponseFactory; @@ -55,7 +55,7 @@ class SetupController extends Controller { try { $check = SystemHealth::check(false); - } catch (\Exception $e) { + } catch (Exception $e) { nlog(['message' => $e->getMessage(), 'action' => 'SetupController::doSetup()']); return response()->json(['message' => $e->getMessage()], 400); @@ -71,9 +71,9 @@ class SetupController extends Controller $db = SystemHealth::dbCheck($request); if ($db['success'] == false) { - throw new \Exception($db['message']); + throw new Exception($db['message']); } - } catch (\Exception $e) { + } catch (Exception $e) { return response([ 'message' => 'Oops, connection to database was not successful.', 'error' => $e->getMessage(), @@ -85,10 +85,10 @@ class SetupController extends Controller $smtp = SystemHealth::testMailServer($request); if ($smtp['success'] == false) { - throw new \Exception($smtp['message']); + throw new Exception($smtp['message']); } } - } catch (\Exception $e) { + } catch (Exception $e) { return response([ 'message' => 'Oops, connection to mail server was not successful.', 'error' => $e->getMessage(), @@ -100,9 +100,10 @@ class SetupController extends Controller $env_values = [ 'APP_URL' => $request->input('url'), 'REQUIRE_HTTPS' => $request->input('https') ? 'true' : 'false', - 'APP_DEBUG' => $request->input('debug') ? 'true' : 'false', + 'APP_DEBUG' => 'false', 'DB_HOST1' => $request->input('db_host'), + 'DB_PORT1' => $request->input('db_port'), 'DB_DATABASE1' => $request->input('db_database'), 'DB_USERNAME1' => $request->input('db_username'), 'DB_PASSWORD1' => $request->input('db_password'), @@ -173,7 +174,7 @@ class SetupController extends Controller } return response($status, 400); - } catch (\Exception $e) { + } catch (Exception $e) { nlog(['message' => $e->getMessage(), 'action' => 'SetupController::checkDB()']); return response()->json(['message' => $e->getMessage()], 400); @@ -203,17 +204,6 @@ class SetupController extends Controller } } - private function failsafeMailCheck($request) - { - $response = SystemHealth::testMailServer($request); - - if ($response['success']) { - true; - } - - return false; - } - public function checkPdf(Request $request) { try { @@ -231,9 +221,10 @@ class SetupController extends Controller ->setHtml('GENERATING PDFs WORKS! Thank you for using Invoice Ninja!') ->generate(); - Storage::put('public/test.pdf', $pdf); + Storage::disk(config('filesystems.default'))->put('test.pdf', $pdf); + Storage::disk('local')->put('test.pdf', $pdf); - return response(['url' => asset('test.pdf')], 200); + return response(['url' => Storage::disk('local')->url('test.pdf')], 200); } catch (Exception $e) { nlog($e->getMessage()); diff --git a/app/Http/Requests/ClientPortal/StoreDocumentRequest.php b/app/Http/Requests/ClientPortal/StoreDocumentRequest.php index 3294abcd48fb..93b5ea17cb9f 100644 --- a/app/Http/Requests/ClientPortal/StoreDocumentRequest.php +++ b/app/Http/Requests/ClientPortal/StoreDocumentRequest.php @@ -29,7 +29,7 @@ class StoreDocumentRequest extends Request public function rules() { return [ - 'file' => 'required|max:10000|mimes:png,svg,jpeg,gif,jpg,bmp', + 'file' => 'required|max:10000|mimes:png,svg,jpeg,gif,jpg,bmp,txt,doc,docx,xls,xlsx,pdf', ]; } diff --git a/app/Http/Requests/Invoice/ActionInvoiceRequest.php b/app/Http/Requests/Invoice/ActionInvoiceRequest.php index d98b43f94c71..fbe5c8c9c222 100644 --- a/app/Http/Requests/Invoice/ActionInvoiceRequest.php +++ b/app/Http/Requests/Invoice/ActionInvoiceRequest.php @@ -18,8 +18,8 @@ use App\Utils\Traits\MakesHash; class ActionInvoiceRequest extends Request { - use MakesHash; - use ActionsInvoice; + use MakesHash; + use ActionsInvoice; /** * Determine if the user is authorized to make this request. * @@ -36,31 +36,28 @@ class ActionInvoiceRequest extends Request public function rules() { - return [ - 'action' => 'required' - ]; + return [ + 'action' => 'required' + ]; } protected function prepareForValidation() { $input = $this->all(); - $this->invoice = Invoice::find($this->decodePrimary($invoice_id)); + $this->invoice = Invoice::find($this->decodePrimary($invoice_id)); - if(!array_key_exists('action', $input)) { - $this->error_msg = 'Action is a required field'; - } - elseif(!$this->invoiceDeletable($this->invoice)){ - unset($input['action']); - $this->error_msg = 'This invoice cannot be deleted'; - } - elseif(!$this->invoiceCancellable($this->invoice)) { - unset($input['action']); - $this->error_msg = 'This invoice cannot be cancelled'; - } - else if(!$this->invoiceReversable($this->invoice)) { - unset($input['action']); - $this->error_msg = 'This invoice cannot be reversed'; + if (!array_key_exists('action', $input)) { + $this->error_msg = 'Action is a required field'; + } elseif (!$this->invoiceDeletable($this->invoice)) { + unset($input['action']); + $this->error_msg = 'This invoice cannot be deleted'; + } elseif (!$this->invoiceCancellable($this->invoice)) { + unset($input['action']); + $this->error_msg = 'This invoice cannot be cancelled'; + } elseif (!$this->invoiceReversable($this->invoice)) { + unset($input['action']); + $this->error_msg = 'This invoice cannot be reversed'; } $this->replace($input); @@ -68,13 +65,8 @@ class ActionInvoiceRequest extends Request public function messages() { - return [ - 'action' => $this->error_msg, - ]; + return [ + 'action' => $this->error_msg, + ]; } - - - - } - diff --git a/app/Http/Requests/Setup/CheckDatabaseRequest.php b/app/Http/Requests/Setup/CheckDatabaseRequest.php index bc4b3c4ae477..689324e15f39 100644 --- a/app/Http/Requests/Setup/CheckDatabaseRequest.php +++ b/app/Http/Requests/Setup/CheckDatabaseRequest.php @@ -35,6 +35,7 @@ class CheckDatabaseRequest extends Request { return [ 'db_host' => ['required'], + 'db_port' => ['required'], 'db_database' => ['required'], 'db_username' => ['required'], ]; diff --git a/app/Jobs/Cron/RecurringInvoicesCron.php b/app/Jobs/Cron/RecurringInvoicesCron.php index f741688994c2..cd6be762e38d 100644 --- a/app/Jobs/Cron/RecurringInvoicesCron.php +++ b/app/Jobs/Cron/RecurringInvoicesCron.php @@ -16,7 +16,6 @@ use App\Libraries\MultiDB; use App\Models\RecurringInvoice; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\Log; class RecurringInvoicesCron { diff --git a/app/Jobs/Entity/CreateEntityPdf.php b/app/Jobs/Entity/CreateEntityPdf.php index 60948d263182..f7d8cc7cce2a 100644 --- a/app/Jobs/Entity/CreateEntityPdf.php +++ b/app/Jobs/Entity/CreateEntityPdf.php @@ -158,11 +158,11 @@ class CreateEntityPdf implements ShouldQueue } if (config('ninja.log_pdf_html')) { - nlog($maker->getCompiledHTML()); + info($maker->getCompiledHTML()); } if ($pdf) { - $instance = Storage::disk($this->disk)->put($file_path, $pdf); + Storage::disk($this->disk)->put($file_path, $pdf); } return $file_path; diff --git a/app/Jobs/Util/Import.php b/app/Jobs/Util/Import.php index 4c42c935647e..67e3c1d8663c 100644 --- a/app/Jobs/Util/Import.php +++ b/app/Jobs/Util/Import.php @@ -390,6 +390,7 @@ class Import implements ShouldQueue foreach ($data as $resource) { $modified = $resource; unset($modified['id']); + unset($modified['password']); //cant import passwords. $user = $user_repository->save($modified, $this->fetchUser($resource['email']), true, true); @@ -464,19 +465,16 @@ class Import implements ShouldQueue $client->fresh(); $new_contacts = $client->contacts; - foreach($resource['contacts'] as $key => $old_contact) - { + foreach ($resource['contacts'] as $key => $old_contact) { $contact_match = $new_contacts->where('contact_key', $old_contact['contact_key'])->first(); - if($contact_match) - { + if ($contact_match) { $this->ids['client_contacts']['client_contacts_'.$old_contact['id']] = [ 'old' => $old_contact['id'], 'new' => $contact_match->id, ]; } } - } $key = "clients_{$resource['id']}"; @@ -629,16 +627,12 @@ class Import implements ShouldQueue unset($modified['id']); - if(array_key_exists('invitations', $resource)) - { - foreach($resource['invitations'] as $key => $invite) - { - + if (array_key_exists('invitations', $resource)) { + foreach ($resource['invitations'] as $key => $invite) { $resource['invitations'][$key]['client_contact_id'] = $this->transformId('client_contacts', $invite['client_contact_id']); $resource['invitations'][$key]['user_id'] = $modified['user_id']; $resource['invitations'][$key]['company_id'] = $this->company->id; unset($resource['invitations'][$key]['recurring_invoice_id']); - } $modified['invitations'] = $resource['invitations']; @@ -694,19 +688,15 @@ class Import implements ShouldQueue unset($modified['id']); - if(array_key_exists('invitations', $resource)) - { - foreach($resource['invitations'] as $key => $invite) - { + if (array_key_exists('invitations', $resource)) { + foreach ($resource['invitations'] as $key => $invite) { $resource['invitations'][$key]['client_contact_id'] = $this->transformId('client_contacts', $invite['client_contact_id']); $resource['invitations'][$key]['user_id'] = $modified['user_id']; $resource['invitations'][$key]['company_id'] = $this->company->id; unset($resource['invitations'][$key]['invoice_id']); - } $modified['invitations'] = $resource['invitations']; - } $invoice = $invoice_repository->save( $modified, @@ -877,7 +867,7 @@ class Import implements ShouldQueue PaymentFactory::create($this->company->id, $modified['user_id']) ); - if($resource['company_gateway_id'] != 'NULL' && $resource['company_gateway_id'] != NULL){ + if ($resource['company_gateway_id'] != 'NULL' && $resource['company_gateway_id'] != null) { $payment->company_gateway_id = $this->transformId('company_gateways', $resource['company_gateway_id']); $payment->save(); } diff --git a/app/Models/Client.php b/app/Models/Client.php index 54b9929d62c9..e09503cb7519 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -46,7 +46,6 @@ class Client extends BaseModel implements HasLocalePreference protected $fillable = [ 'assigned_user_id', - 'currency_id', 'name', 'website', 'private_notes', diff --git a/app/Models/Company.php b/app/Models/Company.php index efa36e138ee5..f13ec767538a 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -45,6 +45,8 @@ class Company extends BaseModel protected $presenter = CompanyPresenter::class; protected $fillable = [ + 'hide_empty_columns_on_pdf', + 'calculate_expense_tax_by_amount', 'invoice_expense_documents', 'invoice_task_documents', 'show_tasks_table', diff --git a/app/Models/Expense.php b/app/Models/Expense.php index 1bc19de6dcc7..f8cc589fa9b5 100644 --- a/app/Models/Expense.php +++ b/app/Models/Expense.php @@ -72,6 +72,11 @@ class Expense extends BaseModel return $this->morphMany(Document::class, 'documentable'); } + public function user() + { + return $this->belongsTo(User::class)->withTrashed(); + } + public function assigned_user() { return $this->belongsTo(User::class, 'assigned_user_id', 'id'); diff --git a/app/Models/ExpenseCategory.php b/app/Models/ExpenseCategory.php index 8ad06ff89547..95463ee58fe2 100644 --- a/app/Models/ExpenseCategory.php +++ b/app/Models/ExpenseCategory.php @@ -20,6 +20,7 @@ class ExpenseCategory extends BaseModel protected $fillable = [ 'name', + 'color', ]; public function getEntityType() diff --git a/app/Models/Presenters/ClientContactPresenter.php b/app/Models/Presenters/ClientContactPresenter.php index 7e1a4de8efb4..09fdbaa3c0ad 100644 --- a/app/Models/Presenters/ClientContactPresenter.php +++ b/app/Models/Presenters/ClientContactPresenter.php @@ -20,13 +20,14 @@ class ClientContactPresenter extends EntityPresenter * @return string */ public function name() - { + { $contact_name = $this->entity->first_name.' '.$this->entity->last_name; - if(strlen($contact_name) > 1) + if (strlen($contact_name) > 1) { return $contact_name; + } - return $this->entity->client->present()->name(); + return $this->entity->client->present()->name(); } public function first_name() diff --git a/app/Models/Project.php b/app/Models/Project.php index c295d666fe79..7d19ca7f90e3 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -36,6 +36,7 @@ class Project extends BaseModel 'custom_value3', 'custom_value4', 'assigned_user_id', + 'color', ]; public function getEntityType() diff --git a/app/Models/RecurringInvoice.php b/app/Models/RecurringInvoice.php index c13fa48aeff3..1c34f6c5e98e 100644 --- a/app/Models/RecurringInvoice.php +++ b/app/Models/RecurringInvoice.php @@ -376,8 +376,9 @@ class RecurringInvoice extends BaseModel $data = []; - if(!Carbon::parse($this->next_send_date)) + if (!Carbon::parse($this->next_send_date)) { return $data; + } $next_send_date = Carbon::parse($this->next_send_date)->copy(); diff --git a/app/Models/Task.php b/app/Models/Task.php index b60145a86a8a..3e3d902dcbdd 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -34,11 +34,12 @@ class Task extends BaseModel 'is_running', 'time_log', 'status_id', - 'status_sort_order', + 'status_sort_order', //deprecated 'invoice_documents', 'rate', 'number', 'is_date_based', + 'status_order', ]; protected $touches = []; diff --git a/app/Models/TaskStatus.php b/app/Models/TaskStatus.php index 557eb6164baf..915893e04b8e 100644 --- a/app/Models/TaskStatus.php +++ b/app/Models/TaskStatus.php @@ -29,5 +29,5 @@ class TaskStatus extends BaseModel */ protected $dates = ['deleted_at']; - protected $fillable = ['name']; + protected $fillable = ['name','color','status_order']; } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index c42d72b0a450..12f366d41576 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -123,8 +123,8 @@ use App\Listeners\Invoice\InvoiceArchivedActivity; use App\Listeners\Invoice\InvoiceCancelledActivity; use App\Listeners\Invoice\InvoiceDeletedActivity; use App\Listeners\Invoice\InvoiceEmailActivity; -use App\Listeners\Invoice\InvoiceEmailFailedActivity; use App\Listeners\Invoice\InvoiceEmailedNotification; +use App\Listeners\Invoice\InvoiceEmailFailedActivity; use App\Listeners\Invoice\InvoicePaidActivity; use App\Listeners\Invoice\InvoiceReminderEmailActivity; use App\Listeners\Invoice\InvoiceRestoredActivity; @@ -132,8 +132,8 @@ use App\Listeners\Invoice\InvoiceReversedActivity; use App\Listeners\Invoice\InvoiceViewedActivity; use App\Listeners\Invoice\UpdateInvoiceActivity; use App\Listeners\Misc\InvitationViewedListener; -use App\Listeners\Payment\PaymentEmailFailureActivity; use App\Listeners\Payment\PaymentEmailedActivity; +use App\Listeners\Payment\PaymentEmailFailureActivity; use App\Listeners\Payment\PaymentNotification; use App\Listeners\Payment\PaymentRestoredActivity; use App\Listeners\Quote\QuoteApprovedActivity; diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 7f64ef8804c0..008092a8088b 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -11,7 +11,6 @@ namespace App\Providers; -use App\Models\RecurringInvoice; use App\Utils\Traits\MakesHash; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; use Illuminate\Support\Facades\Route; @@ -37,7 +36,6 @@ class RouteServiceProvider extends ServiceProvider { // parent::boot(); - } /** diff --git a/app/Repositories/Migration/InvoiceMigrationRepository.php b/app/Repositories/Migration/InvoiceMigrationRepository.php index eddd511cb5c9..832af28d8e44 100644 --- a/app/Repositories/Migration/InvoiceMigrationRepository.php +++ b/app/Repositories/Migration/InvoiceMigrationRepository.php @@ -92,11 +92,11 @@ class InvoiceMigrationRepository extends BaseRepository InvoiceInvitation::unguard(); RecurringInvoiceInvitation::unguard(); - if($model instanceof RecurringInvoice) + if ($model instanceof RecurringInvoice) { $lcfirst_resource_id = 'recurring_invoice_id'; + } - foreach($data['invitations'] as $invitation) - { + foreach ($data['invitations'] as $invitation) { nlog($invitation); $new_invitation = $invitation_factory_class::create($model->company_id, $model->user_id); @@ -107,35 +107,35 @@ class InvoiceMigrationRepository extends BaseRepository InvoiceInvitation::reguard(); RecurringInvoiceInvitation::reguard(); -/* - if (isset($data['invitations'])) { - $invitations = collect($data['invitations']); + /* + if (isset($data['invitations'])) { + $invitations = collect($data['invitations']); - $model->invitations->pluck('key')->diff($invitations->pluck('key'))->each(function ($invitation) use ($resource) { - $this->getInvitation($invitation, $resource)->delete(); - }); + $model->invitations->pluck('key')->diff($invitations->pluck('key'))->each(function ($invitation) use ($resource) { + $this->getInvitation($invitation, $resource)->delete(); + }); - foreach ($data['invitations'] as $invitation) { + foreach ($data['invitations'] as $invitation) { - //if no invitations are present - create one. - if (! $this->getInvitation($invitation, $resource)) { - if (isset($invitation['id'])) { - unset($invitation['id']); - } + //if no invitations are present - create one. + if (! $this->getInvitation($invitation, $resource)) { + if (isset($invitation['id'])) { + unset($invitation['id']); + } - //make sure we are creating an invite for a contact who belongs to the client only! - $contact = ClientContact::find($invitation['client_contact_id']); + //make sure we are creating an invite for a contact who belongs to the client only! + $contact = ClientContact::find($invitation['client_contact_id']); - if ($contact && $model->client_id == $contact->client_id) { - $new_invitation = $invitation_factory_class::create($model->company_id, $model->user_id); - $new_invitation->{$lcfirst_resource_id} = $model->id; - $new_invitation->client_contact_id = $contact->id; - $new_invitation->save(); + if ($contact && $model->client_id == $contact->client_id) { + $new_invitation = $invitation_factory_class::create($model->company_id, $model->user_id); + $new_invitation->{$lcfirst_resource_id} = $model->id; + $new_invitation->client_contact_id = $contact->id; + $new_invitation->save(); + } + } } } - } - } -*/ + */ $model->load('invitations'); /* If no invitations have been created, this is our fail safe to maintain state*/ diff --git a/app/Repositories/TaskRepository.php b/app/Repositories/TaskRepository.php index 0368b94a4f62..817e6f77b630 100644 --- a/app/Repositories/TaskRepository.php +++ b/app/Repositories/TaskRepository.php @@ -42,8 +42,9 @@ class TaskRepository extends BaseRepository $task->description = trim($data['description']); } - if (isset($data['status_sort_order'])) { - $task->status_sort_order = $data['status_sort_order']; + //todo i can't set it - i need to calculate it. + if (isset($data['status_order'])) { + $task->status_order = $data['status_order']; } if (isset($data['time_log'])) { diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php index f22b802dc23e..294c07f7962e 100644 --- a/app/Repositories/UserRepository.php +++ b/app/Repositories/UserRepository.php @@ -63,8 +63,7 @@ class UserRepository extends BaseRepository $user->fill($details); //allow users to change only their passwords - not others! - if(auth()->user()->id == $user->id && array_key_exists('password', $data) && isset($data['password'])) - { + if (auth()->user()->id == $user->id && array_key_exists('password', $data) && isset($data['password'])) { $user->password = Hash::make($data['password']); } diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php index 08271e0e7531..7102bcb237c1 100644 --- a/app/Services/PdfMaker/Design.php +++ b/app/Services/PdfMaker/Design.php @@ -127,7 +127,7 @@ class Design extends BaseDesign ]; } - public function companyDetails() + public function companyDetails(): array { $variables = $this->context['pdf_variables']['company_details']; @@ -309,13 +309,13 @@ class Design extends BaseDesign foreach ($this->context['pdf_variables']["{$type}_columns"] as $column) { if (array_key_exists($column, $aliases)) { - $elements[] = ['element' => 'th', 'content' => $aliases[$column] . '_label']; + $elements[] = ['element' => 'th', 'content' => $aliases[$column] . '_label', 'properties' => ['hidden' => $this->client->company->hide_empty_columns_on_pdf]]; } elseif ($column == '$product.discount' && !$this->client->company->enable_product_discount) { $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'style' => 'display: none;']]; } elseif ($column == '$product.quantity' && !$this->client->company->enable_product_quantity) { $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'style' => 'display: none;']]; } else { - $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th']]; + $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'hidden' => $this->client->company->hide_empty_columns_on_pdf]]; } } @@ -394,16 +394,6 @@ class Design extends BaseDesign $element['elements'][] = ['element' => 'td', 'content' => $row['$product.quantity'], 'properties' => ['data-ref' => 'product_table-product.quantity-td', 'style' => 'display: none;']]; } elseif ($cell == '$task.hours') { $element['elements'][] = ['element' => 'td', 'content' => $row['$task.quantity'], 'properties' => ['data-ref' => 'task_table-task.hours-td']]; - } elseif ($cell == '$task.description') { - $_element = ['element' => 'td', 'content' => '', 'elements' => [ - ['element' => 'span', 'content' => $row[$cell], 'properties' => ['data-ref' => 'task_table-task.description-td']], - ]]; - - foreach ($this->getTaskTimeLogs($row) as $log) { - $_element['elements'][] = ['element' => 'span', 'content' => $log, 'properties' => ['class' => 'task-duration', 'data-ref' => 'task_table-task.duration']]; - } - - $element['elements'][] = $_element; } else { $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => "{$_type}_table-" . substr($cell, 1) . '-td']]; } diff --git a/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php b/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php index a731e4cfdd3a..53330d9b5bd8 100644 --- a/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php +++ b/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php @@ -118,7 +118,7 @@ trait DesignHelpers // This sprintf() will help us convert "task" or "product" into "$task" or "$product" without // evaluating the variable. - if (in_array(sprintf('%s%s.tax', '$', $type), (array) $this->context['pdf_variables']["{$type}_columns"])) { + if (in_array(sprintf('%s%s.tax', '$', $type), (array)$this->context['pdf_variables']["{$type}_columns"])) { $line_items = collect($this->entity->line_items)->filter(function ($item) use ($type_id) { return $item->type_id = $type_id; }); @@ -157,9 +157,9 @@ trait DesignHelpers */ public function calculateColspan(int $taken): int { - $total = (int) count($this->context['pdf_variables']['product_columns']); + $total = (int)count($this->context['pdf_variables']['product_columns']); - return (int) $total - $taken; + return (int)$total - $taken; } /** @@ -184,11 +184,38 @@ trait DesignHelpers public function sharedFooterElements() { - // return ['element' => 'div', 'properties' => ['style' => 'display: flex; justify-content: space-between; margin-top: 1.5rem; page-break-inside: avoid;'], 'elements' => [ - // ['element' => 'img', 'properties' => ['src' => '$invoiceninja.whitelabel', 'style' => 'height: 5rem;', 'hidden' => $this->entity->user->account->isPaid() ? 'true' : 'false']], - // ]]; + // Unminified version, just for the reference. + // By default all table headers are hidden with HTML `hidden` property. + // This will check for table data values & if they're not empty it will remove hidden from the column itself. - return ['element' => 'img', 'properties' => ['src' => '$invoiceninja.whitelabel', 'style' => 'height: 3rem; position: fixed; bottom: 0; left: 0; padding: 5px; margin: 5px;', 'hidden' => $this->entity->user->account->isPaid() ? 'true' : 'false', 'id' => 'invoiceninja-whitelabel-logo']]; + /* document.querySelectorAll("tbody > tr > td").forEach(e => { + if ("" !== e.innerText) { + let t = e.getAttribute("data-ref").slice(0, -3); + document.querySelector(`th[data-ref="${t}-th"]`).removeAttribute("hidden"); + } + }); + + document.querySelectorAll("tbody > tr > td").forEach(e => { + let t = e.getAttribute("data-ref").slice(0, -3); + t = document.querySelector(`th[data-ref="${t}-th"]`); + + if (!t.hasAttribute('hidden')) { + return; + } + + if ("" == e.innerText) { + e.setAttribute('hidden', 'true'); + } + }); + */ + + + $javascript = 'document.querySelectorAll("tbody > tr > td").forEach(t=>{if(""!==t.innerText){let e=t.getAttribute("data-ref").slice(0,-3);document.querySelector(`th[data-ref="${e}-th"]`).removeAttribute("hidden")}}),document.querySelectorAll("tbody > tr > td").forEach(t=>{let e=t.getAttribute("data-ref").slice(0,-3);(e=document.querySelector(`th[data-ref="${e}-th"]`)).hasAttribute("hidden")&&""==t.innerText&&t.setAttribute("hidden","true")});'; + + return ['element' => 'div', 'elements' => [ + ['element' => 'img', 'properties' => ['src' => '$invoiceninja.whitelabel', 'style' => 'height: 3rem; position: fixed; bottom: 0; left: 0; padding: 5px; margin: 5px;', 'hidden' => $this->entity->user->account->isPaid() ? 'true' : 'false', 'id' => 'invoiceninja-whitelabel-logo']], + ['element' => 'script', 'content' => $javascript], + ]]; } public function entityVariableCheck(string $variable): bool @@ -230,48 +257,11 @@ trait DesignHelpers return $html; } - public function getTaskTimeLogs(array $row) - { - if (!array_key_exists('task_id', $row)) { - return []; - } - - $task = Task::find($this->decodePrimaryKey($row['task_id'])); - - if (!$task) { - return []; - } - - $logs = []; - $_logs = json_decode($task->time_log); - - if (!$_logs) { - $_logs = []; - } - - foreach ($_logs as $log) { - $start = Carbon::createFromTimestamp($log[0]); - $finish = Carbon::createFromTimestamp($log[1]); - - if ($start->isSameDay($finish)) { - $logs[] = sprintf('%s: %s - %s', $start->format($this->entity->client->date_format()), $start->format('h:i:s'), $finish->format('h:i:s')); - } else { - $logs[] = sprintf( - '%s - %s', - $start->format($this->entity->client->date_format() . ' h:i:s'), - $finish->format($this->entity->client->date_format() . ' h:i:s') - ); - } - } - - return $logs; - } - public function processCustomColumns(string $type): void { $custom_columns = []; - foreach ((array) $this->client->company->custom_fields as $field => $value) { + foreach ((array)$this->client->company->custom_fields as $field => $value) { info($field); if (\Illuminate\Support\Str::startsWith($field, $type)) { diff --git a/app/Transformers/AccountTransformer.php b/app/Transformers/AccountTransformer.php index f95205099327..10fd3a54df18 100644 --- a/app/Transformers/AccountTransformer.php +++ b/app/Transformers/AccountTransformer.php @@ -74,6 +74,7 @@ class AccountTransformer extends EntityTransformer 'updated_at' => (int) $account->updated_at, 'archived_at' => (int) $account->deleted_at, 'report_errors' => (bool) $account->report_errors, + 'debug_enabled' => (bool) config('ninja.debug_enabled'), ]; } diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index f41c4bddcb46..d194d71ef3f4 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -148,6 +148,8 @@ class CompanyTransformer extends EntityTransformer 'use_credits_payment' => 'always', //todo remove 'default_task_is_date_based' => (bool)$company->default_task_is_date_based, 'enable_product_discount' => (bool)$company->enable_product_discount, + 'calculate_expense_tax_by_amount' =>(bool)$company->calculate_expense_tax_by_amount, + 'hide_empty_columns_on_pdf' => (bool) $company->hide_empty_columns_on_pdf, ]; } @@ -211,10 +213,10 @@ class CompanyTransformer extends EntityTransformer { $transformer = new UserTransformer($this->serializer); - $users = $company->users->map(function ($user) use ($company){ - $user->company_id = $company->id; - return $user; - }); + $users = $company->users->map(function ($user) use ($company) { + $user->company_id = $company->id; + return $user; + }); return $this->includeCollection($users, $transformer, User::class); } @@ -351,4 +353,4 @@ class CompanyTransformer extends EntityTransformer return $this->includeCollection($company->system_logs, $transformer, SystemLog::class); } -} \ No newline at end of file +} diff --git a/app/Transformers/ExpenseCategoryTransformer.php b/app/Transformers/ExpenseCategoryTransformer.php index 7257102bfa66..befc3028e7b7 100644 --- a/app/Transformers/ExpenseCategoryTransformer.php +++ b/app/Transformers/ExpenseCategoryTransformer.php @@ -44,6 +44,7 @@ class ExpenseCategoryTransformer extends EntityTransformer 'id' => $this->encodePrimaryKey($expense_category->id), 'user_id' => $this->encodePrimaryKey($expense_category->user_id), 'name' => (string) $expense_category->name ?: '', + 'color' => (string) $expense_category->color, 'is_deleted' => (bool) $expense_category->is_deleted, 'updated_at' => (int) $expense_category->updated_at, 'archived_at' => (int) $expense_category->deleted_at, diff --git a/app/Transformers/ProjectTransformer.php b/app/Transformers/ProjectTransformer.php index 0c21a9b4c688..736ffb02ae7e 100644 --- a/app/Transformers/ProjectTransformer.php +++ b/app/Transformers/ProjectTransformer.php @@ -62,6 +62,7 @@ class ProjectTransformer extends EntityTransformer 'custom_value2' => (string) $project->custom_value2 ?: '', 'custom_value3' => (string) $project->custom_value3 ?: '', 'custom_value4' => (string) $project->custom_value4 ?: '', + 'color' => (string) $project->color ?: '', ]; } } diff --git a/app/Transformers/TaskStatusTransformer.php b/app/Transformers/TaskStatusTransformer.php index 8ece09175945..c52e6a19d9ac 100644 --- a/app/Transformers/TaskStatusTransformer.php +++ b/app/Transformers/TaskStatusTransformer.php @@ -23,11 +23,13 @@ class TaskStatusTransformer extends EntityTransformer return [ 'id' => (string) $this->encodePrimaryKey($task_status->id), 'name' => (string) $task_status->name, - 'sort_order' => (int) $task_status->sort_order, + 'color' => (string) $task_status->color, + 'sort_order' => (int) $task_status->sort_order, //deprecated 'is_deleted' => (bool) $task_status->is_deleted, 'created_at' => (int) $task_status->created_at, 'updated_at' => (int) $task_status->updated_at, 'archived_at' => (int) $task_status->deleted_at, + 'status_order' => $task_status->status_order, ]; } } diff --git a/app/Transformers/TaskTransformer.php b/app/Transformers/TaskTransformer.php index 71de0e24bf6c..a97c9518d4da 100644 --- a/app/Transformers/TaskTransformer.php +++ b/app/Transformers/TaskTransformer.php @@ -65,8 +65,9 @@ class TaskTransformer extends EntityTransformer 'custom_value3' => $task->custom_value3 ?: '', 'custom_value4' => $task->custom_value4 ?: '', 'status_id' => $this->encodePrimaryKey($task->status_id) ?: '', - 'status_sort_order' => (int) $task->status_sort_order, + 'status_sort_order' => (int) $task->status_sort_order, //deprecated 5.0.34 'is_date_based' => (bool) $task->is_date_based, + 'status_order' => $task->status_order ]; } } diff --git a/app/Utils/SystemHealth.php b/app/Utils/SystemHealth.php index 764bd7f8dd02..f16247dd8e8f 100644 --- a/app/Utils/SystemHealth.php +++ b/app/Utils/SystemHealth.php @@ -45,7 +45,7 @@ class SystemHealth * @param bool $check_database * @return array Result set of checks */ - public static function check($check_database = true) : array + public static function check($check_database = true): array { $system_health = true; @@ -57,7 +57,7 @@ class SystemHealth $system_health = false; } - if (! self::simpleDbCheck() && $check_database) { + if (!self::simpleDbCheck() && $check_database) { info('db fails'); $system_health = false; } @@ -66,18 +66,18 @@ class SystemHealth 'system_health' => $system_health, 'extensions' => self::extensions(), 'php_version' => [ - 'minimum_php_version' => (string) self::$php_version, + 'minimum_php_version' => (string)self::$php_version, 'current_php_version' => phpversion(), - 'current_php_cli_version' => (string) self::checkPhpCli(), + 'current_php_cli_version' => (string)self::checkPhpCli(), 'is_okay' => version_compare(phpversion(), self::$php_version, '>='), ], 'env_writable' => self::checkEnvWritable(), //'mail' => self::testMailServer(), - 'simple_db_check' => (bool) self::simpleDbCheck(), + 'simple_db_check' => (bool)self::simpleDbCheck(), 'cache_enabled' => self::checkConfigCache(), - 'phantom_enabled' => (bool) config('ninja.phantomjs_pdf_generation'), - 'exec' => (bool) self::checkExecWorks(), - 'open_basedir' => (bool) self::checkOpenBaseDir(), + 'phantom_enabled' => (bool)config('ninja.phantomjs_pdf_generation'), + 'exec' => (bool)self::checkExecWorks(), + 'open_basedir' => (bool)self::checkOpenBaseDir(), ]; } @@ -108,7 +108,7 @@ class SystemHealth return true; } - private static function simpleDbCheck() :bool + private static function simpleDbCheck(): bool { $result = true; @@ -135,7 +135,7 @@ class SystemHealth } } - private static function extensions() :array + private static function extensions(): array { $loaded_extensions = []; @@ -151,22 +151,23 @@ class SystemHealth $result = ['success' => false]; if ($request) { - config(['database.connections.db-ninja-01.host'=> $request->input('db_host')]); - config(['database.connections.db-ninja-01.database'=> $request->input('db_database')]); - config(['database.connections.db-ninja-01.username'=> $request->input('db_username')]); - config(['database.connections.db-ninja-01.password'=> $request->input('db_password')]); + config(['database.connections.db-ninja-01.host' => $request->input('db_host')]); + config(['database.connections.db-ninja-01.port' => $request->input('db_port')]); + config(['database.connections.db-ninja-01.database' => $request->input('db_database')]); + config(['database.connections.db-ninja-01.username' => $request->input('db_username')]); + config(['database.connections.db-ninja-01.password' => $request->input('db_password')]); config(['database.default' => 'db-ninja-01']); DB::purge('db-ninja-01'); } - if (! config('ninja.db.multi_db_enabled')) { + if (!config('ninja.db.multi_db_enabled')) { try { $pdo = DB::connection()->getPdo(); $result[] = [DB::connection()->getDatabaseName() => true]; $result['success'] = true; } catch (Exception $e) { - $result[] = [config('database.connections.'.config('database.default').'.database') => false]; + $result[] = [config('database.connections.' . config('database.default') . '.database') => false]; $result['success'] = false; $result['message'] = $e->getMessage(); } @@ -179,7 +180,7 @@ class SystemHealth $result[] = [DB::connection()->getDatabaseName() => true]; $result['success'] = true; } catch (Exception $e) { - $result[] = [config('database.connections.'.config('database.default').'.database') => false]; + $result[] = [config('database.connections.' . config('database.default') . '.database') => false]; $result['success'] = false; $result['message'] = $e->getMessage(); } @@ -222,6 +223,6 @@ class SystemHealth private static function checkEnvWritable() { - return is_writable(base_path().'/.env'); + return is_writable(base_path() . '/.env'); } } diff --git a/app/Utils/Traits/GeneratesCounter.php b/app/Utils/Traits/GeneratesCounter.php index 3e31bc94f9d5..8a7bec7af7b1 100644 --- a/app/Utils/Traits/GeneratesCounter.php +++ b/app/Utils/Traits/GeneratesCounter.php @@ -629,7 +629,6 @@ trait GeneratesCounter } if ($entity->client || ($entity instanceof Client)) { - $client = $entity->client ?: $entity; $search[] = '{$client_custom1}'; diff --git a/config/ninja.php b/config/ninja.php index 22c4e172d22d..635a08a21f46 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -9,10 +9,11 @@ return [ 'version_url' => 'https://raw.githubusercontent.com/invoiceninja/invoiceninja/v5-stable/VERSION.txt', 'app_name' => env('APP_NAME', 'Invoice Ninja'), 'app_env' => env('APP_ENV', 'selfhosted'), + 'debug_enabled' => env('APP_DEBUG', false), 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', ''), - 'app_version' => '5.0.43', + 'app_version' => '5.0.44', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', false), diff --git a/database/migrations/2021_01_03_215053_update_canadian_dollar_symbol.php b/database/migrations/2021_01_03_215053_update_canadian_dollar_symbol.php index 2b4d0e415230..c4fd282f3d21 100644 --- a/database/migrations/2021_01_03_215053_update_canadian_dollar_symbol.php +++ b/database/migrations/2021_01_03_215053_update_canadian_dollar_symbol.php @@ -3,8 +3,6 @@ use App\Models\Currency; use App\Utils\Traits\AppSetup; use Illuminate\Database\Migrations\Migration; -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Support\Facades\Schema; class UpdateCanadianDollarSymbol extends Migration { @@ -18,8 +16,9 @@ class UpdateCanadianDollarSymbol extends Migration { $currency = Currency::find(9); - if($currency) + if ($currency) { $currency->update(['symbol' => '$']); + } $this->buildCache(true); } diff --git a/database/migrations/2021_01_05_013203_improve_decimal_resolution.php b/database/migrations/2021_01_05_013203_improve_decimal_resolution.php new file mode 100644 index 000000000000..aa04b64e7e6b --- /dev/null +++ b/database/migrations/2021_01_05_013203_improve_decimal_resolution.php @@ -0,0 +1,158 @@ +decimal('balance', 20, 6)->change(); + $table->decimal('adjustment', 20, 6)->change(); + }); + + Schema::table('credits', function (Blueprint $table) { + $table->decimal('tax_rate1', 20, 6)->change(); + $table->decimal('tax_rate2', 20, 6)->change(); + $table->decimal('tax_rate3', 20, 6)->change(); + $table->decimal('total_taxes', 20, 6)->change(); + $table->decimal('exchange_rate', 20, 6)->change(); + $table->decimal('balance', 20, 6)->change(); + $table->decimal('partial', 20, 6)->change(); + $table->decimal('amount', 20, 6)->change(); + }); + + Schema::table('invoices', function (Blueprint $table) { + $table->decimal('tax_rate1', 20, 6)->change(); + $table->decimal('tax_rate2', 20, 6)->change(); + $table->decimal('tax_rate3', 20, 6)->change(); + $table->decimal('total_taxes', 20, 6)->change(); + $table->decimal('exchange_rate', 20, 6)->change(); + $table->decimal('balance', 20, 6)->change(); + $table->decimal('partial', 20, 6)->change(); + $table->decimal('amount', 20, 6)->change(); + }); + + Schema::table('quotes', function (Blueprint $table) { + $table->decimal('tax_rate1', 20, 6)->change(); + $table->decimal('tax_rate2', 20, 6)->change(); + $table->decimal('tax_rate3', 20, 6)->change(); + $table->decimal('total_taxes', 20, 6)->change(); + $table->decimal('exchange_rate', 20, 6)->change(); + $table->decimal('balance', 20, 6)->change(); + $table->decimal('partial', 20, 6)->change(); + $table->decimal('amount', 20, 6)->change(); + }); + + Schema::table('expenses', function (Blueprint $table) { + $table->decimal('tax_rate1', 20, 6)->change(); + $table->decimal('tax_rate2', 20, 6)->change(); + $table->decimal('tax_rate3', 20, 6)->change(); + $table->decimal('amount', 20, 6)->change(); + $table->decimal('foreign_amount', 20, 6)->change(); + $table->decimal('exchange_rate', 20, 6)->change(); + }); + + Schema::table('payments', function (Blueprint $table) { + $table->decimal('amount', 20, 6)->change(); + $table->decimal('refunded', 20, 6)->change(); + $table->decimal('applied', 20, 6)->change(); + $table->decimal('exchange_rate', 20, 6)->change(); + }); + + Schema::table('products', function (Blueprint $table) { + $table->decimal('tax_rate1', 20, 6)->change(); + $table->decimal('tax_rate2', 20, 6)->change(); + $table->decimal('tax_rate3', 20, 6)->change(); + }); + + Schema::table('projects', function (Blueprint $table) { + $table->decimal('task_rate', 20, 6)->change(); + $table->decimal('budgeted_hours', 20, 6)->change(); + }); + + Schema::table('recurring_invoices', function (Blueprint $table) { + $table->decimal('tax_rate1', 20, 6)->change(); + $table->decimal('tax_rate2', 20, 6)->change(); + $table->decimal('tax_rate3', 20, 6)->change(); + $table->decimal('total_taxes', 20, 6)->change(); + $table->decimal('balance', 20, 6)->change(); + $table->decimal('amount', 20, 6)->change(); + }); + + Schema::table('recurring_quotes', function (Blueprint $table) { + $table->decimal('tax_rate1', 20, 6)->change(); + $table->decimal('tax_rate2', 20, 6)->change(); + $table->decimal('tax_rate3', 20, 6)->change(); + $table->decimal('total_taxes', 20, 6)->change(); + $table->decimal('balance', 20, 6)->change(); + $table->decimal('amount', 20, 6)->change(); + }); + + Schema::table('clients', function (Blueprint $table) { + $table->decimal('balance', 20, 6)->change(); + $table->decimal('paid_to_date', 20, 6)->change(); + $table->decimal('credit_balance', 20, 6)->change(); + }); + + Schema::table('tasks', function (Blueprint $table) { + $table->decimal('rate', 20, 6)->change(); + $table->integer('status_sort_order')->nullable()->default(null)->change(); + }); + + Schema::table('task_statuses', function (Blueprint $table){ + $table->string('color')->default('#fff'); + $table->integer('status_sort_order')->nullable()->default(null)->change(); + }); + + Schema::table('tax_rates', function (Blueprint $table) { + $table->decimal('rate', 20, 6)->change(); + }); + + Schema::table('companies', function (Blueprint $table) { + $table->boolean('calculate_expense_tax_by_amount')->false(); + $table->boolean('hide_empty_columns_on_pdf')->false(); + }); + + Schema::table('expense_categories', function (Blueprint $table){ + $table->string('color')->default('#fff'); + }); + + Schema::table('projects', function (Blueprint $table){ + $table->string('color')->default('#fff'); + }); + + + Task::query()->update(['status_sort_order' => NULL]); + TaskStatus::query()->update(['status_sort_order' => NULL]); + + Schema::table('tasks', function (Blueprint $table) { + $table->integer('status_order')->nullable(); + }); + + Schema::table('task_statuses', function (Blueprint $table){ + $table->integer('status_order')->nullable(); + }); + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/public/js/setup/setup.js b/public/js/setup/setup.js index 5c1fca244bfd..ca8d5321618a 100755 --- a/public/js/setup/setup.js +++ b/public/js/setup/setup.js @@ -1,2 +1,2 @@ /*! For license information please see setup.js.LICENSE.txt */ -!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=12)}({12:function(e,t,n){e.exports=n("tM+k")},"2SVd":function(e,t,n){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},"5oMp":function(e,t,n){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},"8oxB":function(e,t){var n,r,o=e.exports={};function i(){throw new Error("setTimeout has not been defined")}function s(){throw new Error("clearTimeout has not been defined")}function a(e){if(n===setTimeout)return setTimeout(e,0);if((n===i||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:i}catch(e){n=i}try{r="function"==typeof clearTimeout?clearTimeout:s}catch(e){r=s}}();var u,c=[],f=!1,l=-1;function d(){f&&u&&(f=!1,u.length?c=u.concat(c):l=-1,c.length&&p())}function p(){if(!f){var e=a(d);f=!0;for(var t=c.length;t;){for(u=c,c=[];++l1)for(var n=1;n=200&&e<300}};u.headers={common:{Accept:"application/json, text/plain, */*"}},r.forEach(["delete","get","head"],(function(e){u.headers[e]={}})),r.forEach(["post","put","patch"],(function(e){u.headers[e]=r.merge(i)})),e.exports=u}).call(this,n("8oxB"))},LYNF:function(e,t,n){"use strict";var r=n("OH9c");e.exports=function(e,t,n,o,i){var s=new Error(e);return r(s,t,n,o,i)}},Lmem:function(e,t,n){"use strict";e.exports=function(e){return!(!e||!e.__CANCEL__)}},MLWZ:function(e,t,n){"use strict";var r=n("xTJ+");function o(e){return encodeURIComponent(e).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}e.exports=function(e,t,n){if(!t)return e;var i;if(n)i=n(t);else if(r.isURLSearchParams(t))i=t.toString();else{var s=[];r.forEach(t,(function(e,t){null!=e&&(r.isArray(e)?t+="[]":e=[e],r.forEach(e,(function(e){r.isDate(e)?e=e.toISOString():r.isObject(e)&&(e=JSON.stringify(e)),s.push(o(t)+"="+o(e))})))})),i=s.join("&")}if(i){var a=e.indexOf("#");-1!==a&&(e=e.slice(0,a)),e+=(-1===e.indexOf("?")?"?":"&")+i}return e}},OH9c:function(e,t,n){"use strict";e.exports=function(e,t,n,r,o){return e.config=t,n&&(e.code=n),e.request=r,e.response=o,e.isAxiosError=!0,e.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code}},e}},OTTw:function(e,t,n){"use strict";var r=n("xTJ+");e.exports=r.isStandardBrowserEnv()?function(){var e,t=/(msie|trident)/i.test(navigator.userAgent),n=document.createElement("a");function o(e){var r=e;return t&&(n.setAttribute("href",r),r=n.href),n.setAttribute("href",r),{href:n.href,protocol:n.protocol?n.protocol.replace(/:$/,""):"",host:n.host,search:n.search?n.search.replace(/^\?/,""):"",hash:n.hash?n.hash.replace(/^#/,""):"",hostname:n.hostname,port:n.port,pathname:"/"===n.pathname.charAt(0)?n.pathname:"/"+n.pathname}}return e=o(window.location.href),function(t){var n=r.isString(t)?o(t):t;return n.protocol===e.protocol&&n.host===e.host}}():function(){return!0}},"Rn+g":function(e,t,n){"use strict";var r=n("LYNF");e.exports=function(e,t,n){var o=n.config.validateStatus;!o||o(n.status)?e(n):t(r("Request failed with status code "+n.status,n.config,null,n.request,n))}},SntB:function(e,t,n){"use strict";var r=n("xTJ+");e.exports=function(e,t){t=t||{};var n={},o=["url","method","params","data"],i=["headers","auth","proxy"],s=["baseURL","url","transformRequest","transformResponse","paramsSerializer","timeout","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","maxContentLength","validateStatus","maxRedirects","httpAgent","httpsAgent","cancelToken","socketPath"];r.forEach(o,(function(e){void 0!==t[e]&&(n[e]=t[e])})),r.forEach(i,(function(o){r.isObject(t[o])?n[o]=r.deepMerge(e[o],t[o]):void 0!==t[o]?n[o]=t[o]:r.isObject(e[o])?n[o]=r.deepMerge(e[o]):void 0!==e[o]&&(n[o]=e[o])})),r.forEach(s,(function(r){void 0!==t[r]?n[r]=t[r]:void 0!==e[r]&&(n[r]=e[r])}));var a=o.concat(i).concat(s),u=Object.keys(t).filter((function(e){return-1===a.indexOf(e)}));return r.forEach(u,(function(r){void 0!==t[r]?n[r]=t[r]:void 0!==e[r]&&(n[r]=e[r])})),n}},UnBK:function(e,t,n){"use strict";var r=n("xTJ+"),o=n("xAGQ"),i=n("Lmem"),s=n("JEQr");function a(e){e.cancelToken&&e.cancelToken.throwIfRequested()}e.exports=function(e){return a(e),e.headers=e.headers||{},e.data=o(e.data,e.headers,e.transformRequest),e.headers=r.merge(e.headers.common||{},e.headers[e.method]||{},e.headers),r.forEach(["delete","get","head","post","put","patch","common"],(function(t){delete e.headers[t]})),(e.adapter||s.adapter)(e).then((function(t){return a(e),t.data=o(t.data,t.headers,e.transformResponse),t}),(function(t){return i(t)||(a(e),t&&t.response&&(t.response.data=o(t.response.data,t.response.headers,e.transformResponse))),Promise.reject(t)}))}},endd:function(e,t,n){"use strict";function r(e){this.message=e}r.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},r.prototype.__CANCEL__=!0,e.exports=r},eqyj:function(e,t,n){"use strict";var r=n("xTJ+");e.exports=r.isStandardBrowserEnv()?{write:function(e,t,n,o,i,s){var a=[];a.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&a.push("expires="+new Date(n).toGMTString()),r.isString(o)&&a.push("path="+o),r.isString(i)&&a.push("domain="+i),!0===s&&a.push("secure"),document.cookie=a.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}:{write:function(){},read:function(){return null},remove:function(){}}},g7np:function(e,t,n){"use strict";var r=n("2SVd"),o=n("5oMp");e.exports=function(e,t){return e&&!r(t)?o(e,t):t}},"jfS+":function(e,t,n){"use strict";var r=n("endd");function o(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise((function(e){t=e}));var n=this;e((function(e){n.reason||(n.reason=new r(e),t(n.reason))}))}o.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},o.source=function(){var e;return{token:new o((function(t){e=t})),cancel:e}},e.exports=o},"tM+k":function(e,t,n){"use strict";n.r(t);var r=n("vDqi"),o=n.n(r);function i(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:null;e.classList.remove("alert-failure"),e.innerText="Success!",e.classList.add("alert-success"),t&&(document.getElementById(t).classList.remove("hidden"),document.getElementById(t).scrollIntoView({behavior:"smooth",block:"center"}))}},{key:"handleFailure",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;e.classList.remove("alert-success"),e.innerText=t||"Oops, looks like something isn't correct!",e.classList.add("alert-failure")}},{key:"handle",value:function(){var e=this;this.checkDbButton.addEventListener("click",(function(){return e.handleDatabaseCheck()})),this.checkSmtpButton.addEventListener("click",(function(){return e.handleSmtpCheck()})),this.checkPdfButton.addEventListener("click",(function(){return e.handleTestPdfCheck()}))}}])&&i(t.prototype,n),r&&i(t,r),e}())).handle()},tQ2B:function(e,t,n){"use strict";var r=n("xTJ+"),o=n("Rn+g"),i=n("MLWZ"),s=n("g7np"),a=n("w0Vi"),u=n("OTTw"),c=n("LYNF");e.exports=function(e){return new Promise((function(t,f){var l=e.data,d=e.headers;r.isFormData(l)&&delete d["Content-Type"];var p=new XMLHttpRequest;if(e.auth){var h=e.auth.username||"",m=e.auth.password||"";d.Authorization="Basic "+btoa(h+":"+m)}var v=s(e.baseURL,e.url);if(p.open(e.method.toUpperCase(),i(v,e.params,e.paramsSerializer),!0),p.timeout=e.timeout,p.onreadystatechange=function(){if(p&&4===p.readyState&&(0!==p.status||p.responseURL&&0===p.responseURL.indexOf("file:"))){var n="getAllResponseHeaders"in p?a(p.getAllResponseHeaders()):null,r={data:e.responseType&&"text"!==e.responseType?p.response:p.responseText,status:p.status,statusText:p.statusText,headers:n,config:e,request:p};o(t,f,r),p=null}},p.onabort=function(){p&&(f(c("Request aborted",e,"ECONNABORTED",p)),p=null)},p.onerror=function(){f(c("Network Error",e,null,p)),p=null},p.ontimeout=function(){var t="timeout of "+e.timeout+"ms exceeded";e.timeoutErrorMessage&&(t=e.timeoutErrorMessage),f(c(t,e,"ECONNABORTED",p)),p=null},r.isStandardBrowserEnv()){var y=n("eqyj"),g=(e.withCredentials||u(v))&&e.xsrfCookieName?y.read(e.xsrfCookieName):void 0;g&&(d[e.xsrfHeaderName]=g)}if("setRequestHeader"in p&&r.forEach(d,(function(e,t){void 0===l&&"content-type"===t.toLowerCase()?delete d[t]:p.setRequestHeader(t,e)})),r.isUndefined(e.withCredentials)||(p.withCredentials=!!e.withCredentials),e.responseType)try{p.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&p.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&p.upload&&p.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then((function(e){p&&(p.abort(),f(e),p=null)})),void 0===l&&(l=null),p.send(l)}))}},vDqi:function(e,t,n){e.exports=n("zuR4")},w0Vi:function(e,t,n){"use strict";var r=n("xTJ+"),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,n,i,s={};return e?(r.forEach(e.split("\n"),(function(e){if(i=e.indexOf(":"),t=r.trim(e.substr(0,i)).toLowerCase(),n=r.trim(e.substr(i+1)),t){if(s[t]&&o.indexOf(t)>=0)return;s[t]="set-cookie"===t?(s[t]?s[t]:[]).concat([n]):s[t]?s[t]+", "+n:n}})),s):s}},xAGQ:function(e,t,n){"use strict";var r=n("xTJ+");e.exports=function(e,t,n){return r.forEach(n,(function(n){e=n(e,t)})),e}},"xTJ+":function(e,t,n){"use strict";var r=n("HSsa"),o=Object.prototype.toString;function i(e){return"[object Array]"===o.call(e)}function s(e){return void 0===e}function a(e){return null!==e&&"object"==typeof e}function u(e){return"[object Function]"===o.call(e)}function c(e,t){if(null!=e)if("object"!=typeof e&&(e=[e]),i(e))for(var n=0,r=e.length;n1)for(var n=1;n=200&&e<300}};u.headers={common:{Accept:"application/json, text/plain, */*"}},r.forEach(["delete","get","head"],(function(e){u.headers[e]={}})),r.forEach(["post","put","patch"],(function(e){u.headers[e]=r.merge(i)})),e.exports=u}).call(this,n("8oxB"))},LYNF:function(e,t,n){"use strict";var r=n("OH9c");e.exports=function(e,t,n,o,i){var s=new Error(e);return r(s,t,n,o,i)}},Lmem:function(e,t,n){"use strict";e.exports=function(e){return!(!e||!e.__CANCEL__)}},MLWZ:function(e,t,n){"use strict";var r=n("xTJ+");function o(e){return encodeURIComponent(e).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}e.exports=function(e,t,n){if(!t)return e;var i;if(n)i=n(t);else if(r.isURLSearchParams(t))i=t.toString();else{var s=[];r.forEach(t,(function(e,t){null!=e&&(r.isArray(e)?t+="[]":e=[e],r.forEach(e,(function(e){r.isDate(e)?e=e.toISOString():r.isObject(e)&&(e=JSON.stringify(e)),s.push(o(t)+"="+o(e))})))})),i=s.join("&")}if(i){var a=e.indexOf("#");-1!==a&&(e=e.slice(0,a)),e+=(-1===e.indexOf("?")?"?":"&")+i}return e}},OH9c:function(e,t,n){"use strict";e.exports=function(e,t,n,r,o){return e.config=t,n&&(e.code=n),e.request=r,e.response=o,e.isAxiosError=!0,e.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code}},e}},OTTw:function(e,t,n){"use strict";var r=n("xTJ+");e.exports=r.isStandardBrowserEnv()?function(){var e,t=/(msie|trident)/i.test(navigator.userAgent),n=document.createElement("a");function o(e){var r=e;return t&&(n.setAttribute("href",r),r=n.href),n.setAttribute("href",r),{href:n.href,protocol:n.protocol?n.protocol.replace(/:$/,""):"",host:n.host,search:n.search?n.search.replace(/^\?/,""):"",hash:n.hash?n.hash.replace(/^#/,""):"",hostname:n.hostname,port:n.port,pathname:"/"===n.pathname.charAt(0)?n.pathname:"/"+n.pathname}}return e=o(window.location.href),function(t){var n=r.isString(t)?o(t):t;return n.protocol===e.protocol&&n.host===e.host}}():function(){return!0}},"Rn+g":function(e,t,n){"use strict";var r=n("LYNF");e.exports=function(e,t,n){var o=n.config.validateStatus;!o||o(n.status)?e(n):t(r("Request failed with status code "+n.status,n.config,null,n.request,n))}},SntB:function(e,t,n){"use strict";var r=n("xTJ+");e.exports=function(e,t){t=t||{};var n={},o=["url","method","params","data"],i=["headers","auth","proxy"],s=["baseURL","url","transformRequest","transformResponse","paramsSerializer","timeout","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","maxContentLength","validateStatus","maxRedirects","httpAgent","httpsAgent","cancelToken","socketPath"];r.forEach(o,(function(e){void 0!==t[e]&&(n[e]=t[e])})),r.forEach(i,(function(o){r.isObject(t[o])?n[o]=r.deepMerge(e[o],t[o]):void 0!==t[o]?n[o]=t[o]:r.isObject(e[o])?n[o]=r.deepMerge(e[o]):void 0!==e[o]&&(n[o]=e[o])})),r.forEach(s,(function(r){void 0!==t[r]?n[r]=t[r]:void 0!==e[r]&&(n[r]=e[r])}));var a=o.concat(i).concat(s),u=Object.keys(t).filter((function(e){return-1===a.indexOf(e)}));return r.forEach(u,(function(r){void 0!==t[r]?n[r]=t[r]:void 0!==e[r]&&(n[r]=e[r])})),n}},UnBK:function(e,t,n){"use strict";var r=n("xTJ+"),o=n("xAGQ"),i=n("Lmem"),s=n("JEQr");function a(e){e.cancelToken&&e.cancelToken.throwIfRequested()}e.exports=function(e){return a(e),e.headers=e.headers||{},e.data=o(e.data,e.headers,e.transformRequest),e.headers=r.merge(e.headers.common||{},e.headers[e.method]||{},e.headers),r.forEach(["delete","get","head","post","put","patch","common"],(function(t){delete e.headers[t]})),(e.adapter||s.adapter)(e).then((function(t){return a(e),t.data=o(t.data,t.headers,e.transformResponse),t}),(function(t){return i(t)||(a(e),t&&t.response&&(t.response.data=o(t.response.data,t.response.headers,e.transformResponse))),Promise.reject(t)}))}},endd:function(e,t,n){"use strict";function r(e){this.message=e}r.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},r.prototype.__CANCEL__=!0,e.exports=r},eqyj:function(e,t,n){"use strict";var r=n("xTJ+");e.exports=r.isStandardBrowserEnv()?{write:function(e,t,n,o,i,s){var a=[];a.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&a.push("expires="+new Date(n).toGMTString()),r.isString(o)&&a.push("path="+o),r.isString(i)&&a.push("domain="+i),!0===s&&a.push("secure"),document.cookie=a.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}:{write:function(){},read:function(){return null},remove:function(){}}},g7np:function(e,t,n){"use strict";var r=n("2SVd"),o=n("5oMp");e.exports=function(e,t){return e&&!r(t)?o(e,t):t}},"jfS+":function(e,t,n){"use strict";var r=n("endd");function o(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise((function(e){t=e}));var n=this;e((function(e){n.reason||(n.reason=new r(e),t(n.reason))}))}o.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},o.source=function(){var e;return{token:new o((function(t){e=t})),cancel:e}},e.exports=o},"tM+k":function(e,t,n){"use strict";n.r(t);var r=n("vDqi"),o=n.n(r);function i(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:null;e.classList.remove("alert-failure"),e.innerText="Success!",e.classList.add("alert-success"),t&&(document.getElementById(t).classList.remove("hidden"),document.getElementById(t).scrollIntoView({behavior:"smooth",block:"center"}))}},{key:"handleFailure",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;e.classList.remove("alert-success"),e.innerText=t||"Oops, looks like something isn't correct!",e.classList.add("alert-failure")}},{key:"handle",value:function(){var e=this;this.checkDbButton.addEventListener("click",(function(){return e.handleDatabaseCheck()})),this.checkSmtpButton.addEventListener("click",(function(){return e.handleSmtpCheck()})),this.checkPdfButton.addEventListener("click",(function(){return e.handleTestPdfCheck()}))}}])&&i(t.prototype,n),r&&i(t,r),e}())).handle()},tQ2B:function(e,t,n){"use strict";var r=n("xTJ+"),o=n("Rn+g"),i=n("MLWZ"),s=n("g7np"),a=n("w0Vi"),u=n("OTTw"),c=n("LYNF");e.exports=function(e){return new Promise((function(t,f){var l=e.data,d=e.headers;r.isFormData(l)&&delete d["Content-Type"];var p=new XMLHttpRequest;if(e.auth){var h=e.auth.username||"",m=e.auth.password||"";d.Authorization="Basic "+btoa(h+":"+m)}var v=s(e.baseURL,e.url);if(p.open(e.method.toUpperCase(),i(v,e.params,e.paramsSerializer),!0),p.timeout=e.timeout,p.onreadystatechange=function(){if(p&&4===p.readyState&&(0!==p.status||p.responseURL&&0===p.responseURL.indexOf("file:"))){var n="getAllResponseHeaders"in p?a(p.getAllResponseHeaders()):null,r={data:e.responseType&&"text"!==e.responseType?p.response:p.responseText,status:p.status,statusText:p.statusText,headers:n,config:e,request:p};o(t,f,r),p=null}},p.onabort=function(){p&&(f(c("Request aborted",e,"ECONNABORTED",p)),p=null)},p.onerror=function(){f(c("Network Error",e,null,p)),p=null},p.ontimeout=function(){var t="timeout of "+e.timeout+"ms exceeded";e.timeoutErrorMessage&&(t=e.timeoutErrorMessage),f(c(t,e,"ECONNABORTED",p)),p=null},r.isStandardBrowserEnv()){var y=n("eqyj"),g=(e.withCredentials||u(v))&&e.xsrfCookieName?y.read(e.xsrfCookieName):void 0;g&&(d[e.xsrfHeaderName]=g)}if("setRequestHeader"in p&&r.forEach(d,(function(e,t){void 0===l&&"content-type"===t.toLowerCase()?delete d[t]:p.setRequestHeader(t,e)})),r.isUndefined(e.withCredentials)||(p.withCredentials=!!e.withCredentials),e.responseType)try{p.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&p.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&p.upload&&p.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then((function(e){p&&(p.abort(),f(e),p=null)})),void 0===l&&(l=null),p.send(l)}))}},vDqi:function(e,t,n){e.exports=n("zuR4")},w0Vi:function(e,t,n){"use strict";var r=n("xTJ+"),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,n,i,s={};return e?(r.forEach(e.split("\n"),(function(e){if(i=e.indexOf(":"),t=r.trim(e.substr(0,i)).toLowerCase(),n=r.trim(e.substr(i+1)),t){if(s[t]&&o.indexOf(t)>=0)return;s[t]="set-cookie"===t?(s[t]?s[t]:[]).concat([n]):s[t]?s[t]+", "+n:n}})),s):s}},xAGQ:function(e,t,n){"use strict";var r=n("xTJ+");e.exports=function(e,t,n){return r.forEach(n,(function(n){e=n(e,t)})),e}},"xTJ+":function(e,t,n){"use strict";var r=n("HSsa"),o=Object.prototype.toString;function i(e){return"[object Array]"===o.call(e)}function s(e){return void 0===e}function a(e){return null!==e&&"object"==typeof e}function u(e){return"[object Function]"===o.call(e)}function c(e,t){if(null!=e)if("object"!=typeof e&&(e=[e]),i(e))for(var n=0,r=e.length;n this.handleSuccess(this.checkDbAlert, 'mail-wrapper') ) .catch((e) => this.handleFailure(this.checkDbAlert, e.response.data.message) - ); + ).finally(() => this.checkDbButton.disabled = false); } handleSmtpCheck() { @@ -113,7 +116,7 @@ class Setup { document.getElementById(nextStep).classList.remove('hidden'); document .getElementById(nextStep) - .scrollIntoView({ behavior: 'smooth', block: 'center' }); + .scrollIntoView({behavior: 'smooth', block: 'center'}); } } diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 350d4cc43f36..1c850c61c839 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -2459,6 +2459,34 @@ return [ 'currency_bahraini_dinar' => 'Bahraini Dinar', 'currency_venezuelan_bolivars' => 'Venezuelan Bolivars', + + 'currency_south_korean_won' => 'South Korean Won', + 'currency_moroccan_dirham' => 'Moroccan Dirham', + 'currency_jamaican_dollar' => 'Jamaican Dollar', + 'currency_angolan_kwanza' => 'Angolan Kwanza', + 'currency_haitian_gourde' => 'Haitian Gourde', + 'currency_zambian_kwacha' => 'Zambian Kwacha', + 'currency_nepalese_rupee' => 'Nepalese Rupee', + 'currency_cfp_franc' => 'CFP Franc', + 'currency_mauritian_rupee' => 'Mauritian Rupee', + 'currency_cape_verdean_escudo' => 'Cape Verdean Escudo', + 'currency_kuwaiti_dinar' => 'Kuwaiti Dinar', + 'currency_algerian_dinar' => 'Algerian Dinar', + 'currency_macedonian_denar' => 'Macedonian Denar', + 'currency_fijian_dollar' => 'Fijian Dollar', + 'currency_bolivian_boliviano' => 'Bolivian Boliviano', + 'currency_albanian_lek' => 'Albanian Lek', + 'currency_serbian_dinar' => 'Serbian Dinar', + 'currency_lebanese_pound' => 'Lebanese Pound', + 'currency_armenian_dram' => 'Armenian Dram', + 'currency_azerbaijan_manat' => 'Azerbaijan Manat', + 'currency_bosnia_and_herzegovina_convertible_mark' => 'Bosnia and Herzegovina Convertible Mark', + 'currency_belarusian_ruble' => 'Belarusian Ruble', + 'currency_moldovan_leu' => 'Moldovan Leu', + 'currency_kazakhstani_tenge' => 'Kazakhstani Tenge', + 'currency_gibraltar_pound' => 'Gibraltar Pound', + 'currency_ethiopian_birr' => 'Ethiopian Birr', + 'review_app_help' => 'We hope you\'re enjoying using the app.
If you\'d consider :link we\'d greatly appreciate it!', 'writing_a_review' => 'writing a review', @@ -3332,4 +3360,6 @@ return [ 'setup_phantomjs_note' => 'Note about Phantom JS. Read more.', 'currency_armenian_dram' => 'Armenian Dram', 'currency_albanian_lek' => 'Albanian Lek', + + 'endless' => 'Endless', ]; diff --git a/resources/views/index/index.blade.php b/resources/views/index/index.blade.php index 165f82c7eaf2..074c013daf9e 100644 --- a/resources/views/index/index.blade.php +++ b/resources/views/index/index.blade.php @@ -80,7 +80,7 @@ diff --git a/resources/views/portal/ninja2020/components/livewire/recurring-invoices-table.blade.php b/resources/views/portal/ninja2020/components/livewire/recurring-invoices-table.blade.php index 6bbd5e24a061..7866ce75bc2a 100644 --- a/resources/views/portal/ninja2020/components/livewire/recurring-invoices-table.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/recurring-invoices-table.blade.php @@ -62,7 +62,7 @@ {{ \App\Utils\Number::formatMoney($invoice->amount, $invoice->client) }} - + @lang('texts.view') diff --git a/resources/views/portal/ninja2020/recurring_invoices/show.blade.php b/resources/views/portal/ninja2020/recurring_invoices/show.blade.php index 23581e6f5c28..f126a683aa9b 100644 --- a/resources/views/portal/ninja2020/recurring_invoices/show.blade.php +++ b/resources/views/portal/ninja2020/recurring_invoices/show.blade.php @@ -43,7 +43,8 @@ {{ ctrans('texts.cycles_remaining') }}
- {{ $invoice->remaining_cycles }} + {{ $invoice->remaining_cycles == '-1' ? ctrans('texts.endless') : $invoice->remaining_cycles }} + @if($invoice->remaining_cycles == '-1') ∞ @endif
@@ -79,4 +80,4 @@
-@endsection \ No newline at end of file +@endsection diff --git a/resources/views/setup/_application.blade.php b/resources/views/setup/_application.blade.php index 8124584be89b..89676687bd58 100644 --- a/resources/views/setup/_application.blade.php +++ b/resources/views/setup/_application.blade.php @@ -14,7 +14,7 @@ {{ ctrans('texts.url') }}*
- (including http:// or https://) @@ -32,16 +32,6 @@
-
- {{ ctrans('texts.debug') }} -
-
- - {{ ctrans('texts.enable') }} - ({{ ctrans('texts.enable_only_for_development') }}) -
-
-
{{ ctrans('texts.reports') }}
@@ -53,14 +43,14 @@ about how we use this.
-
+
+
+ {{ ctrans('texts.port') }}* +
+
+ +
+
+
{{ ctrans('texts.database') }}*
@@ -49,15 +57,15 @@ FLUSH PRIVILEGES;
-
-
+
+
{{ ctrans('texts.username') }}*
-
+
{{ ctrans('texts.password') }}
@@ -65,14 +73,14 @@ FLUSH PRIVILEGES;
-
+
-
+
diff --git a/tests/Feature/PreviewTest.php b/tests/Feature/PreviewTest.php index 87f395f47c50..661a6878778e 100644 --- a/tests/Feature/PreviewTest.php +++ b/tests/Feature/PreviewTest.php @@ -37,50 +37,49 @@ class PreviewTest extends TestCase public function testPreviewRoute() { - $data = $this->getData(); + $data = $this->getData(); - $response = $this->withHeaders([ + $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, ])->post('/api/v1/preview/', $data); - $response->assertStatus(200); + $response->assertStatus(200); } public function testPreviewHtmlResponse() { - $data = $this->getData(); + $data = $this->getData(); - $response = $this->withHeaders([ + $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, ])->post('/api/v1/preview?html=true', $data); - $response->assertStatus(200); - + $response->assertStatus(200); } private function getData() { - $data = - [ - 'entity_type' => 'invoice', - 'entity_id' => '', - 'design' => [ - 'name' => '', - 'design' => [ + $data = + [ + 'entity_type' => 'invoice', + 'entity_id' => '', + 'design' => [ + 'name' => '', + 'design' => [ 'includes' => '', 'header' => '', 'body' => '
', 'product' => '', 'task' => '', 'footer' => '' - ], - ], + ], + ], 'is_custom' => 1, ]; return $data; } -} \ No newline at end of file +} diff --git a/tests/Unit/TaskSortingTest.php b/tests/Unit/TaskSortingTest.php new file mode 100644 index 000000000000..65d2b5245285 --- /dev/null +++ b/tests/Unit/TaskSortingTest.php @@ -0,0 +1,64 @@ +collection = collect([ + ['id' => 1, 'name' =>'pizza', 'order' => 9999], + ['id' => 2, 'name' =>'pineapple', 'order' => 9999], + ['id' => 3, 'name' =>'ethereum', 'order' => 9999], + ['id' => 4, 'name' =>'bitcoin', 'order' => 9999], + ['id' => 5, 'name' =>'zulu', 'order' => 9999], + ['id' => 6, 'name' =>'alpha', 'order' => 9999], + ['id' => 7, 'name' =>'ninja', 'order' => 9999], + ]); + + } + + public function testSorting() + { + + $index = 3; + $item = $this->collection->where('id', 7)->first(); + + $new_collection = $this->collection->reject(function ($task)use($item){ + return $item['id'] == $task['id']; + }); + + $sorted_tasks = $new_collection->filter(function($task, $key)use($index){ + return $key < $index; + })->push($item)->merge($new_collection->filter(function($task, $key)use($index){ + return $key >= $index; + }))->map(function ($item,$key){ + $item['order'] = $key; + return $item; + }); + + $index_item = $sorted_tasks->splice($index, 1)->all(); + + $this->assertEquals($sorted_tasks->first()['name'], 'pizza'); + $this->assertEquals($sorted_tasks->last()['name'], 'alpha'); + $this->assertEquals($index_item[0]['name'],'ninja'); + + } +}