Vendor management

This commit is contained in:
steenrabol 2016-01-06 15:23:58 +01:00
parent d3ee8ed813
commit dba3a39559
44 changed files with 2035 additions and 38 deletions

View File

@ -0,0 +1,21 @@
<?php namespace App\Events;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
class VendorWasArchived extends Event
{
use SerializesModels;
public $vendor;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($vendor)
{
$this->vendor = $vendor;
}
}

View File

@ -0,0 +1,21 @@
<?php namespace App\Events;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
class VendorWasCreated extends Event
{
use SerializesModels;
public $vendor;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($vendor)
{
$this->vendor = $vendor;
}
}

View File

@ -0,0 +1,21 @@
<?php namespace App\Events;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
class VendorWasDeleted extends Event
{
use SerializesModels;
public $vendor;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($vendor)
{
$this->vendor = $vendor;
}
}

View File

@ -0,0 +1,21 @@
<?php namespace App\Events;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
class VendorWasRestored extends Event
{
use SerializesModels;
public $vendor;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($vendor)
{
$this->vendor = $vendor;
}
}

View File

@ -0,0 +1,21 @@
<?php namespace App\Events;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
class VendorWasUpdated extends Event
{
use SerializesModels;
public $vendor;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($vendor)
{
$this->vendor = $vendor;
}
}

View File

@ -121,12 +121,15 @@ class BaseAPIController extends Controller
} elseif ($include == 'clients') {
$data[] = 'clients.contacts';
$data[] = 'clients.user';
} elseif ($include) {
} elseif ($include == 'vendors') {
$data[] = 'vendors.vendorcontacts';
$data[] = 'vendors.user';
}
elseif ($include) {
$data[] = $include;
}
}
return $data;
}
}

View File

@ -13,6 +13,8 @@ use App\Models\Credit;
use App\Models\Task;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Vendor;
use App\Models\VendorContact;
class ExportController extends BaseController
{
@ -155,6 +157,25 @@ class ExportController extends BaseController
->get();
}
if ($request->input(ENTITY_VENDOR)) {
$data['clients'] = Vendor::scope()
->with('user', 'vendorcontacts', 'country')
->withTrashed()
->where('is_deleted', '=', false)
->get();
$data['vendor_contacts'] = VendorContact::scope()
->with('user', 'vendor.contacts')
->withTrashed()
->get();
/*
$data['expenses'] = Credit::scope()
->with('user', 'client.contacts')
->get();
*/
}
return $data;
}
}

View File

@ -0,0 +1,27 @@
<?php namespace App\Http\Controllers;
use Auth;
use DB;
use Datatable;
use Utils;
use View;
use App\Models\Vendor;
use App\Models\VendorActivity;
use App\Services\VendorActivityService;
class VendorActivityController extends BaseController
{
protected $activityService;
public function __construct(VendorActivityService $activityService)
{
parent::__construct();
$this->activityService = $activityService;
}
public function getDatatable($vendorPublicId)
{
return $this->activityService->getDatatable($vendorPublicId);
}
}

View File

@ -0,0 +1,94 @@
<?php namespace App\Http\Controllers;
use Utils;
use Response;
use Input;
use Auth;
use App\Models\Vendor;
use App\Ninja\Repositories\VendorRepository;
use App\Http\Requests\CreateVendorRequest;
use App\Http\Controllers\BaseAPIController;
use App\Ninja\Transformers\VendorTransformer;
class VendorApiController extends BaseAPIController
{
protected $vendorRepo;
public function __construct(VendorRepository $vendorRepo)
{
parent::__construct();
$this->vendorRepo = $vendorRepo;
}
public function ping()
{
$headers = Utils::getApiHeaders();
return Response::make('', 200, $headers);
}
/**
* @SWG\Get(
* path="/vendors",
* summary="List of vendors",
* tags={"vendor"},
* @SWG\Response(
* response=200,
* description="A list with vendors",
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Vendor"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function index()
{
$vendors = Vendor::scope()
->with($this->getIncluded())
->orderBy('created_at', 'desc')
->paginate();
$transformer = new VendorTransformer(Auth::user()->account, Input::get('serializer'));
$paginator = Vendor::scope()->paginate();
$data = $this->createCollection($vendors, $transformer, ENTITY_VENDOR, $paginator);
return $this->response($data);
}
/**
* @SWG\Post(
* path="/vendors",
* tags={"vendor"},
* summary="Create a vendor",
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/Vendor")
* ),
* @SWG\Response(
* response=200,
* description="New vendor",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Vendor"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function store(CreateVendorRequest $request)
{
$vendor = $this->vendorRepo->save($request->input());
$vendor = Vendor::scope($vendor->public_id)
->with('country', 'vendorcontacts', 'industry', 'size', 'currency')
->first();
$transformer = new VendorTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($vendor, $transformer, ENTITY_VENDOR);
return $this->response($data);
}
}

View File

@ -0,0 +1,211 @@
<?php namespace App\Http\Controllers;
use Auth;
use Datatable;
use Utils;
use View;
use URL;
use Validator;
use Input;
use Session;
use Redirect;
use Cache;
use App\Models\Activity;
use App\Models\Vendor;
use App\Models\Account;
use App\Models\VendorContact;
use App\Models\Size;
use App\Models\PaymentTerm;
use App\Models\Industry;
use App\Models\Currency;
use App\Models\Country;
use App\Ninja\Repositories\VendorRepository;
use App\Services\VendorService;
use App\Http\Requests\CreateVendorRequest;
use App\Http\Requests\UpdateVendorRequest;
class VendorController extends BaseController
{
protected $vendorService;
protected $vendorRepo;
public function __construct(VendorRepository $vendorRepo, VendorService $vendorService)
{
parent::__construct();
$this->vendorRepo = $vendorRepo;
$this->vendorService = $vendorService;
}
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
return View::make('list', array(
'entityType' => 'vendor',
'title' => trans('texts.vendors'),
'sortCol' => '4',
'columns' => Utils::trans([
'checkbox',
'vendor',
'contact',
'email',
'date_created',
//'last_login',
'balance',
''
]),
));
}
public function getDatatable()
{
return $this->vendorService->getDatatable(Input::get('sSearch'));
}
/**
* Store a newly created resource in storage.
*
* @return Response
*/
public function store(CreateVendorRequest $request)
{
$vendor = $this->vendorService->save($request->input());
Session::flash('message', trans('texts.created_vendor'));
return redirect()->to($vendor->getRoute());
}
/**
* Display the specified resource.
*
* @param int $id
* @return Response
*/
public function show($publicId)
{
$vendor = Vendor::withTrashed()->scope($publicId)->with('vendorcontacts', 'size', 'industry')->firstOrFail();
Utils::trackViewed($vendor->getDisplayName(), 'vendor');
$actionLinks = [
['label' => trans('texts.new_expense'), 'url' => '/expenses/create/'.$vendor->public_id]
];
$data = array(
'actionLinks' => $actionLinks,
'showBreadcrumbs' => false,
'vendor' => $vendor,
'credit' => $vendor->getTotalCredit(),
'title' => trans('texts.view_vendor'),
'hasRecurringInvoices' => false,
'hasQuotes' => false,
'hasTasks' => false,
'gatewayLink' => $vendor->getGatewayLink(),
);
return View::make('vendors.show', $data);
}
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function create()
{
if (Vendor::scope()->count() > Auth::user()->getMaxNumVendors()) {
return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumVendors()." vendors"]);
}
$data = [
'vendor' => null,
'method' => 'POST',
'url' => 'vendors',
'title' => trans('texts.new_vendor'),
];
$data = array_merge($data, self::getViewModel());
return View::make('vendors.edit', $data);
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return Response
*/
public function edit($publicId)
{
$vendor = Vendor::scope($publicId)->with('vendorcontacts')->firstOrFail();
$data = [
'vendor' => $vendor,
'method' => 'PUT',
'url' => 'vendors/'.$publicId,
'title' => trans('texts.edit_vendor'),
];
$data = array_merge($data, self::getViewModel());
if (Auth::user()->account->isNinjaAccount()) {
if ($account = Account::whereId($vendor->public_id)->first()) {
$data['proPlanPaid'] = $account['pro_plan_paid'];
}
}
return View::make('vendors.edit', $data);
}
private static function getViewModel()
{
return [
'data' => Input::old('data'),
'account' => Auth::user()->account,
'sizes' => Cache::get('sizes'),
'paymentTerms' => Cache::get('paymentTerms'),
'industries' => Cache::get('industries'),
'currencies' => Cache::get('currencies'),
'languages' => Cache::get('languages'),
'countries' => Cache::get('countries'),
'customLabel1' => Auth::user()->account->custom_vendor_label1,
'customLabel2' => Auth::user()->account->custom_vendor_label2,
];
}
/**
* Update the specified resource in storage.
*
* @param int $id
* @return Response
*/
public function update(UpdateVendorRequest $request)
{
$vendor = $this->vendorService->save($request->input());
Session::flash('message', trans('texts.updated_vendor'));
return redirect()->to($vendor->getRoute());
}
public function bulk()
{
$action = Input::get('action');
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
$count = $this->vendorService->bulk($ids, $action);
$message = Utils::pluralize($action.'d_vendor', $count);
Session::flash('message', $message);
if ($action == 'restore' && $count == 1) {
return Redirect::to('vendors/' . Utils::getFirst($ids));
} else {
return Redirect::to('vendors');
}
}
}

View File

@ -0,0 +1,44 @@
<?php namespace app\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
class CreateVendorRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'vendorcontacts' => 'valid_contacts',
];
}
public function validator($factory)
{
// support submiting the form with a single contact record
$input = $this->input();
if (isset($input['vendor_contact'])) {
$input['vendor_contacts'] = [$input['vendor_contact']];
unset($input['vendor_contact']);
$this->replace($input);
}
return $factory->make(
$this->input(), $this->container->call([$this, 'rules']), $this->messages()
);
}
}

View File

@ -181,6 +181,14 @@ Route::group(['middleware' => 'auth'], function() {
get('/resend_confirmation', 'AccountController@resendConfirmation');
post('/update_setup', 'AppController@updateSetup');
// vendor
Route::resource('vendors', 'VendorController');
Route::get('api/vendor', array('as'=>'api.vendors', 'uses'=>'VendorController@getDatatable'));
Route::get('api/vendoractivities/{vendor_id?}', array('as'=>'api.vendoractivities', 'uses'=>'VendorActivityController@getDatatable'));
Route::post('vendors/bulk', 'VendorController@bulk');
});
// Route groups for API
@ -202,6 +210,8 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
Route::post('hooks', 'IntegrationController@subscribe');
Route::post('email_invoice', 'InvoiceApiController@emailInvoice');
Route::get('user_accounts','AccountApiController@getUserAccounts');
// Vendor
Route::resource('vendors', 'VendorApiController');
});
// Redirects for legacy links
@ -258,10 +268,13 @@ if (!defined('CONTACT_EMAIL')) {
define('ENTITY_TAX_RATE', 'tax_rate');
define('ENTITY_PRODUCT', 'product');
define('ENTITY_ACTIVITY', 'activity');
define('ENTITY_VENDOR','vendor');
define('ENTITY_EXPENSE', 'expense');
define('PERSON_CONTACT', 'contact');
define('PERSON_USER', 'user');
define('PERSON_VENDOR_CONTACT','vendorcontact');
define('BASIC_SETTINGS', 'basic_settings');
define('ADVANCED_SETTINGS', 'advanced_settings');
@ -327,6 +340,12 @@ if (!defined('CONTACT_EMAIL')) {
define('ACTIVITY_TYPE_RESTORE_CREDIT', 28);
define('ACTIVITY_TYPE_APPROVE_QUOTE', 29);
// Vendors
define('ACTIVITY_TYPE_CREATE_VENDOR', 30);
define('ACTIVITY_TYPE_ARCHIVE_VENDOR', 31);
define('ACTIVITY_TYPE_DELETE_VENDOR', 32);
define('ACTIVITY_TYPE_RESTORE_VENDOR', 33);
define('DEFAULT_INVOICE_NUMBER', '0001');
define('RECENTLY_VIEWED_LIMIT', 8);
define('LOGGED_ERROR_LIMIT', 100);
@ -356,6 +375,11 @@ if (!defined('CONTACT_EMAIL')) {
define('LEGACY_CUTOFF', 57800);
define('ERROR_DELAY', 3);
define('MAX_NUM_VENDORS', 100);
define('MAX_NUM_VENDORS_PRO', 20000);
define('MAX_NUM_VENDORS_LEGACY', 500);
define('INVOICE_STATUS_DRAFT', 1);
define('INVOICE_STATUS_SENT', 2);
define('INVOICE_STATUS_VIEWED', 3);
@ -426,6 +450,7 @@ if (!defined('CONTACT_EMAIL')) {
define('EVENT_CREATE_INVOICE', 2);
define('EVENT_CREATE_QUOTE', 3);
define('EVENT_CREATE_PAYMENT', 4);
define('EVENT_CREATE_VENDOR',5);
define('REQUESTED_PRO_PLAN', 'REQUESTED_PRO_PLAN');
define('DEMO_ACCOUNT_ID', 'DEMO_ACCOUNT_ID');

View File

@ -574,6 +574,11 @@ class Utils
}
}
public static function getVendorDisplayName($model)
{
return $model->getDisplayName();
}
public static function getPersonDisplayName($firstName, $lastName, $email)
{
if ($firstName || $lastName) {
@ -605,7 +610,9 @@ class Utils
return EVENT_CREATE_QUOTE;
} elseif ($eventName == 'create_payment') {
return EVENT_CREATE_PAYMENT;
} else {
} elseif ($eventName == 'create_vendor') {
return EVENT_CREATE_VENDOR;
}else {
return false;
}
}

View File

@ -9,6 +9,8 @@ use App\Events\InvoiceWasCreated;
use App\Events\CreditWasCreated;
use App\Events\PaymentWasCreated;
use App\Events\VendorWasCreated;
class SubscriptionListener
{
public function createdClient(ClientWasCreated $event)
@ -44,4 +46,9 @@ class SubscriptionListener
Utils::notifyZapier($subscription, $entity);
}
}
public function createdVendor(VendorWasCreated $event)
{
$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_VENDOR, $event->vendor);
}
}

View File

@ -153,6 +153,20 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return MAX_NUM_CLIENTS;
}
public function getMaxNumVendors()
{
if ($this->isPro()) {
return MAX_NUM_VENDORS_PRO;
}
if ($this->id < LEGACY_CUTOFF) {
return MAX_NUM_VENDORS_LEGACY;
}
return MAX_NUM_VENDORS;
}
public function getRememberToken()
{
return $this->remember_token;

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

@ -0,0 +1,302 @@
<?php namespace App\Models;
use Utils;
use DB;
use Carbon;
use App\Events\VendorWasCreated;
use App\Events\VendorWasUpdated;
use Laracasts\Presenter\PresentableTrait;
use Illuminate\Database\Eloquent\SoftDeletes;
class Vendor extends EntityModel {
use PresentableTrait;
use SoftDeletes;
protected $presenter = 'App\Ninja\Presenters\VendorPresenter';
protected $dates = ['deleted_at'];
protected $fillable = [
'name',
'id_number',
'vat_number',
'work_phone',
'custom_value1',
'custom_value2',
'address1',
'address2',
'city',
'state',
'postal_code',
'country_id',
'private_notes',
'size_id',
'industry_id',
'currency_id',
'language_id',
'payment_terms',
'website',
];
public static $fieldName = 'name';
public static $fieldPhone = 'work_phone';
public static $fieldAddress1 = 'address1';
public static $fieldAddress2 = 'address2';
public static $fieldCity = 'city';
public static $fieldState = 'state';
public static $fieldPostalCode = 'postal_code';
public static $fieldNotes = 'notes';
public static $fieldCountry = 'country';
public static function getImportColumns()
{
return [
Vendor::$fieldName,
Vendor::$fieldPhone,
Vendor::$fieldAddress1,
Vendor::$fieldAddress2,
Vendor::$fieldCity,
Vendor::$fieldState,
Vendor::$fieldPostalCode,
Vendor::$fieldCountry,
Vendor::$fieldNotes,
VendorContact::$fieldFirstName,
VendorContact::$fieldLastName,
VendorContact::$fieldPhone,
VendorContact::$fieldEmail,
];
}
public static function getImportMap()
{
return [
'first' => 'first_name',
'last' => 'last_name',
'email' => 'email',
'mobile|phone' => 'phone',
'name|organization' => 'name',
'street2|address2' => 'address2',
'street|address|address1' => 'address1',
'city' => 'city',
'state|province' => 'state',
'zip|postal|code' => 'postal_code',
'country' => 'country',
'note' => 'notes',
];
}
public function account()
{
return $this->belongsTo('App\Models\Account');
}
public function user()
{
return $this->belongsTo('App\Models\User');
}
public function payments()
{
return $this->hasMany('App\Models\Payment');
}
public function vendorContacts()
{
return $this->hasMany('App\Models\VendorContact');
}
public function country()
{
return $this->belongsTo('App\Models\Country');
}
public function currency()
{
return $this->belongsTo('App\Models\Currency');
}
public function language()
{
return $this->belongsTo('App\Models\Language');
}
public function size()
{
return $this->belongsTo('App\Models\Size');
}
public function industry()
{
return $this->belongsTo('App\Models\Industry');
}
public function addVendorContact($data, $isPrimary = false)
{
$publicId = isset($data['public_id']) ? $data['public_id'] : false;
if ($publicId && $publicId != '-1') {
$contact = VendorContact::scope($publicId)->firstOrFail();
} else {
$contact = VendorContact::createNew();
//$contact->send_invoice = true;
}
$contact->fill($data);
$contact->is_primary = $isPrimary;
return $this->vendorContacts()->save($contact);
}
public function updateBalances($balanceAdjustment, $paidToDateAdjustment)
{
if ($balanceAdjustment === 0 && $paidToDateAdjustment === 0) {
return;
}
$this->balance = $this->balance + $balanceAdjustment;
$this->paid_to_date = $this->paid_to_date + $paidToDateAdjustment;
$this->save();
}
public function getRoute()
{
return "/vendors/{$this->public_id}";
}
public function getTotalCredit()
{
return 0;
}
public function getName()
{
return $this->name;
}
public function getDisplayName()
{
if ($this->name) {
return $this->name;
}
if ( ! count($this->contacts)) {
return '';
}
$contact = $this->contacts[0];
return $contact->getDisplayName();
}
public function getCityState()
{
$swap = $this->country && $this->country->swap_postal_code;
return Utils::cityStateZip($this->city, $this->state, $this->postal_code, $swap);
}
public function getEntityType()
{
return 'vendor';
}
public function hasAddress()
{
$fields = [
'address1',
'address2',
'city',
'state',
'postal_code',
'country_id',
];
foreach ($fields as $field) {
if ($this->$field) {
return true;
}
}
return false;
}
public function getDateCreated()
{
if ($this->created_at == '0000-00-00 00:00:00') {
return '---';
} else {
return $this->created_at->format('m/d/y h:i a');
}
}
public function getGatewayToken()
{
$this->account->load('account_gateways');
if (!count($this->account->account_gateways)) {
return false;
}
$accountGateway = $this->account->getGatewayConfig(GATEWAY_STRIPE);
if (!$accountGateway) {
return false;
}
$token = AccountGatewayToken::where('vendor_id', '=', $this->id)->where('account_gateway_id', '=', $accountGateway->id)->first();
return $token ? $token->token : false;
}
public function getGatewayLink()
{
$token = $this->getGatewayToken();
return $token ? "https://dashboard.stripe.com/customers/{$token}" : false;
}
public function getCurrencyId()
{
if ($this->currency_id) {
return $this->currency_id;
}
if (!$this->account) {
$this->load('account');
}
return $this->account->currency_id ?: DEFAULT_CURRENCY;
}
/*
public function getCounter($isQuote)
{
return $isQuote ? $this->quote_number_counter : $this->invoice_number_counter;
}
*/
public function markLoggedIn()
{
//$this->last_login = Carbon::now()->toDateTimeString();
$this->save();
}
}
Vendor::creating(function ($vendor) {
$vendor->setNullValues();
});
Vendor::created(function ($vendor) {
event(new VendorWasCreated($vendor));
});
Vendor::updating(function ($vendor) {
$vendor->setNullValues();
});
Vendor::updated(function ($vendor) {
event(new VendorWasUpdated($vendor));
});

View File

@ -0,0 +1,56 @@
<?php namespace App\Models;
use Auth;
use Eloquent;
use Utils;
use Session;
use Request;
use Carbon;
class VendorActivity extends Eloquent {
public $timestamps = true;
public function scopeScope($query)
{
return $query->whereAccountId(Auth::user()->account_id);
}
public function account()
{
return $this->belongsTo('App\Models\Account');
}
public function user()
{
return $this->belongsTo('App\Models\User')->withTrashed();
}
public function vendorContact()
{
return $this->belongsTo('App\Models\VendorContact')->withTrashed();
}
public function vendor()
{
return $this->belongsTo('App\Models\Vendor')->withTrashed();
}
public function getMessage()
{
$activityTypeId = $this->activity_type_id;
$account = $this->account;
$vendor = $this->vendor;
$user = $this->user;
$contactId = $this->contact_id;
$isSystem = $this->is_system;
$data = [
'vendor' => link_to($vendor->getRoute(), $vendor->getDisplayName()),
'user' => $isSystem ? '<i>' . trans('texts.system') . '</i>' : $user->getDisplayName(),
'vendorcontact' => $contactId ? $vendor->getDisplayName() : $user->getDisplayName(),
];
return trans("texts.activity_{$activityTypeId}", $data);
}
}

View File

@ -0,0 +1,68 @@
<?php namespace App\Models;
use HTML;
use Illuminate\Database\Eloquent\SoftDeletes;
class VendorContact extends EntityModel
{
use SoftDeletes;
protected $dates = ['deleted_at'];
protected $table = 'vendor_contacts';
protected $fillable = [
'first_name',
'last_name',
'email',
'phone',
'send_invoice',
];
public static $fieldFirstName = 'first_name';
public static $fieldLastName = 'last_name';
public static $fieldEmail = 'email';
public static $fieldPhone = 'phone';
public function account()
{
return $this->belongsTo('App\Models\Account');
}
public function user()
{
return $this->belongsTo('App\Models\User');
}
public function vendor()
{
return $this->belongsTo('App\Models\Vendor')->withTrashed();
}
public function getPersonType()
{
return PERSON_VENDOR_CONTACT;
}
public function getName()
{
return $this->getDisplayName();
}
public function getDisplayName()
{
if ($this->getFullName()) {
return $this->getFullName();
} else {
return $this->email;
}
}
public function getFullName()
{
if ($this->first_name || $this->last_name) {
return $this->first_name.' '.$this->last_name;
} else {
return '';
}
}
}

View File

@ -0,0 +1,90 @@
<?php namespace App\Models;
use Utils;
use Carbon;
use Illuminate\Database\Eloquent\SoftDeletes;
class VendorInvitation extends EntityModel
{
use SoftDeletes;
protected $dates = ['deleted_at'];
public function vendorContact()
{
return $this->belongsTo('App\Models\VendorContact')->withTrashed();
}
public function user()
{
return $this->belongsTo('App\Models\User')->withTrashed();
}
public function account()
{
return $this->belongsTo('App\Models\Account');
}
public function getLink($type = 'view')
{
if (!$this->account) {
$this->load('account');
}
$url = SITE_URL;
$iframe_url = $this->account->iframe_url;
if ($this->account->isPro()) {
if ($iframe_url) {
return "{$iframe_url}/?{$this->invitation_key}";
} elseif ($this->account->subdomain) {
$url = Utils::replaceSubdomain($url, $this->account->subdomain);
}
}
return "{$url}/{$type}/{$this->invitation_key}";
}
public function getStatus()
{
$hasValue = false;
$parts = [];
$statuses = $this->message_id ? ['sent', 'opened', 'viewed'] : ['sent', 'viewed'];
foreach ($statuses as $status) {
$field = "{$status}_date";
$date = '';
if ($this->$field && $this->field != '0000-00-00 00:00:00') {
$date = Utils::dateToString($this->$field);
$hasValue = true;
}
$parts[] = trans('texts.invitation_status.' . $status) . ': ' . $date;
}
return $hasValue ? implode($parts, '<br/>') : false;
}
public function getName()
{
return $this->invitation_key;
}
public function markSent($messageId = null)
{
$this->message_id = $messageId;
$this->email_error = null;
$this->sent_date = Carbon::now()->toDateTimeString();
$this->save();
}
public function markViewed()
{
//$invoice = $this->invoice;
//$client = $invoice->client;
$this->viewed_date = Carbon::now()->toDateTimeString();
$this->save();
//$invoice->markViewed();
//$client->markLoggedIn();
}
}

View File

@ -87,4 +87,11 @@ class BaseTransformer extends TransformerAbstract
return isset($this->maps[ENTITY_INVOICE.'_'.ENTITY_CLIENT][$invoiceNumber])? $this->maps[ENTITY_INVOICE.'_'.ENTITY_CLIENT][$invoiceNumber] : null;
}
protected function getVendorId($name)
{
$name = strtolower($name);
return isset($this->maps[ENTITY_VENDOR][$name]) ? $this->maps[ENTITY_VENDOR][$name] : null;
}
}

View File

@ -0,0 +1,35 @@
<?php namespace App\Ninja\Import\CSV;
use App\Ninja\Import\BaseTransformer;
use League\Fractal\Resource\Item;
class VendorTransformer extends BaseTransformer
{
public function transform($data)
{
if (isset($data->name) && $this->hasVendor($data->name)) {
return false;
}
return new Item($data, function ($data) {
return [
'name' => $this->getString($data, 'name'),
'work_phone' => $this->getString($data, 'work_phone'),
'address1' => $this->getString($data, 'address1'),
'city' => $this->getString($data, 'city'),
'state' => $this->getString($data, 'state'),
'postal_code' => $this->getString($data, 'postal_code'),
'private_notes' => $this->getString($data, 'notes'),
'contacts' => [
[
'first_name' => $this->getString($data, 'first_name'),
'last_name' => $this->getString($data, 'last_name'),
'email' => $this->getString($data, 'email'),
'phone' => $this->getString($data, 'phone'),
],
],
'country_id' => isset($data->country) ? $this->getCountryId($data->country) : null,
];
});
}
}

View File

@ -0,0 +1,36 @@
<?php namespace App\Ninja\Import\FreshBooks;
use App\Ninja\Import\BaseTransformer;
use League\Fractal\Resource\Item;
class VendorTransformer extends BaseTransformer
{
public function transform($data)
{
if ($this->hasVendor($data->organization)) {
return false;
}
return new Item($data, function ($data) {
return [
'name' => $data->organization,
'work_phone' => $data->busphone,
'address1' => $data->street,
'address2' => $data->street2,
'city' => $data->city,
'state' => $data->province,
'postal_code' => $data->postalcode,
'private_notes' => $data->notes,
'contacts' => [
[
'first_name' => $data->firstname,
'last_name' => $data->lastname,
'email' => $data->email,
'phone' => $data->mobphone ?: $data->homephone,
],
],
'country_id' => $this->getCountryId($data->country),
];
});
}
}

View File

@ -0,0 +1,24 @@
<?php namespace App\Ninja\Import\Harvest;
use App\Ninja\Import\BaseTransformer;
use League\Fractal\Resource\Item;
class VendorContactTransformer extends BaseTransformer
{
public function transform($data)
{
if ( ! $this->hasVendor($data->vendor)) {
return false;
}
return new Item($data, function ($data) {
return [
'vendor_id' => $this->getVendorId($data->vendor),
'first_name' => $data->first_name,
'last_name' => $data->last_name,
'email' => $data->email,
'phone' => $data->office_phone ?: $data->mobile_phone,
];
});
}
}

View File

