<?php

namespace App\Http\Controllers;

use App\Http\Requests\CategoryRequest;

abstract class NestedSetController extends Controller
{
    /**
     * The resource name.
     *
     * @var string
     */
    public $resource = 'Category';

    /**
     * The nested set routes.
     *
     * @var array
     */
    public $routes = [];

    /**
     * The repository to create categories through.
     *
     * @var mixed
     */
    protected $repository;

    /**
     * Displays a list of all categories.
     *
     * @return \Illuminate\View\View
     */
    public function index()
    {
        return view('categories.index', [
            'routes'   => $this->routes,
            'resource' => $this->resource,
        ]);
    }

    /**
     * Shows the form for creating a new category
     * or category node if an ID is supplied.
     *
     * @param int|string $id
     *
     * @return \Illuminate\View\View
     */
    public function create($id = null)
    {
        // If an ID is supplied then we're creating a child category
        if ($id) {
            $category = $this->repository->find($id);

            if ($category) {
                return view('categories.nodes.create', [
                    'category' => $category,
                    'resource' => $this->resource,
                    'routes'   => $this->routes,
                ]);
            }
        }

        return view('categories.create', [
            'resource' => $this->resource,
            'routes'   => $this->routes,
        ]);
    }

    /**
     * Processes storing a new category or storing a new category
     * underneath another if an ID is specified.
     *
     * @param CategoryRequest $request
     * @param int|string      $id
     *
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
     */
    public function store(CategoryRequest $request, $id = null)
    {
        $category = $this->repository->create($request, $id);

        if ($category) {
            $message = "Successfully created $this->resource";

            return redirect()->route($this->routes['index'])->withSuccess($message);
        } else {
            $message = "There was an issue creating a $this->resource. Please try again.";

            return redirect()->route($this->routes['create'], $id)->withErrors($message);
        }
    }

    /**
     * Show the form for editing the specified category.
     *
     * @param int|string $id
     *
     * @return \Illuminate\View\View
     */
    public function edit($id)
    {
        $category = $this->repository->find($id);

        return view('categories.edit', [
            'category' => $category,
            'resource' => $this->resource,
            'routes'   => $this->routes,
        ]);
    }

    /**
     * Processes updating the specified category.
     *
     * @param CategoryRequest $request
     * @param int|string      $id
     *
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
     */
    public function update(CategoryRequest $request, $id)
    {
        $category = $this->repository->update($request, $id);

        if ($category) {
            $message = "Successfully updated $this->resource";

            return redirect()->route($this->routes['index'])->withSuccess($message);
        } else {
            $message = "There was an issue updating this $this->resource. Please try again.";

            return redirect()->route($this->routes['edit'], $id)->withErrors($message);
        }
    }

    /**
     * Processes removing the specified category.
     *
     * @param int|string $id
     *
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
     */
    public function destroy($id)
    {
        $record = $this->repository->find($id);

        if ($record->delete()) {
            $message = "Successfully deleted $this->resource.";

            return redirect()->route($this->routes['index'])->withSuccess($message);
        } else {
            $message = "There was an issue deleting this $this->resource. Please try again.";

            return redirect()->route($this->routes['index'])->withErrors($message);
        }
    }
}


<?php

namespace App\Jobs\Inventory;

use App\Http\Requests\Inventory\InventoryRequest;
use App\Jobs\Job;
use App\Models\Inventory;

class Update extends Job
{
    /**
     * @var InventoryRequest
     */
    protected $request;

    /**
     * @var Inventory
     */
    protected $inventory;

    /**
     * Constructor.
     *
     * @param InventoryRequest $request
     * @param Inventory        $inventory
     */
    public function __construct(InventoryRequest $request, Inventory $inventory)
    {
        $this->request = $request;
        $this->inventory = $inventory;
    }

    /**
     * Execute the job.
     *
     * @return bool
     */
    public function handle()
    {
        $this->inventory->category_id = $this->request->input('category', $this->inventory->category_id);
        $this->inventory->metric_id = $this->request->input('metric', $this->inventory->metric_id);
        $this->inventory->name = $this->request->input('name', $this->inventory->name);
        $this->inventory->description = $this->request->clean($this->request->input('description', $this->inventory->description));

        return $this->inventory->save();
    }
}


<?php

namespace App\Http\Presenters\WorkOrder;

use App\Http\Presenters\Presenter;
use App\Models\WorkOrder;
use App\Models\WorkOrderSession;
use Orchestra\Contracts\Html\Table\Column;
use Orchestra\Contracts\Html\Table\Grid as TableGrid;

class WorkOrderSessionPresenter extends Presenter
{
    /**
     * Returns a new table of all work order sessions.
     *
     * @param WorkOrder $workOrder
     *
     * @return \Orchestra\Contracts\Html\Builder
     */
    public function table(WorkOrder $workOrder)
    {
        $sessions = $workOrder->sessions();

        return $this->table->of('work-orders.sessions', function (TableGrid $table) use ($sessions) {
            $table->with($sessions)->paginate($this->perPage);

            $table->attributes([
                'class' => 'table table-hover table-striped',
            ]);

            $table->column('user', function (Column $column) {
                $column->value = function (WorkOrderSession $session) {
                    return $session->user->fullname;
                };
            });

            $table->column('Hours', function (Column $column) {
                $column->value = function (WorkOrderSession $session) {
                    return $session->getHours();
                };
            });

            $table->column('in');

            $table->column('out', function (Column $column) {
                $column->value = function (WorkOrderSession $session) {
                    return $session->getOutLabel();
                };
            });
        });
    }

    /**
     * Displays unique sessions per worker and totals their hours.
     *
     * @param WorkOrder $workOrder
     *
     * @return \Orchestra\Contracts\Html\Builder
     */
    public function tablePerWorker(WorkOrder $workOrder)
    {
        $sessions = $workOrder->getUniqueSessions();

        return $this->table->of('work-orders.sessions.per-worker', function (TableGrid $table) use ($sessions) {
            $table->with($sessions);

            $table->attributes([
                'class' => 'table table-hover table-striped',
            ]);

            $table->column('worker', function (Column $column) {
                $column->value = function (WorkOrderSession $session) {
                    return $session->user->fullname;
                };
            });

            $table->column('total_hours', function (Column $column) {
                $column->value = function (WorkOrderSession $session) {
                    return $session->total_hours;
                };
            });
        });
    }

    /**
     * Returns a new navbar for the current work orders sessions.
     *
     * @return \Illuminate\Support\Fluent
     */
    public function navbar()
    {
        return $this->fluent([
            'id'         => 'work-order-sessions',
            'title'      => 'Work Order Sessions',
            'menu'       => view('work-orders.sessions._nav'),
            'attributes' => [
                'class' => 'navbar-default',
            ],
        ]);
    }
}


<?php

namespace App\Models\Observers;

use App\Models\Inventory;

class InventoryObserver
{
    /**
     * Captures the Inventory models deleted event
     * and cascades the delete to all of it's stocks.
     *
     * @param Inventory $model
     */
    public function deleted(Inventory $model)
    {
        $stocks = $model->stocks()->get();

        if (count($stocks) > 0) {
            foreach ($stocks as $stock) {
                $stock->delete();
            }
        }
    }

    /**
     * Captures the Inventory models restored event
     * and cascades the restore to all of it's stocks.
     *
     * @param Inventory $model
     */
    public function restored(Inventory $model)
    {
        $stocks = $model->stocks()->onlyTrashed()->get();

        if (count($stocks) > 0) {
            foreach ($stocks as $stock) {
                $stock->restore();
            }
        }
    }
}


<?php

namespace App\Processors\Inventory;

use App\Http\Presenters\Inventory\InventoryPresenter;
use App\Http\Requests\Inventory\InventoryRequest;
use App\Jobs\Inventory\Store;
use App\Jobs\Inventory\Update;
use App\Models\Inventory;
use App\Processors\Processor;

class InventoryProcessor extends Processor
{
    /**
     * @var Inventory
     */
    protected $inventory;

    /**
     * @var InventoryPresenter
     */
    protected $presenter;

    /**
     * Constructor.
     *
     * @param Inventory          $inventory
     * @param InventoryPresenter $presenter
     */
    public function __construct(Inventory $inventory, InventoryPresenter $presenter)
    {
        $this->inventory = $inventory;
        $this->presenter = $presenter;
    }

    /**
     * Displays all inventory items.
     *
     * @return \Illuminate\View\View
     */
    public function index()
    {
        $inventory = $this->presenter->tableNoVariants($this->inventory);

        $navbar = $this->presenter->navbar();

        return view('inventory.index', compact('inventory', 'navbar'));
    }

    /**
     * Displays the form for creating a new inventory item.
     *
     * @return \Illuminate\View\View
     */
    public function create()
    {
        $form = $this->presenter->form($this->inventory);

        return view('inventory.create', compact('form'));
    }

    /**
     * Creates a new inventory item.
     *
     * @param InventoryRequest $request
     *
     * @return bool
     */
    public function store(InventoryRequest $request)
    {
        $item = $this->inventory->newInstance();

        return $this->dispatch(new Store($request, $item));
    }

    /**
     * Displays the specified inventory item.
     *
     * @param int|string $id
     *
     * @return \Illuminate\View\View
     */
    public function show($id)
    {
        $item = $this->inventory->find($id);

        $variants = $this->presenter->tableVariants($item);

        $stocks = $this->presenter->tableStocks($item);

        $navbarProfile = $this->presenter->navbarProfile($item);

        $navbarVariants = $this->presenter->navbarVariants($item);

        $navbarStocks = $this->presenter->navbarStocks($item);

        return view('inventory.show', compact(
            'item',
            'stocks',
            'variants',
            'navbarProfile',
            'navbarVariants',
            'navbarStocks'
        ));
    }

    /**
     * Displays the form for editing the specified inventory item.
     *
     * @param int|string $id
     *
     * @return \Illuminate\View\View
     */
    public function edit($id)
    {
        $item = $this->inventory->findOrFail($id);

        $form = $this->presenter->form($item);

        return view('inventory.edit', compact('form'));
    }

    /**
     * Updates the specified inventory item.
     *
     * @param InventoryRequest $request
     * @param int|string       $id
     *
     * @return bool
     */
    public function update(InventoryRequest $request, $id)
    {
        $item = $this->inventory->findOrFail($id);

        return $this->dispatch(new Update($request, $item));
    }

    /**
     * Deletes the specified inventory item.
     *
     * @param int|string $id
     *
     * @return bool
     */
    public function destroy($id)
    {
        $item = $this->inventory->findOrFail($id);

        return $item->delete();
    }
}


<?php

namespace App\Jobs\WorkRequest;

use App\Http\Requests\WorkRequest as WorkHttpRequest;
use App\Jobs\Job;
use App\Models\WorkRequest;
use Illuminate\Foundation\Bus\DispatchesJobs;

class Store extends Job
{
    use DispatchesJobs;

    /**
     * @var WorkHttpRequest
     */
    protected $request;

    /**
     * @var WorkRequest
     */
    protected $workRequest;

    /**
     * Constructor.
     *
     * @param WorkHttpRequest $request
     * @param WorkRequest     $workRequest
     */
    public function __construct(WorkHttpRequest $request, WorkRequest $workRequest)
    {
        $this->request = $request;
        $this->workRequest = $workRequest;
    }

    /**
     * Execute the job.
     *
     * @return bool
     */
    public function handle()
    {
        $this->workRequest->user_id = auth()->id();
        $this->workRequest->subject = $this->request->input('subject');
        $this->workRequest->best_time = $this->request->input('best_time');
        $this->workRequest->description = $this->request->clean($this->request->input('description'));

        return $this->workRequest->save();
    }
}


<?php

namespace App\Http\Presenters\Inventory;

use App\Http\Presenters\Presenter;
use App\Models\Inventory;
use App\Models\InventoryStock;
use App\Models\InventoryStockMovement;
use App\Models\Location;
use App\Models\User;
use Orchestra\Contracts\Html\Form\Fieldset;
use Orchestra\Contracts\Html\Form\Grid as FormGrid;
use Orchestra\Contracts\Html\Table\Column;
use Orchestra\Contracts\Html\Table\Grid as TableGrid;
use Orchestra\Support\Facades\HTML;

class InventoryStockPresenter extends Presenter
{
    /**
     * Returns a new table of all stocks of the specified inventory item.
     *
     * @param Inventory $item
     *
     * @return \Orchestra\Contracts\Html\Builder
     */
    public function table(Inventory $item)
    {
        $stocks = $item->stocks();

        return $this->table->of('inventory.stocks', function (TableGrid $table) use ($item, $stocks) {
            $table->with($stocks);

            $table->attributes([
                'class' => 'table table-hover table-striped',
            ]);

            $table->column('quantity');

            $table->column('location', function (Column $column) use ($item) {
                $column->value = function (InventoryStock $stock) use ($item) {
                    $name = $stock->location->trail;

                    return link_to_route('maintenance.inventory.stocks.show', $name, [$item->getKey(), $stock->getKey()]);
                };
            });

            $table->column('last_movement', function (Column $column) {
                $column->value = function (InventoryStock $stock) {
                    return $stock->last_movement;
                };
            });

            $table->column('last_movement_by', function (Column $column) {
                $column->value = function (InventoryStock $stock) {
                    return $stock->last_movement_by;
                };
            });
        });
    }

    /**
     * Returns a new table of all movements for the specified inventory stock.
     *
     * @param Inventory      $item
     * @param InventoryStock $stock
     *
     * @return \Orchestra\Contracts\Html\Builder
     */
    public function tableMovements(Inventory $item, InventoryStock $stock)
    {
        $movements = $stock->movements();

        return $this->table->of('inventory.stocks.movements', function (TableGrid $table) use ($item, $movements) {
            $table->with($movements)->paginate($this->perPage);

            $table->pageName = 'stock-movements';

            $table->column('before');

            $table->column('after');

            $table->column('change');

            $table->column('cost');

            $table->column('reason');

            $table->column('Change By', function (Column $column) {
                return $column->value = function (InventoryStockMovement $movement) {
                    if ($movement->user instanceof User) {
                        return $movement->user->getRecipientName();
                    }

                    return HTML::create('em', 'Unknown');
                };
            });

            $table->column('Change On', 'created_at');
        });
    }

    /**
     * Returns a new form of the specified inventory stock.
     *
     * @param Inventory      $item
     * @param InventoryStock $stock
     *
     * @return \Orchestra\Contracts\Html\Builder
     */
    public function form(Inventory $item, InventoryStock $stock)
    {
        return $this->form->of('inventory.stocks', function (FormGrid $form) use ($item, $stock) {
            if ($stock->exists) {
                $method = 'PATCH';
                $url = route('maintenance.inventory.stocks.update', [$item->getKey(), $stock->getKey()]);
                $form->submit = 'Save';
            } else {
                $method = 'POST';
                $url = route('maintenance.inventory.stocks.store', [$item->getKey()]);
                $form->submit = 'Create';
            }

            $locations = Location::getSelectHierarchy();

            $form->with($stock);
            $form->attributes(compact('method', 'url'));

            $form->fieldset(function (Fieldset $fieldset) use ($locations) {
                $fieldset
                    ->control('select', 'location')
                    ->value(function (InventoryStock $stock) {
                        return $stock->location_id;
                    })
                    ->options($locations);

                $fieldset
                    ->control('input:text', 'quantity')
                    ->attributes([
                        'placeholder' => 'ex. 45',
                    ]);

                $fieldset
                    ->control('input:text', 'reason')
                    ->attributes([
                        'placeholder' => 'ex. Stock Update',
                    ]);

                $fieldset
                    ->control('input:text', 'cost')
                    ->attributes([
                        'placeholder' => 'ex. 15.00',
                    ]);
            });
        });
    }

    /**
     * Returns a new navbar for the inventory stock index.
     *
     * @param Inventory $item
     *
     * @return \Illuminate\Support\Fluent
     */
    public function navbar(Inventory $item)
    {
        return $this->fluent([
            'id'         => 'inventory-stocks',
            'title'      => 'Item Stocks',
            'url'        => route('maintenance.inventory.stocks.index', [$item->getKey()]),
            'menu'       => view('inventory.stocks._nav', compact('item')),
            'attributes' => [
                'class' => 'navbar-default',
            ],
        ]);
    }
}


<?php

namespace App\Seeders;

use App\Repositories\WorkOrder\PriorityRepository;
use App\Services\ConfigService;
use Illuminate\Database\Seeder;

class PrioritySeeder extends Seeder
{
    /**
     * @var PriorityRepository
     */
    protected $priority;

    /**
     * @var ConfigService
     */
    protected $config;

    /**
     * Constructor.
     *
     * @param PriorityRepository $priority
     * @param ConfigService      $config
     */
    public function __construct(PriorityRepository $priority, ConfigService $config)
    {
        $this->priority = $priority;
        $this->config = $config->setPrefix('maintenance');
    }

    /**
     * Runs the seeding operations.
     */
    public function run()
    {
        $priorities = $this->getSeedData();

        foreach ($priorities as $priority) {
            $this->priority->model()->create($priority);
        }
    }

    /**
     * Retrieves the seed data from the maintenance configuration.
     *
     * @return mixed
     */
    private function getSeedData()
    {
        return $this->config->get('seed.priorities');
    }
}


<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class CreateAssetTable extends Migration
{
    /**
     * Run the migrations.
     */
    public function up()
    {
        Schema::create('assets', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
            $table->softDeletes();
            $table->date('acquired_at')->nullable();
            $table->date('end_of_life')->nullable();
            $table->integer('user_id')->unsigned()->nullable();
            $table->integer('location_id')->unsigned()->nullable();
            $table->integer('category_id')->unsigned();
            $table->string('tag')->nullable();
            $table->string('name');
            $table->text('description')->nullable();
            $table->integer('condition')->nullable();
            $table->string('size')->nullable();
            $table->string('weight')->nullable();
            $table->string('vendor')->nullable();
            $table->string('make')->nullable();
            $table->string('model')->nullable();
            $table->string('serial')->nullable();
            $table->decimal('price', 10, 2)->nullable();

            $table->foreign('user_id')->references('id')->on('users')
                ->onUpdate('restrict')
                ->onDelete('set null');

            $table->foreign('location_id')->references('id')->on('locations')
                ->onUpdate('restrict')
                ->onDelete('set null');

            $table->foreign('category_id')->references('id')->on('categories')
                ->onUpdate('restrict')
                ->onDelete('cascade');
        });

        Schema::create('asset_meters', function (Blueprint $table) {
            $table->integer('asset_id')->unsigned();
            $table->integer('meter_id')->unsigned();

            $table->foreign('asset_id')->references('id')->on('assets')
                ->onUpdate('restrict')
                ->onDelete('cascade');

            $table->foreign('meter_id')->references('id')->on('meters')
                ->onUpdate('restrict')
                ->onDelete('cascade');

        });
    }

    /**
     * Reverse the migrations.
     */
    public function down()
    {
        Schema::drop('asset_meters');
        Schema::drop('assets');
    }
}


<?php

namespace App\Http\Presenters\WorkOrder;

use App\Http\Presenters\Inventory\InventoryPresenter;
use App\Http\Presenters\Presenter;
use App\Models\Inventory;
use App\Models\InventoryStock;
use App\Models\WorkOrder;
use Orchestra\Contracts\Html\Table\Column;
use Orchestra\Contracts\Html\Table\Grid as TableGrid;

class WorkOrderPartPresenter extends Presenter
{
    /**
     * Returns a new table of all parts attached to the specified work order.
     *
     * @param WorkOrder $workOrder
     *
     * @return \Orchestra\Contracts\Html\Builder
     */
    public function table(WorkOrder $workOrder)
    {
        $parts = $workOrder->parts();

        return $this->table->of('work-orders.parts', function (TableGrid $table) use ($workOrder, $parts) {
            $table->with($parts)->paginate($this->perPage);

            $table->pageName = 'page-parts';

            $table->column('ID', 'id');

            $table->column('SKU', function (Column $column) {
                $column->value = function (InventoryStock $stock) {
                    return $stock->item->getSku();
                };
            });

            $table->column('name', function (Column $column) {
                $column->value = function (InventoryStock $stock) {
                    return $stock->item->name;
                };
            });

            $table->column('location', function (Column $column) {
                $column->value = function (InventoryStock $stock) {
                    return $stock->location->trail;
                };
            });

            $table->column('taken', function (Column $column) {
                $column->value = function (InventoryStock $stock) {
                    return $stock->quantity;
                };
            });

            $table->column('return', function (Column $column) use ($workOrder) {
                $column->label = 'Return Stock';
                $column->value = function (InventoryStock $stock) use ($workOrder) {
                    $route = 'maintenance.work-orders.parts.stocks.put';

                    $params = [$workOrder->getKey(), $stock->item->getKey(), $stock->getKey()];

                    $attributes = [
                        'class' => 'btn btn-default btn-sm',
                    ];

                    return link_to_route($route, 'Return', $params, $attributes);
                };
            });
        });
    }

    /**
     * Displays all inventory available for selection.
     *
     * @param WorkOrder $workOrder
     * @param Inventory $inventory
     *
     * @return \Orchestra\Contracts\Html\Builder
     */
    public function tableInventory(WorkOrder $workOrder, Inventory $inventory)
    {
        $inventory = $inventory->noVariants();

        return $this->table->of('work-orders.inventory', function (TableGrid $table) use ($inventory, $workOrder) {
            $table->with($inventory)->paginate($this->perPage);

            $table->pageName = 'page-inventory';

            $table->column('ID', 'id');

            $table->column('sku', function (Column $column) {
                $column->label = 'SKU';

                $column->value = function (Inventory $item) {
                    return $item->getSku();
                };
            });

            $table->column('name', function (Column $column) {
                $column->value = function (Inventory $item) {
                    return link_to_route('maintenance.inventory.show', $item->name, [$item->getKey()]);
                };
            });

            $table->column('category', function (Column $column) {
                $column->value = function (Inventory $item) {
                    return $item->category->trail;
                };
            });

            $table->column('current_stock', function (Column $column) {
                $column->value = function (Inventory $item) {
                    return $item->getTotalStock();
                };
            });

            $table->column('select', function (Column $column) use ($workOrder) {
                $column->value = function (Inventory $item) use ($workOrder) {
                    $route = 'maintenance.work-orders.parts.stocks.index';

                    $params = [$workOrder->getKey(), $item->getKey()];

                    $attributes = [
                        'class' => 'btn btn-default btn-sm',
                    ];

                    return link_to_route($route, 'Select', $params, $attributes);
                };
            });
        });
    }

    /**
     * Returns a new navbar for the work order parts table.
     *
     * @param WorkOrder $workOrder
     *
     * @return \Illuminate\Support\Fluent
     */
    public function navbarParts(WorkOrder $workOrder)
    {
        return $this->fluent([
            'id'         => 'work-orders-parts',
            'title'      => 'Parts Added',
            'url'        => route('maintenance.work-orders.parts.index', [$workOrder->getKey()]),
            'menu'       => view('work-orders.parts._nav', compact('workOrder')),
            'attributes' => [
                'class' => 'navbar-default',
            ],
        ]);
    }

    /**
     * Returns a new navbar for the inventory table.
     *
     * @return \Illuminate\Support\Fluent
     */
    public function navbarInventory()
    {
        $presenter = new InventoryPresenter($this->form, $this->table);

        return $presenter->navbar();
    }
}


<?php

namespace App\Processors\WorkOrder;

use App\Http\Presenters\WorkOrder\WorkOrderSessionPresenter;
use App\Jobs\WorkOrder\Session\End;
use App\Jobs\WorkOrder\Session\Start;
use App\Models\WorkOrder;
use App\Processors\Processor;

class WorkOrderSessionProcessor extends Processor
{
    /**
     * @var WorkOrder
     */
    protected $workOrder;

    /**
     * @var WorkOrderSessionPresenter
     */
    protected $presenter;

    /**
     * Constructor.
     *
     * @param WorkOrder                 $workOrder
     * @param WorkOrderSessionPresenter $presenter
     */
    public function __construct(WorkOrder $workOrder, WorkOrderSessionPresenter $presenter)
    {
        $this->workOrder = $workOrder;
        $this->presenter = $presenter;
    }

    /**
     * Displays all sessions for the specified work order.
     *
     * @param int|string $workOrderId
     *
     * @return \Illuminate\View\View
     */
    public function index($workOrderId)
    {
        $workOrder = $this->workOrder->findOrFail($workOrderId);

        $sessions = $this->presenter->table($workOrder);

        $navbar = $this->presenter->navbar();

        return view('work-orders.sessions.index', compact('sessions', 'navbar'));
    }

    /**
     * Starts the current users session for the specified work order.
     *
     * @param int|string $workOrderId
     *
     * @return mixed
     */
    public function start($workOrderId)
    {
        $workOrder = $this->workOrder->findOrFail($workOrderId);

        return $this->dispatch(new Start($workOrder));
    }

    /**
     * Ends the current users session for the specified work order.
     *
     * @param int|string $workOrderId
     *
     * @return mixed
     */
    public function end($workOrderId)
    {
        $workOrder = $this->workOrder->findOrFail($workOrderId);

        return $this->dispatch(new End($workOrder));
    }
}


<?php

namespace App\Jobs\WorkOrder\Priority;

use App\Http\Requests\WorkOrder\PriorityRequest;
use App\Jobs\Job;
use App\Models\Priority;

class Store extends Job
{
    /**
     * @var PriorityRequest
     */
    protected $request;

    /**
     * @var Priority
     */
    protected $priority;

    /**
     * Constructor.
     *
     * @param PriorityRequest $request
     * @param Priority        $priority
     */
    public function __construct(PriorityRequest $request, Priority $priority)
    {
        $this->request = $request;
        $this->priority = $priority;
    }

    /**
     * Execute the job.
     *
     * @return bool
     */
    public function handle()
    {
        $this->priority->user_id = auth()->id();
        $this->priority->name = $this->request->input('name');
        $this->priority->color = $this->request->input('color');

        return $this->priority->save();
    }
}


<?php

namespace App\Jobs\WorkOrder\Status;

use App\Http\Requests\WorkOrder\StatusRequest;
use App\Jobs\Job;
use App\Models\Status;

class Update extends Job
{
    /**
     * @var StatusRequest
     */
    protected $request;

    /**
     * @var Status
     */
    protected $status;

    /**
     * Constructor.
     *
     * @param StatusRequest $request
     * @param Status        $status
     */
    public function __construct(StatusRequest $request, Status $status)
    {
        $this->request = $request;
        $this->status = $status;
    }

    /**
     * Execute the job.
     *
     * @return bool
     */
    public function handle()
    {
        $this->status->name = $this->request->input('name', $this->status->name);
        $this->status->color = $this->request->input('color', $this->status->color);

        return $this->status->save();
    }
}


<?php

namespace App\Models;

use App\Models\Traits\HasUserTrait;
use App\Viewers\Inventory\InventoryStockMovementViewer;
use Illuminate\Database\Eloquent\SoftDeletes;
use Stevebauman\Inventory\Traits\InventoryStockMovementTrait;

class InventoryStockMovement extends Model
{
    use SoftDeletes;

    use InventoryStockMovementTrait;

    use HasUserTrait;

    /**
     * The inventory stock movements table.
     *
     * @var string
     */
    protected $table = 'inventory_stock_movements';

    /**
     * The fillable inventory stock movement attributes.
     *
     * @var array
     */
    protected $fillable = [
        'stock_id',
        'user_id',
        'before',
        'after',
        'cost',
        'reason',
    ];

    /**
     * The inventory stock movement viewer.
     *
     * @var string
     */
    protected $viewer = InventoryStockMovementViewer::class;

    /**
     * The belongsTo stock relationship.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function stock()
    {
        return $this->belongsTo(InventoryStock::class, 'stock_id', 'id');
    }

    /**
     * Returns the cost of the movement. If no cost is available it will return 0.00.
     *
     * @param int|string $cost
     *
     * @return string
     */
    public function getCostAttribute($cost)
    {
        if ($cost === null) {
            return '0.00';
        }

        return $cost;
    }

    /**
     * Returns the change of a stock.
     *
     * For example: '+ 25' or '- 25'
     *
     * @return string
     */
    public function getChangeAttribute()
    {
        if ($this->before > $this->after) {
            return sprintf('- %s', $this->before - $this->after);
        } elseif ($this->after > $this->before) {
            return sprintf('+ %s', $this->after - $this->before);
        } else {
            return 'None';
        }
    }
}


<?php

namespace App\Http\Controllers\Inventory;

use App\Http\Controllers\Controller as BaseController;
use App\Http\Requests\NoteRequest;
use App\Repositories\Inventory\Repository as InventoryRepository;

class NoteController extends BaseController
{
    /**
     * @var InventoryRepository
     */
    protected $inventory;

    /**
     * Constructor.
     *
     * @param InventoryRepository $inventory
     */
    public function __construct(InventoryRepository $inventory)
    {
        $this->inventory = $inventory;
    }

    /**
     * Displays the form for creating a new inventory note.
     *
     * @param int|string $id
     *
     * @return \Illuminate\View\View
     */
    public function create($id)
    {
        $item = $this->inventory->find($id);

        return view('inventory.notes.create', compact('item'));
    }

    /**
     * Creates a note for the specified inventory.
     *
     * @param NoteRequest $request
     * @param int|string  $id
     *
     * @return \Illuminate\Http\RedirectResponse
     */
    public function store(NoteRequest $request, $id)
    {
        $note = $this->inventory->createNote($request, $id);

        if ($note) {
            $message = 'Successfully created note.';

            return redirect()->route('maintenance.inventory.show', [$id])->withSuccess($message);
        } else {
            $message = 'There was an issue creating a note. Please try again.';

            return redirect()->route('maintenance.inventory.notes.create', [$id])->withErrors($message);
        }
    }

    /**
     * Displays the specified inventory note.
     *
     * @param int|string $id
     * @param int|string $noteId
     *
     * @return \Illuminate\View\View
     */
    public function show($id, $noteId)
    {
        $item = $this->inventory->find($id);

        $note = $item->notes()->find($noteId);

        if ($note) {
            return view('inventory.notes.show', compact('item', 'note'));
        }

        abort(404);
    }

    /**
     * Displays the form for editing the specified inventory note.
     *
     * @param int|string $id
     * @param int|string $noteId
     *
     * @return \Illuminate\View\View
     */
    public function edit($id, $noteId)
    {
        $item = $this->inventory->find($id);

        $note = $item->notes()->find($noteId);

        if ($note) {
            return view('inventory.notes.edit', compact('item', 'note'));
        }

        abort(404);
    }

    /**
     * Updates the specified note for the specified inventory.
     *
     * @param NoteRequest $request
     * @param int|string  $id
     * @param int|string  $noteId
     *
     * @return \Illuminate\Http\RedirectResponse
     */
    public function update(NoteRequest $request, $id, $noteId)
    {
        $note = $this->inventory->updateNote($request, $id, $noteId);

        if ($note) {
            $message = 'Successfully updated note.';

            return redirect()->route('maintenance.inventory.notes.show', [$id, $noteId])->withSuccess($message);
        } else {
            $message = 'There was an issue updating this note. Please try again.';

            return redirect()->route('maintenance.inventory.notes.update', [$id, $noteId])->withErrors($message);
        }
    }

    /**
     * Deletes the specified note attached to the specified inventory.
     *
     * @param int|string $id
     * @param int|string $noteId
     *
     * @return \Illuminate\Http\RedirectResponse
     */
    public function destroy($id, $noteId)
    {
        if ($this->inventory->deleteNote($id, $noteId)) {
            $message = 'Successfully updated note.';

            return redirect()->route('maintenance.inventory.show', [$id])->withSuccess($message);
        } else {
            $message = 'There was an issue deleting this note. Please try again.';

            return redirect()->route('maintenance.inventory.notes.show', [$id, $noteId])->withErrors($message);
        }
    }
}


<?php

namespace App\Jobs\WorkOrder\Status;

use App\Http\Requests\WorkOrder\StatusRequest;
use App\Jobs\Job;
use App\Models\Status;

class Store extends Job
{
    /**
     * @var StatusRequest
     */
    protected $request;

    /**
     * @var Status
     */
    protected $status;

    /**
     * Constructor.
     *
     * @param StatusRequest $request
     * @param Status        $status
     */
    public function __construct(StatusRequest $request, Status $status)
    {
        $this->request = $request;
        $this->status = $status;
    }

    /**
     * Executes the job.
     *
     * @return bool
     */
    public function handle()
    {
        $this->status->user_id = auth()->id();
        $this->status->name = $this->request->input('name');
        $this->status->color = $this->request->input('color');

        return $this->status->save();
    }
}


<?php

namespace App\Models;

use App\Models\Traits\HasLocationTrait;
use App\Models\Traits\HasUserTrait;
use App\Viewers\Inventory\InventoryStockViewer;
use Illuminate\Database\Eloquent\SoftDeletes;
use Stevebauman\Inventory\Traits\InventoryStockTrait;

class InventoryStock extends Model
{
    use SoftDeletes;

    use InventoryStockTrait;
    use HasUserTrait;
    use HasLocationTrait;

    /**
     * The database table to store inventory stock records.
     *
     * @var string
     */
    protected $table = 'inventory_stocks';

    /**
     * The fillable eloquent attribute array for allowing mass assignments.
     *
     * @var array
     */
    protected $fillable = [
        'inventory_id',
        'location_id',
        'quantity',
        'aisle',
        'row',
        'bin',
    ];

    /**
     * The columns to keep revisions of.
     *
     * @var array
     */
    protected $revisionColumns = [
        'location_id',
        'aisle',
        'row',
        'bin',
    ];

    /**
     * The revisionable formatted field names.
     *
     * @var array
     */
    protected $revisionColumnsFormatted = [
        'location_id' => 'Location',
        'quantity'    => 'Quantity',
    ];

    /**
     * The inventory stock viewer class.
     *
     * @var string
     */
    protected $viewer = InventoryStockViewer::class;

    /*
    * The belongsTo item relationship.
    *
    * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
    */

    public function item()
    {
        return $this->belongsTo(Inventory::class, 'inventory_id', 'id');
    }

    /**
     * The hasOne location relationship.
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     */
    public function location()
    {
        return $this->hasOne(Location::class, 'id', 'location_id');
    }

    /**
     * The hasMany transactions relationship.
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function transactions()
    {
        return $this->hasMany(InventoryTransaction::class, 'stock_id', 'id');
    }

    /**
     * The hasMany movements relationship.
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function movements()
    {
        return $this->hasMany(InventoryStockMovement::class, 'stock_id')->latest();
    }

    /**
     * Accessor for viewing the last movement of the stock.
     *
     * @return null|string
     */
    public function getLastMovementAttribute()
    {
        if ($this->movements->count() > 0) {
            $movement = $this->movements->first();

            if ($movement instanceof InventoryStockMovement
                && $movement->after > $movement->before) {
                return sprintf('%s (Stock was added - %s) - Reason: %s', $movement->change, $movement->created_at, $movement->reason);
            } elseif ($movement->before > $movement->after) {
                return sprintf('%s (Stock was removed - %s) - Reason: %s', $movement->change, $movement->created_at, $movement->reason);
            } else {
                return sprintf('%s (No Change - %s) - Reason: %s', $movement->change, $movement->created_at, $movement->reason);
            }
        }

        return;
    }

    /**
     * Accessor for viewing the user responsible for the last
     * movement.
     *
     * @return null|string
     */
    public function getLastMovementByAttribute()
    {
        if ($this->movements->count() > 0) {
            $movement = $this->movements->first();

            if ($movement instanceof InventoryStockMovement
                && $movement->user instanceof User) {
                return $movement->user->getRecipientName();
            }
        }

        return;
    }

    /**
     * Accessor for viewing the quantity combined with the item metric.
     *
     * @return string
     */
    public function getQuantityMetricAttribute()
    {
        $quantity = $this->getAttribute('quantity');

        if ($this->item && $this->item->metric) {
            $metric = $this->item->metric->name;
        } else {
            $metric = null;
        }

        return trim(sprintf('%s %s', $quantity, $metric));
    }
}


<?php

namespace App\Viewers\Inventory;

use App\Models\WorkOrder;
use App\Viewers\BaseViewer;

class InventoryStockViewer extends BaseViewer
{
    /**
     * Returns the stocks profile view.
     *
     * @return \Illuminate\View\View
     */
    public function profile()
    {
        return view('viewers.inventory.stock.profile', ['stock' => $this->entity]);
    }

    /**
     * Returns the stocks actions button view.
     *
     * @return \Illuminate\View\View
     */
    public function btnActions()
    {
        return view('viewers.inventory.stock.buttons.actions', ['stock' => $this->entity]);
    }

    /**
     * Returns the stocks edit button view.
     *
     * @return \Illuminate\View\View
     */
    public function btnEdit()
    {
        return view('viewers.inventory.stock.buttons.edit', ['stock' => $this->entity]);
    }

    /**
     * Returns the stocks delete button view.
     *
     * @return \Illuminate\View\View
     */
    public function btnDelete()
    {
        return view('viewers.inventory.stock.buttons.delete', ['stock' => $this->entity]);
    }

    /**
     * Returns the stocks add to work order button view.
     *
     * @param WorkOrder $workOrder
     *
     * @return \Illuminate\View\View
     */
    public function btnAddForWorkOrder(WorkOrder $workOrder)
    {
        return view('viewers.inventory.stock.buttons.add-to-work-order', [
            'workOrder' => $workOrder,
            'stock'     => $this->entity,
        ]);
    }

    /**
     * Returns the stocks put back some work order button view.
     *
     * @param WorkOrder $workOrder
     *
     * @return \Illuminate\View\View
     */
    public function btnPutBackSomeForWorkOrder(WorkOrder $workOrder)
    {
        if ($this->entity->item) {
            return view('viewers.inventory.stock.buttons.put-back-some-work-order', [
                'workOrder' => $workOrder,
                'stock'     => $this->entity,
            ]);
        }

        return;
    }

    /**
     * Returns the stocks put back all work order button view.
     *
     * @param WorkOrder $workOrder
     *
     * @return \Illuminate\View\View
     */
    public function btnPutBackAllForWorkOrder(WorkOrder $workOrder)
    {
        if ($this->entity->item) {
            return view('viewers.inventory.stock.buttons.put-back-all-work-order', [
                'workOrder' => $workOrder,
                'stock'     => $this->entity,
            ]);
        }

        return;
    }
}


<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class CreateWorkOrderTables extends Migration
{
    /**
     * Run the migrations.
     */
    public function up()
    {
        Schema::create('work_requests', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
            $table->softDeletes();
            $table->integer('user_id')->unsigned()->nullable();
            $table->string('subject');
            $table->text('description');
            $table->string('best_time');

            $table->foreign('user_id')->references('id')->on('users')
                ->onUpdate('restrict')
                ->onDelete('set null');
        });

        Schema::create('work_orders', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
            $table->softDeletes();
            $table->integer('user_id')->unsigned()->nullable();
            $table->integer('category_id')->unsigned()->nullable();
            $table->integer('location_id')->unsigned()->nullable();
            $table->integer('request_id')->unsigned()->nullable();
            $table->integer('status_id')->unsigned();
            $table->integer('priority_id')->unsigned();
            $table->dateTime('started_at')->nullable();
            $table->dateTime('completed_at')->nullable();
            $table->string('subject');
            $table->text('description')->nullable();

            $table->foreign('user_id')->references('id')->on('users')
                ->onUpdate('restrict')
                ->onDelete('set null');

            $table->foreign('location_id')->references('id')->on('locations')
                ->onUpdate('restrict')
                ->onDelete('set null');

            $table->foreign('category_id')->references('id')->on('categories')
                ->onUpdate('restrict')
                ->onDelete('set null');

            $table->foreign('request_id')->references('id')->on('work_requests')
                ->onUpdate('restrict')
                ->onDelete('set null');

            $table->foreign('status_id')->references('id')->on('statuses')
                ->onUpdate('restrict')
                ->onDelete('cascade');

            $table->foreign('priority_id')->references('id')->on('priorities')
                ->onUpdate('restrict')
                ->onDelete('cascade');

            /*
             * Allow only one work request to be attached per work order
             *
             * Possible incompatibility with SQL Server
             */
            $table->unique(['request_id']);
        });

        Schema::create('work_order_notifications', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
            $table->integer('user_id')->unsigned();
            $table->integer('work_order_id')->unsigned();
            $table->tinyInteger('status')->default(0);
            $table->tinyInteger('priority')->default(0);
            $table->tinyInteger('parts')->default(0);
            $table->tinyInteger('customer_updates')->default(0);
            $table->tinyInteger('technician_updates')->default(0);
            $table->tinyInteger('completion_report')->default(0);

            $table->foreign('user_id')->references('id')->on('users')
                ->onUpdate('restrict')
                ->onDelete('cascade');

            $table->foreign('work_order_id')->references('id')->on('work_orders')
                ->onUpdate('restrict')
                ->onDelete('cascade');

        });

        Schema::create('work_order_reports', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
            $table->integer('user_id')->unsigned();
            $table->integer('work_order_id')->unsigned();
            $table->text('description');

            $table->foreign('user_id')->references('id')->on('users')
                ->onUpdate('restrict')
                ->onDelete('cascade');

            $table->foreign('work_order_id')->references('id')->on('work_orders')
                ->onUpdate('restrict')
                ->onDelete('cascade');
        });

        Schema::create('work_order_sessions', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
            $table->integer('user_id')->unsigned();
            $table->integer('work_order_id')->unsigned();
            $table->dateTime('in');
            $table->dateTime('out')->nullable();
            $table->decimal('hours', 5, 2)->nullable(); //Over-ride hours

            $table->foreign('user_id')->references('id')->on('users')
                ->onUpdate('restrict')
                ->onDelete('cascade');

            $table->foreign('work_order_id')->references('id')->on('work_orders')
                ->onUpdate('restrict')
                ->onDelete('cascade');
        });

        Schema::create('work_order_attachments', function (Blueprint $table) {
            $table->integer('work_order_id')->unsigned();
            $table->integer('attachment_id')->unsigned();

            $table->foreign('work_order_id')->references('id')->on('work_orders')
                ->onUpdate('restrict')
                ->onDelete('cascade');

            $table->foreign('attachment_id')->references('id')->on('attachments')
                ->onUpdate('restrict')
                ->onDelete('cascade');

        });

        Schema::create('work_order_assets', function (Blueprint $table) {
            $table->integer('work_order_id')->unsigned();
            $table->integer('asset_id')->unsigned();

            $table->foreign('work_order_id')->references('id')->on('work_orders')
                ->onUpdate('restrict')
                ->onDelete('cascade');

            $table->foreign('asset_id')->references('id')->on('assets')
                ->onUpdate('restrict')
                ->onDelete('cascade');
        });

        Schema::create('work_order_parts', function (Blueprint $table) {
            $table->timestamps();
            $table->integer('work_order_id')->unsigned();
            $table->integer('stock_id')->unsigned();
            $table->decimal('quantity', 8, 2);

            $table->foreign('work_order_id')->references('id')->on('work_orders')
                ->onUpdate('restrict')
                ->onDelete('cascade');

            $table->foreign('stock_id')->references('id')->on('inventory_stocks')
                ->onUpdate('restrict')
                ->onDelete('cascade');
        });

        Schema::create('work_order_assignments', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
            $table->integer('work_order_id')->unsigned();
            $table->integer('by_user_id')->unsigned();
            $table->integer('to_user_id')->unsigned();

            $table->foreign('work_order_id')->references('id')->on('work_orders')
                ->onUpdate('restrict')
                ->onDelete('cascade');

            $table->foreign('by_user_id')->references('id')->on('users')
                ->onUpdate('restrict')
                ->onDelete('cascade');

            $table->foreign('to_user_id')->references('id')->on('users')
                ->onUpdate('restrict')
                ->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down()
    {
        Schema::drop('work_order_assignments');
        Schema::drop('work_order_parts');
        Schema::drop('work_order_assets');
        Schema::drop('work_order_attachments');
        Schema::drop('work_order_sessions');
        Schema::drop('work_order_reports');
        Schema::drop('work_order_notifications');
        Schema::drop('work_orders');
        Schema::drop('work_requests');
    }
}


<?php

namespace App\Jobs\Metric;

use App\Http\Requests\MetricRequest;
use App\Jobs\Job;
use App\Models\Metric;

class Store extends Job
{
    /**
     * @var MetricRequest
     */
    protected $request;

    /**
     * @var Metric
     */
    protected $metric;

    /**
     * Constructor.
     *
     * @param MetricRequest $request
     * @param Metric        $metric
     */
    public function __construct(MetricRequest $request, Metric $metric)
    {
        $this->request = $request;
        $this->metric = $metric;
    }

    /**
     * Execute the job.
     *
     * @return bool
     */
    public function handle()
    {
        $this->metric->user_id = auth()->id();
        $this->metric->name = $this->request->input('name');
        $this->metric->symbol = $this->request->input('symbol');

        return $this->metric->save();
    }
}


<?php

namespace App\Http\Controllers\WorkOrder;

use App\Http\Controllers\Controller;
use App\Processors\WorkOrder\WorkOrderSessionProcessor;

class WorkOrderSessionController extends Controller
{
    /**
     * @var WorkOrderSessionProcessor
     */
    protected $processor;

    /**
     * Constructor.
     *
     * @param WorkOrderSessionProcessor $processor
     */
    public function __construct(WorkOrderSessionProcessor $processor)
    {
        $this->processor = $processor;
    }

    /**
     * Displays all the sessions for the specified work order.
     *
     * @param int|string $workOrderId
     *
     * @return \Illuminate\View\View
     */
    public function index($workOrderId)
    {
        return $this->processor->index($workOrderId);
    }

    /**
     * Starts a maintenance workers session on a work order.
     *
     * @param int|string $workOrderId
     *
     * @return \Illuminate\Http\RedirectResponse
     */
    public function start($workOrderId)
    {
        if ($this->processor->start($workOrderId)) {
            flash()->success('Success!', "Successfully started your session. Don't forget to check out!");

            return redirect()->route('maintenance.work-orders.show', [$workOrderId]);
        } else {
            flash()->error('Error!', 'There was an issue creating your session. Please try again.');

            return redirect()->route('maintenance.work-orders.show', [$workOrderId]);
        }
    }

    /**
     * Ends a maintenance workers session on a work order.
     *
     * @param int|string $workOrderId
     *
     * @return \Illuminate\Http\RedirectResponse
     */
    public function end($workOrderId)
    {
        if ($this->processor->end($workOrderId)) {
            flash()->success('Success!', 'Successfully ended your session. Your hours have been logged.');

            return redirect()->route('maintenance.work-orders.show', [$workOrderId]);
        } else {
            flash()->error('Error!', 'There was an issue ending your session. Please try again.');

            return redirect()->route('maintenance.work-orders.show', [$workOrderId]);
        }
    }
}


<?php

namespace App\Http\Presenters\WorkRequest;

use App\Http\Presenters\Presenter;
use App\Models\WorkRequest;
use Orchestra\Contracts\Html\Form\Fieldset;
use Orchestra\Contracts\Html\Form\Grid as FormGrid;
use Orchestra\Contracts\Html\Table\Column;
use Orchestra\Contracts\Html\Table\Grid as TableGrid;

class WorkRequestPresenter extends Presenter
{
    /**
     * Returns a new table of all work requests.
     *
     * @param WorkRequest $workRequest
     *
     * @return \Orchestra\Contracts\Html\Builder
     */
    public function table($workRequest)
    {
        return $this->table->of('work-requests', function (TableGrid $table) use ($workRequest) {
            $table->with($workRequest)->paginate($this->perPage);

            $table->attributes([
                'class' => 'table table-hover table-striped',
            ]);

            $table->column('ID', 'id');

            $table->column('subject', function (Column $column) {
                $column->value = function (WorkRequest $workRequest) {
                    return link_to_route('maintenance.work-requests.show', $workRequest->subject, [$workRequest->getKey()]);
                };
            });

            $table->column('best_time');

            $table->column('created_at');
        });
    }

    /**
     * Returns a new form for the specified work request.
     *
     * @param WorkRequest $request
     *
     * @return \Orchestra\Contracts\Html\Builder
     */
    public function form(WorkRequest $request)
    {
        return $this->form->of('work-requests', function (FormGrid $form) use ($request) {
            if ($request->exists) {
                $method = 'PATCH';
                $url = route('maintenance.work-requests.update', [$request->getKey()]);
                $form->submit = 'Save';
            } else {
                $method = 'POST';
                $url = route('maintenance.work-requests.store');
                $form->submit = 'Create';
            }

            $form->with($request);

            $form->attributes(compact('method', 'url'));

            $form->fieldset(function (Fieldset $fieldset) {
                $fieldset
                    ->control('input:text', 'subject')
                    ->attributes([
                        'placeholder' => 'Enter Subject',
                    ]);

                $fieldset
                    ->control('input:text', 'best_time')
                    ->attributes([
                        'placeholder' => 'Enter Best Time',
                    ]);

                $fieldset
                    ->control('input:textarea', 'description');
            });
        });
    }

    /**
     * Returns a new navbar for the work request index.
     *
     * @return \Illuminate\Support\Fluent
     */
    public function navbar()
    {
        return $this->fluent([
            'id'         => 'work-requests',
            'title'      => 'Work Requests',
            'menu'       => view('work-requests._nav'),
            'attributes' => [
                'class' => 'navbar-default',
            ],
        ]);
    }
}


<?php

namespace App\Http\Controllers\WorkOrder;

use App\Http\Controllers\Controller as BaseController;
use App\Services\WorkOrder\AssignmentService;
use App\Services\WorkOrder\WorkOrderService;
use App\Validators\AssignmentValidator;

