mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-31 10:14:36 -04:00
commit
dda1a8ef2c
28
.github/workflows/phpunit.yml
vendored
28
.github/workflows/phpunit.yml
vendored
@ -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
|
||||
|
||||
|
@ -1 +1 @@
|
||||
5.0.43
|
||||
5.0.44
|
@ -1,55 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class GenerateSetupKey extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'ninja:generate-setup-key';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generate random APP_KEY value';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$randomString = base64_encode(\Illuminate\Support\Str::random(32));
|
||||
|
||||
$this->info('Success! Copy the following content into your .env or docker-compose.yml:');
|
||||
$this->warn('base64:' . $randomString);
|
||||
}
|
||||
}
|
@ -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="________"),
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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',
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,7 @@ class CheckDatabaseRequest extends Request
|
||||
{
|
||||
return [
|
||||
'db_host' => ['required'],
|
||||
'db_port' => ['required'],
|
||||
'db_database' => ['required'],
|
||||
'db_username' => ['required'],
|
||||
];
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -46,7 +46,6 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
|
||||
protected $fillable = [
|
||||
'assigned_user_id',
|
||||
'currency_id',
|
||||
'name',
|
||||
'website',
|
||||
'private_notes',
|
||||
|
@ -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',
|
||||
|
@ -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');
|
||||
|
@ -20,6 +20,7 @@ class ExpenseCategory extends BaseModel
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'color',
|
||||
];
|
||||
|
||||
public function getEntityType()
|
||||
|
@ -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()
|
||||
|
@ -36,6 +36,7 @@ class Project extends BaseModel
|
||||
'custom_value3',
|
||||
'custom_value4',
|
||||
'assigned_user_id',
|
||||
'color',
|
||||
];
|
||||
|
||||
public function getEntityType()
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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 = [];
|
||||
|
@ -29,5 +29,5 @@ class TaskStatus extends BaseModel
|
||||
*/
|
||||
protected $dates = ['deleted_at'];
|
||||
|
||||
protected $fillable = ['name'];
|
||||
protected $fillable = ['name','color','status_order'];
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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*/
|
||||
|
@ -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'])) {
|
||||
|
@ -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']);
|
||||
}
|
||||
|
||||
|
@ -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']];
|
||||
}
|
||||
|
@ -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)) {
|
||||
|
@ -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'),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 ?: '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -629,7 +629,6 @@ trait GeneratesCounter
|
||||
}
|
||||
|
||||
if ($entity->client || ($entity instanceof Client)) {
|
||||
|
||||
$client = $entity->client ?: $entity;
|
||||
|
||||
$search[] = '{$client_custom1}';
|
||||
|
@ -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),
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Task;
|
||||
use App\Models\TaskStatus;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ImproveDecimalResolution extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
|
||||
Schema::table('company_ledgers', function (Blueprint $table) {
|
||||
$table->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()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
2
public/js/setup/setup.js
vendored
2
public/js/setup/setup.js
vendored
File diff suppressed because one or more lines are too long
@ -15,6 +15,6 @@
|
||||
"/js/clients/quotes/approve.js": "/js/clients/quotes/approve.js?id=85bcae0a646882e56b12",
|
||||
"/js/clients/shared/multiple-downloads.js": "/js/clients/shared/multiple-downloads.js?id=5c35d28cf0a3286e7c45",
|
||||
"/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js?id=fa54bb4229aba6b0817c",
|
||||
"/js/setup/setup.js": "/js/setup/setup.js?id=29e88ab480038cba57df",
|
||||
"/js/setup/setup.js": "/js/setup/setup.js?id=8cb5e2bb0d404725c20a",
|
||||
"/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ad"
|
||||
}
|
||||
|
7
resources/js/setup/setup.js
vendored
7
resources/js/setup/setup.js
vendored
@ -25,6 +25,7 @@ class Setup {
|
||||
handleDatabaseCheck() {
|
||||
let data = {
|
||||
db_host: document.querySelector('input[name="db_host"]').value,
|
||||
db_port: document.querySelector('input[name="db_port"]').value,
|
||||
db_database: document.querySelector('input[name="db_database"]')
|
||||
.value,
|
||||
db_username: document.querySelector('input[name="db_username"]')
|
||||
@ -33,13 +34,15 @@ class Setup {
|
||||
.value,
|
||||
};
|
||||
|
||||
this.checkDbButton.disabled = true;
|
||||
|
||||
Axios.post('/setup/check_db', data)
|
||||
.then((response) =>
|
||||
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'});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.<br/>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',
|
||||
];
|
||||
|
@ -80,7 +80,7 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@if (request()->clear)
|
||||
@if (request()->clear_local)
|
||||
window.onload = function() {
|
||||
window.localStorage.clear();
|
||||
}
|
||||
@ -96,7 +96,7 @@
|
||||
document.getElementById('loader').style.display = 'none';
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
function invokeServiceWorkerUpdateFlow() {
|
||||
// you have a better UI here, reloading is not a great user experince here.
|
||||
const confirmed = confirm('New version of the app is available. Refresh now');
|
||||
@ -143,7 +143,7 @@
|
||||
}
|
||||
|
||||
handleServiceWorker();
|
||||
*/
|
||||
|
||||
</script>
|
||||
|
||||
<script defer src="main.dart.js?v={{ config('ninja.app_version') }}" type="application/javascript"></script>
|
||||
|
@ -62,7 +62,7 @@
|
||||
{{ \App\Utils\Number::formatMoney($invoice->amount, $invoice->client) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap flex items-center justify-end text-sm leading-5 font-medium">
|
||||
<a href="{{ route('client.recurring_invoices.show', $invoice->hashed_id) }}" class="text-blue-600 hover:text-indigo-900 focus:outline-none focus:underline">
|
||||
<a href="{{ route('client.recurring_invoice.show', $invoice->hashed_id) }}" class="text-blue-600 hover:text-indigo-900 focus:outline-none focus:underline">
|
||||
@lang('texts.view')
|
||||
</a>
|
||||
</td>
|
||||
|
@ -43,7 +43,8 @@
|
||||
{{ ctrans('texts.cycles_remaining') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ $invoice->remaining_cycles }}
|
||||
{{ $invoice->remaining_cycles == '-1' ? ctrans('texts.endless') : $invoice->remaining_cycles }}
|
||||
@if($invoice->remaining_cycles == '-1') ∞ @endif
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
@ -79,4 +80,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@endsection
|
||||
|
@ -14,7 +14,7 @@
|
||||
{{ ctrans('texts.url') }}*
|
||||
</dt>
|
||||
<dd class="text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<input
|
||||
<input
|
||||
type="url" class="input w-full" name="url" placeholder="https://example.com"
|
||||
pattern="https?://.*" size="45" value="{{ old('url', 'https://') }}" required>
|
||||
<small>(including http:// or https://)</small>
|
||||
@ -32,16 +32,6 @@
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:flex sm:items-center">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.debug') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<input type="checkbox" class="form-checkbox mr-1" name="debug" {{ old('debug') ? 'checked': '' }}>
|
||||
<span>{{ ctrans('texts.enable') }}</span>
|
||||
<span class="text-gray-600 text-xs ml-2">({{ ctrans('texts.enable_only_for_development') }})</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:flex sm:items-center">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.reports') }}
|
||||
</dt>
|
||||
@ -53,14 +43,14 @@
|
||||
about how we use this.</a>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:flex sm:items-center">
|
||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:flex sm:items-center">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
<button type="button" class="button button-primary bg-blue-600 py-2 px-3 text-xs" id="test-pdf">
|
||||
{{ ctrans('texts.test_pdf') }}
|
||||
</button>
|
||||
</dt>
|
||||
<dd class="text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<div class="alert py-2 bg-gray-50" id="test-pdf-response"></div>
|
||||
<div class="alert py-2 bg-white" id="test-pdf-response"></div>
|
||||
</dd>
|
||||
<a target="_blank" class="block text-sm text-gray-900 leading-5 underline"
|
||||
href="https://invoiceninja.github.io/selfhost.html#phantom-js">
|
||||
|
@ -42,6 +42,14 @@ FLUSH PRIVILEGES;
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:flex sm:items-center">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.port') }}*
|
||||
</dt>
|
||||
<dd class="text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<input type="text" class="input w-full" name="db_port" required value="{{ old('db_port') ?: '3306'}}">
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:flex sm:items-center">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.database') }}*
|
||||
</dt>
|
||||
@ -49,15 +57,15 @@ FLUSH PRIVILEGES;
|
||||
<input type="text" class="input w-full" name="db_database" required value="{{ old('database') ?: 'db-ninja-01'}}">
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:flex sm:items-center">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500" value="{{ old('username') }}">
|
||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:flex sm:items-center">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.username') }}*
|
||||
</dt>
|
||||
<dd class="text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<input type="text" class="input w-full" name="db_username" required value="{{ old('db_username') ?: 'ninja' }}">
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:flex sm:items-center">
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:flex sm:items-center">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.password') }}
|
||||
</dt>
|
||||
@ -65,14 +73,14 @@ FLUSH PRIVILEGES;
|
||||
<input type="password" class="input w-full" name="db_password" value="{{ old('db_password') ?: 'ninja' }}">
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:flex sm:items-center">
|
||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:flex sm:items-center">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
<button type="button" class="button button-primary bg-blue-600 py-2 px-3 text-xs" id="test-db-connection">
|
||||
{{ ctrans('texts.test_connection') }}
|
||||
</button>
|
||||
</dt>
|
||||
<dd class="text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<div class="alert py-2 bg-gray-50" id="database-response"></div>
|
||||
<div class="alert py-2 bg-white" id="database-response"></div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
@ -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' => '</style>',
|
||||
'header' => '<div id="header"></div>',
|
||||
'body' => '<div id="body">',
|
||||
'product' => '',
|
||||
'task' => '',
|
||||
'footer' => '<div id="footer">$entity_footer</div>'
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'is_custom' => 1,
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
64
tests/Unit/TaskSortingTest.php
Normal file
64
tests/Unit/TaskSortingTest.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
namespace Tests\Unit;
|
||||
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
class TaskSortingTest extends TestCase
|
||||
{
|
||||
public $collection;
|
||||
|
||||
public function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->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');
|
||||
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user