@ -0,0 +1,20 @@
<?php namespace App\Ninja\Import\Harvest;
use App\Ninja\Import\BaseTransformer;
use League\Fractal\Resource\Item;
class VendorTransformer extends BaseTransformer
{
public function transform($data)
{
if ($this->hasVendor($data->vendor_name)) {
return false;
}
return new Item($data, function ($data) {
return [
'name' => $data->vendor_name,
];
});
}
}

View File

@ -0,0 +1,35 @@
<?php namespace App\Ninja\Import\Hiveage;
use App\Ninja\Import\BaseTransformer;
use League\Fractal\Resource\Item;
class VendorTransformer extends BaseTransformer
{
public function transform($data)
{
if ($this->hasVendor($data->name)) {
return false;
}
return new Item($data, function ($data) {
return [
'name' => $data->name,
'contacts' => [
[
'first_name' => $this->getFirstName($data->primary_contact),
'last_name' => $this->getLastName($data->primary_contactk),
'email' => $data->business_email,
],
],
'address1' => $data->address_1,
'address2' => $data->address_2,
'city' => $data->city,
'state' => $data->state_name,
'postal_code' => $data->zip_code,
'work_phone' => $data->phone,
'website' => $data->website,
'country_id' => $this->getCountryId($data->country),
];
});
}
}

View File

@ -0,0 +1,34 @@
<?php namespace App\Ninja\Import\Invoiceable;
use App\Ninja\Import\BaseTransformer;
use League\Fractal\Resource\Item;
class VendorTransformer extends BaseTransformer
{
public function transform($data)
{
if ($this->hasVendor($data->vendor_name)) {
return false;
}
return new Item($data, function ($data) {
return [
'name' => $data->vendor_name,
'work_phone' => $data->tel,
'website' => $data->website,
'address1' => $data->address,
'city' => $data->city,
'state' => $data->state,
'postal_code' => $data->postcode,
'country_id' => $this->getCountryIdBy2($data->country),
'private_notes' => $data->notes,
'contacts' => [
[
'email' => $data->email,
'phone' => $data->mobile,
],
],
];
});
}
}

View File

@ -0,0 +1,35 @@
<?php namespace App\Ninja\Import\Nutcache;
use App\Ninja\Import\BaseTransformer;
use League\Fractal\Resource\Item;
class VendorTransformer extends BaseTransformer
{
public function transform($data)
{
if ($this->hasVendor($data->name)) {
return false;
}
return new Item($data, function ($data) {
return [
'name' => $data->name,
'city' => isset($data->city) ? $data->city : '',
'state' => isset($data->city) ? $data->stateprovince : '',
'id_number' => isset($data->registration_number) ? $data->registration_number : '',
'postal_code' => isset($data->postalzip_code) ? $data->postalzip_code : '',
'private_notes' => isset($data->notes) ? $data->notes : '',
'work_phone' => isset($data->phone) ? $data->phone : '',
'contacts' => [
[
'first_name' => isset($data->contact_name) ? $this->getFirstName($data->contact_name) : '',
'last_name' => isset($data->contact_name) ? $this->getLastName($data->contact_name) : '',
'email' => $data->email,
'phone' => isset($data->mobile) ? $data->mobile : '',
],
],
'country_id' => isset($data->country) ? $this->getCountryId($data->country) : null,
];
});
}
}

View File

@ -0,0 +1,28 @@
<?php namespace App\Ninja\Import\Ronin;
use App\Ninja\Import\BaseTransformer;
use League\Fractal\Resource\Item;
class VendorTransformer extends BaseTransformer
{
public function transform($data)
{
if ($this->hasVendor($data->company)) {
return false;
}
return new Item($data, function ($data) {
return [
'name' => $data->company,
'work_phone' => $data->phone,
'contacts' => [
[
'first_name' => $this->getFirstName($data->name),
'last_name' => $this->getLastName($data->name),
'email' => $data->email,
],
],
];
});
}
}

View File

@ -0,0 +1,38 @@
<?php namespace App\Ninja\Import\Wave;
use App\Ninja\Import\BaseTransformer;
use League\Fractal\Resource\Item;
class VendorTransformer extends BaseTransformer
{
public function transform($data)
{
if ($this->hasVendor($data->customer_name)) {
return false;
}
return new Item($data, function ($data) {
return [
'name' => $data->customer_name,
'id_number' => $data->account_number,
'work_phone' => $data->phone,
'website' => $data->website,
'address1' => $data->address_line_1,
'address2' => $data->address_line_2,
'city' => $data->city,
'state' => $data->provincestate,
'postal_code' => $data->postal_codezip_code,
'private_notes' => $data->delivery_instructions,
'contacts' => [
[
'first_name' => $data->contact_first_name,
'last_name' => $data->contact_last_name,
'email' => $data->email,
'phone' => $data->mobile,
],
],
'country_id' => $this->getCountryId($data->country),
];
});
}
}

View File

@ -0,0 +1,37 @@
<?php namespace App\Ninja\Import\Zoho;
use App\Ninja\Import\BaseTransformer;
use League\Fractal\Resource\Item;
class VendorTransformer extends BaseTransformer
{
public function transform($data)
{
if ($this->hasVendor($data->customer_name)) {
return false;
}
return new Item($data, function ($data) {
return [
'name' => $data->customer_name,
'id_number' => $data->customer_id,
'work_phone' => $data->phonek,
'address1' => $data->billing_address,
'city' => $data->billing_city,
'state' => $data->billing_state,
'postal_code' => $data->billing_code,
'private_notes' => $data->notes,
'website' => $data->website,
'contacts' => [
[
'first_name' => $data->first_name,
'last_name' => $data->last_name,
'email' => $data->emailid,
'phone' => $data->mobilephone,
],
],
'country_id' => $this->getCountryId($data->billing_country),
];
});
}
}

View File

