Locations
This commit is contained in:
parent
d4662c808e
commit
0265bc1603
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Factory;
|
||||
|
||||
use App\Models\Location;
|
||||
|
||||
class LocationFactory
|
||||
{
|
||||
public static function create(int $company_id, int $user_id): Location
|
||||
{
|
||||
$location = new Location();
|
||||
$location->company_id = $company_id;
|
||||
$location->user_id = $user_id;
|
||||
$location->name = '';
|
||||
$location->country_id = null;
|
||||
$location->is_deleted = false;
|
||||
|
||||
return $location;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
/**
|
||||
* LocationFilters.
|
||||
*/
|
||||
class LocationFilters extends QueryFilters
|
||||
{
|
||||
/**
|
||||
* Filter based on search text.
|
||||
*
|
||||
* @param string $filter
|
||||
* @return Builder
|
||||
* @deprecated
|
||||
*/
|
||||
public function filter(string $filter = ''): Builder
|
||||
{
|
||||
if (strlen($filter) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
return $this->builder->where('name', 'like', '%'.$filter.'%');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the list based on $sort.
|
||||
*
|
||||
* @param string $sort formatted as column|asc
|
||||
* @return Builder
|
||||
*/
|
||||
public function sort(string $sort = ''): Builder
|
||||
{
|
||||
$sort_col = explode('|', $sort);
|
||||
|
||||
if (!is_array($sort_col) || count($sort_col) != 2 || !in_array($sort_col[0], \Illuminate\Support\Facades\Schema::getColumnListing($this->builder->getModel()->getTable()))) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
if (is_array($sort_col) && in_array($sort_col[1], ['asc', 'desc']) && in_array($sort_col[0], ['name'])) {
|
||||
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
|
||||
}
|
||||
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the query by the users company ID.
|
||||
*
|
||||
* @return Builder
|
||||
*/
|
||||
public function entityFilter(): Builder
|
||||
{
|
||||
return $this->builder->company();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,480 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Factory\LocationFactory;
|
||||
use App\Filters\LocationFilters;
|
||||
use App\Http\Requests\Location\CreateLocationRequest;
|
||||
use App\Http\Requests\Location\DestroyLocationRequest;
|
||||
use App\Http\Requests\Location\EditLocationRequest;
|
||||
use App\Http\Requests\Location\ShowLocationRequest;
|
||||
use App\Http\Requests\Location\StoreLocationRequest;
|
||||
use App\Http\Requests\Location\UpdateLocationRequest;
|
||||
use App\Models\Expense;
|
||||
use App\Models\Location;
|
||||
use App\Repositories\BaseRepository;
|
||||
use App\Transformers\LocationTransformer;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
/**
|
||||
* Class LocationController.
|
||||
*/
|
||||
class LocationController extends BaseController
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
protected $entity_type = Location::class;
|
||||
|
||||
protected $entity_transformer = LocationTransformer::class;
|
||||
|
||||
protected $base_repo;
|
||||
|
||||
public function __construct(BaseRepository $base_repo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->base_repo = $base_repo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/locations",
|
||||
* operationId="getLocations",
|
||||
* tags={"locations"},
|
||||
* summary="Gets a list of locations",
|
||||
* description="Lists tax rates",
|
||||
* @OA\Parameter(ref="#/components/parameters/index"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="A list of locations",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Location"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*
|
||||
*
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return Response| \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function index(LocationFilters $filters)
|
||||
{
|
||||
$locations = Location::filter($filters);
|
||||
|
||||
return $this->listResponse($locations);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @param CreateLocationRequest $request
|
||||
* @return Response| \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*
|
||||
*
|
||||
* @OA\Get(
|
||||
* path="/api/v1/locations/create",
|
||||
* operationId="getLocationCreate",
|
||||
* tags={"locations"},
|
||||
* summary="Gets a new blank Expens Category object",
|
||||
* description="Returns a blank object with default values",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="A blank Expens Category object",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Location"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function create(CreateLocationRequest $request)
|
||||
{
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$location = LocationFactory::create($user->company()->id, auth()->user()->id);
|
||||
|
||||
return $this->itemResponse($location);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param StoreLocationRequest $request
|
||||
* @return Response| \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/locations",
|
||||
* operationId="storeLocation",
|
||||
* tags={"locations"},
|
||||
* summary="Adds a expense category",
|
||||
* description="Adds an expense category to the system",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the saved invoice object",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Location"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function store(StoreLocationRequest $request)
|
||||
{
|
||||
/** @var \App\Models\User $user **/
|
||||
$user = auth()->user();
|
||||
|
||||
$location = LocationFactory::create($user->company()->id, $user->id);
|
||||
$location->fill($request->all());
|
||||
$location->save();
|
||||
|
||||
return $this->itemResponse($location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param ShowLocationRequest $request
|
||||
* @param Location $location
|
||||
* @return Response| \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*
|
||||
* @OA\Get(
|
||||
* path="/api/v1/locations/{id}",
|
||||
* operationId="showLocation",
|
||||
* tags={"locations"},
|
||||
* summary="Shows a Expens Category",
|
||||
* description="Displays an Location by id",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Location Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the Expens Category object",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Location"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function show(ShowLocationRequest $request, Location $location)
|
||||
{
|
||||
return $this->itemResponse($location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param EditLocationRequest $request
|
||||
* @param Location $location
|
||||
* @return Response| \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*
|
||||
* @OA\Get(
|
||||
* path="/api/v1/locations/{id}/edit",
|
||||
* operationId="editLocation",
|
||||
* tags={"locations"},
|
||||
* summary="Shows a Expens Category for editting",
|
||||
* description="Displays a Expens Category by id",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Location Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the Expens Category object",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Location"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function edit(EditLocationRequest $request, Location $location)
|
||||
{
|
||||
return $this->itemResponse($location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param UpdateLocationRequest $request
|
||||
* @param Location $location
|
||||
* @return Response| \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*
|
||||
*
|
||||
* @OA\Put(
|
||||
* path="/api/v1/locations/{id}",
|
||||
* operationId="updateLocation",
|
||||
* tags={"locations"},
|
||||
* summary="Updates a tax rate",
|
||||
* description="Handles the updating of a tax rate by id",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Location Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the Location object",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Location"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function update(UpdateLocationRequest $request, Location $location)
|
||||
{
|
||||
$location->fill($request->all());
|
||||
$location->save();
|
||||
|
||||
return $this->itemResponse($location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param DestroyLocationRequest $request
|
||||
* @param Location $location
|
||||
* @return Response| \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*
|
||||
* @throws \Exception
|
||||
* @OA\Delete(
|
||||
* path="/api/v1/locations/{id}",
|
||||
* operationId="deleteLocation",
|
||||
* tags={"locations"},
|
||||
* summary="Deletes a Location",
|
||||
* description="Handles the deletion of an Location by id",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Location Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns a HTTP status",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function destroy(DestroyLocationRequest $request, Location $location)
|
||||
{
|
||||
$location->is_deleted = true;
|
||||
$location->save();
|
||||
$location->delete();
|
||||
|
||||
return $this->itemResponse($location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform bulk actions on the list view.
|
||||
*
|
||||
* @return Response| \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/locations/bulk",
|
||||
* operationId="bulkLocations",
|
||||
* tags={"locations"},
|
||||
* summary="Performs bulk actions on an array of Locations",
|
||||
* description="",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/index"),
|
||||
* @OA\RequestBody(
|
||||
* description="Expens Categorys",
|
||||
* required=true,
|
||||
* @OA\MediaType(
|
||||
* mediaType="application/json",
|
||||
* @OA\Schema(
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* type="integer",
|
||||
* description="Array of hashed IDs to be bulk 'actioned",
|
||||
* example="[0,1,2,3]",
|
||||
* ),
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="The Location List response",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Webhook"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function bulk()
|
||||
{
|
||||
/** @var \App\Models\User $user **/
|
||||
$user = auth()->user();
|
||||
|
||||
$action = request()->input('action');
|
||||
|
||||
$ids = request()->input('ids');
|
||||
|
||||
$locations = Location::withTrashed()->find($this->transformKeys($ids));
|
||||
|
||||
$locations->each(function ($location, $key) use ($action, $user) {
|
||||
if ($user->can('edit', $location)) {
|
||||
$this->base_repo->{$action}($location);
|
||||
}
|
||||
});
|
||||
|
||||
return $this->listResponse(Location::withTrashed()->whereIn('id', $this->transformKeys($ids)));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Location;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\BulkOptions;
|
||||
|
||||
class BulkLocationRequest extends Request
|
||||
{
|
||||
use BulkOptions;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Location;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\Location;
|
||||
|
||||
class CreateLocationRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth()->user()->can('create', Location::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Location;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class DestroyLocationRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth()->user()->can('edit', $this->location);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Location;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class EditLocationRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth()->user()->can('edit', $this->location);
|
||||
}
|
||||
|
||||
// public function prepareForValidation()
|
||||
// {
|
||||
// $input = $this->all();
|
||||
|
||||
// //$input['id'] = $this->encodePrimaryKey($input['id']);
|
||||
|
||||
// $this->replace($input);
|
||||
|
||||
// }
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Location;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class ShowLocationRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth()->user()->can('view', $this->location);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Location;
|
||||
|
||||
use App\Models\Expense;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\Location;
|
||||
|
||||
class StoreLocationRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
return $user->can('create', Location::class) || $user->can('create', Expense::class);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$rules = [];
|
||||
|
||||
$rules['name'] = 'required|unique:expense_categories,name,null,null,company_id,'.$user->companyId();
|
||||
|
||||
return $this->globalRules($rules);
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
if (array_key_exists('color', $input) && is_null($input['color'])) {
|
||||
$input['color'] = '';
|
||||
}
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Location;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateLocationRequest extends Request
|
||||
{
|
||||
use ChecksEntityStatus;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
return $user->can('edit', $this->location);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$rules = [];
|
||||
|
||||
if ($this->input('name')) {
|
||||
$rules['name'] = Rule::unique('locations')->where('company_id', $user->company()->id)->ignore($this->location->id);
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
||||
|
|
@ -320,6 +320,11 @@ class Client extends BaseModel implements HasLocalePreference
|
|||
return $this->hasMany(Project::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function locations(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(Location::class)->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the specific payment token per
|
||||
* gateway - per payment method.
|
||||
|
|
|
|||
|
|
@ -478,6 +478,11 @@ class Company extends BaseModel
|
|||
return $this->hasMany(ClientContact::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function locations(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(Location::class)->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Traits\Excludable;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* App\Models\Client
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $company_id
|
||||
* @property int $user_id
|
||||
* @property int|null $assigned_user_id
|
||||
* @property string|null $name
|
||||
* @property string|null $website
|
||||
* @property string|null $private_notes
|
||||
* @property string|null $public_notes
|
||||
* @property string|null $client_hash
|
||||
* @property string|null $logo
|
||||
* @property string|null $phone
|
||||
* @property string|null $routing_id
|
||||
* @property float $balance
|
||||
* @property float $paid_to_date
|
||||
* @property float $credit_balance
|
||||
* @property int|null $last_login
|
||||
* @property int|null $industry_id
|
||||
* @property int|null $size_id
|
||||
* @property object|array|null $e_invoice
|
||||
* @property string|null $address1
|
||||
* @property string|null $address2
|
||||
* @property string|null $city
|
||||
* @property string|null $state
|
||||
* @property string|null $postal_code
|
||||
* @property int|null $country_id
|
||||
* @property string|null $custom_value1
|
||||
* @property string|null $custom_value2
|
||||
* @property string|null $custom_value3
|
||||
* @property string|null $custom_value4
|
||||
* @property bool $is_deleted
|
||||
* @property int|null $created_at
|
||||
* @property int|null $updated_at
|
||||
* @property int|null $deleted_at
|
||||
* @property-read mixed $hashed_id
|
||||
* @property-read \App\Models\User $user
|
||||
* @property-read \App\Models\Client $client
|
||||
* @property-read \App\Models\Vendor $vendor
|
||||
* @property-read \App\Models\Company $company
|
||||
* @property-read \App\Models\Country|null $country
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Location extends BaseModel
|
||||
{
|
||||
|
||||
use SoftDeletes;
|
||||
use Filterable;
|
||||
use Excludable;
|
||||
|
||||
protected $hidden = [
|
||||
'id',
|
||||
'user_id',
|
||||
'company_id',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'address1',
|
||||
'address2',
|
||||
'city',
|
||||
'state',
|
||||
'postal_code',
|
||||
'country_id',
|
||||
'custom_value1',
|
||||
'custom_value2',
|
||||
'custom_value3',
|
||||
'custom_value4',
|
||||
'is_deleted',
|
||||
];
|
||||
|
||||
|
||||
protected $touches = [];
|
||||
|
||||
public function client()
|
||||
{
|
||||
return $this->belongsTo(Client::class);
|
||||
}
|
||||
|
||||
public function vendor()
|
||||
{
|
||||
return $this->belongsTo(Vendor::class);
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function country()
|
||||
{
|
||||
return $this->belongsTo(Country::class);
|
||||
}
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
return self::class;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -215,6 +215,11 @@ class Vendor extends BaseModel
|
|||
return $this->hasMany(Activity::class);
|
||||
}
|
||||
|
||||
public function locations(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(Location::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function getCurrencyCode(): string
|
||||
{
|
||||
if ($this->currency()) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
/**
|
||||
* Class ClientPolicy.
|
||||
*/
|
||||
class LocationPolicy extends EntityPolicy
|
||||
{
|
||||
/**
|
||||
* Checks if the user has create permissions.
|
||||
*
|
||||
* @param User $user
|
||||
* @return bool
|
||||
*/
|
||||
public function create(User $user): bool
|
||||
{
|
||||
return $user->isAdmin() || $user->hasPermission('create_vendor') || $user->hasPermission('create_client') || $user->hasPermission('create_all');
|
||||
}
|
||||
}
|
||||
|
|
@ -11,72 +11,74 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\Activity;
|
||||
use App\Models\BankIntegration;
|
||||
use App\Models\BankTransaction;
|
||||
use App\Models\BankTransactionRule;
|
||||
use App\Models\Task;
|
||||
use App\Models\User;
|
||||
use App\Models\Quote;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\CompanyToken;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Design;
|
||||
use App\Models\Document;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Company;
|
||||
use App\Models\Expense;
|
||||
use App\Models\ExpenseCategory;
|
||||
use App\Models\GroupSetting;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentTerm;
|
||||
use App\Models\Product;
|
||||
use App\Models\Project;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringExpense;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\RecurringQuote;
|
||||
use App\Models\Scheduler;
|
||||
use App\Models\Subscription;
|
||||
use App\Models\Task;
|
||||
use App\Models\TaskStatus;
|
||||
use App\Models\TaxRate;
|
||||
use App\Models\User;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Webhook;
|
||||
use App\Policies\ActivityPolicy;
|
||||
use App\Policies\BankIntegrationPolicy;
|
||||
use App\Policies\BankTransactionPolicy;
|
||||
use App\Policies\BankTransactionRulePolicy;
|
||||
use App\Models\Activity;
|
||||
use App\Models\Document;
|
||||
use App\Models\Location;
|
||||
use App\Models\Scheduler;
|
||||
use App\Models\TaskStatus;
|
||||
use App\Models\PaymentTerm;
|
||||
use App\Models\CompanyToken;
|
||||
use App\Models\GroupSetting;
|
||||
use App\Models\Subscription;
|
||||
use App\Policies\TaskPolicy;
|
||||
use App\Policies\UserPolicy;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Policies\QuotePolicy;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\RecurringQuote;
|
||||
use App\Policies\ClientPolicy;
|
||||
use App\Policies\CompanyGatewayPolicy;
|
||||
use App\Policies\CompanyPolicy;
|
||||
use App\Policies\CompanyTokenPolicy;
|
||||
use App\Policies\CreditPolicy;
|
||||
use App\Policies\DesignPolicy;
|
||||
use App\Policies\DocumentPolicy;
|
||||
use App\Policies\ExpenseCategoryPolicy;
|
||||
use App\Policies\VendorPolicy;
|
||||
use App\Models\BankIntegration;
|
||||
use App\Models\BankTransaction;
|
||||
use App\Models\ExpenseCategory;
|
||||
use App\Policies\CompanyPolicy;
|
||||
use App\Policies\ExpensePolicy;
|
||||
use App\Policies\GroupSettingPolicy;
|
||||
use App\Policies\InvoicePolicy;
|
||||
use App\Policies\PaymentPolicy;
|
||||
use App\Policies\PaymentTermPolicy;
|
||||
use App\Policies\ProductPolicy;
|
||||
use App\Policies\ProjectPolicy;
|
||||
use App\Policies\TaxRatePolicy;
|
||||
use App\Policies\WebhookPolicy;
|
||||
use App\Models\RecurringExpense;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Policies\ActivityPolicy;
|
||||
use App\Policies\DocumentPolicy;
|
||||
use App\Policies\LocationPolicy;
|
||||
use App\Policies\SchedulerPolicy;
|
||||
use App\Policies\TaskStatusPolicy;
|
||||
use App\Models\BankTransactionRule;
|
||||
use App\Policies\PaymentTermPolicy;
|
||||
use App\Policies\CompanyTokenPolicy;
|
||||
use App\Policies\GroupSettingPolicy;
|
||||
use App\Policies\SubscriptionPolicy;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use App\Policies\PurchaseOrderPolicy;
|
||||
use App\Policies\QuotePolicy;
|
||||
use App\Policies\CompanyGatewayPolicy;
|
||||
use App\Policies\RecurringQuotePolicy;
|
||||
use App\Policies\BankIntegrationPolicy;
|
||||
use App\Policies\BankTransactionPolicy;
|
||||
use App\Policies\ExpenseCategoryPolicy;
|
||||
use App\Policies\RecurringExpensePolicy;
|
||||
use App\Policies\RecurringInvoicePolicy;
|
||||
use App\Policies\RecurringQuotePolicy;
|
||||
use App\Policies\SchedulerPolicy;
|
||||
use App\Policies\SubscriptionPolicy;
|
||||
use App\Policies\TaskPolicy;
|
||||
use App\Policies\TaskStatusPolicy;
|
||||
use App\Policies\TaxRatePolicy;
|
||||
use App\Policies\UserPolicy;
|
||||
use App\Policies\VendorPolicy;
|
||||
use App\Policies\WebhookPolicy;
|
||||
use App\Policies\BankTransactionRulePolicy;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
|
@ -101,6 +103,7 @@ class AuthServiceProvider extends ServiceProvider
|
|||
ExpenseCategory::class => ExpenseCategoryPolicy::class,
|
||||
GroupSetting::class => GroupSettingPolicy::class,
|
||||
Invoice::class => InvoicePolicy::class,
|
||||
Location::class => LocationPolicy::class,
|
||||
Payment::class => PaymentPolicy::class,
|
||||
PaymentTerm::class => PaymentTermPolicy::class,
|
||||
Product::class => ProductPolicy::class,
|
||||
|
|
|
|||
|
|
@ -11,16 +11,17 @@
|
|||
|
||||
namespace App\Transformers;
|
||||
|
||||
use App\Models\Activity;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\CompanyLedger;
|
||||
use App\Models\Document;
|
||||
use App\Models\GroupSetting;
|
||||
use App\Models\SystemLog;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use stdClass;
|
||||
use App\Models\Client;
|
||||
use App\Models\Activity;
|
||||
use App\Models\Document;
|
||||
use App\Models\Location;
|
||||
use App\Models\SystemLog;
|
||||
use App\Models\GroupSetting;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\CompanyLedger;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Models\ClientGatewayToken;
|
||||
|
||||
/**
|
||||
* class ClientTransformer.
|
||||
|
|
@ -43,6 +44,7 @@ class ClientTransformer extends EntityTransformer
|
|||
'ledger',
|
||||
'system_logs',
|
||||
'group_settings',
|
||||
'locations',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -76,6 +78,18 @@ class ClientTransformer extends EntityTransformer
|
|||
return $this->includeCollection($client->contacts, $transformer, ClientContact::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Client $client
|
||||
*
|
||||
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function includeLocations(Client $client)
|
||||
{
|
||||
$transformer = new LocationTransformer($this->serializer);
|
||||
|
||||
return $this->includeCollection($client->locations, $transformer, Location::class);
|
||||
}
|
||||
|
||||
public function includeGatewayTokens(Client $client)
|
||||
{
|
||||
$transformer = new ClientGatewayTokenTransformer($this->serializer);
|
||||
|
|
|
|||
|
|
@ -11,43 +11,44 @@
|
|||
|
||||
namespace App\Transformers;
|
||||
|
||||
use App\Models\Account;
|
||||
use App\Models\Activity;
|
||||
use App\Models\BankIntegration;
|
||||
use App\Models\BankTransaction;
|
||||
use App\Models\BankTransactionRule;
|
||||
use stdClass;
|
||||
use App\Models\Task;
|
||||
use App\Models\User;
|
||||
use App\Models\Quote;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\CompanyLedger;
|
||||
use App\Models\CompanyToken;
|
||||
use App\Models\CompanyUser;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Design;
|
||||
use App\Models\Document;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Account;
|
||||
use App\Models\Company;
|
||||
use App\Models\Expense;
|
||||
use App\Models\ExpenseCategory;
|
||||
use App\Models\GroupSetting;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentTerm;
|
||||
use App\Models\Product;
|
||||
use App\Models\Project;
|
||||
use App\Models\TaxRate;
|
||||
use App\Models\Webhook;
|
||||
use App\Models\Activity;
|
||||
use App\Models\Document;
|
||||
use App\Models\Location;
|
||||
use App\Models\Scheduler;
|
||||
use App\Models\SystemLog;
|
||||
use App\Models\TaskStatus;
|
||||
use App\Models\CompanyUser;
|
||||
use App\Models\PaymentTerm;
|
||||
use App\Models\CompanyToken;
|
||||
use App\Models\GroupSetting;
|
||||
use App\Models\Subscription;
|
||||
use App\Models\CompanyLedger;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\BankIntegration;
|
||||
use App\Models\BankTransaction;
|
||||
use App\Models\ExpenseCategory;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Models\RecurringExpense;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\Scheduler;
|
||||
use App\Models\Subscription;
|
||||
use App\Models\SystemLog;
|
||||
use App\Models\Task;
|
||||
use App\Models\TaskStatus;
|
||||
use App\Models\TaxRate;
|
||||
use App\Models\User;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Webhook;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use stdClass;
|
||||
use App\Models\BankTransactionRule;
|
||||
|
||||
/**
|
||||
* Class CompanyTransformer.
|
||||
|
|
@ -107,6 +108,7 @@ class CompanyTransformer extends EntityTransformer
|
|||
'bank_transaction_rules',
|
||||
'task_schedulers',
|
||||
'schedulers',
|
||||
'locations',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -297,6 +299,14 @@ class CompanyTransformer extends EntityTransformer
|
|||
return $this->includeCollection($company->schedulers, $transformer, Scheduler::class);
|
||||
}
|
||||
|
||||
public function includeLocations(Company $company)
|
||||
{
|
||||
$transformer = new LocationTransformer($this->serializer);
|
||||
|
||||
return $this->includeCollection($company->locations, $transformer, Location::class);
|
||||
}
|
||||
|
||||
|
||||
public function includeBankTransactionRules(Company $company)
|
||||
{
|
||||
$transformer = new BankTransactionRuleTransformer($this->serializer);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Transformers;
|
||||
|
||||
use App\Models\Location;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* class LocationTransformer.
|
||||
*/
|
||||
class LocationTransformer extends EntityTransformer
|
||||
{
|
||||
use MakesHash;
|
||||
use SoftDeletes;
|
||||
|
||||
protected array $defaultIncludes = [
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $availableIncludes = [
|
||||
];
|
||||
|
||||
/**
|
||||
* @param Location $location
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function transform(Location $location)
|
||||
{
|
||||
return [
|
||||
'id' => $location->hashed_id,
|
||||
'user_id' => $this->encodePrimaryKey($location->user_id),
|
||||
'vendor_id' => $this->encodePrimaryKey($location->vendor_id),
|
||||
'client_id' => $this->encodePrimaryKey($location->client_id),
|
||||
'name' => (string) $location->name ?: '',
|
||||
'address1' => $location->address1 ?: '',
|
||||
'address2' => $location->address2 ?: '',
|
||||
'phone' => $location->phone ?: '',
|
||||
'city' => $location->city ?: '',
|
||||
'state' => $location->state ?: '',
|
||||
'postal_code' => $location->postal_code ?: '',
|
||||
'country_id' => (string) $location->country_id ?: '',
|
||||
'custom_value1' => $location->custom_value1 ?: '',
|
||||
'custom_value2' => $location->custom_value2 ?: '',
|
||||
'custom_value3' => $location->custom_value3 ?: '',
|
||||
'custom_value4' => $location->custom_value4 ?: '',
|
||||
'is_deleted' => (bool) $location->is_deleted,
|
||||
'updated_at' => (int) $location->updated_at,
|
||||
'archived_at' => (int) $location->deleted_at,
|
||||
'created_at' => (int) $location->created_at,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -11,9 +11,10 @@
|
|||
|
||||
namespace App\Transformers;
|
||||
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Activity;
|
||||
use App\Models\Document;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Location;
|
||||
use App\Models\VendorContact;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
|
|
@ -34,6 +35,7 @@ class VendorTransformer extends EntityTransformer
|
|||
*/
|
||||
protected array $availableIncludes = [
|
||||
'activities',
|
||||
'locations',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -67,6 +69,18 @@ class VendorTransformer extends EntityTransformer
|
|||
return $this->includeCollection($vendor->documents, $transformer, Document::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Vendor $vendor
|
||||
*
|
||||
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function includeLocations(Vendor $vendor)
|
||||
{
|
||||
$transformer = new LocationTransformer($this->serializer);
|
||||
|
||||
return $this->includeCollection($vendor->locations, $transformer, Location::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Vendor $vendor
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Str;
|
||||
|
||||
class LocationFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->company(),
|
||||
'custom_value1' => $this->faker->dateTime(),
|
||||
'custom_value2' => $this->faker->colorName(),
|
||||
'custom_value3' => $this->faker->word(),
|
||||
'custom_value4' => $this->faker->email(),
|
||||
'address1' => $this->faker->buildingNumber(),
|
||||
'address2' => $this->faker->streetAddress(),
|
||||
'city' => $this->faker->city(),
|
||||
'state' => $this->faker->state(),
|
||||
'postal_code' => $this->faker->postcode(),
|
||||
'country_id' => 4,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('locations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->unsignedInteger('user_id');
|
||||
$table->unsignedInteger('client_id')->nullable()->index();
|
||||
$table->unsignedInteger('vendor_id')->nullable()->index();
|
||||
$table->unsignedInteger('company_id')->index();
|
||||
|
||||
$table->string('name')->nullable();
|
||||
$table->string('address1')->nullable();
|
||||
$table->string('address2')->nullable();
|
||||
$table->string('city')->nullable();
|
||||
$table->string('state')->nullable();
|
||||
$table->string('postal_code')->nullable();
|
||||
$table->boolean('is_deleted')->default(false);
|
||||
$table->unsignedInteger('country_id')->nullable();
|
||||
|
||||
$table->text('custom_value1')->nullable();
|
||||
$table->text('custom_value2')->nullable();
|
||||
$table->text('custom_value3')->nullable();
|
||||
$table->text('custom_value4')->nullable();
|
||||
|
||||
$table->softDeletes('deleted_at', 6);
|
||||
$table->timestamps(6);
|
||||
|
||||
$table->foreign('client_id')->references('id')->on('clients')->onDelete('cascade')->onUpdate('cascade');
|
||||
$table->foreign('vendor_id')->references('id')->on('vendors')->onDelete('cascade')->onUpdate('cascade');
|
||||
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
|
|
@ -10,16 +10,13 @@
|
|||
| is assigned the "api" middleware group. Enjoy building your API!
|
||||
|
|
||||
*/
|
||||
use App\Http\Controllers\EInvoicePeppolController;
|
||||
use App\Http\Controllers\EInvoiceTokenController;
|
||||
use App\Http\Controllers\SubscriptionStepsController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\BaseController;
|
||||
use App\Http\Controllers\BrevoController;
|
||||
use App\Http\Controllers\PingController;
|
||||
use App\Http\Controllers\SmtpController;
|
||||
use App\Http\Controllers\TaskController;
|
||||
use App\Http\Controllers\UserController;
|
||||
use App\Http\Controllers\BrevoController;
|
||||
use App\Http\Controllers\ChartController;
|
||||
use App\Http\Controllers\EmailController;
|
||||
use App\Http\Controllers\QuoteController;
|
||||
|
|
@ -42,8 +39,6 @@ use App\Http\Controllers\ExpenseController;
|
|||
use App\Http\Controllers\InvoiceController;
|
||||
use App\Http\Controllers\LicenseController;
|
||||
use App\Http\Controllers\MailgunController;
|
||||
use App\Http\Controllers\MigrationController;
|
||||
use App\Http\Controllers\OneTimeTokenController;
|
||||
use App\Http\Controllers\PaymentController;
|
||||
use App\Http\Controllers\PreviewController;
|
||||
use App\Http\Controllers\ProductController;
|
||||
|
|
@ -53,30 +48,35 @@ use App\Http\Controllers\WebCronController;
|
|||
use App\Http\Controllers\WebhookController;
|
||||
use App\Http\Controllers\ActivityController;
|
||||
use App\Http\Controllers\DocumentController;
|
||||
use App\Http\Controllers\EInvoiceController;
|
||||
use App\Http\Controllers\LocationController;
|
||||
use App\Http\Controllers\PostMarkController;
|
||||
use App\Http\Controllers\TemplateController;
|
||||
use App\Http\Controllers\MigrationController;
|
||||
use App\Http\Controllers\SchedulerController;
|
||||
use App\Http\Controllers\SubdomainController;
|
||||
use App\Http\Controllers\SystemLogController;
|
||||
use App\Http\Controllers\TwoFactorController;
|
||||
use App\Http\Controllers\Auth\LoginController;
|
||||
use App\Http\Controllers\ImportJsonController;
|
||||
use App\Http\Controllers\ImportQuickbooksController;
|
||||
use App\Http\Controllers\SelfUpdateController;
|
||||
use App\Http\Controllers\TaskStatusController;
|
||||
use App\Http\Controllers\Bank\YodleeController;
|
||||
use App\Http\Controllers\CompanyUserController;
|
||||
use App\Http\Controllers\PaymentTermController;
|
||||
use App\PaymentDrivers\PayPalPPCPPaymentDriver;
|
||||
use App\PaymentDrivers\BlockonomicsPaymentDriver;
|
||||
use App\Http\Controllers\EmailHistoryController;
|
||||
use App\Http\Controllers\GroupSettingController;
|
||||
use App\Http\Controllers\OneTimeTokenController;
|
||||
use App\Http\Controllers\SubscriptionController;
|
||||
use App\Http\Controllers\Bank\NordigenController;
|
||||
use App\Http\Controllers\CompanyLedgerController;
|
||||
use App\Http\Controllers\EInvoiceTokenController;
|
||||
use App\Http\Controllers\PurchaseOrderController;
|
||||
use App\Http\Controllers\TaskSchedulerController;
|
||||
use App\PaymentDrivers\BlockonomicsPaymentDriver;
|
||||
use App\Http\Controllers\CompanyGatewayController;
|
||||
use App\Http\Controllers\EInvoicePeppolController;
|
||||
use App\Http\Controllers\PaymentWebhookController;
|
||||
use App\Http\Controllers\RecurringQuoteController;
|
||||
use App\Http\Controllers\BankIntegrationController;
|
||||
|
|
@ -86,9 +86,11 @@ use App\Http\Controllers\ExpenseCategoryController;
|
|||
use App\Http\Controllers\HostedMigrationController;
|
||||
use App\Http\Controllers\TemplatePreviewController;
|
||||
use App\Http\Controllers\ConnectedAccountController;
|
||||
use App\Http\Controllers\ImportQuickbooksController;
|
||||
use App\Http\Controllers\RecurringExpenseController;
|
||||
use App\Http\Controllers\RecurringInvoiceController;
|
||||
use App\Http\Controllers\ProtectedDownloadController;
|
||||
use App\Http\Controllers\SubscriptionStepsController;
|
||||
use App\Http\Controllers\ClientGatewayTokenController;
|
||||
use App\Http\Controllers\Reports\TaskReportController;
|
||||
use App\Http\Controllers\Auth\ForgotPasswordController;
|
||||
|
|
@ -96,7 +98,6 @@ use App\Http\Controllers\BankTransactionRuleController;
|
|||
use App\Http\Controllers\InAppPurchase\AppleController;
|
||||
use App\Http\Controllers\Reports\QuoteReportController;
|
||||
use App\Http\Controllers\Auth\PasswordTimeoutController;
|
||||
use App\Http\Controllers\EInvoiceController;
|
||||
use App\Http\Controllers\PreviewPurchaseOrderController;
|
||||
use App\Http\Controllers\Reports\ClientReportController;
|
||||
use App\Http\Controllers\Reports\CreditReportController;
|
||||
|
|
@ -277,6 +278,9 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
|
|||
Route::post('invoices/bulk', [InvoiceController::class, 'bulk'])->name('invoices.bulk');
|
||||
Route::post('invoices/update_reminders', [InvoiceController::class, 'update_reminders'])->name('invoices.update_reminders');
|
||||
|
||||
Route::resource('locations', LocationController::class); // name = (locations. index / create / show / update / destroy / edit
|
||||
Route::post('locations/bulk', [LocationController::class, 'bulk'])->name('locations.bulk');
|
||||
|
||||
Route::post('logout', [LogoutController::class, 'index'])->name('logout');
|
||||
|
||||
Route::post('migrate', [MigrationController::class, 'index'])->name('migrate.start');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,175 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Location;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
class LocationApiTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
use MockAccountData;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->makeTestData();
|
||||
Session::start();
|
||||
}
|
||||
|
||||
public function testLocationPost()
|
||||
{
|
||||
$data = [
|
||||
'name' => 'Test Location',
|
||||
'address1' => '123 Test St',
|
||||
'address2' => 'Suite 100',
|
||||
'city' => 'Test City',
|
||||
'state' => 'TS',
|
||||
'postal_code' => '12345',
|
||||
'country_id' => '840', // USA
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/locations', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
$this->assertEquals($data['name'], $arr['data']['name']);
|
||||
$this->assertEquals($data['address1'], $arr['data']['address1']);
|
||||
}
|
||||
|
||||
public function testLocationGet()
|
||||
{
|
||||
$location = Location::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->get('/api/v1/locations/' . $this->encodePrimaryKey($location->id));
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
$this->assertEquals($location->name, $arr['data']['name']);
|
||||
}
|
||||
|
||||
public function testLocationPut()
|
||||
{
|
||||
$location = Location::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
$data = [
|
||||
'name' => 'Updated Location',
|
||||
'address1' => '456 Update St',
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/locations/' . $this->encodePrimaryKey($location->id), $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
$this->assertEquals($data['name'], $arr['data']['name']);
|
||||
$this->assertEquals($data['address1'], $arr['data']['address1']);
|
||||
}
|
||||
|
||||
public function testLocationDelete()
|
||||
{
|
||||
$location = Location::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->deleteJson('/api/v1/locations/' . $this->encodePrimaryKey($location->id));
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testLocationList()
|
||||
{
|
||||
Location::factory()->count(3)->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->get('/api/v1/locations');
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
$this->assertCount(3, $arr['data']);
|
||||
}
|
||||
|
||||
public function testLocationValidation()
|
||||
{
|
||||
$data = [
|
||||
'name' => '', // Required field is empty
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/locations', $data);
|
||||
|
||||
$response->assertStatus(422);
|
||||
}
|
||||
|
||||
public function testBulkActions()
|
||||
{
|
||||
$locations = Location::factory()->count(3)->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
$data = [
|
||||
'action' => 'archive',
|
||||
'ids' => $locations->pluck('hashed_id')->values()->toArray(),
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/locations/bulk', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
foreach ($locations as $location) {
|
||||
$this->assertNotNull($location->fresh()->deleted_at);
|
||||
}
|
||||
}
|
||||
|
||||
public function testLocationRestore()
|
||||
{
|
||||
$location = Location::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'deleted_at' => now(),
|
||||
]);
|
||||
|
||||
$data = [
|
||||
'action' => 'restore',
|
||||
'ids' => [$location->hashed_id],
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/locations/bulk', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$this->assertNull($location->fresh()->deleted_at);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue