Expense and Vendors (#3226)

* add expenses, vendors and vendor_contacts along with factories and test data

* padding out vendors, expenses

* Minor fixes

* Add Expense and Company TransformerS
This commit is contained in:
David Bomba 2020-01-20 12:31:58 +11:00 committed by GitHub
parent 27d06a2ae1
commit 84642bf035
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 2693 additions and 28 deletions

View File

@ -241,7 +241,6 @@ class CreateTestData extends Command
'company_id' => $company->id
]);
factory(\App\Models\ClientContact::class, 1)->create([
'user_id' => $user->id,
'client_id' => $client->id,
@ -262,9 +261,53 @@ class CreateTestData extends Command
for ($x=0; $x<$y; $x++) {
$this->createInvoice($client);
$this->createQuote($client);
$this->createExpense($client);
$this->createVendor($client);
}
}
private function createExpense($client)
{
factory(\App\Models\Expense::class, rand(10, 50))->create([
'user_id' => $client->user->id,
'client_id' => $client->id,
'company_id' => $client->company->id
]);
}
private function createVendor($client)
{
$vendor = factory(\App\Models\Vendor::class)->create([
'user_id' => $client->user->id,
'company_id' => $client->company->id
]);
factory(\App\Models\VendorContact::class, 1)->create([
'user_id' => $client->user->id,
'vendor_id' => $vendor->id,
'company_id' => $client->company->id,
'is_primary' => 1
]);
factory(\App\Models\VendorContact::class, rand(1, 50))->create([
'user_id' => $client->user->id,
'vendor_id' => $vendor->id,
'company_id' => $client->company->id,
'is_primary' => 0
]);
factory(\App\Models\Vendor::class, rand(10, 50))->create([
'user_id' => $client->user->id,
'company_id' => $client->company->id
]);
}
private function createInvoice($client)
{
$faker = \Faker\Factory::create();

View File

@ -11,7 +11,7 @@
namespace App\DataMapper;
use Parsedown;
use League\CommonMark\CommonMarkConverter;
class EmailTemplateDefaults
{
@ -23,7 +23,14 @@ class EmailTemplateDefaults
public static function emailInvoiceTemplate()
{
return Parsedown::instance()->line(self::transformText('invoice_message'));
$converter = new CommonMarkConverter([
'html_input' => 'strip',
'allow_unsafe_links' => false,
]);
return $converter->convertToHtml(self::transformText('invoice_message'));
//return Parsedown::instance()->line(self::transformText('invoice_message'));
}
public static function emailQuoteSubject()
@ -35,7 +42,13 @@ class EmailTemplateDefaults
public static function emailQuoteTemplate()
{
return Parsedown::instance()->line(self::transformText('quote_message'));
$converter = new CommonMarkConverter([
'html_input' => 'strip',
'allow_unsafe_links' => false,
]);
return $converter->convertToHtml(self::transformText('quote_message'));
//return Parsedown::instance()->line(self::transformText('quote_message'));
}
public static function emailPaymentSubject()
@ -56,7 +69,7 @@ class EmailTemplateDefaults
public static function emailReminder1Template()
{
return Parsedown::instance()->line('First Email Reminder Text');
// return Parsedown::instance()->line('First Email Reminder Text');
}
public static function emailReminder2Subject()
@ -67,7 +80,7 @@ class EmailTemplateDefaults
public static function emailReminder2Template()
{
return Parsedown::instance()->line('Second Email Reminder Text');
// return Parsedown::instance()->line('Second Email Reminder Text');
}
public static function emailReminder3Subject()
@ -78,7 +91,7 @@ class EmailTemplateDefaults
public static function emailReminder3Template()
{
return Parsedown::instance()->line('Third Email Reminder Text');
// return Parsedown::instance()->line('Third Email Reminder Text');
}
public static function emailReminderEndlessSubject()

View File

@ -0,0 +1,39 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Factory;
use App\DataMapper\ClientSettings;
use App\DataMapper\CompanySettings;
use App\Models\Expense;
use Illuminate\Support\Facades\Log;
class ExpenseFactory
{
public static function create(int $company_id, int $user_id) :Expense
{
$expense = new Expense();
$expense->user_id = $user_id;
$expense->company_id = $company_id;
$expense->is_deleted = false;
$expense->should_be_invoiced = false;
$expense->tax_name1 = '';
$expense->tax_rate1 = 0;
$expense->tax_name2 = '';
$expense->tax_rate2 = 0;
$expense->tax_name3 = '';
$expense->tax_rate3 = 0;
$expense->expense_date = null;
$expense->payment_date = null;
return $expense;
}
}

View File

@ -40,6 +40,8 @@ class InvoiceFactory
$invoice->tax_rate1 = 0;
$invoice->tax_name2 = '';
$invoice->tax_rate2 = 0;
$invoice->tax_name3 = '';
$invoice->tax_rate3 = 0;
$invoice->custom_value1 = 0;
$invoice->custom_value2 = 0;
$invoice->custom_value3 = 0;

View File

@ -0,0 +1,28 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Factory;
use App\Models\VendorContact;
class VendorContactFactory
{
public static function create(int $company_id, int $user_id) :VendorContact
{
$vendor_contact = new VendorContact;
$vendor_contact->first_name = "";
$vendor_contact->user_id = $user_id;
$vendor_contact->company_id = $company_id;
$vendor_contact->id = 0;
return $vendor_contact;
}
}

View File

@ -0,0 +1,37 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Factory;
use App\Models\Vendor;
use Illuminate\Support\Str;
class VendorFactory
{
public static function create(int $company_id, int $user_id) :Vendor
{
$vendor = new Vendor;
$vendor->company_id = $company_id;
$vendor->user_id = $user_id;
$vendor->name = '';
$vendor->website = '';
$vendor->private_notes = '';
$vendor->balance = 0;
$vendor->paid_to_date = 0;
$vendor->country_id = 4;
$vendor->is_deleted = 0;
$vendor_contact = VendorContactFactory::create($company_id, $user_id);
$vendor->contacts->add($vendor_contact);
return $vendor;
}
}

View File

@ -0,0 +1,196 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Filters;
use App\Models\Vendor;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
/**
* VendorFilters
*/
class VendorFilters extends QueryFilters
{
/**
* Filter by balance
*
* @param string $balance
* @return Illuminate\Database\Query\Builder
*/
public function balance(string $balance): Builder
{
$parts = $this->split($balance);
return $this->builder->where('balance', $parts->operator, $parts->value);
}
/**
* Filter between balances
*
* @param string balance
* @return Illuminate\Database\Query\Builder
*/
public function between_balance(string $balance): Builder
{
$parts = explode(":", $balance);
return $this->builder->whereBetween('balance', [$parts[0], $parts[1]]);
}
/**
* Filter based on search text
*
* @param string query filter
* @return Illuminate\Database\Query\Builder
* @deprecated
*
*/
public function filter(string $filter = '') : Builder
{
if (strlen($filter) == 0) {
return $this->builder;
}
return $this->builder->where(function ($query) use ($filter) {
$query->where('vendors.name', 'like', '%'.$filter.'%')
->orWhere('vendors.id_number', 'like', '%'.$filter.'%')
->orWhere('vendor_contacts.first_name', 'like', '%'.$filter.'%')
->orWhere('vendor_contacts.last_name', 'like', '%'.$filter.'%')
->orWhere('vendor_contacts.email', 'like', '%'.$filter.'%')
->orWhere('vendors.custom_value1', 'like', '%'.$filter.'%')
->orWhere('vendors.custom_value2', 'like', '%'.$filter.'%')
->orWhere('vendors.custom_value3', 'like', '%'.$filter.'%')
->orWhere('vendors.custom_value4', 'like', '%'.$filter.'%');
});
}
/**
* Filters the list based on the status
* archived, active, deleted
*
* @param string filter
* @return Illuminate\Database\Query\Builder
*/
public function status(string $filter = '') : Builder
{
if (strlen($filter) == 0) {
return $this->builder;
}
$table = 'vendors';
$filters = explode(',', $filter);
return $this->builder->where(function ($query) use ($filters, $table) {
$query->whereNull($table . '.id');
if (in_array(parent::STATUS_ACTIVE, $filters)) {
$query->orWhereNull($table . '.deleted_at');
}
if (in_array(parent::STATUS_ARCHIVED, $filters)) {
$query->orWhere(function ($query) use ($table) {
$query->whereNotNull($table . '.deleted_at');
if (! in_array($table, ['users'])) {
$query->where($table . '.is_deleted', '=', 0);
}
});
}
if (in_array(parent::STATUS_DELETED, $filters)) {
$query->orWhere($table . '.is_deleted', '=', 1);
}
});
}
/**
* Sorts the list based on $sort
*
* @param string sort formatted as column|asc
* @return Illuminate\Database\Query\Builder
*/
public function sort(string $sort) : Builder
{
$sort_col = explode("|", $sort);
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
}
/**
* Returns the base query
*
* @param int company_id
* @return Illuminate\Database\Query\Builder
* @deprecated
*/
public function baseQuery(int $company_id, User $user) : Builder
{
$query = DB::table('vendors')
->join('companies', 'companies.id', '=', 'vendors.company_id')
->join('vendor_contacts', 'vendor_contacts.vendor_id', '=', 'vendors.id')
->where('vendors.company_id', '=', $company_id)
->where('vendor_contacts.is_primary', '=', true)
->where('vendor_contacts.deleted_at', '=', null)
//->whereRaw('(vendors.name != "" or contacts.first_name != "" or contacts.last_name != "" or contacts.email != "")') // filter out buy now invoices
->select(
// DB::raw('COALESCE(vendors.currency_id, companies.currency_id) currency_id'),
DB::raw('COALESCE(vendors.country_id, companies.country_id) country_id'),
DB::raw("CONCAT(COALESCE(vendor_contacts.first_name, ''), ' ', COALESCE(vendor_contacts.last_name, '')) contact"),
'vendors.id',
'vendors.name',
'vendors.private_notes',
'vendor_contacts.first_name',
'vendor_contacts.last_name',
'vendors.custom_value1',
'vendors.custom_value2',
'vendors.custom_value3',
'vendors.custom_value4',
'vendors.balance',
'vendors.last_login',
'vendors.created_at',
'vendors.created_at as vendor_created_at',
'vendor_contacts.phone',
'vendor_contacts.email',
'vendors.deleted_at',
'vendors.is_deleted',
'vendors.user_id',
'vendors.id_number',
'vendors.settings'
);
/**
* If the user does not have permissions to view all invoices
* limit the user to only the invoices they have created
*/
if (Gate::denies('view-list', Vendor::class)) {
$query->where('vendors.user_id', '=', $user->id);
}
return $query;
}
/**
* Filters the query by the users company ID
*
* @param $company_id The company Id
* @return Illuminate\Database\Query\Builder
*/
public function entityFilter()
{
//return $this->builder->whereCompanyId(auth()->user()->company()->id);
return $this->builder->company();
}
}

View File

@ -86,6 +86,8 @@ class BaseController extends Controller
//'company.payments',
'company.payments.paymentables',
'company.quotes',
'company.vendors',
'company.expenses',
];
} else {
$include = [

View File

@ -0,0 +1,510 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers;
use App\Filters\VendorFilters;
use App\Jobs\Entity\ActionEntity;
use App\Jobs\Util\ProcessBulk;
use App\Jobs\Util\UploadAvatar;
use App\Models\Country;
use App\Models\Currency;
use App\Models\Size;
use App\Models\Vendor;
use App\Repositories\BaseRepository;
use App\Transformers\VendorTransformer;
use App\Utils\Traits\BulkOptions;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\Uploadable;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
/**
* Class VendorController
* @package App\Http\Controllers
* @covers App\Http\Controllers\VendorController
*/
class VendorController extends BaseController
{
use MakesHash;
use Uploadable;
use BulkOptions;
protected $entity_type = Vendor::class;
protected $entity_transformer = VendorTransformer::class;
/**
* @var Vendorepository
*/
protected $vendor_repo;
/**
* VendorController constructor.
* @param VendorRepository $vendorRepo
*/
public function __construct(VendorRepository $vendor_repo)
{
parent::__construct();
$this->vendor_repo = $vendor_repo;
}
/**
* @OA\Get(
* path="/api/v1/vendors",
* operationId="getVendors",
* tags={"vendors"},
* summary="Gets a list of vendors",
* description="Lists vendors, search and filters allow fine grained lists to be generated.
Query parameters can be added to performed more fine grained filtering of the vendors, these are handled by the VendorFilters class which defines the methods available",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(ref="#/components/parameters/index"),
* @OA\Response(
* response=200,
* description="A list of vendors",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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/Vendor"),
* ),
* @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 index(VendorFilters $filters)
{
$vendors = Vendor::filter($filters);
return $this->listResponse($vendors);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*
*
* @OA\Get(
* path="/api/v1/vendors/{id}",
* operationId="showVendor",
* tags={"vendors"},
* summary="Shows a client",
* description="Displays a client by id",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Vendor Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the vendor object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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/Vendor"),
* ),
* @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(ShowVendorRequest $request, Vendor $vendor)
{
return $this->itemResponse($vendor);
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*
*
* @OA\Get(
* path="/api/v1/vendors/{id}/edit",
* operationId="editVendor",
* tags={"vendors"},
* summary="Shows a client for editting",
* description="Displays a client by id",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Vendor Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the client object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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/Vendor"),
* ),
* @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(EditVendorRequest $request, Vendor $vendor)
{
return $this->itemResponse($vendor);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param App\Models\Vendor $vendor
* @return \Illuminate\Http\Response
*
*
*
* @OA\Put(
* path="/api/v1/vendors/{id}",
* operationId="updateVendor",
* tags={"vendors"},
* summary="Updates a client",
* description="Handles the updating of a client by id",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Vendor Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the client object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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/Vendor"),
* ),
* @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(UpdateVendorRequest $request, Vendor $vendor)
{
if($request->entityIsDeleted($vendor))
return $request->disallowUpdate();
$vendor = $this->client_repo->save($request->all(), $vendor);
$this->uploadLogo($request->file('company_logo'), $vendor->company, $vendor);
return $this->itemResponse($vendor->fresh());
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*
*
*
* @OA\Get(
* path="/api/v1/vendors/create",
* operationId="getVendorsCreate",
* tags={"vendors"},
* summary="Gets a new blank client object",
* description="Returns a blank object with default values",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @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="A blank client object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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/Vendor"),
* ),
* @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(CreateVendorRequest $request)
{
$vendor = VendorFactory::create(auth()->user()->company()->id, auth()->user()->id);
return $this->itemResponse($vendor);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*
*
*
* @OA\Post(
* path="/api/v1/vendors",
* operationId="storeVendor",
* tags={"vendors"},
* summary="Adds a client",
* description="Adds an client to a company",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @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 client object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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/Vendor"),
* ),
* @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(StoreVendorRequest $request)
{
$vendor = $this->client_repo->save($request->all(), VendorFactory::create(auth()->user()->company()->id, auth()->user()->id));
$vendor->load('contacts', 'primary_contact');
$this->uploadLogo($request->file('company_logo'), $vendor->company, $vendor);
return $this->itemResponse($vendor);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*
*
* @OA\Delete(
* path="/api/v1/vendors/{id}",
* operationId="deleteVendor",
* tags={"vendors"},
* summary="Deletes a client",
* description="Handles the deletion of a client by id",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Vendor 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-API-Version", ref="#/components/headers/X-API-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(DestroyVendorRequest $request, Vendor $vendor)
{
//may not need these destroy routes as we are using actions to 'archive/delete'
$vendor->delete();
return response()->json([], 200);
}
/**
* Perform bulk actions on the list view
*
* @param BulkVendorRequest $request
* @return \Illuminate\Http\Response
*
*
* @OA\Post(
* path="/api/v1/vendors/bulk",
* operationId="bulkVendors",
* tags={"vendors"},
* summary="Performs bulk actions on an array of vendors",
* description="",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @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="User credentials",
* 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 Vendor User response",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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/Vendor"),
* ),
* @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()
{
$action = request()->input('action');
$ids = request()->input('ids');
$vendors = Vendor::withTrashed()->find($this->transformKeys($ids));
$vendors->each(function ($vendor, $key) use ($action) {
if (auth()->user()->can('edit', $vendor)) {
$this->client_repo->{$action}($vendor);
}
});
return $this->listResponse(Vendor::withTrashed()->whereIn('id', $this->transformKeys($ids)));
}
/**
* Returns a client statement
*
* @return [type] [description]
*/
public function statement()
{
//todo
}
}

View File

@ -71,7 +71,7 @@ class SendingController extends Controller
$send_logs = false;
if($request->has('send_logs'));
if($request->has('send_logs'))
$send_logs = $request->input('send_logs');
Mail::to(config('ninja.contact.ninja_official_contact'))

View File

@ -11,7 +11,7 @@
namespace App\Http\Controllers;
use Parsedown;
use League\CommonMark\CommonMarkConverter;
class TemplateController extends BaseController
{
@ -106,9 +106,14 @@ class TemplateController extends BaseController
$subject = request()->input('subject');
$body = request()->input('body');
$converter = new CommonMarkConverter([
'html_input' => 'strip',
'allow_unsafe_links' => false,
]);
$data = [
'subject' => request()->input('subject'),
'body' => Parsedown::instance()->text(request()->input('body')),
'body' => $converter->convertToHtml(request()->input('body')),
];
return response()->json($data, 200);

View File

@ -0,0 +1,509 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers;
use App\Filters\VendorFilters;
use App\Jobs\Entity\ActionEntity;
use App\Jobs\Util\ProcessBulk;
use App\Jobs\Util\UploadAvatar;
use App\Models\Country;
use App\Models\Currency;
use App\Models\Size;
use App\Models\Vendor;
use App\Repositories\BaseRepository;
use App\Utils\Traits\BulkOptions;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\Uploadable;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
/**
* Class VendorController
* @package App\Http\Controllers
* @covers App\Http\Controllers\VendorController
*/
class VendorController extends BaseController
{
use MakesHash;
use Uploadable;
use BulkOptions;
protected $entity_type = Vendor::class;
protected $entity_transformer = VendorTransformer::class;
/**
* @var Vendorepository
*/
protected $vendor_repo;
/**
* VendorController constructor.
* @param VendorRepository $vendorRepo
*/
public function __construct(VendorRepository $vendor_repo)
{
parent::__construct();
$this->vendor_repo = $vendor_repo;
}
/**
* @OA\Get(
* path="/api/v1/vendors",
* operationId="getVendors",
* tags={"vendors"},
* summary="Gets a list of vendors",
* description="Lists vendors, search and filters allow fine grained lists to be generated.
Query parameters can be added to performed more fine grained filtering of the vendors, these are handled by the VendorFilters class which defines the methods available",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(ref="#/components/parameters/index"),
* @OA\Response(
* response=200,
* description="A list of vendors",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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/Vendor"),
* ),
* @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 index(VendorFilters $filters)
{
$vendors = Vendor::filter($filters);
return $this->listResponse($vendors);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*
*
* @OA\Get(
* path="/api/v1/vendors/{id}",
* operationId="showVendor",
* tags={"vendors"},
* summary="Shows a client",
* description="Displays a client by id",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Vendor Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the vendor object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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/Vendor"),
* ),
* @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(ShowVendorRequest $request, Vendor $vendor)
{
return $this->itemResponse($vendor);
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*
*
* @OA\Get(
* path="/api/v1/vendors/{id}/edit",
* operationId="editVendor",
* tags={"vendors"},
* summary="Shows a client for editting",
* description="Displays a client by id",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Vendor Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the client object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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/Vendor"),
* ),
* @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(EditVendorRequest $request, Vendor $vendor)
{
return $this->itemResponse($vendor);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param App\Models\Vendor $vendor
* @return \Illuminate\Http\Response
*
*
*
* @OA\Put(
* path="/api/v1/vendors/{id}",
* operationId="updateVendor",
* tags={"vendors"},
* summary="Updates a client",
* description="Handles the updating of a client by id",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Vendor Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the client object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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/Vendor"),
* ),
* @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(UpdateVendorRequest $request, Vendor $vendor)
{
if($request->entityIsDeleted($vendor))
return $request->disallowUpdate();
$vendor = $this->client_repo->save($request->all(), $vendor);
$this->uploadLogo($request->file('company_logo'), $vendor->company, $vendor);
return $this->itemResponse($vendor->fresh());
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*
*
*
* @OA\Get(
* path="/api/v1/vendors/create",
* operationId="getVendorsCreate",
* tags={"vendors"},
* summary="Gets a new blank client object",
* description="Returns a blank object with default values",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @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="A blank client object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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/Vendor"),
* ),
* @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(CreateVendorRequest $request)
{
$vendor = VendorFactory::create(auth()->user()->company()->id, auth()->user()->id);
return $this->itemResponse($vendor);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*
*
*
* @OA\Post(
* path="/api/v1/vendors",
* operationId="storeVendor",
* tags={"vendors"},
* summary="Adds a client",
* description="Adds an client to a company",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @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 client object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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/Vendor"),
* ),
* @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(StoreVendorRequest $request)
{
$vendor = $this->client_repo->save($request->all(), VendorFactory::create(auth()->user()->company()->id, auth()->user()->id));
$vendor->load('contacts', 'primary_contact');
$this->uploadLogo($request->file('company_logo'), $vendor->company, $vendor);
return $this->itemResponse($vendor);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*
*
* @OA\Delete(
* path="/api/v1/vendors/{id}",
* operationId="deleteVendor",
* tags={"vendors"},
* summary="Deletes a client",
* description="Handles the deletion of a client by id",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Vendor 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-API-Version", ref="#/components/headers/X-API-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(DestroyVendorRequest $request, Vendor $vendor)
{
//may not need these destroy routes as we are using actions to 'archive/delete'
$vendor->delete();
return response()->json([], 200);
}
/**
* Perform bulk actions on the list view
*
* @param BulkVendorRequest $request
* @return \Illuminate\Http\Response
*
*
* @OA\Post(
* path="/api/v1/vendors/bulk",
* operationId="bulkVendors",
* tags={"vendors"},
* summary="Performs bulk actions on an array of vendors",
* description="",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @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="User credentials",
* 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 Vendor User response",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-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/Vendor"),
* ),
* @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()
{
$action = request()->input('action');
$ids = request()->input('ids');
$vendors = Vendor::withTrashed()->find($this->transformKeys($ids));
$vendors->each(function ($vendor, $key) use ($action) {
if (auth()->user()->can('edit', $vendor)) {
$this->client_repo->{$action}($vendor);
}
});
return $this->listResponse(Vendor::withTrashed()->whereIn('id', $this->transformKeys($ids)));
}
/**
* Returns a client statement
*
* @return [type] [description]
*/
public function statement()
{
//todo
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Requests\Vendor;
use App\Utils\Traits\BulkOptions;
use Illuminate\Foundation\Http\FormRequest;
use App\Models\Vendor;
class BulkVendorRequest extends FormRequest
{
use BulkOptions;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
if (!$this->has('action')) {
return false;
}
if (!in_array($this->action, $this->getBulkOptions(), true)) {
return false;
}
return auth()->user()->can(auth()->user()->isAdmin(), Vendor::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = $this->getGlobalRules();
/** We don't require IDs on bulk storing. */
if ($this->action !== self::$STORE_METHOD) {
$rules['ids'] = ['required'];
}
return $rules;
}
}

View File

@ -0,0 +1,29 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Vendor;
use App\Http\Requests\Request;
use App\Models\Vendor;
class CreateVendorRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('create', Vendor::class);
}
}

View File

@ -0,0 +1,29 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Vendor;
use App\Http\Requests\Request;
use App\Models\Vendor;
class DestroyVendorRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->vendor);
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Vendor;
use App\Http\Requests\Request;
use App\Models\Vendor;
class EditVendorRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->vendor);
}
// public function prepareForValidation()
// {
// $input = $this->all();
// //$input['id'] = $this->encodePrimaryKey($input['id']);
// $this->replace($input);
// }
}

View File

@ -0,0 +1,29 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Vendor;
use App\Http\Requests\Request;
use App\Models\Vendor;
class ShowVendorRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('view', $this->vendor);
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Vendor;
use App\DataMapper\VendorSettings;
use App\Http\Requests\Request;
use App\Http\ValidationRules\ValidVendorGroupSettingsRule;
use App\Models\Vendor;
use App\Utils\Traits\MakesHash;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Rule;
class StoreVendorRequest extends Request
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('create', Vendor::class);
}
public function rules()
{
/* Ensure we have a client name, and that all emails are unique*/
//$rules['name'] = 'required|min:1';
$rules['id_number'] = 'unique:vendors,id_number,' . $this->id . ',id,company_id,' . $this->company_id;
//$rules['settings'] = new ValidVendorGroupSettingsRule();
$rules['contacts.*.email'] = 'nullable|distinct';
$contacts = request('contacts');
if (is_array($contacts)) {
for ($i = 0; $i < count($contacts); $i++) {
//$rules['contacts.' . $i . '.email'] = 'nullable|email|distinct';
}
}
return $rules;
}
protected function prepareForValidation()
{
$input = $this->all();
if (!isset($input['settings'])) {
$input['settings'] = VendorSettings::defaults();
}
$this->replace($input);
}
public function messages()
{
return [
'unique' => ctrans('validation.unique', ['attribute' => 'email']),
//'required' => trans('validation.required', ['attribute' => 'email']),
'contacts.*.email.required' => ctrans('validation.email', ['attribute' => 'email']),
];
}
}