@ -0,0 +1,101 @@
<?php namespace App\Ninja\Repositories;
use DB;
use Auth;
use Utils;
use Request;
use App\Models\VendorActivity;
use App\Models\Vendor;
class VendorActivityRepository
{
private $vendor;
public function create($entity, $activityTypeId, $balanceChange = 0, $paidToDateChange = 0, $altEntity = null)
{
if ($entity instanceof Vendor) {
$vendor = $entity;
} elseif ($entity instanceof Invitation) {
$vendor = $entity->invoice->vendor;
} else {
$vendor = $entity->vendor;
}
$this->vendor = $vendor;
// init activity and copy over context
$activity = self::getBlank($altEntity ?: $vendor);
$activity = Utils::copyContext($activity, $entity);
$activity = Utils::copyContext($activity, $altEntity);
$activity->vendor_id = $vendor->id;
$activity->activity_type_id = $activityTypeId;
$activity->adjustment = $balanceChange;
$activity->balance = $vendor->balance + $balanceChange;
$keyField = $entity->getKeyField();
$activity->$keyField = $entity->id;
$activity->ip = Request::getClientIp();
$activity->save();
$vendor->updateBalances($balanceChange, $paidToDateChange);
return $activity;
}
private function getBlank($entity)
{
$activity = new VendorActivity();
if (Auth::check() && Auth::user()->account_id == $entity->account_id) {
$activity->user_id = Auth::user()->id;
$activity->account_id = Auth::user()->account_id;
} else {
$activity->user_id = $entity->user_id;
$activity->account_id = $entity->account_id;
if ( ! $entity instanceof Invitation) {
$activity->is_system = true;
}
}
$activity->token_id = session('token_id');
return $activity;
}
public function findByVendorId($vendorId)
{
return DB::table('vendor_activities')
->join('accounts', 'accounts.id', '=', 'activities.account_id')
->join('users', 'users.id', '=', 'activities.user_id')
->join('vendors', 'vendors.id', '=', 'activities.vendor_id')
->leftJoin('vendor_contacts', 'vendor_contacts.vendor_id', '=', 'vendors.id')
->where('vendors.id', '=', $vendorId)
->where('vendor_contacts.is_primary', '=', 1)
->whereNull('vendor_contacts.deleted_at')
->select(
DB::raw('COALESCE(vendors.currency_id, accounts.currency_id) currency_id'),
DB::raw('COALESCE(vendors.country_id, accounts.country_id) country_id'),
'vendor_activities.id',
'vendor_activities.created_at',
'vendor_activities.contact_id',
'vendor_activities.activity_type_id',
'vendor_activities.is_system',
'vendor_activities.balance',
'vendor_activities.adjustment',
'users.first_name as user_first_name',
'users.last_name as user_last_name',
'users.email as user_email',
'vendors.name as vendor_name',
'vendors.public_id as vendor_public_id',
'vendor_contacts.id as contact',
'vendor_contacts.first_name as first_name',
'vendor_contacts.last_name as last_name',
'vendor_contacts.email as email'
);
}
}

View File

@ -0,0 +1,26 @@
<?php namespace App\Ninja\Repositories;
use App\Models\Vendor;
use App\Models\VendorContact;
class VendorContactRepository extends BaseRepository
{
public function save($data)
{
$publicId = isset($data['public_id']) ? $data['public_id'] : false;
if (!$publicId || $publicId == '-1') {
$contact = VendorContact::createNew();
//$contact->send_invoice = true;
$contact->vendor_id = $data['vendor_id'];
$contact->is_primary = VendorContact::scope()->where('vendor_id', '=', $contact->vendor_id)->count() == 0;
} else {
$contact = VendorContact::scope($publicId)->firstOrFail();
}
$contact->fill($data);
$contact->save();
return $contact;
}
}

View File

@ -0,0 +1,102 @@
<?php namespace App\Ninja\Repositories;
use DB;
use App\Ninja\Repositories\BaseRepository;
use App\Models\Vendor;
use App\Models\VendorContact;
use App\Models\Activity;
class VendorRepository extends BaseRepository
{
public function getClassName()
{
return 'App\Models\Vendor';
}
public function all()
{
return Vendor::scope()
->with('user', 'vendorcontacts', 'country')
->withTrashed()
->where('is_deleted', '=', false)
->get();
}
public function find($filter = null)
{
$query = DB::table('vendors')
->join('accounts', 'accounts.id', '=', 'vendors.account_id')
->join('vendor_contacts', 'vendor_contacts.vendor_id', '=', 'vendors.id')
->where('vendors.account_id', '=', \Auth::user()->account_id)
->where('vendor_contacts.is_primary', '=', true)
->where('vendor_contacts.deleted_at', '=', null)
->select(
DB::raw('COALESCE(vendors.currency_id, accounts.currency_id) currency_id'),
DB::raw('COALESCE(vendors.country_id, accounts.country_id) country_id'),
'vendors.public_id',
'vendors.name',
'vendor_contacts.first_name',
'vendor_contacts.last_name',
'vendors.balance',
//'vendors.last_login',
'vendors.created_at',
'vendors.work_phone',
'vendor_contacts.email',
'vendors.deleted_at',
'vendors.is_deleted'
);
if (!\Session::get('show_trash:vendor')) {
$query->where('vendors.deleted_at', '=', null);
}
if ($filter) {
$query->where(function ($query) use ($filter) {
$query->where('vendors.name', 'like', '%'.$filter.'%')
->orWhere('vendor_contacts.first_name', 'like', '%'.$filter.'%')
->orWhere('vendor_contacts.last_name', 'like', '%'.$filter.'%')
->orWhere('vendor_contacts.email', 'like', '%'.$filter.'%');
});
}
return $query;
}
public function save($data)
{
$publicId = isset($data['public_id']) ? $data['public_id'] : false;
if (!$publicId || $publicId == '-1') {
$vendor = Vendor::createNew();
} else {
$vendor = Vendor::scope($publicId)->with('vendorcontacts')->firstOrFail();
}
$vendor->fill($data);
$vendor->save();
if ( ! isset($data['vendor_contact']) && ! isset($data['vendor_contacts'])) {
return $vendor;
}
$first = true;
$vendorcontacts = isset($data['vendor_contact']) ? [$data['vendor_contact']] : $data['vendor_contacts'];
$vendorcontactIds = [];
foreach ($vendorcontacts as $vendorcontact) {
$vendorcontact = $vendor->addVendorContact($vendorcontact, $first);
$vendorcontactIds[] = $vendorcontact->public_id;
$first = false;
}
foreach ($vendor->vendorcontacts as $vendorcontact) {
if (!in_array($vendorcontact->public_id, $vendorcontactIds)) {
$vendorcontact->delete();
}
}
return $vendor;
}
}

View File

@ -53,11 +53,8 @@ class AppServiceProvider extends ServiceProvider {
$str .= '<li class="divider"></li>
<li><a href="'.URL::to('credits').'">'.trans("texts.credits").'</a></li>
<li><a href="'.URL::to('credits/create').'">'.trans("texts.new_credit").'</a></li>';
} else if($type == 'expense'){
} else if ($type == ENTITY_EXPENSE) {
$str .= '<li class="divider"></li>
<li><a href="'.URL::to('expenses').'">'.trans("texts.expenses").'</a></li>
<li><a href="'.URL::to('expensesn/create').'">'.trans("texts.new_expense").'</a></li>
<li class="divider"></li>
<li><a href="'.URL::to('vendors').'">'.trans("texts.vendors").'</a></li>
<li><a href="'.URL::to('vendors/create').'">'.trans("texts.new_vendor").'</a></li>';
}

View File