class AssignmentController extends BaseController
{
    /**
     * @var AssignmentService
     */
    protected $assignment;

    /**
     * @var WorkOrderService
     */
    protected $workOrder;

    /**
     * @var AssignmentValidator
     */
    protected $assignmentValidator;

    /**
     * Constructor.
     *
     * @param AssignmentService   $assignment
     * @param WorkOrderService    $workOrder
     * @param AssignmentValidator $assignmentValidator
     */
    public function __construct(AssignmentService $assignment, WorkOrderService $workOrder, AssignmentValidator $assignmentValidator)
    {
        $this->assignment = $assignment;
        $this->workOrder = $workOrder;
        $this->assignmentValidator = $assignmentValidator;
    }

    /**
     * Display a listing of the resource.
     *
     * @param $workOrder_id
     */
    public function index($workOrder_id)
    {
    }

    /**
     * Show the form for creating a new resource.
     *
     * @param $workOrder_id
     */
    public function create($workOrder_id)
    {
    }

    /**
     * Assigns workers to the specified work order ID.
     *
     * @param string|int $workOrder_id
     *
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
     */
    public function store($workOrder_id)
    {
        if ($this->assignmentValidator->passes()) {
            $workOrder = $this->workOrder->find($workOrder_id);

            $data = $this->inputAll();
            $data['work_order_id'] = $workOrder->id;

            $records = $this->assignment->setInput($data)->create();

            if ($records) {
                $this->message = 'Successfully assigned worker(s)';
                $this->messageType = 'success';
                $this->redirect = route('maintenance.work-orders.show', [$workOrder->id]);
            } else {
                $this->message = 'There was an error trying to assign workers to this work order. Please try again.';
                $this->messageType = 'danger';
                $this->redirect = route('maintenance.work-orders.show', [$workOrder->id]);
            }
        } else {
            $this->errors = $this->assignmentValidator->getErrors();
            $this->redirect = route('maintenance.work-orders.show', [$workOrder_id]);
        }

        return $this->response();
    }

    /**
     * Removes the specified assignent from the specified work order.
     *
     * @param string|int $workOrder_id
     * @param string|int $assignment_id
     *
     * @return \Illuminate\Http\JsonResponse|mixed
     */
    public function destroy($workOrder_id, $assignment_id)
    {
        if ($this->assignment->destroy($assignment_id)) {
            $this->message = 'Successfully removed worker from this work order.';
            $this->messageType = 'success';
            $this->redirect = route('maintenance.work-orders.show', [$workOrder_id]);
        } else {
            $this->message = 'There was an error trying to remove this worker from this work order. Please try again later.';
            $this->messageType = 'danger';
            $this->redirect = route('maintenance.work-orders.show', [$workOrder_id]);
        }

        return $this->response();
    }
}


<?php

namespace App\Models;

use App\Models\Traits\HasUserTrait;
use App\Viewers\MeterReadingViewer;

class MeterReading extends Model
{
    use HasUserTrait;

    /**
     * The meter readings table.
     *
     * @var string
     */
    protected $table = 'meter_readings';

    /**
     * The meter reading viewer.
     *
     * @var string
     */
    protected $viewer = MeterReadingViewer::class;

    /**
     * The fillable meter reading attributes.
     *
     * @var array
     */
    protected $fillable = [
        'user_id',
        'meter_id',
        'reading',
        'comment',
    ];

    /**
     * The belongsTo meter relationship.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function meter()
    {
        return $this->belongsTo(Meter::class, 'meter_id');
    }

    /**
     * Accessor for retrieving the current meter reading
     * with it's metric symbol. For example: '120.00 Gal'.
     *
     * @return string
     */
    public function getReadingWithMetricAttribute()
    {
        if ($this->meter && $this->meter->metric) {
            return $this->reading.' '.$this->meter->metric->symbol;
        }

        return $this->reading;
    }
}


<?php

namespace Collective\Html\Concerns;

use Illuminate\Support\Arr;
use Collective\Html\HtmlBuilder;
use Illuminate\Support\Collection;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Contracts\Support\Arrayable;

trait Selection
{
    /**
     * Create a select box field.
     *
     * @param  string $name
     * @param  iterable  $list
     * @param  string|array|null  $selected
     * @param  array  $selectAttributes
     * @param  array  $optionsAttributes
     * @param  array  $optgroupsAttributes
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    public function select(
        string $name,
        iterable $list = [],
        $selected = null,
        array $selectAttributes = [],
        array $optionsAttributes = [],
        array $optgroupsAttributes = []
    ): Htmlable {
        $this->type = 'select';

        // When building a select box the "value" attribute is really the selected one
        // so we will use that when checking the model or session for a value which
        // should provide a convenient method of re-populating the forms on post.
        $selected = $this->getValueAttribute($name, $selected);

        $selectAttributes['id'] = $this->getIdAttribute($name, $selectAttributes);

        if (! isset($selectAttributes['name'])) {
            $selectAttributes['name'] = $name;
        }

        // We will simply loop through the options and build an HTML value for each of
        // them until we have an array of HTML declarations. Then we will join them
        // all together into one single HTML element that can be put on the form.
        $html = [];

        if (isset($selectAttributes['placeholder'])) {
            $html[] = $this->placeholderOption($selectAttributes['placeholder'], $selected);
            unset($selectAttributes['placeholder']);
        }

        if (isset($selectAttributes['native-placeholder'])) {
            $selectAttributes['placeholder'] = $selectAttributes['native-placeholder'];
            unset($selectAttributes['native-placeholder']);
        }

        foreach ($list as $value => $display) {
            $html[] = $this->getSelectOption(
                $display, $value, $selected, $optionsAttributes[$value] ?? [], $optgroupsAttributes[$value] ?? []
            );
        }

        // Once we have all of this HTML, we can join this into a single element after
        // formatting the attributes into an HTML "attributes" string, then we will
        // build out a final select statement, which will contain all the values.
        $options = $this->getHtmlBuilder()->attributes($selectAttributes);

        $list = \implode('', $html);

        return $this->toHtmlString("<select{$options}>{$list}</select>");
    }

    /**
     * Create a datalist box field.
     *
     * @param  string $id
     * @param  array  $list
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    public function datalist(string $id, array $list = []): Htmlable
    {
        $this->type = 'datalist';

        $attributes['id'] = $id;

        $html = [];

        if (Arr::isAssoc($list)) {
            foreach ($list as $value => $display) {
                $html[] = $this->option($display, $value, null, []);
            }
        } else {
            foreach ($list as $value) {
                $html[] = $this->option($value, $value, null, []);
            }
        }

        $attributes = $this->getHtmlBuilder()->attributes($attributes);

        $list = \implode('', $html);

        return $this->toHtmlString("<datalist{$attributes}>{$list}</datalist>");
    }

    /**
     * Get the select option for the given value.
     *
     * @param  string|array  $display
     * @param  string  $value
     * @param  string|array|null  $selected
     * @param  array  $attributes
     * @param  array  $optgroups
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    public function getSelectOption(
        $display,
        string $value,
        $selected,
        array $attributes = [],
        array $optgroups = []
    ): Htmlable {
        if (\is_iterable($display)) {
            return $this->optionGroup($display, $value, $selected, $optgroups, $attributes);
        }

        return $this->option($display, $value, $selected, $attributes);
    }

    /**
     * Create an option group form element.
     *
     * @param  iterable   $list
     * @param  string  $label
     * @param  string|array|null  $selected
     * @param  array  $attributes
     * @param  array  $optionsAttributes
     * @param  int  $level
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    protected function optionGroup(
        iterable $list,
        string $label,
        $selected,
        array $attributes = [],
        array $optionsAttributes = [],
        int $level = 0
    ): Htmlable {
        $html  = [];
        $space = \str_repeat(' ', $level);

        foreach ($list as $value => $display) {
            if (\is_iterable($display)) {
                $html[] = $this->option(
                    $display, $value, $selected, $optionsAttributes[$value] ?? [], $level + 5
                );
            } else {
                $html[] = $this->option(
                    $space.$display, $value, $selected, $optionsAttributes[$value] ?? []
                );
            }
        }

        return $this->toHtmlString(\sprintf(
            '<optgroup label="%s"%s>%s</optgroup>',
            $this->entities($label),
            $this->getHtmlBuilder()->attributes($attributes),
            \implode('', $html)
        ));
    }

    /**
     * Create a select element option.
     *
     * @param  string  $display
     * @param  string  $value
     * @param  string|array|\Illuminate\Contracts\Support\Arrayable|bool  $selected
     * @param  array  $attributes
     * @param  array  $optgroups
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    protected function option(
        string $display,
        string $value,
        $selected,
        array $attributes = [],
        array $optgroups = []
    ): Htmlable {
        $selected = $this->getSelectedValue($value, $selected);

        $options = ['value' => $value, 'selected' => $selected] + $attributes;

        return $this->toHtmlString(\sprintf(
            '<option%s>%s</option>',
            $this->getHtmlBuilder()->attributes($options),
            $this->entities($display)
        ));
    }

    /**
     * Create a placeholder select element option.
     *
     * @param string  $display
     * @param string|iterable|null  $selected
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    protected function placeholderOption(string $display, $selected): Htmlable
    {
        $options = [
            'selected' => $this->getSelectedValue(null, $selected),
            'disabled' => true,
            'value'    => '',
        ];

        return $this->toHtmlString(\sprintf(
            '<option%s>%s</option>',
            $this->getHtmlBuilder()->attributes($options),
            $this->entities($display)
        ));
    }

    /**
     * Determine if the value is selected.
     *
     * @param  string|null $value
     * @param  string|array|\Illuminate\Contracts\Support\Arrayable|bool|null  $selected
     *
     * @return string|null
     */
    protected function getSelectedValue(?string $value, $selected): ?string
    {
        $selection = $selected instanceof Arrayable ? $selected->toArray() : $selected;

        if (\is_array($selection)) {
            if (\in_array($value, $selection, true) || \in_array((string) $value, $selection, true)) {
                return 'selected';
            } elseif ($selected instanceof Collection) {
                return $selected->contains($value) ? 'selected' : null;
            }

            return null;
        }

        if (\is_int($value) && \is_bool($selected)) {
            return (bool) $value === $selected;
        }

        return ((string) $value === (string) $selected) ? 'selected' : null;
    }

    /**
     * Create a select range field.
     *
     * @param  string  $name
     * @param  string|int  $begin
     * @param  string|int  $end
     * @param  string|array|null  $selected
     * @param  array   $options
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    public function selectRange(string $name, $begin, $end, $selected = null, array $options = []): Htmlable
    {
        $range = \array_combine($range = \range($begin, $end), $range);

        return $this->select($name, $range, $selected, $options);
    }

    /**
     * Create a select year field.
     *
     * @param  string  $name
     * @param  string|int  $begin
     * @param  string|int  $end
     * @param  string|array|null  $selected
     * @param  array   $options
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    public function selectYear(string $name, $begin, $end, $selected = null, array $options = []): Htmlable
    {
        return $this->selectRange($name, $begin, $end, $selected, $options);
    }

    /**
     * Create a select month field.
     *
     * @param  string  $name
     * @param  string|array|null  $selected
     * @param  array   $options
     * @param  string  $format
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    public function selectMonth(string $name, $selected = null, array $options = [], string $format = '%B'): Htmlable
    {
        $months = [];

        foreach (\range(1, 12) as $month) {
            $months[$month] = \ucfirst(\strftime($format, \mktime(0, 0, 0, $month, 1)));
        }

        return $this->select($name, $months, $selected, $options);
    }

    /**
     * Convert an HTML string to entities.
     *
     * @param  string  $value
     * @param  bool  $encoding
     *
     * @return string
     */
    abstract protected function entities(string $value, bool $encoding = false): string;

    /**
     * Get html builder.
     *
     * @return \Collective\Html\HtmlBuilder
     */
    abstract public function getHtmlBuilder(): HtmlBuilder;

    /**
     * Transform the string to an Html serializable object.
     *
     * @param  string  $html
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    abstract protected function toHtmlString(string $html): Htmlable;

    /**
     * Get the ID attribute for a field name.
     *
     * @param  string  $name
     * @param  array   $attributes
     *
     * @return string
     */
    abstract public function getIdAttribute(string $name, array $attributes): string;

    /**
     * Get the value that should be assigned to the field.
     *
     * @param  string  $name
     * @param  string|array|null  $value
     *
     * @return mixed
     */
    abstract public function getValueAttribute(string $name, $value = null);
}


<?php

namespace Collective\Html\Concerns;

use Illuminate\Support\Arr;
use Collective\Html\HtmlBuilder;
use Illuminate\Contracts\Support\Htmlable;

trait Creator
{
    /**
     * The current model instance for the form.
     *
     * @var mixed
     */
    protected $model;

    /**
     * An array of label names we've created.
     *
     * @var array
     */
    protected $labels = [];

    /**
     * The URL generator instance.
     *
     * @var \Illuminate\Contracts\Routing\UrlGenerator
     */
    protected $url;

    /**
     * The reserved form open attributes.
     *
     * @var array
     */
    protected $reserved = ['method', 'url', 'route', 'action', 'files'];

    /**
     * The form methods that should be spoofed, in uppercase.
     *
     * @var array
     */
    protected $spoofedMethods = ['DELETE', 'PATCH', 'PUT'];

    /**
     * Open up a new HTML form.
     *
     * @param  array   $options
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    public function open(array $options = []): Htmlable
    {
        $method = $options['method'] ?? 'post';

        // We need to extract the proper method from the attributes. If the method is
        // something other than GET or POST we'll use POST since we will spoof the
        // actual method since forms don't support the reserved methods in HTML.
        $attributes = [
            'method'         => $this->getMethod($method),
            'action'         => $this->getAction($options),
            'accept-charset' => 'UTF-8',
        ];

        // If the method is PUT, PATCH or DELETE we will need to add a spoofer hidden
        // field that will instruct the Symfony request to pretend the method is a
        // different method than it actually is, for convenience from the forms.
        $append = $this->getAppendage($method);

        if (isset($options['files']) && $options['files']) {
            $options['enctype'] = 'multipart/form-data';
        }

        // Finally we're ready to create the final form HTML field. We will attribute
        // format the array of attributes. We will also add on the appendage which
        // is used to spoof requests for this PUT, PATCH, etc. methods on forms.
        $attributes = \array_merge($attributes, Arr::except($options, $this->reserved));

        // Finally, we will concatenate all of the attributes into a single string so
        // we can build out the final form open statement. We'll also append on an
        // extra value for the hidden _method field if it's needed for the form.
        $attributes = $this->getHtmlBuilder()->attributes($attributes);

        return $this->toHtmlString('<form'.$attributes.'>'.$append);
    }

    /**
     * Close the current form.
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    public function close(): Htmlable
    {
        $this->labels = [];

        $this->model = null;

        return $this->toHtmlString('</form>');
    }

    /**
     * Get the form appendage for the given method.
     *
     * @param  string  $method
     *
     * @return string
     */
    protected function getAppendage(string $method): string
    {
        list($method, $appendage) = [\strtoupper($method), ''];

        // If the HTTP method is in this list of spoofed methods, we will attach the
        // method spoofer hidden input to the form. This allows us to use regular
        // form to initiate PUT and DELETE requests in addition to the typical.
        if (\in_array($method, $this->spoofedMethods)) {
            $appendage .= $this->hidden('_method', $method);
        }

        // If the method is something other than GET we will go ahead and attach the
        // CSRF token to the form, as this can't hurt and is convenient to simply
        // always have available on every form the developers creates for them.
        if ($method != 'GET') {
            $appendage .= $this->token();
        }

        return $appendage;
    }

    /**
     * Parse the form action method.
     *
     * @param  string  $method
     *
     * @return string
     */
    protected function getMethod(string $method): string
    {
        $method = \strtoupper($method);

        return $method != 'GET' ? 'POST' : $method;
    }

    /**
     * Get the form action from the options.
     *
     * @param  array   $options
     *
     * @return string
     */
    protected function getAction(array $options): string
    {
        // We will also check for a "route" or "action" parameter on the array so that
        // developers can easily specify a route or controller action when creating
        // a form providing a convenient interface for creating the form actions.
        if (isset($options['url'])) {
            return $this->getUrlAction($options['url']);
        }

        // If an action is available, we are attempting to open a form to a controller
        // action route. So, we will use the URL generator to get the path to these
        // actions and return them from the method. Otherwise, we'll use current.

        if (isset($options['route'])) {
            return $this->getRouteAction($options['route']);
        } elseif (isset($options['action'])) {
            return $this->getControllerAction($options['action']);
        }

        return $this->url->current();
    }

    /**
     * Get the action for a "url" option.
     *
     * @param  array|string  $options
     *
     * @return string
     */
    protected function getUrlAction($options): string
    {
        if (\is_array($options)) {
            return $this->url->to($options[0], \array_slice($options, 1));
        }

        return $this->url->to($options);
    }

    /**
     * Get the action for a "route" option.
     *
     * @param  array|string  $options
     *
     * @return string
     */
    protected function getRouteAction($options): string
    {
        if (\is_array($options)) {
            return $this->url->route($options[0], \array_slice($options, 1));
        }

        return $this->url->route($options);
    }

    /**
     * Get the action for an "action" option.
     *
     * @param  array|string  $options
     *
     * @return string
     */
    protected function getControllerAction($options): string
    {
        if (\is_array($options)) {
            return $this->url->action($options[0], \array_slice($options, 1));
        }

        return $this->url->action($options);
    }

    /**
     * Generate a hidden field with the current CSRF token.
     *
     * @return string
     */
    abstract public function token(): string;

    /**
     * Get html builder.
     *
     * @return \Collective\Html\HtmlBuilder
     */
    abstract public function getHtmlBuilder(): HtmlBuilder;

    /**
     * Transform the string to an Html serializable object.
     *
     * @param  string  $html
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    abstract protected function toHtmlString(string $html): Htmlable;
}


<?php

namespace Collective\Html;

use BadMethodCallException;
use Illuminate\Support\HtmlString;
use Illuminate\Contracts\Support\Htmlable;

trait Componentable
{
    /**
     * The registered components.
     *
     * @var array
     */
    protected static $components = [];

    /**
     * Register a custom component.
     *
     * @param  string  $name
     * @param  mixed  $view
     * @param  array  $signature
     *
     * @return void
     */
    public static function component(string $name, $view, array $signature): void
    {
        static::$components[$name] = compact('view', 'signature');
    }

    /**
     * Check if a component is registered.
     *
     * @param  string  $name
     *
     * @return bool
     */
    public static function hasComponent(string $name): bool
    {
        return isset(static::$components[$name]);
    }

    /**
     * Render a custom component.
     *
     * @param  string  $name
     * @param  array  $arguments
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    protected function renderComponent(string $name, array $arguments): Htmlable
    {
        $component = static::$components[$name];
        $data      = $this->getComponentData($component['signature'], $arguments);

        return new HtmlString(
          $this->view->make($component['view'], $data)->render()
        );
    }

    /**
     * Prepare the component data, while respecting provided defaults.
     *
     * @param  array $signature
     * @param  array $arguments
     *
     * @return array
     */
    protected function getComponentData(array $signature, array $arguments): array
    {
        $data = [];

        $i = 0;
        foreach ($signature as $variable => $default) {
            // If the "variable" value is actually a numeric key, we can assume that
            // no default had been specified for the component argument and we'll
            // just use null instead, so that we can treat them all the same.
            if (\is_numeric($variable)) {
                $variable = $default;
                $default  = null;
            }

            $data[$variable] = $arguments[$i] ?? $default;

            ++$i;
        }

        return $data;
    }