View File

@ -0,0 +1,73 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Vendor;
use App\Http\Requests\Request;
use App\Http\ValidationRules\IsDeletedRule;
use App\Http\ValidationRules\ValidVendorGroupSettingsRule;
use App\Utils\Traits\ChecksEntityStatus;
use App\Utils\Traits\MakesHash;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Rule;
class UpdateVendorRequest extends Request
{
use MakesHash;
use ChecksEntityStatus;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->vendor);
}
public function rules()
{
/* Ensure we have a client name, and that all emails are unique*/
$rules['country_id'] = 'integer|nullable';
//$rules['id_number'] = 'unique:clients,id_number,,id,company_id,' . auth()->user()->company()->id;
$rules['id_number'] = 'unique:clients,id_number,' . $this->id . ',id,company_id,' . $this->company_id;
$rules['contacts.*.email'] = 'nullable|distinct';
$contacts = request('contacts');
if (is_array($contacts)) {
// for ($i = 0; $i < count($contacts); $i++) {
// // $rules['contacts.' . $i . '.email'] = 'nullable|email|unique:client_contacts,email,' . isset($contacts[$i]['id'].',company_id,'.$this->company_id);
// //$rules['contacts.' . $i . '.email'] = 'nullable|email';
// }
}
return $rules;
}
public function messages()
{
return [
'unique' => ctrans('validation.unique', ['attribute' => 'email']),
'email' => ctrans('validation.email', ['attribute' => 'email']),
'name.required' => ctrans('validation.required', ['attribute' => 'name']),
'required' => ctrans('validation.required', ['attribute' => 'email']),
];
}
protected function prepareForValidation()
{
$input = $this->all();
$this->replace($input);
}
}