@ -137,6 +137,21 @@ class EventServiceProvider extends ServiceProvider {
'App\Listeners\HandleUserSettingsChanged',
],
// vendor events
'App\Events\VendorWasCreated' => [
'App\Listeners\VendorActivityListener@createdVendor',
'App\Listeners\SubscriptionListener@createdVendor',
],
'App\Events\VendorWasArchived' => [
'App\Listeners\VendorActivityListener@archivedVendor',
],
'App\Events\VendorWasDeleted' => [
'App\Listeners\VendorActivityListener@deletedVendor',
],
'App\Events\VendorWasRestored' => [
'App\Listeners\VendorActivityListener@restoredVendor',
],
];
/**

View File

@ -0,0 +1,67 @@
<?php namespace App\Services;
use Utils;
use App\Models\Vendor;
use App\Services\BaseService;
use App\Ninja\Repositories\VendorActivityRepository;
class VendorActivityService extends BaseService
{
protected $activityRepo;
protected $datatableService;
public function __construct(VendorActivityRepository $activityRepo, DatatableService $datatableService)
{
$this->activityRepo = $activityRepo;
$this->datatableService = $datatableService;
}
public function getDatatable($vendorPublicId = null)
{
$vendorId = Vendor::getPrivateId($vendorPublicId);
$query = $this->activityRepo->findByVendorId($vendorId);
return $this->createDatatable(ENTITY_ACTIVITY, $query);
}
protected function getDatatableColumns($entityType, $hideVendor)
{
return [
[
'vendor_activities.id',
function ($model) {
return Utils::timestampToDateTimeString(strtotime($model->created_at));
}
],
[
'activity_type_id',
function ($model) {
$data = [
'vendor' => link_to('/vendors/' . $model->vendor_public_id, Utils::getVendorDisplayName($model)),
'user' => $model->is_system ? '<i>' . trans('texts.system') . '</i>' : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email),
'invoice' => $model->invoice ? link_to('/invoices/' . $model->invoice_public_id, $model->is_recurring ? trans('texts.recurring_invoice') : $model->invoice) : null,
'quote' => $model->invoice ? link_to('/quotes/' . $model->invoice_public_id, $model->invoice) : null,
'contact' => $model->contact_id ? link_to('/vendors/' . $model->vendor_public_id, Utils::getVendorDisplayName($model)) : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email),
'payment' => $model->payment ?: '',
'credit' => Utils::formatMoney($model->credit, $model->currency_id, $model->country_id)
];
return trans("texts.activity_{$model->activity_type_id}", $data);
}
],
[
'balance',
function ($model) {
return Utils::formatMoney($model->balance, $model->currency_id, $model->country_id);
}
],
[
'adjustment',
function ($model) {
return $model->adjustment != 0 ? Utils::wrapAdjustment($model->adjustment, $model->currency_id, $model->country_id) : '';
}
]
];
}
}

View File

@ -0,0 +1,103 @@
<?php namespace App\Services;
use Utils;
use URL;
use Auth;
use App\Services\BaseService;
use App\Ninja\Repositories\VendorRepository;
use App\Ninja\Repositories\NinjaRepository;
class VendorService extends BaseService
{
protected $vendorRepo;
protected $datatableService;
public function __construct(VendorRepository $vendorRepo, DatatableService $datatableService, NinjaRepository $ninjaRepo)
{
$this->vendorRepo = $vendorRepo;
$this->ninjaRepo = $ninjaRepo;
$this->datatableService = $datatableService;
}
protected function getRepo()
{
return $this->vendorRepo;
}
public function save($data)
{
if (Auth::user()->account->isNinjaAccount() && isset($data['pro_plan_paid'])) {
$this->ninjaRepo->updateProPlanPaid($data['public_id'], $data['pro_plan_paid']);
}
return $this->vendorRepo->save($data);
}
public function getDatatable($search)
{
$query = $this->vendorRepo->find($search);
return $this->createDatatable(ENTITY_VENDOR, $query);
}
protected function getDatatableColumns($entityType, $hideVendor)
{
return [
[
'name',
function ($model) {
return link_to("vendors/{$model->public_id}", $model->name ?: '');
}
],
[
'first_name',
function ($model) {
return link_to("vendors/{$model->public_id}", $model->first_name.' '.$model->last_name);
}
],
[
'email',
function ($model) {
return link_to("vendors/{$model->public_id}", $model->email ?: '');
}
],
[
'vendors.created_at',
function ($model) {
return Utils::timestampToDateString(strtotime($model->created_at));
}
],
/*[
'last_login',
function ($model) {
return Utils::timestampToDateString(strtotime($model->last_login));
}
],*/
[
'balance',
function ($model) {
return Utils::formatMoney($model->balance, $model->currency_id, $model->country_id);
}
]
];
}
protected function getDatatableActions($entityType)
{
return [
[
trans('texts.edit_vendor'),
function ($model) {
return URL::to("vendors/{$model->public_id}/edit");
}
],
[],
[
trans('texts.enter_expense'),
function ($model) {
return URL::to("expenses/create/{$model->public_id}");
}
]
];
}
}