    /**
     * Dynamically handle calls to the class.
     *
     * @param  string $method
     * @param  array  $parameters
     *
     * @throws \BadMethodCallException
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    public function __call(string $method, array $parameters): Htmlable
    {
        if (static::hasComponent($method)) {
            return $this->renderComponent($method, $parameters);
        }

        throw new BadMethodCallException("Method {$method} does not exist.");
    }
}


<?php

namespace Collective\Html;

use Illuminate\Support\Str;
use Illuminate\Support\ServiceProvider;
use Illuminate\View\Compilers\BladeCompiler;

class BladeServiceProvider extends ServiceProvider
{
    /**
     * Supported Blade Directives.
     *
     * @var array
     */
    protected $directives = [
        'entities', 'decode', 'script', 'style', 'image', 'favicon', 'link', 'secureLink', 'linkAsset',
        'linkSecureAsset', 'linkRoute', 'linkAction', 'mailto', 'email', 'ol', 'ul', 'dl', 'meta',
        'tag', 'open', 'model', 'close', 'token', 'label', 'input', 'text', 'password', 'hidden',
        'email', 'tel', 'number', 'date', 'datetime', 'datetimeLocal', 'time', 'url', 'file',
        'textarea', 'select', 'selectRange', 'selectYear', 'selectMonth', 'getSelectOption',
        'checkbox', 'radio', 'reset', 'image', 'color', 'submit', 'button', 'old',
    ];

    /**
     * Register Blade directives.
     *
     * @return void
     */
    protected function register()
    {
        $this->app->afterResolving('blade.compiler', function (BladeCompiler $bladeCompiler) {
            $namespaces = [
                'Html' => \get_class_methods(HtmlBuilder::class),
                'Form' => \get_class_methods(FormBuilder::class),
            ];

            foreach ($namespaces as $namespace => $methods) {
                foreach ($methods as $method) {
                    if (\in_array($method, $this->directives)) {
                        $snakeMethod = Str::snake($method);
                        $directive = \strtolower($namespace).'_'.$snakeMethod;

                        $bladeCompiler->directive($directive, function ($expression) use ($namespace, $method) {
                            return "<?php echo $namespace::$method($expression); ?>";
                        });
                    }
                }
            }
        });
    }
}


<?php

namespace Collective\Html;

use BadMethodCallException;
use Illuminate\Http\Request;
use Illuminate\Support\HtmlString;
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Contracts\View\Factory as ViewFactoryContract;
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
use Illuminate\Contracts\Routing\UrlGenerator as UrlGeneratorContract;

class FormBuilder
{
    use Componentable,
        Concerns\Checker,
        Concerns\Creator,
        Concerns\Input,
        Concerns\Selection,
        Concerns\SessionHelper,
        Macroable {
            Componentable::__call as componentCall;
            Macroable::__call as macroCall;
        }

    /**
     * The HTML builder instance.
     *
     * @var \Collective\Html\HtmlBuilder
     */
    protected $html;

    /**
     * The View factory instance.
     *
     * @var \Illuminate\Contracts\View\Factory
     */
    protected $view;

    /**
     * The request implementation.
     *
     * @var \Illuminate\Http\Request
     */
    protected $request;

    /**
     * Consider Request variables while auto fill.
     *
     * @var bool
     */
    protected $considerRequest = false;

    /**
     * Create a new form builder instance.
     *
     * @param  \Collective\Html\HtmlBuilder  $html
     * @param  \Illuminate\Contracts\Routing\UrlGenerator  $url
     * @param  \Illuminate\Contracts\View\Factory  $view
     * @param  \Illuminate\Http\Request|null  $request
     */
    public function __construct(
        HtmlBuilder $html,
        UrlGeneratorContract $url,
        ViewFactoryContract $view,
        Request $request = null
    ) {
        $this->url     = $url;
        $this->html    = $html;
        $this->view    = $view;
        $this->request = $request;
    }

    /**
     * Convert an HTML string to entities.
     *
     * @param  string  $value
     * @param  bool  $encoding
     *
     * @return string
     */
    protected function entities(string $value, bool $encoding = false): string
    {
        return $this->html->entities($value, $encoding);
    }

    /**
     * Generate a hidden field with the current CSRF token.
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    public function token(): Htmlable
    {
        if (empty($this->csrfToken) && ! \is_null($this->session)) {
            $this->csrfToken = $this->session->token();
        }

        return $this->hidden('_token', $this->csrfToken);
    }

    /**
     * Create a new model based form builder.
     *
     * @param  mixed  $model
     * @param  array  $options
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    public function model($model, array $options = []): Htmlable
    {
        $this->model = $model;

        return $this->open($options);
    }

    /**
     * Set the model instance on the form builder.
     *
     * @param  mixed  $model
     *
     * @return void
     */
    public function setModel($model): void
    {
        $this->model = $model;
    }

    /**
     * Get the current model instance on the form builder.
     *
     * @return mixed
     */
    public function getModel()
    {
        return $this->model;
    }

    /**
     * Create a form label element.
     *
     * @param  string  $name
     * @param  string  $value
     * @param  array   $options
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    public function label(string $name, ?string $value = null, array $options = []): Htmlable
    {
        $this->labels[] = $name;

        $options = $this->html->attributes($options);

        $value = $this->entities($this->formatLabel($name, $value));

        return $this->toHtmlString('<label for="'.$name.'"'.$options.'>'.$value.'</label>');
    }

    /**
     * Format the label value.
     *
     * @param  string  $name
     * @param  string|null  $value
     *
     * @return string
     */
    protected function formatLabel(string $name, ?string $value): string
    {
        return $value ?? \ucwords(\str_replace('_', ' ', $name));
    }

    /**
     * Determine if old input or model input exists for a key.
     *
     * @param  string  $name
     *
     * @return bool
     */
    protected function missingOldAndModel(string $name): bool
    {
        return \is_null($this->old($name)) && \is_null($this->getModelValueAttribute($name));
    }

    /**
     * Create a HTML reset input element.
     *
     * @param  string|null  $value
     * @param  array  $attributes
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    public function reset(?string $value = null, array $attributes = []): Htmlable
    {
        return $this->input('reset', null, $value, $attributes);
    }

    /**
     * Create a hidden input field.
     *
     * @param  string  $name
     * @param  string|null  $value
     * @param  array  $options
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    public function hidden(string $name, ?string $value = null, array $options = []): Htmlable
    {
        return $this->input('hidden', $name, $value, $options);
    }

    /**
     * Create a submit button element.
     *
     * @param  string|null  $value
     * @param  array  $options
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    public function submit(?string $value = null, array $options = []): Htmlable
    {
        return $this->input('submit', null, $value, $options);
    }

    /**
     * Create a button element.
     *
     * @param  string|null  $value
     * @param  array  $options
     * @param  bool  $escape
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    public function button(?string $value = null, array $options = [], bool $escape = true): Htmlable
    {
        if (! \array_key_exists('type', $options)) {
            $options['type'] = 'button';
        }

        if ($escape) {
            $value = $this->html->entities($value);
        }

        return $this->toHtmlString('<button'.$this->html->attributes($options).'>'.$value.'</button>');
    }

    /**
     * Get the ID attribute for a field name.
     *
     * @param  string|null  $name
     * @param  array  $attributes
     *
     * @return string|null
     */
    public function getIdAttribute(?string $name, array $attributes): ?string
    {
        if (\array_key_exists('id', $attributes)) {
            return $attributes['id'];
        }

        if (\in_array($name, $this->labels)) {
            return $name;
        }

        return null;
    }

    /**
     * Get the value that should be assigned to the field.
     *
     * @param  string|null  $name
     * @param  mixed  $value
     *
     * @return mixed
     */
    public function getValueAttribute(?string $name, $value = null)
    {
        if (\is_null($name)) {
            return $value;
        }

        if (! \is_null($old = $this->old($name)) && $name !== '_method') {
            return $old;
        }

        if (\class_exists(Kernel::class, false)) {
            $hasNullMiddleware = \app(Kernel::class)->hasMiddleware(ConvertEmptyStringsToNull::class);

            if ($hasNullMiddleware && \is_null($old) && \is_null($value)
                && ! \is_null($this->view->shared('errors'))
                && \count($this->view->shared('errors')) > 0
            ) {
                return null;
            }
        }

        if (! \is_null($request = $this->request($name)) && $name !== '_method') {
            return $request;
        }

        if (! \is_null($value)) {
            return $value;
        }

        if (isset($this->model)) {
            return $this->getModelValueAttribute($name);
        }
    }

    /**
     * Get value from current Request.
     *
     * @param string $name
     *
     * @return array|string|null
     */
    protected function request(string $name)
    {
        if (! $this->considerRequest || ! isset($this->request)) {
            return null;
        }

        return $this->request->input($this->transformKey($name));
    }

    /**
     * Get the model value that should be assigned to the field.
     *
     * @param  string  $name
     *
     * @return mixed
     */
    protected function getModelValueAttribute(string $name)
    {
        if (\method_exists($this->model, 'getFormValue')) {
            return $this->model->getFormValue($this->transformKey($name));
        }

        return \data_get($this->model, $this->transformKey($name));
    }

    /**
     * Transform key from array to dot syntax.
     *
     * @param  string  $key
     *
     * @return string
     */
    protected function transformKey(string $key): string
    {
        return \str_replace(['.', '[]', '[', ']'], ['_', '', '.', ''], $key);
    }

    /**
     * Transform the string to an Html serializable object.
     *
     * @param  string  $html
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    protected function toHtmlString(string $html): Htmlable
    {
        return new HtmlString($html);
    }

    /**
     * Get html builder.
     *
     * @return \Collective\Html\HtmlBuilder
     */
    public function getHtmlBuilder(): HtmlBuilder
    {
        return $this->html;
    }

    /**
     * Dynamically handle calls to the class.
     *
     * @param  string  $method
     * @param  array  $parameters
     *
     * @throws \BadMethodCallException
     *
     * @return \Illuminate\Contracts\Support\Htmlable|mixed
     */
    public function __call(string $method, array $parameters)
    {
        if (static::hasComponent($method)) {
            return $this->renderComponent($method, $parameters);
        }

        if (static::hasMacro($method)) {
            return $this->macroCall($method, $parameters);
        }

        throw new BadMethodCallException("Method {$method} does not exist.");
    }

    /**
     * Take Request in fill process.
     *
     * @param  bool  $consider
     *
     * @return $this
     */
    public function considerRequest(bool $consider = true): self
    {
        $this->considerRequest = $consider;

        return $this;
    }
}