View File

@ -114,6 +114,14 @@ class Company extends BaseModel
return $this->hasMany(Client::class)->withTrashed();
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function vendors()
{
return $this->hasMany(Vendor::class)->withTrashed();
}
public function activities()
{
return $this->hasMany(Activity::class);

View File

@ -13,26 +13,51 @@ namespace App\Models;
use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Expense extends BaseModel
{
use MakesHash;
use SoftDeletes;
protected $guarded = [
'id',
protected $fillable = [
'client_id',
'vendor_id',
'expense_currency_id',
'expense_date',
'invoice_currency_id',
'amount',
'foreign_amount',
'exchange_rate',
'private_notes',
'public_notes',
'bank_id',
'transaction_id',
'expense_category_id',
'tax_rate1',
'tax_name1',
'tax_rate2',
'tax_name2',
'tax_rate3',
'tax_name3',
'payment_date',
'payment_type_id',
'transaction_reference',
'invoice_documents',
'should_be_invoiced',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
];
protected $appends = ['expense_id'];
public function getRouteKeyName()
{
return 'expense_id';
}
protected $casts = [
'is_deleted' => 'boolean',
'updated_at' => 'timestamp',
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',
];
public function getExpenseIdAttribute()
{
return $this->encodePrimaryKey($this->id);
}
public function documents()
{

View File

@ -0,0 +1,34 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Models;
use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class ExpenseCategory extends BaseModel
{
use SoftDeletes;
protected $fillable = [
'name',
];
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function expense()
{
return $this->belongsTo('App\Models\Expense');
}
}

73
app/Models/Vendor.php Normal file
View File

@ -0,0 +1,73 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Models;
use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Vendor extends BaseModel
{
use SoftDeletes;
protected $fillable = [
'name',
'id_number',
'vat_number',
'work_phone',
'address1',
'address2',
'city',
'state',
'postal_code',
'country_id',
'private_notes',
'currency_id',
'website',
'transaction_name',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
];
protected $casts = [
'country_id' => 'string',
'currency_id' => 'string',
'is_deleted' => 'boolean',
'updated_at' => 'timestamp',
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',
];
public function documents()
{
return $this->morphMany(Document::class, 'documentable');
}
public function assigned_user()
{
return $this->belongsTo(User::class, 'assigned_user_id', 'id');
}
public function contacts()
{
return $this->hasMany(VendorContact::class)->orderBy('is_primary', 'desc');
}
public function activities()
{
return $this->hasMany(Activity::class);
}
}

View File

@ -0,0 +1,135 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Models;
use App\Models\Company;
use App\Models\Language;
use App\Models\User;
use App\Notifications\ClientContactResetPassword as ResetPasswordNotification;
use App\Notifications\ClientContactResetPassword;
use App\Utils\Traits\MakesHash;
use Hashids\Hashids;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Contracts\Translation\HasLocalePreference;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Laracasts\Presenter\PresentableTrait;
class VendorContact extends Authenticatable implements HasLocalePreference
{
use Notifiable;
use MakesHash;
use PresentableTrait;
use SoftDeletes;
/* Used to authenticate a vendor */
protected $guard = 'vendor';
/* Allow microtime timestamps */
protected $dateFormat = 'Y-m-d H:i:s.u';
protected $dates = [
'deleted_at'
];
protected $appends = [
'hashed_id'
];
protected $with = [
'vendor',
'company'
];
protected $casts = [
'updated_at' => 'timestamp',
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',
];
protected $fillable = [
'first_name',
'last_name',
'phone',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
'email',
'is_primary',
];
public function getHashedIdAttribute()
{
return $this->encodePrimaryKey($this->id);
}
public function getContactIdAttribute()
{
return $this->encodePrimaryKey($this->id);
}
public function vendor()
{
return $this->belongsTo(Vendor::class)->withTrashed();
}
public function primary_contact()
{
return $this->where('is_primary', true);
}
public function company()
{
return $this->belongsTo(Company::class);
}
public function user()
{
return $this->belongsTo(User::class)->withTrashed();
}
public function sendPasswordResetNotification($token)
{
$this->notify(new ClientContactResetPassword($token));
}
public function preferredLocale()
{
$languages = Cache::get('languages');
return $languages->filter(function ($item) {
return $item->id == $this->client->getSetting('language_id');
})->first()->locale;
//$lang = Language::find($this->client->getSetting('language_id'));
//return $lang->locale;
}
/**
* Retrieve the model for a bound value.
*
* @param mixed $value
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function resolveRouteBinding($value)
{
return $this
->withTrashed()
->where('id', $this->decodePrimaryKey($value))->firstOrFail();
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Policies;
use App\Models\Expense;
use App\Models\User;
/**
* Class ExpensePolicy
* @package App\Policies
*/
class ExpensePolicy 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_expense') || $user->hasPermission('create_all');
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Policies;
use App\Models\Vendor;
use App\Models\User;
/**
* Class VendorPolicy
* @package App\Policies
*/
class VendorPolicy 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_all');
}
}

View File

@ -15,6 +15,7 @@ use App\Models\Activity;
use App\Models\Client;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\Expense;
use App\Models\GroupSetting;
use App\Models\Invoice;
use App\Models\Payment;
@ -24,10 +25,12 @@ use App\Models\RecurringInvoice;
use App\Models\RecurringQuote;
use App\Models\TaxRate;
use App\Models\User;
use App\Models\Vendor;
use App\Policies\ActivityPolicy;
use App\Policies\ClientPolicy;
use App\Policies\CompanyGatewayPolicy;
use App\Policies\CompanyPolicy;
use App\Policies\ExpensePolicy;
use App\Policies\GroupSettingPolicy;
use App\Policies\InvoicePolicy;
use App\Policies\PaymentPolicy;
@ -37,6 +40,7 @@ use App\Policies\RecurringInvoicePolicy;
use App\Policies\RecurringQuotePolicy;
use App\Policies\TaxRatePolicy;
use App\Policies\UserPolicy;
use App\Policies\VendorPolicy;
use Auth;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
@ -62,6 +66,8 @@ class AuthServiceProvider extends ServiceProvider
GroupSetting::class => GroupSettingPolicy::class,
CompanyGateway::class => CompanyGatewayPolicy::class,
TaxRate::class => TaxRatePolicy::class,
Vendor::class => VendorPolicy::class,
Expense::class => ExpensePolicy::class,
];
/**

View File

@ -72,7 +72,6 @@ class InvoiceRepository extends BaseRepository
}
}
if (isset($data['invitations'])) {
$invitations = collect($data['invitations']);
@ -81,7 +80,6 @@ class InvoiceRepository extends BaseRepository
InvoiceInvitation::destroy($invitation);
});
foreach ($data['invitations'] as $invitation) {
$inv = false;
@ -96,6 +94,7 @@ class InvoiceRepository extends BaseRepository
$new_invitation->invoice_id = $invoice->id;
$new_invitation->client_contact_id = $this->decodePrimaryKey($invitation['client_contact_id']);
$new_invitation->save();
}
}
}

View File

@ -0,0 +1,76 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Repositories;
use App\Models\Vendor;
use App\Models\VendorContact;
use Illuminate\Support\Str;
/**
* VendorContactRepository
*/
class VendorContactRepository extends BaseRepository
{
public function save($contacts, Vendor $vendor) : void
{
/* Convert array to collection */
$contacts = collect($contacts);
/* Get array of IDs which have been removed from the contacts array and soft delete each contact */
collect($vendor->contacts->pluck('id'))->diff($contacts->pluck('id'))->each(function ($contact) {
VendorContact::destroy($contact);
});
$this->is_primary = true;
/* Set first record to primary - always */
$contacts = $contacts->sortByDesc('is_primary')->map(function ($contact) {
$contact['is_primary'] = $this->is_primary;
$this->is_primary = false;
return $contact;
});
//loop and update/create contacts
$contacts->each(function ($contact) use ($vendor) {
$update_contact = null;
if (isset($contact['id'])) {
$update_contact = VendorContact::find($this->decodePrimaryKey($contact['id']));
}
if (!$update_contact) {
$update_contact = new VendorContact;
$update_contact->vendor_id = $vendor->id;
$update_contact->company_id = $vendor->company_id;
$update_contact->user_id = $vendor->user_id;
$update_contact->contact_key = Str::random(40);
}
$update_contact->fill($contact);
$update_contact->save();
});
//always made sure we have one blank contact to maintain state
if ($contacts->count() == 0) {
$new_contact = new VendorContact;
$new_contact->vendor_id = $vendor->id;
$new_contact->company_id = $vendor->company_id;
$new_contact->user_id = $vendor->user_id;
$new_contact->contact_key = Str::random(40);
$new_contact->is_primary = true;
$new_contact->save();
}
}
}

View File

@ -0,0 +1,95 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Repositories;
use App\Factory\VendorFactory;
use App\Models\Vendor;
use App\Repositories\VSendorContactRepository;
use App\Utils\Traits\GeneratesCounter;
use Illuminate\Http\Request;
/**
* VendorRepository
*/
class VendorRepository extends BaseRepository
{
use GeneratesCounter;
/**
* @var vendorContactRepository
*/
protected $contact_repo;
/**
* vendorController constructor.
* @param vendorContactRepository $contact_repo
*/
public function __construct(VendorContactRepository $contact_repo)
{
$this->contact_repo = $contact_repo;
}
/**
* Gets the class name.
*
* @return string The class name.
*/
public function getClassName()
{
return Vendor::class;
}
/**
* Saves the vendor and its contacts
*
* @param array $data The data
* @param \App\Models\vendor $vendor The vendor
*
* @return vendor|\App\Models\vendor|null vendor Object
*/
public function save(array $data, Vendor $vendor) : ?Vendor
{
$vendor->fill($data);
$vendor->save();
if ($vendor->id_number == "" || !$vendor->id_number) {
$vendor->id_number = $this->getNextVendorNumber($vendor);
} //todo write tests for this and make sure that custom vendor numbers also works as expected from here
$vendor->save();
if (isset($data['contacts'])) {
$contacts = $this->contact_repo->save($data['contacts'], $vendor);
}
if (empty($data['name'])) {
$data['name'] = $vendor->present()->name();
}
return $vendor;
}
/**
* Store vendors in bulk.
*
* @param array $vendor
* @return vendor|null
*/
public function create($vendor): ?Vendor
{
return $this->save(
$vendor,
VendorFactory::create(auth()->user()->company()->id, auth()->user()->id)
);
}
}

View File

@ -17,6 +17,7 @@ use App\Models\Client;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\CompanyUser;
use App\Models\Expense;
use App\Models\GroupSetting;
use App\Models\Payment;
use App\Models\Product;
@ -53,6 +54,7 @@ class CompanyTransformer extends EntityTransformer
'timezone',
'language',
'expenses',
'vendors',
'payments',
'company_user',
'groups',
@ -137,6 +139,20 @@ class CompanyTransformer extends EntityTransformer
return $this->includeCollection($company->clients, $transformer, Client::class);
}
public function includeExpenses(Company $company)
{
$transformer = new ExpenseTransformer($this->serializer);
return $this->includeCollection($company->expenses, $transformer, Expense::class);
}
public function includeVendors(Company $company)
{
$transformer = new VendorTransformer($this->serializer);
return $this->includeCollection($company->vendors, $transformer, Vendor::class);
}
public function includeGroups(Company $company)
{
$transformer = new GroupSettingTransformer($this->serializer);

View File

@ -0,0 +1,80 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Transformers;
use App\Models\Expense;
use App\Utils\Traits\MakesHash;
/**
* class ExpenseTransformer
*/
class ExpenseTransformer extends EntityTransformer
{
use MakesHash;
protected $defaultIncludes = [
];
/**
* @var array
*/
protected $availableIncludes = [
];
/**
* @param Expense $expense
*
* @return array
*/
public function transform(Expense $expense)
{
return [
'id' => $this->encodePrimaryKey($expense->id),
'user_id' => $this->encodePrimaryKey($expense->user_id),
'assigned_user_id' => $this->encodePrimaryKey($expense->assigned_user_id),
'vendor_id' => $this->encodePrimaryKey($expense->vendor_id),
'invoice_id' => $this->encodePrimaryKey($expense->invoice_id),
'client_id' => $this->encodePrimaryKey($expense->client_id),
'bank_id' => (string)$expense->bank_id ?: '',
'invoice_currency_id' => (string)$expense->invoice_currency_id ?: '',
'expense_currency_id' => (string)$expense->expense_currency_id ?: '',
'invoice_category_id' => (string)$expense->invoice_category_id ?: '',
'payment_type_id' => (string)$expense->payment_type_id ?: '',
'recurring_expense_id' => (string)$expense->recurring_expense_id ?: '',
'is_deleted' => (bool) $expense->is_deleted,
'should_be_invoiced' => (bool) $expense->should_be_invoiced,
'invoice_documents' => (bool) $expense->invoice_documents,
'amount' => (float)$expense->amount ?: 0,
'foreign_amount' => (float)$expense->foreign_amount ?: 0,
'exchange_rate' => (float)$expense->exchange_rate ?: 0,
'tax_name1' => $expense->tax_name1 ? $expense->tax_name1 : '',
'tax_rate1' => (float) $expense->tax_rate1,
'tax_name2' => $expense->tax_name2 ? $expense->tax_name2 : '',
'tax_rate2' => (float) $expense->tax_rate2,
'tax_name3' => $expense->tax_name3 ? $expense->tax_name3 : '',
'tax_rate3' => (float) $expense->tax_rate3,
'private_notes' => (string) $expense->private_notes ?: '',
'public_notes' => (string) $expense->public_notes ?: '',
'transaction_reference' => (string) $expense->transaction_reference ?: '',
'transaction_id' => (string) $expense->transaction_id ?: '',
'expense_date' => $expense->expense_date ?: '',
'payment_date' => $expense->payment_date ?: '',
'custom_value1' => $expense->custom_value1 ?: '',
'custom_value2' => $expense->custom_value2 ?: '',
'custom_value3' => $expense->custom_value3 ?: '',
'custom_value4' => $expense->custom_value4 ?: '',
'updated_at' => $expense->updated_at,
'archived_at' => $expense->deleted_at,
];
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Transformers;
use App\Models\VendorContact;
use App\Utils\Traits\MakesHash;
/**
* Class VendorContactTransformer.
*
*/
class VendorContactTransformer extends EntityTransformer
{
use MakesHash;
/**
* @param ClientContact $vendor
*
* @return array
*
*/
public function transform(VendorContact $vendor)
{
return [
'id' => $this->encodePrimaryKey($vendor->id),
'first_name' => $vendor->first_name ?: '',
'last_name' => $vendor->last_name ?: '',
'email' => $vendor->email ?: '',
'updated_at' => $vendor->updated_at,
'archived_at' => $vendor->deleted_at,
'is_primary' => (bool) $vendor->is_primary,
'phone' => $vendor->phone ?: '',
'custom_value1' => $vendor->custom_value1 ?: '',
'custom_value2' => $vendor->custom_value2 ?: '',
'custom_value3' => $vendor->custom_value3 ?: '',
'custom_value4' => $vendor->custom_value4 ?: '',
];
}
}

View File

@ -0,0 +1,98 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Transformers;
use App\Models\Activity;
use App\Models\Vendor;
use App\Models\VendorContact;
use App\Models\VendorGatewayToken;
use App\Transformers\ActivityTransformer;
use App\Transformers\VendorGatewayTokenTransformer;
use App\Utils\Traits\MakesHash;
/**
* class VendorTransformer
*/
class VendorTransformer extends EntityTransformer
{
use MakesHash;
protected $defaultIncludes = [
'contacts',
];
/**
* @var array
*/
protected $availableIncludes = [
'activities',
];
/**
* @param Vendor $vendor
*
* @return \League\Fractal\Resource\Collection
*/
public function includeActivities(Vendor $vendor)
{
$transformer = new ActivityTransformer($this->serializer);
return $this->includeCollection($vendor->activities, $transformer, Activity::class);
}
/**
* @param Vendor $vendor
*
* @return \League\Fractal\Resource\Collection
*/
public function includeContacts(Vendor $vendor)
{
$transformer = new VendorContactTransformer($this->serializer);
return $this->includeCollection($vendor->contacts, $transformer, VendorContact::class);
}
/**
* @param Vendor $vendor
*
* @return array
*/
public function transform(Vendor $vendor)
{
return [
'id' => $this->encodePrimaryKey($vendor->id),
'user_id' => $this->encodePrimaryKey($vendor->user_id),
'assigned_user_id' => $this->encodePrimaryKey($vendor->assigned_user_id),
'name' => $vendor->name ?: '',
'website' => $vendor->website ?: '',
'private_notes' => $vendor->private_notes ?: '',
'last_login' => (int)$vendor->last_login,
'address1' => $vendor->address1 ?: '',
'address2' => $vendor->address2 ?: '',
'phone' => $vendor->phone ?: '',
'city' => $vendor->city ?: '',
'state' => $vendor->state ?: '',
'postal_code' => $vendor->postal_code ?: '',
'country_id' => (string)$vendor->country_id ?: '',
'custom_value1' => $vendor->custom_value1 ?: '',
'custom_value2' => $vendor->custom_value2 ?: '',
'custom_value3' => $vendor->custom_value3 ?: '',
'custom_value4' => $vendor->custom_value4 ?: '',
'is_deleted' => (bool) $vendor->is_deleted,
'vat_number' => $vendor->vat_number ?: '',
'id_number' => $vendor->id_number ?: '',
'updated_at' => $vendor->updated_at,
'archived_at' => $vendor->deleted_at,
];
}
}

View File

@ -14,6 +14,7 @@ namespace App\Utils\Traits;
use App\Models\ClientContact;
use App\Models\Invoice;
use Illuminate\Support\Carbon;
use League\CommonMark\CommonMarkConverter;
use Parsedown;
/**
@ -84,7 +85,14 @@ trait InvoiceEmailBuilder
//process markdown
if ($is_markdown) {
$data = Parsedown::instance()->line($data);
//$data = Parsedown::instance()->line($data);
$converter = new CommonMarkConverter([
'html_input' => 'strip',
'allow_unsafe_links' => false,
]);
$data = $converter->convertToHtml($data);
}
return $data;

View File

@ -0,0 +1,22 @@
<?php
use App\DataMapper\ClientSettings;
use App\DataMapper\CompanySettings;
use App\Factory\InvoiceItemFactory;
use Faker\Generator as Faker;
$factory->define(App\Models\Expense::class, function (Faker $faker) {
return [
'amount' => $faker->numberBetween(1,10),
'custom_value1' => $faker->text(10),
'custom_value2' => $faker->text(10),
'custom_value3' => $faker->text(10),
'custom_value4' => $faker->text(10),
'exchange_rate' => $faker->randomFloat(2,0,1),
'expense_date' => $faker->date(),
'is_deleted' => false,
'public_notes' => $faker->text(50),
'private_notes' => $faker->text(50),
'transaction_reference' => $faker->text(5),
];
});

View File

@ -0,0 +1,24 @@
<?php
use Faker\Generator as Faker;
/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| This directory should contain each of the model factory definitions for
| your application. Factories provide a convenient way to generate new
| model instances for testing / seeding your application's database.
|
*/
$factory->define(App\Models\VendorContact::class, function (Faker $faker) {
return [
'first_name' => $faker->firstName,
'last_name' => $faker->lastName,
'phone' => $faker->phoneNumber,
'email' => $faker->unique()->safeEmail,
];
});

View File

@ -0,0 +1,23 @@
<?php
use Faker\Generator as Faker;
$factory->define(App\Models\Vendor::class, function (Faker $faker) {
return [
'name' => $faker->name(),
'website' => $faker->url,
'private_notes' => $faker->text(200),
'vat_number' => $faker->text(25),
'id_number' => $faker->text(20),
'custom_value1' => $faker->text(20),
'custom_value2' => $faker->text(20),
'custom_value3' => $faker->text(20),
'custom_value4' => $faker->text(20),
'address1' => $faker->buildingNumber,
'address2' => $faker->streetAddress,
'city' => $faker->city,
'state' => $faker->state,
'postal_code' => $faker->postcode,
'country_id' => 4,
];
});

View File

@ -1222,6 +1222,122 @@ class CreateUsersTable extends Migration
$table->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
});
Schema::create('vendors', function (Blueprint $table) {
$table->increments('id');
$table->timestamps(6);
$table->softDeletes();
$table->unsignedInteger('user_id');
$table->unsignedInteger('assigned_user_id');
$table->unsignedInteger('company_id');
$table->unsignedInteger('currency_id')->nullable();
$table->string('name')->nullable();
$table->string('address1');
$table->string('address2');
$table->string('city');
$table->string('state');
$table->string('postal_code');
$table->unsignedInteger('country_id')->nullable();
$table->string('work_phone');
$table->text('private_notes');
$table->string('website');
$table->tinyInteger('is_deleted')->default(0);
$table->string('vat_number')->nullable();
$table->string('transaction_name')->nullable();
$table->string('id_number')->nullable();
$table->string('custom_value1')->nullable();
$table->string('custom_value2')->nullable();
$table->string('custom_value3')->nullable();
$table->string('custom_value4')->nullable();
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('country_id')->references('id')->on('countries');
$table->foreign('currency_id')->references('id')->on('currencies');
});
Schema::create('vendor_contacts', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('company_id');
$table->unsignedInteger('user_id');
$table->unsignedInteger('vendor_id')->index();
$table->timestamps(6);
$table->softDeletes();
$table->boolean('is_primary')->default(0);
$table->string('first_name')->nullable();
$table->string('last_name')->nullable();
$table->string('email')->nullable();
$table->string('phone')->nullable();
$table->string('custom_value1')->nullable();
$table->string('custom_value2')->nullable();
$table->string('custom_value3')->nullable();
$table->string('custom_value4')->nullable();
$table->foreign('vendor_id')->references('id')->on('vendors')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
});
Schema::create('expense_categories', function ($table) {
$table->increments('id');
$table->unsignedInteger('user_id');
$table->unsignedInteger('company_id')->index();
$table->timestamps(6);
$table->softDeletes();
$table->string('name')->nullable();
});
Schema::create('expenses', function (Blueprint $table) {
$table->increments('id');
$table->timestamps(6);
$table->softDeletes();
$table->unsignedInteger('company_id')->index();
$table->unsignedInteger('vendor_id')->nullable();
$table->unsignedInteger('user_id');
$table->unsignedInteger('assigned_user_id');
$table->unsignedInteger('invoice_id')->nullable();
$table->unsignedInteger('client_id')->nullable();
$table->unsignedInteger('bank_id')->nullable();
$table->unsignedInteger('invoice_currency_id')->nullable(false);
$table->unsignedInteger('expense_currency_id')->nullable(false);
$table->unsignedInteger('invoice_category_id')->nullable();
$table->unsignedInteger('payment_type_id')->nullable();
$table->unsignedInteger('recurring_expense_id')->nullable();
$table->boolean('is_deleted')->default(false);
$table->decimal('amount', 13, 2);
$table->decimal('foreign_amount', 13, 2);
$table->decimal('exchange_rate', 13, 4);
$table->string('tax_name1')->nullable();
$table->decimal('tax_rate1', 13, 3)->default(0);
$table->string('tax_name2')->nullable();
$table->decimal('tax_rate2', 13, 3)->default(0);
$table->string('tax_name3')->nullable();
$table->decimal('tax_rate3', 13, 3)->default(0);
$table->date('expense_date')->nullable();
$table->date('payment_date')->nullable();
$table->text('private_notes');
$table->text('public_notes');
$table->text('transaction_reference');
$table->boolean('should_be_invoiced')->default(false);
$table->boolean('invoice_documents')->default();
$table->string('transaction_id')->nullable();
$table->string('custom_value1')->nullable();
$table->string('custom_value2')->nullable();
$table->string('custom_value3')->nullable();
$table->string('custom_value4')->nullable();
// Relations
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});
}
/**

View File

@ -62,6 +62,14 @@ Route::group(['middleware' => ['api_db','api_secret_check','token_auth','locale'
Route::post('recurring_quotes/bulk', 'RecurringQuoteController@bulk')->name('recurring_quotes.bulk');
Route::resource('expenses', 'ExpenseController'); // name = (expenses. index / create / show / update / destroy / edit
Route::post('expenses/bulk', 'ExpenseController@bulk')->name('expenses.bulk');
Route::resource('vendors', 'VendorController'); // name = (vendors. index / create / show / update / destroy / edit
Route::post('vendors/bulk', 'VendorController@bulk')->name('vendors.bulk');
Route::resource('client_statement', 'ClientStatementController@statement'); // name = (client_statement. index / create / show / update / destroy / edit
Route::resource('payments', 'PaymentController'); // name = (payments. index / create / show / update / destroy / edit
@ -103,9 +111,7 @@ Route::group(['middleware' => ['api_db','api_secret_check','token_auth','locale'
Route::post('credits/bulk', 'CreditController@bulk')->name('credits.bulk');
Route::resource('expenses', 'ExpenseController'); // name = (expenses. index / create / show / update / destroy / edit
Route::post('expenses/bulk', 'ExpenseController@bulk')->name('expenses.bulk');
Route::get('settings', 'SettingsController@index')->name('user.settings');

View File

@ -14,7 +14,6 @@ use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Parsedown;
use Tests\MockAccountData;
use Tests\TestCase;