46
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "f3b07ee34ed5086cd684a7207fd3617e",
"hash": "71b1d7519dd65f8e028c1c417354606d",
"content-hash": "7305e2f5d6864894aeb23ac91f3e1fe7",
"packages": [
{
@ -1272,33 +1272,33 @@
},
{
"name": "doctrine/cache",
"version": "v1.5.4",
"version": "v1.6.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/cache.git",
"reference": "47cdc76ceb95cc591d9c79a36dc3794975b5d136"
"reference": "f8af318d14bdb0eff0336795b428b547bd39ccb6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/cache/zipball/47cdc76ceb95cc591d9c79a36dc3794975b5d136",
"reference": "47cdc76ceb95cc591d9c79a36dc3794975b5d136",
"url": "https://api.github.com/repos/doctrine/cache/zipball/f8af318d14bdb0eff0336795b428b547bd39ccb6",
"reference": "f8af318d14bdb0eff0336795b428b547bd39ccb6",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
"php": "~5.5|~7.0"
},
"conflict": {
"doctrine/common": ">2.2,<2.4"
},
"require-dev": {
"phpunit/phpunit": ">=3.7",
"phpunit/phpunit": "~4.8|~5.0",
"predis/predis": "~1.0",
"satooshi/php-coveralls": "~0.6"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.5.x-dev"
"dev-master": "1.6.x-dev"
}
},
"autoload": {
@ -1338,7 +1338,7 @@
"cache",
"caching"
],
"time": "2015-12-19 05:03:47"
"time": "2015-12-31 16:37:02"
},
{
"name": "doctrine/collections",
@ -2254,12 +2254,12 @@
"source": {
"type": "git",
"url": "https://github.com/Intervention/image.git",
"reference": "e6acb1609ce89f2c1ec7864fbbda0a20a3eeca70"
"reference": "9f29360b8ab94585cb9e80cf9045abd5b85feb89"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Intervention/image/zipball/e6acb1609ce89f2c1ec7864fbbda0a20a3eeca70",
"reference": "e6acb1609ce89f2c1ec7864fbbda0a20a3eeca70",
"url": "https://api.github.com/repos/Intervention/image/zipball/9f29360b8ab94585cb9e80cf9045abd5b85feb89",
"reference": "9f29360b8ab94585cb9e80cf9045abd5b85feb89",
"shasum": ""
},
"require": {
@ -2308,7 +2308,7 @@
"thumbnail",
"watermark"
],
"time": "2015-12-04 17:09:36"
"time": "2016-01-02 19:15:13"
},
{
"name": "ircmaxell/password-compat",
@ -7593,16 +7593,16 @@
},
{
"name": "facebook/webdriver",
"version": "1.1.0",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/facebook/php-webdriver.git",
"reference": "518a9e0635e69777f07e41619cdf5d82fae284b6"
"reference": "1c98108ba3eb435b681655764de11502a0653705"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facebook/php-webdriver/zipball/518a9e0635e69777f07e41619cdf5d82fae284b6",
"reference": "518a9e0635e69777f07e41619cdf5d82fae284b6",
"url": "https://api.github.com/repos/facebook/php-webdriver/zipball/1c98108ba3eb435b681655764de11502a0653705",
"reference": "1c98108ba3eb435b681655764de11502a0653705",
"shasum": ""
},
"require": {
@ -7632,7 +7632,7 @@
"selenium",
"webdriver"
],
"time": "2015-12-08 17:04:30"
"time": "2015-12-31 15:58:49"
},
{
"name": "fzaninotto/faker",
@ -7722,16 +7722,16 @@
},
{
"name": "phpspec/phpspec",
"version": "2.4.0",
"version": "2.4.1",
"source": {
"type": "git",
"url": "https://github.com/phpspec/phpspec.git",
"reference": "1d3938e6d9ffb1bd4805ea8ddac62ea48767f358"
"reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/phpspec/zipball/1d3938e6d9ffb1bd4805ea8ddac62ea48767f358",
"reference": "1d3938e6d9ffb1bd4805ea8ddac62ea48767f358",
"url": "https://api.github.com/repos/phpspec/phpspec/zipball/5528ce1e93a1efa090c9404aba3395c329b4e6ed",
"reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed",
"shasum": ""
},
"require": {
@ -7796,7 +7796,7 @@
"testing",
"tests"
],
"time": "2015-11-29 02:03:49"
"time": "2016-01-01 10:17:54"
},
{
"name": "phpspec/prophecy",

View File

@ -27,7 +27,7 @@ class CreateVendorsTable extends Migration {
$table->string('city');
$table->string('state');
$table->string('postal_code');
$table->integer('country_id',false, true);
$table->integer('country_id')->default(0);
$table->string('work_phone');
$table->text('private_notes');
$table->decimal('balance',13,2);
@ -36,11 +36,11 @@ class CreateVendorsTable extends Migration {
//$table->dateTime('last_login');
$table->string('website');
$table->integer('industry_id',false, true);
$table->integer('size_id');
$table->integer('industry_id')->nullable();
$table->integer('size_id')->nullable();
$table->tinyInteger('is_deleted')->default(0);
$table->integer('payment_terms')->nullable();
$table->integer('public_id',false, true);
$table->integer('public_id')->default(0);
$table->string('custom_value1')->nullable();
$table->string('custom_value2')->nullable();
$table->string('vat_number')->nullable();

View File

@ -0,0 +1,51 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateVendorcontactsTable extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::dropIfExists('vendor_contacts');
Schema::create('vendor_contacts', function(Blueprint $table)
{
$table->increments('id');
$table->unsignedInteger('account_id');
$table->unsignedInteger('user_id');
$table->unsignedInteger('vendor_id')->index();
$table->timestamps();
$table->softDeletes();
$table->boolean('is_primary')->default(0);
//$table->boolean('send_invoice')->default(0);
$table->string('first_name')->nullable();
$table->string('last_name')->nullable();
$table->string('email')->nullable();
$table->string('phone')->nullable();
$table->timestamp('last_login')->nullable();
$table->foreign('vendor_id')->references('id')->on('vendors')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
$table->unsignedInteger('public_id')->nullable();
$table->unique( array('account_id','public_id') );
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('vendor_contacts');
}
}

View File

@ -0,0 +1,55 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateTableVendorInvitations extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::dropIfExists('vendor_invitations');
Schema::create('vendor_invitations', function(Blueprint $table)
{
$table->increments('id');
$table->unsignedInteger('account_id');
$table->unsignedInteger('user_id');
$table->unsignedInteger('contact_id');
//$table->unsignedInteger('invoice_id')->index();
$table->string('invitation_key')->index()->unique();
$table->timestamps();
$table->softDeletes();
$table->string('transaction_reference')->nullable();
$table->timestamp('sent_date')->nullable();
$table->timestamp('viewed_date')->nullable();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
$table->foreign('contact_id')->references('id')->on('vendor_contacts')->onDelete('cascade');
//$table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
$table->unsignedInteger('public_id')->index();
$table->unique( array('account_id','public_id') );
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('vendor_invitations', function(Blueprint $table)
{
//
});
Schema::dropIfExists('vendor_invitations');
}
}

View File

@ -0,0 +1,58 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateTableVendorActivities extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::dropIfExists('vendor_activities');
Schema::create('vendor_activities', function(Blueprint $table)
{
$table->increments('id');
$table->timestamps();
$table->unsignedInteger('account_id');
$table->unsignedInteger('vendor_id');
$table->unsignedInteger('user_id');
$table->unsignedInteger('contact_id')->nullable();
$table->unsignedInteger('payment_id')->nullable();
//$table->unsignedInteger('invoice_id')->nullable();
$table->unsignedInteger('credit_id')->nullable();
$table->unsignedInteger('invitation_id')->nullable();
$table->text('message')->nullable();
$table->text('json_backup')->nullable();
$table->integer('activity_type_id');
$table->decimal('adjustment', 13, 2)->nullable();
$table->decimal('balance', 13, 2)->nullable();
$table->unsignedInteger('token_id')->nullable();
$table->string('ip')->nullable();
$table->boolean('is_system')->default(0);
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$table->foreign('vendor_id')->references('id')->on('vendors')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('vendor_activities', function(Blueprint $table)
{
//
});
Schema::dropIfExists('vendor_activities');
}
}

View File

@ -256,6 +256,14 @@ return array(
'deleted_credits' => 'Successfully deleted :count credits',
'imported_file' => 'Successfully imported file',
'updated_vendor' => 'Successfully updated vendor',
'created_vendor' => 'Successfully created vendor',
'archived_vendor' => 'Successfully archived vendor',
'archived_vendors' => 'Successfully archived :count vendors',
'deleted_vendor' => 'Successfully deleted vendor',
'deleted_vendors' => 'Successfully deleted :count vendors',
// Emails
'confirmation_subject' => 'Invoice Ninja Account Confirmation',
'confirmation_header' => 'Account Confirmation',
@ -997,5 +1005,16 @@ return array(
'white_label_custom_css' => ':link for $'.WHITE_LABEL_PRICE.' to enable custom styling and help support our project.',
'white_label_purchase_link' => 'Purchase a white label license',
// Expense / vendor
'expenses' => 'Expenses',
'new_expense' => 'Create expense',
'vendors' => 'Vendors',
'new_vendor' => 'Create vendor',
'payment_terms_net' => 'Net',
'vendor' => 'Vendor',
'edit_vendor' => 'Edit vendor',
'archive_vendor' => 'Archive vendor',
'delete_vendor' => 'Delete vendor',
'view_vendor' => 'View vendor',
);

View File

@ -374,7 +374,7 @@
{!! HTML::menu_link('task') !!}
{!! HTML::menu_link('invoice') !!}
{!! HTML::menu_link('payment') !!}
{!! HTML::menu_link('expense') !!}
{!! HTML::menu_link('expense') !!}
</ul>
<div class="navbar-form navbar-right">