diff --git a/.gitignore b/.gitignore index b643269b604a..5a86589d7a21 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ tests/_bootstrap.php # composer stuff /c3.php + +_ide_helper.php \ No newline at end of file diff --git a/app/Events/ExpenseWasArchived.php b/app/Events/ExpenseWasArchived.php new file mode 100644 index 000000000000..a4b2af4bdf31 --- /dev/null +++ b/app/Events/ExpenseWasArchived.php @@ -0,0 +1,22 @@ +expense = $expense; + } + +} diff --git a/app/Events/ExpenseWasCreated.php b/app/Events/ExpenseWasCreated.php new file mode 100644 index 000000000000..ab462fe60253 --- /dev/null +++ b/app/Events/ExpenseWasCreated.php @@ -0,0 +1,21 @@ +expense = $expense; + } +} diff --git a/app/Events/ExpenseWasDeleted.php b/app/Events/ExpenseWasDeleted.php new file mode 100644 index 000000000000..1549b483b497 --- /dev/null +++ b/app/Events/ExpenseWasDeleted.php @@ -0,0 +1,23 @@ +expense = $expense; + } + +} diff --git a/app/Events/ExpenseWasRestored.php b/app/Events/ExpenseWasRestored.php new file mode 100644 index 000000000000..b52a2d119a2d --- /dev/null +++ b/app/Events/ExpenseWasRestored.php @@ -0,0 +1,23 @@ +expense = $expense; + } + +} diff --git a/app/Events/ExpenseWasUpdated.php b/app/Events/ExpenseWasUpdated.php new file mode 100644 index 000000000000..1066d90de4f7 --- /dev/null +++ b/app/Events/ExpenseWasUpdated.php @@ -0,0 +1,21 @@ +expense = $expense; + } +} diff --git a/app/Events/VendorWasArchived.php b/app/Events/VendorWasArchived.php new file mode 100644 index 000000000000..ca268441f0d4 --- /dev/null +++ b/app/Events/VendorWasArchived.php @@ -0,0 +1,22 @@ +vendor = $vendor; + } +} diff --git a/app/Events/VendorWasCreated.php b/app/Events/VendorWasCreated.php new file mode 100644 index 000000000000..b2d7e81c9394 --- /dev/null +++ b/app/Events/VendorWasCreated.php @@ -0,0 +1,22 @@ +vendor = $vendor; + } +} diff --git a/app/Events/VendorWasDeleted.php b/app/Events/VendorWasDeleted.php new file mode 100644 index 000000000000..553bece3ccdc --- /dev/null +++ b/app/Events/VendorWasDeleted.php @@ -0,0 +1,22 @@ +vendor = $vendor; + } +} diff --git a/app/Events/VendorWasRestored.php b/app/Events/VendorWasRestored.php new file mode 100644 index 000000000000..88c24693e611 --- /dev/null +++ b/app/Events/VendorWasRestored.php @@ -0,0 +1,22 @@ +vendor = $vendor; + } +} diff --git a/app/Events/VendorWasUpdated.php b/app/Events/VendorWasUpdated.php new file mode 100644 index 000000000000..eb90a68f46c0 --- /dev/null +++ b/app/Events/VendorWasUpdated.php @@ -0,0 +1,21 @@ +vendor = $vendor; + } +} diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index e77bdf0b23ce..cc51a06de32c 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -20,6 +20,7 @@ use App\Models\Account; use App\Models\Gateway; use App\Models\InvoiceDesign; use App\Models\TaxRate; +use App\Models\PaymentTerm; use App\Ninja\Repositories\AccountRepository; use App\Ninja\Repositories\ReferralRepository; use App\Ninja\Mailers\UserMailer; @@ -158,6 +159,8 @@ class AccountController extends BaseController return self::showProducts(); } elseif ($section === ACCOUNT_TAX_RATES) { return self::showTaxRates(); + } elseif ($section === ACCOUNT_PAYMENT_TERMS) { + return self::showPaymentTerms(); } elseif ($section === ACCOUNT_SYSTEM_SETTINGS) { return self::showSystemSettings(); } else { @@ -313,6 +316,17 @@ class AccountController extends BaseController return View::make('accounts.tax_rates', $data); } + private function showPaymentTerms() + { + $data = [ + 'account' => Auth::user()->account, + 'title' => trans('texts.payment_terms'), + 'taxRates' => PaymentTerm::scope()->get(['id', 'name', 'num_days']), + ]; + + return View::make('accounts.payment_terms', $data); + } + private function showInvoiceDesign($section) { $account = Auth::user()->account->load('country'); @@ -322,13 +336,15 @@ class AccountController extends BaseController $invoiceItem = new stdClass(); $client->name = 'Sample Client'; - $client->address1 = ''; - $client->city = ''; - $client->state = ''; - $client->postal_code = ''; - $client->work_phone = ''; - $client->work_email = ''; - + $client->address1 = 'address 1'; + $client->city = ' city'; + $client->state = 'state'; + $client->postal_code = 'postal code'; + $client->work_phone = 'work phone'; + $client->work_email = 'work email'; + $client->id_number = 'cleint id'; + $client->vat_number = 'vat number'; + $invoice->invoice_number = '0000'; $invoice->invoice_date = Utils::fromSqlDate(date('Y-m-d')); $invoice->account = json_decode($account->toJson()); @@ -347,7 +363,7 @@ class AccountController extends BaseController $invoice->client = $client; $invoice->invoice_items = [$invoiceItem]; - + $data['account'] = $account; $data['invoice'] = $invoice; $data['invoiceLabels'] = json_decode($account->invoice_labels) ?: []; @@ -355,7 +371,7 @@ class AccountController extends BaseController $data['invoiceDesigns'] = InvoiceDesign::getDesigns(); $data['invoiceFonts'] = Cache::get('fonts'); $data['section'] = $section; - + $design = false; foreach ($data['invoiceDesigns'] as $item) { if ($item->id == $account->invoice_design_id) { @@ -444,6 +460,8 @@ class AccountController extends BaseController return AccountController::saveProducts(); } elseif ($section === ACCOUNT_TAX_RATES) { return AccountController::saveTaxRates(); + } elseif ($section === ACCOUNT_PAYMENT_TERMS) { + return AccountController::savePaymetTerms(); } } @@ -695,7 +713,7 @@ class AccountController extends BaseController $account->primary_color = Input::get('primary_color'); $account->secondary_color = Input::get('secondary_color'); $account->invoice_design_id = Input::get('invoice_design_id'); - + if (Input::has('font_size')) { $account->font_size = intval(Input::get('font_size')); } diff --git a/app/Http/Controllers/BaseAPIController.php b/app/Http/Controllers/BaseAPIController.php index e5878e22ab55..4d783556022e 100644 --- a/app/Http/Controllers/BaseAPIController.php +++ b/app/Http/Controllers/BaseAPIController.php @@ -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; } - } diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 27d4ab04af57..e56da3cb8017 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -6,11 +6,14 @@ use View; use App\Models\Activity; use App\Models\Invoice; use App\Models\Payment; +use App\Models\VendorActivity; +use App\Models\ExpenseActivity; class DashboardController extends BaseController { public function index() { + // total_income, billed_clients, invoice_sent and active_clients $select = DB::raw('COUNT(DISTINCT CASE WHEN invoices.id IS NOT NULL THEN clients.id ELSE null END) billed_clients, SUM(CASE WHEN invoices.invoice_status_id >= '.INVOICE_STATUS_SENT.' THEN 1 ELSE 0 END) invoices_sent, @@ -68,6 +71,19 @@ class DashboardController extends BaseController ->take(50) ->get(); + $vendoractivities = VendorActivity::where('vendor_activities.account_id', '=', Auth::user()->account_id) + ->with('vendor.vendorcontacts', 'user', 'account') + ->where('activity_type_id', '>', 0) + ->orderBy('created_at', 'desc') + ->take(50) + ->get(); + + $expenseactivities = ExpenseActivity::where('expense_activities.account_id', '=', Auth::user()->account_id) + ->where('activity_type_id', '>', 0) + ->orderBy('created_at', 'desc') + ->take(50) + ->get(); + $pastDue = DB::table('invoices') ->leftJoin('clients', 'clients.id', '=', 'invoices.client_id') ->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id') @@ -141,6 +157,8 @@ class DashboardController extends BaseController 'payments' => $payments, 'title' => trans('texts.dashboard'), 'hasQuotes' => $hasQuotes, + 'vendoractivities' => $vendoractivities, + 'expenseactivities' => $expenseactivities, ]; return View::make('dashboard', $data); diff --git a/app/Http/Controllers/ExpenseActivityController.php b/app/Http/Controllers/ExpenseActivityController.php new file mode 100644 index 000000000000..0993123129c5 --- /dev/null +++ b/app/Http/Controllers/ExpenseActivityController.php @@ -0,0 +1,27 @@ +activityService = $activityService; + } + + public function getDatatable($vendorPublicId) + { + return $this->activityService->getDatatable($vendorPublicId); + } +} diff --git a/app/Http/Controllers/ExpenseApiController.php b/app/Http/Controllers/ExpenseApiController.php new file mode 100644 index 000000000000..484538455586 --- /dev/null +++ b/app/Http/Controllers/ExpenseApiController.php @@ -0,0 +1,244 @@ +expenseRepo = $expenseRepo; + $this->expenseService = $expenseService; + } + + /** + * Display a listing of the resource. + * + * @return Response + */ + public function index() + { + return View::make('list', array( + 'entityType' => ENTITY_EXPENSE, + 'title' => trans('texts.expenses'), + 'sortCol' => '1', + 'columns' => Utils::trans([ + 'checkbox', + 'vendor', + 'expense_amount', + 'expense_date', + 'public_notes', + 'is_invoiced', + 'should_be_invoiced', + '' + ]), + )); + } + + public function getDatatable($expensePublicId = null) + { + return $this->expenseService->getDatatable($expensePublicId, Input::get('sSearch')); + } + + public function getDatatableVendor($vendorPublicId = null) + { + return $this->expenseService->getDatatableVendor($vendorPublicId); + } + + public function create($vendorPublicId = 0) + { + if($vendorPublicId != 0) { + $vendor = Vendor::scope($vendorPublicId)->with('vendorcontacts')->firstOrFail(); + } else { + $vendor = null; + } + $data = array( + 'vendorPublicId' => Input::old('vendor') ? Input::old('vendor') : $vendorPublicId, + 'expense' => null, + 'method' => 'POST', + 'url' => 'expenses', + 'title' => trans('texts.new_expense'), + 'vendors' => Vendor::scope()->with('vendorcontacts')->orderBy('name')->get(), + 'vendor' => $vendor, + 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), + 'clientPublicId' => null, + ); + + $data = array_merge($data, self::getViewModel()); + + return View::make('expenses.edit', $data); + } + + public function edit($publicId) + { + $expense = Expense::scope($publicId)->firstOrFail(); + $expense->expense_date = Utils::fromSqlDate($expense->expense_date); + + $data = array( + 'vendor' => null, + 'expense' => $expense, + 'method' => 'PUT', + 'url' => 'expenses/'.$publicId, + 'title' => 'Edit Expense', + 'vendors' => Vendor::scope()->with('vendorcontacts')->orderBy('name')->get(), + 'vendorPublicId' => $expense->vendor_id, + 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), + 'clientPublicId' => $expense->invoice_client_id, + ); + + $data = array_merge($data, self::getViewModel()); + + if (Auth::user()->account->isNinjaAccount()) { + if ($account = Account::whereId($client->public_id)->first()) { + $data['proPlanPaid'] = $account['pro_plan_paid']; + } + } + + return View::make('expenses.edit', $data); + } + + /** + * Update the specified resource in storage. + * + * @param int $id + * @return Response + */ + public function update(UpdateExpenseRequest $request) + { + $client = $this->expenseRepo->save($request->input()); + + Session::flash('message', trans('texts.updated_expense')); + + return redirect()->to('expenses'); + } + + public function store(CreateExpenseRequest $request) + { + $expense = $this->expenseRepo->save($request->input()); + + Session::flash('message', trans('texts.created_expense')); + + return redirect()->to('expenses'); + } + + public function bulk() + { + $action = Input::get('action'); + $ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids'); + + switch($action) + { + case 'invoice': + $expenses = Expense::scope($ids)->get(); + $clientPublicId = null; + $data = []; + + // Validate that either all expenses do not have a client or if there is a client, it is the same client + foreach ($expenses as $expense) + { + if ($expense->client_id) { + if (!$clientPublicId) { + $clientPublicId = $expense->client_id; + } else if ($clientPublicId != $expense->client_id) { + Session::flash('error', trans('texts.expense_error_multiple_clients')); + return Redirect::to('expenses'); + } + } + + if ($expense->invoice_id) { + Session::flash('error', trans('texts.expense_error_invoiced')); + return Redirect::to('expenses'); + } + + if ($expense->should_be_invoiced == 0) { + Session::flash('error', trans('texts.expense_error_should_not_be_invoiced')); + return Redirect::to('expenses'); + } + + $account = Auth::user()->account; + $data[] = [ + 'publicId' => $expense->public_id, + 'description' => $expense->public_notes, + 'qty' => 1, + 'cost' => $expense->amount, + ]; + } + + return Redirect::to("invoices/create/{$clientPublicId}")->with('expenses', $data); + break; + + default: + $count = $this->expenseService->bulk($ids, $action); + } + + if ($count > 0) { + $message = Utils::pluralize($action.'d_expense', $count); + Session::flash('message', $message); + } + + return Redirect::to('expenses'); + } + + 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, + ]; + } + + public function show($publicId) + { + $expense = Expense::withTrashed()->scope($publicId)->firstOrFail(); + + if($expense) { + Utils::trackViewed($expense->getDisplayName(), 'expense'); + } + + $actionLinks = [ + ['label' => trans('texts.new_expense'), 'url' => '/expenses/create/'] + ]; + + $data = array( + 'actionLinks' => $actionLinks, + 'showBreadcrumbs' => false, + 'expense' => $expense, + 'credit' =>0, + 'vendor' => $expense->vendor, + 'title' => trans('texts.view_expense',['expense' => $expense->public_id]), + ); + + return View::make('expenses.show', $data); + } +} diff --git a/app/Http/Controllers/ExpenseController.php b/app/Http/Controllers/ExpenseController.php new file mode 100644 index 000000000000..a6d82f17539c --- /dev/null +++ b/app/Http/Controllers/ExpenseController.php @@ -0,0 +1,239 @@ +expenseRepo = $expenseRepo; + $this->expenseService = $expenseService; + } + + /** + * Display a listing of the resource. + * + * @return Response + */ + public function index() + { + return View::make('list', array( + 'entityType' => ENTITY_EXPENSE, + 'title' => trans('texts.expenses'), + 'sortCol' => '1', + 'columns' => Utils::trans([ + 'checkbox', + 'vendor', + 'expense_amount', + 'expense_date', + 'public_notes', + 'is_invoiced', + 'should_be_invoiced', + '' + ]), + )); + } + + public function getDatatable($expensePublicId = null) + { + return $this->expenseService->getDatatable($expensePublicId, Input::get('sSearch')); + } + + public function create($vendorPublicId = 0) + { + if($vendorPublicId != 0) { + $vendor = Vendor::scope($vendorPublicId)->with('vendorcontacts')->firstOrFail(); + } else { + $vendor = null; + } + $data = array( + 'vendorPublicId' => Input::old('vendor') ? Input::old('vendor') : $vendorPublicId, + 'expense' => null, + 'method' => 'POST', + 'url' => 'expenses', + 'title' => trans('texts.new_expense'), + 'vendors' => Vendor::scope()->with('vendorcontacts')->orderBy('name')->get(), + 'vendor' => $vendor, + 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), + 'clientPublicId' => null, + ); + + $data = array_merge($data, self::getViewModel()); + + return View::make('expenses.edit', $data); + } + + public function edit($publicId) + { + $expense = Expense::scope($publicId)->firstOrFail(); + $expense->expense_date = Utils::fromSqlDate($expense->expense_date); + + $data = array( + 'vendor' => null, + 'expense' => $expense, + 'method' => 'PUT', + 'url' => 'expenses/'.$publicId, + 'title' => 'Edit Expense', + 'vendors' => Vendor::scope()->with('vendorcontacts')->orderBy('name')->get(), + 'vendorPublicId' => $expense->vendor_id, + 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), + 'clientPublicId' => $expense->invoice_client_id, + ); + + $data = array_merge($data, self::getViewModel()); + + if (Auth::user()->account->isNinjaAccount()) { + if ($account = Account::whereId($client->public_id)->first()) { + $data['proPlanPaid'] = $account['pro_plan_paid']; + } + } + + return View::make('expenses.edit', $data); + } + + /** + * Update the specified resource in storage. + * + * @param int $id + * @return Response + */ + public function update(UpdateExpenseRequest $request) + { + $client = $this->expenseRepo->save($request->input()); + + Session::flash('message', trans('texts.updated_expense')); + + return redirect()->to('expenses'); + } + + public function store(CreateExpenseRequest $request) + { + $expense = $this->expenseRepo->save($request->input()); + + Session::flash('message', trans('texts.created_expense')); + + return redirect()->to('expenses'); + } + + public function bulk() + { + $action = Input::get('action'); + $ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids'); + + switch($action) + { + case 'invoice': + $expenses = Expense::scope($ids)->get(); + $clientPublicId = null; + $data = []; + + // Validate that either all expenses do not have a client or if there is a client, it is the same client + foreach ($expenses as $expense) + { + if ($expense->client_id) { + if (!$clientPublicId) { + $clientPublicId = $expense->client_id; + } else if ($clientPublicId != $expense->client_id) { + Session::flash('error', trans('texts.expense_error_multiple_clients')); + return Redirect::to('expenses'); + } + } + + if ($expense->invoice_id) { + Session::flash('error', trans('texts.expense_error_invoiced')); + return Redirect::to('expenses'); + } + + if ($expense->should_be_invoiced == 0) { + Session::flash('error', trans('texts.expense_error_should_not_be_invoiced')); + return Redirect::to('expenses'); + } + + $account = Auth::user()->account; + $data[] = [ + 'publicId' => $expense->public_id, + 'description' => $expense->public_notes, + 'qty' => 1, + 'cost' => $expense->amount, + ]; + } + + return Redirect::to("invoices/create/{$clientPublicId}")->with('expenses', $data); + break; + + default: + $count = $this->expenseService->bulk($ids, $action); + } + + if ($count > 0) { + $message = Utils::pluralize($action.'d_expense', $count); + Session::flash('message', $message); + } + + return Redirect::to('expenses'); + } + + 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, + ]; + } + + public function show($publicId) + { + $expense = Expense::withTrashed()->scope($publicId)->firstOrFail(); + + if($expense) { + Utils::trackViewed($expense->getDisplayName(), 'expense'); + } + + $actionLinks = [ + ['label' => trans('texts.new_expense'), 'url' => '/expenses/create/'] + ]; + + $data = array( + 'actionLinks' => $actionLinks, + 'showBreadcrumbs' => false, + 'expense' => $expense, + 'credit' =>0, + 'vendor' => $expense->vendor, + 'title' => trans('texts.view_expense',['expense' => $expense->public_id]), + ); + + return View::make('expenses.show', $data); + } +} diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php index 79d5f82fa341..540e38d97296 100644 --- a/app/Http/Controllers/ExportController.php +++ b/app/Http/Controllers/ExportController.php @@ -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; } } \ No newline at end of file diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 1d165d0de9cb..44f33b5cc6a4 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -319,6 +319,7 @@ class InvoiceController extends BaseController 'recurringDueDateHelp' => $recurringDueDateHelp, 'invoiceLabels' => Auth::user()->account->getInvoiceLabels(), 'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null, + 'expenses' => Session::get('expenses') ? json_encode(Session::get('expenses')) : null, ]; } diff --git a/app/Http/Controllers/PaymentTermController.php b/app/Http/Controllers/PaymentTermController.php new file mode 100644 index 000000000000..623ca1bf42da --- /dev/null +++ b/app/Http/Controllers/PaymentTermController.php @@ -0,0 +1,103 @@ +paymentTermService = $paymentTermService; + } + + public function index() + { + return Redirect::to('settings/' . ACCOUNT_PAYMENT_TERMS); + } + + public function getDatatable() + { + return $this->paymentTermService->getDatatable(); + } + + public function edit($publicId) + { + $data = [ + 'paymentTerm' => PaymentTerm::scope($publicId)->firstOrFail(), + 'method' => 'PUT', + 'url' => 'payment_terms/'.$publicId, + 'title' => trans('texts.edit_payment_term'), + ]; + + return View::make('accounts.payment_term', $data); + } + + public function create() + { + $data = [ + 'paymentTerm' => null, + 'method' => 'POST', + 'url' => 'payment_terms', + 'title' => trans('texts.create_payment_term'), + ]; + + return View::make('accounts.payment_term', $data); + } + + public function store() + { + return $this->save(); + } + + public function update($publicId) + { + return $this->save($publicId); + } + + private function save($publicId = false) + { + if ($publicId) { + $paymentTerm = PaymentTerm::scope($publicId)->firstOrFail(); + } else { + $paymentTerm = PaymentTerm::createNew(); + } + + $paymentTerm->name = trim(Input::get('name')); + $paymentTerm->num_days = Utils::parseInt(Input::get('num_days')); + $paymentTerm->save(); + + $message = $publicId ? trans('texts.updated_payment_term') : trans('texts.created_payment_term'); + Session::flash('message', $message); + + return Redirect::to('settings/' . ACCOUNT_PAYMENT_TERMS); + } + + public function bulk() + { + $action = Input::get('bulk_action'); + $ids = Input::get('bulk_public_id'); + $count = $this->paymentTermService->bulk($ids, $action); + + Session::flash('message', trans('texts.archived_payment_term')); + + return Redirect::to('settings/' . ACCOUNT_PAYMENT_TERMS); + } + +} diff --git a/app/Http/Controllers/PublicVendorController.php b/app/Http/Controllers/PublicVendorController.php new file mode 100644 index 000000000000..32fb7eeb874e --- /dev/null +++ b/app/Http/Controllers/PublicVendorController.php @@ -0,0 +1,198 @@ +activityRepo = $activityRepo; + $this->vendor = $activityRepo->vendor; + } + + public function dashboard() + { + if (!$invitation = $this->getInvitation()) { + return $this->returnError(); + } + + $account = $invitation->account; + $color = $account->primary_color ? $account->primary_color : '#0b4d78'; + + $data = [ + 'color' => $color, + 'account' => $account, + 'client' => $this->vendor, + 'hideLogo' => $account->isWhiteLabel(), + 'clientViewCSS' => $account->clientViewCSS(), + ]; + + return response()->view('invited.dashboard', $data); + } + + public function activityDatatable() + { + if (!$invitation = $this->getInvitation()) { + return false; + } + + + $query = $this->activityRepo->findByClientId($invoice->client_id); + $query->where('vendor_activities.adjustment', '!=', 0); + + return Datatable::query($query) + ->addColumn('vendor_activities.id', function ($model) { return Utils::timestampToDateTimeString(strtotime($model->created_at)); }) + ->addColumn('activity_type_id', function ($model) { + $data = [ + 'client' => Utils::getClientDisplayName($model), + 'user' => $model->is_system ? ('' . trans('texts.system') . '') : ($model->user_first_name . ' ' . $model->user_last_name), + 'invoice' => trans('texts.invoice') . ' ' . $model->invoice, + 'contact' => Utils::getClientDisplayName($model), + 'payment' => trans('texts.payment') . ($model->payment ? ' ' . $model->payment : ''), + ]; + + return trans("texts.activity_{$model->activity_type_id}", $data); + }) + ->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id, $model->country_id); }) + ->addColumn('adjustment', function ($model) { return $model->adjustment != 0 ? Utils::wrapAdjustment($model->adjustment, $model->currency_id, $model->country_id) : ''; }) + ->make(); + } + + public function invoiceIndex() + { + if (!$invitation = $this->getInvitation()) { + return $this->returnError(); + } + $account = $invitation->account; + $color = $account->primary_color ? $account->primary_color : '#0b4d78'; + + $data = [ + 'color' => $color, + 'hideLogo' => $account->isWhiteLabel(), + 'clientViewCSS' => $account->clientViewCSS(), + 'title' => trans('texts.invoices'), + 'entityType' => ENTITY_INVOICE, + 'columns' => Utils::trans(['invoice_number', 'invoice_date', 'invoice_total', 'balance_due', 'due_date']), + ]; + + return response()->view('public_list', $data); + } + + public function invoiceDatatable() + { + if (!$invitation = $this->getInvitation()) { + return ''; + } + + return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_INVOICE, Input::get('sSearch')); + } + + + public function paymentIndex() + { + if (!$invitation = $this->getInvitation()) { + return $this->returnError(); + } + $account = $invitation->account; + $color = $account->primary_color ? $account->primary_color : '#0b4d78'; + + $data = [ + 'color' => $color, + 'hideLogo' => $account->isWhiteLabel(), + 'clientViewCSS' => $account->clientViewCSS(), + 'entityType' => ENTITY_PAYMENT, + 'title' => trans('texts.payments'), + 'columns' => Utils::trans(['invoice', 'transaction_reference', 'method', 'payment_amount', 'payment_date']) + ]; + + return response()->view('public_list', $data); + } + + public function paymentDatatable() + { + if (!$invitation = $this->getInvitation()) { + return false; + } + $payments = $this->paymentRepo->findForContact($invitation->contact->id, Input::get('sSearch')); + + return Datatable::query($payments) + ->addColumn('invoice_number', function ($model) { return $model->invitation_key ? link_to('/view/'.$model->invitation_key, $model->invoice_number) : $model->invoice_number; }) + ->addColumn('transaction_reference', function ($model) { return $model->transaction_reference ? $model->transaction_reference : 'Manual entry'; }) + ->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? 'Online payment' : ''); }) + ->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); }) + ->addColumn('payment_date', function ($model) { return Utils::dateToString($model->payment_date); }) + ->make(); + } + + public function quoteIndex() + { + if (!$invitation = $this->getInvitation()) { + return $this->returnError(); + } + $account = $invitation->account; + $color = $account->primary_color ? $account->primary_color : '#0b4d78'; + + $data = [ + 'color' => $color, + 'hideLogo' => $account->isWhiteLabel(), + 'clientViewCSS' => $account->clientViewCSS(), + 'title' => trans('texts.quotes'), + 'entityType' => ENTITY_QUOTE, + 'columns' => Utils::trans(['quote_number', 'quote_date', 'quote_total', 'due_date']), + ]; + + return response()->view('public_list', $data); + } + + + public function quoteDatatable() + { + if (!$invitation = $this->getInvitation()) { + return false; + } + + return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_QUOTE, Input::get('sSearch')); + } + + private function returnError() + { + return response()->view('error', [ + 'error' => trans('texts.invoice_not_found'), + 'hideHeader' => true, + 'clientViewCSS' => $account->clientViewCSS(), + ]); + } + + private function getInvitation() + { + $invitationKey = session('invitation_key'); + + if (!$invitationKey) { + return false; + } + + $invitation = VendorInvitation::where('invitation_key', '=', $invitationKey)->first(); + + if (!$invitation || $invitation->is_deleted) { + return false; + } + + $invoice = $invitation->invoice; + + if (!$invoice || $invoice->is_deleted) { + return false; + } + + return $invitation; + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/VendorActivityController.php b/app/Http/Controllers/VendorActivityController.php new file mode 100644 index 000000000000..4c3ddd7d656d --- /dev/null +++ b/app/Http/Controllers/VendorActivityController.php @@ -0,0 +1,27 @@ +activityService = $activityService; + } + + public function getDatatable($vendorPublicId) + { + return $this->activityService->getDatatable($vendorPublicId); + } +} diff --git a/app/Http/Controllers/VendorApiController.php b/app/Http/Controllers/VendorApiController.php new file mode 100644 index 000000000000..80236226dda0 --- /dev/null +++ b/app/Http/Controllers/VendorApiController.php @@ -0,0 +1,94 @@ +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); + } +} diff --git a/app/Http/Controllers/VendorController.php b/app/Http/Controllers/VendorController.php new file mode 100644 index 000000000000..c7eced17f500 --- /dev/null +++ b/app/Http/Controllers/VendorController.php @@ -0,0 +1,212 @@ +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', + '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_vendor'), 'url' => '/vendors/create/' . $vendor->public_id] + ]; + + $data = array( + 'actionLinks' => $actionLinks, + 'showBreadcrumbs' => false, + 'vendor' => $vendor, + 'totalexpense' => $vendor->getTotalExpense(), + 'title' => trans('texts.view_vendor'), + 'hasRecurringInvoices' => false, + 'hasQuotes' => false, + 'hasTasks' => false, + ); + + 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'), + 'paymentTerms' => PaymentTerm::get(), + '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'); + } + } +} diff --git a/app/Http/Requests/CreateExpenseRequest.php b/app/Http/Requests/CreateExpenseRequest.php new file mode 100644 index 000000000000..a2c08cdfb8d7 --- /dev/null +++ b/app/Http/Requests/CreateExpenseRequest.php @@ -0,0 +1,30 @@ + 'required|positive', + ]; + } +} diff --git a/app/Http/Requests/CreatePaymentTermRequest.php b/app/Http/Requests/CreatePaymentTermRequest.php new file mode 100644 index 000000000000..d8581793160e --- /dev/null +++ b/app/Http/Requests/CreatePaymentTermRequest.php @@ -0,0 +1,30 @@ + 'required', + 'name' => 'required', + ]; + } +} diff --git a/app/Http/Requests/CreateVendorRequest.php b/app/Http/Requests/CreateVendorRequest.php new file mode 100644 index 000000000000..7186077fc666 --- /dev/null +++ b/app/Http/Requests/CreateVendorRequest.php @@ -0,0 +1,44 @@ + '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() + ); + } +} diff --git a/app/Http/Requests/UpdateExpenseRequest.php b/app/Http/Requests/UpdateExpenseRequest.php new file mode 100644 index 000000000000..9170e9df02ab --- /dev/null +++ b/app/Http/Requests/UpdateExpenseRequest.php @@ -0,0 +1,32 @@ + 'required|positive', + 'public_notes' => 'required', + 'expense_date' => 'required', + ]; + } +} diff --git a/app/Http/Requests/UpdatePaymentRequest.php b/app/Http/Requests/UpdatePaymentRequest.php index 83b192280849..29ac70e85e74 100644 --- a/app/Http/Requests/UpdatePaymentRequest.php +++ b/app/Http/Requests/UpdatePaymentRequest.php @@ -2,7 +2,6 @@ use App\Http\Requests\Request; use Illuminate\Validation\Factory; -use App\Models\Invoice; class UpdatePaymentRequest extends Request { @@ -24,5 +23,6 @@ class UpdatePaymentRequest extends Request public function rules() { return []; + } } diff --git a/app/Http/Requests/UpdatePaymentTermRequest.php b/app/Http/Requests/UpdatePaymentTermRequest.php new file mode 100644 index 000000000000..b3d4f536bc6e --- /dev/null +++ b/app/Http/Requests/UpdatePaymentTermRequest.php @@ -0,0 +1,30 @@ + 'required|positive', + ]; + + } +} diff --git a/app/Http/Requests/UpdateVendorRequest.php b/app/Http/Requests/UpdateVendorRequest.php new file mode 100644 index 000000000000..568166735d8c --- /dev/null +++ b/app/Http/Requests/UpdateVendorRequest.php @@ -0,0 +1,29 @@ + 'valid_contacts', + ]; + } +} diff --git a/app/Http/routes.php b/app/Http/routes.php index d2b7d42a4bf5..3445ced1cbb1 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -119,6 +119,11 @@ Route::group(['middleware' => 'auth'], function() { Route::get('settings/{section?}', 'AccountController@showSection'); Route::post('settings/{section?}', 'AccountController@doSection'); + // Payment term + Route::get('api/payment_terms', array('as'=>'api.payment_terms', 'uses'=>'PaymentTermController@getDatatable')); + Route::resource('payment_terms', 'PaymentTermController'); + Route::post('payment_terms/bulk', 'PaymentTermController@bulk'); + Route::get('account/getSearchData', array('as' => 'getSearchData', 'uses' => 'AccountController@getSearchData')); Route::post('user/setTheme', 'UserController@setTheme'); Route::post('remove_logo', 'AccountController@removeLogo'); @@ -181,6 +186,21 @@ 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'); + + // Expense + Route::resource('expenses', 'ExpenseController'); + Route::get('expenses/create/{vendor_id?}', 'ExpenseController@create'); + Route::get('api/expense', array('as'=>'api.expenses', 'uses'=>'ExpenseApiController@getDatatable')); + Route::get('api/expenseVendor/{id}', array('as'=>'api.expense', 'uses'=>'ExpenseApiController@getDatatableVendor')); + Route::get('api/expenseactivities/{expense_id?}', array('as'=>'api.expenseactivities', 'uses'=>'ExpenseActivityController@getDatatable')); + Route::post('expenses/bulk', 'ExpenseController@bulk'); }); // Route groups for API @@ -202,6 +222,12 @@ 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'); + + //Expense + Route::resource('expenses', 'ExpenseApiController'); }); // Redirects for legacy links @@ -242,6 +268,7 @@ if (!defined('CONTACT_EMAIL')) { define('ENV_STAGING', 'staging'); define('RECENTLY_VIEWED', 'RECENTLY_VIEWED'); + define('ENTITY_CLIENT', 'client'); define('ENTITY_CONTACT', 'contact'); define('ENTITY_INVOICE', 'invoice'); @@ -258,9 +285,15 @@ if (!defined('CONTACT_EMAIL')) { define('ENTITY_TAX_RATE', 'tax_rate'); define('ENTITY_PRODUCT', 'product'); define('ENTITY_ACTIVITY', 'activity'); + define('ENTITY_VENDOR','vendor'); + define('ENTITY_VENDOR_ACTIVITY','vendor_activity'); + define('ENTITY_EXPENSE', 'expense'); + define('ENTITY_PAYMENT_TERM','payment_term'); + define('ENTITY_EXPENSE_ACTIVITY','expense_activity'); define('PERSON_CONTACT', 'contact'); define('PERSON_USER', 'user'); + define('PERSON_VENDOR_CONTACT','vendorcontact'); define('BASIC_SETTINGS', 'basic_settings'); define('ADVANCED_SETTINGS', 'advanced_settings'); @@ -287,6 +320,7 @@ if (!defined('CONTACT_EMAIL')) { define('ACCOUNT_API_TOKENS', 'api_tokens'); define('ACCOUNT_CUSTOMIZE_DESIGN', 'customize_design'); define('ACCOUNT_SYSTEM_SETTINGS', 'system_settings'); + define('ACCOUNT_PAYMENT_TERMS','payment_terms'); define('ACTION_RESTORE', 'restore'); define('ACTION_ARCHIVE', 'archive'); @@ -327,6 +361,18 @@ 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); + + // expenses + define('ACTIVITY_TYPE_CREATE_EXPENSE', 34); + define('ACTIVITY_TYPE_ARCHIVE_EXPENSE', 35); + define('ACTIVITY_TYPE_DELETE_EXPENSE', 36); + define('ACTIVITY_TYPE_RESTORE_EXPENSE', 37); + define('DEFAULT_INVOICE_NUMBER', '0001'); define('RECENTLY_VIEWED_LIMIT', 8); define('LOGGED_ERROR_LIMIT', 100); @@ -358,6 +404,10 @@ 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); @@ -430,6 +480,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'); diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index d86ff218ca70..cf3bbc2e0e41 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -142,7 +142,7 @@ class Utils $history = Session::get(RECENTLY_VIEWED); $last = $history[0]; $penultimate = count($history) > 1 ? $history[1] : $last; - + return Request::url() == $last->url ? $penultimate->url : $last->url; } @@ -254,7 +254,7 @@ class Utils $data = Cache::get($type)->filter(function($item) use ($id) { return $item->id == $id; }); - + return $data->first(); } @@ -349,7 +349,7 @@ class Utils if (!$date) { return false; } - + $dateTime = new DateTime($date); $timestamp = $dateTime->getTimestamp(); $format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT); @@ -473,7 +473,7 @@ class Utils } array_unshift($data, $object); - + if (isset($counts[Auth::user()->account_id]) && $counts[Auth::user()->account_id] > RECENTLY_VIEWED_LIMIT) { array_pop($data); } @@ -582,6 +582,17 @@ class Utils } } + public static function getVendorDisplayName($model) + { + if(is_null($model)) + return ''; + + if($model->vendor_name) + return $model->vendor_name; + + return 'No vendor name'; + } + public static function getPersonDisplayName($firstName, $lastName, $email) { if ($firstName || $lastName) { @@ -613,7 +624,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; } } @@ -671,7 +684,7 @@ class Utils if ($publicId) { $data['id'] = $publicId; } - + return $data; } @@ -717,7 +730,7 @@ class Utils $str .= 'ENTITY_DELETED '; } } - + if ($model->deleted_at && $model->deleted_at != '0000-00-00') { $str .= 'ENTITY_ARCHIVED '; } @@ -737,7 +750,7 @@ class Utils fwrite($output, "\n"); } - + public static function getFirst($values) { if (is_array($values)) { @@ -902,7 +915,7 @@ class Utils if (!preg_match("~^(?:f|ht)tps?://~i", $url)) { $url = "http://" . $url; } - + return $url; } } diff --git a/app/Listeners/ExpenseActivityListener.php b/app/Listeners/ExpenseActivityListener.php new file mode 100644 index 000000000000..5e3cf1d9e705 --- /dev/null +++ b/app/Listeners/ExpenseActivityListener.php @@ -0,0 +1,57 @@ +activityRepo = $activityRepo; + } + + // Expenses + public function createdExpense(ExpenseWasCreated $event) + { + $this->activityRepo->create( + $event->expense, + ACTIVITY_TYPE_CREATE_EXPENSE + ); + } + + public function deletedExpense(ExpenseWasDeleted $event) + { + $this->activityRepo->create( + $event->expense, + ACTIVITY_TYPE_DELETE_EXPENSE + ); + } + + public function archivedExpense(ExpenseWasArchived $event) + { + /* + if ($event->client->is_deleted) { + return; + } + */ + + $this->activityRepo->create( + $event->expense, + ACTIVITY_TYPE_ARCHIVE_EXPENSE + ); + } + + public function restoredExpense(ExpenseWasRestored $event) + { + $this->activityRepo->create( + $event->expense, + ACTIVITY_TYPE_RESTORE_EXPENSE + ); + } +} diff --git a/app/Listeners/ExpenseListener.php b/app/Listeners/ExpenseListener.php new file mode 100644 index 000000000000..c8b0e7db5966 --- /dev/null +++ b/app/Listeners/ExpenseListener.php @@ -0,0 +1,25 @@ +expenseRepo = $expenseRepo; + } + + public function deletedInvoice(InvoiceWasDeleted $event) + { + // Release any tasks associated with the deleted invoice + Expense::where('invoice_id', '=', $event->invoice->id) + ->update(['invoice_id' => null]); + } +} diff --git a/app/Listeners/SubscriptionListener.php b/app/Listeners/SubscriptionListener.php index 7ef7a1116e74..fab5a2c57493 100644 --- a/app/Listeners/SubscriptionListener.php +++ b/app/Listeners/SubscriptionListener.php @@ -9,6 +9,9 @@ use App\Events\InvoiceWasCreated; use App\Events\CreditWasCreated; use App\Events\PaymentWasCreated; +use App\Events\VendorWasCreated; +use App\Events\ExpenseWasCreated; + class SubscriptionListener { public function createdClient(ClientWasCreated $event) @@ -44,4 +47,15 @@ class SubscriptionListener Utils::notifyZapier($subscription, $entity); } } + + public function createdVendor(VendorWasCreated $event) + { + $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_VENDOR, $event->vendor); + } + + public function createdExpense(ExpenseWasCreated $event) + { + $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_EXPENSE, $event->expense); + } + } diff --git a/app/Listeners/VendorActivityListener.php b/app/Listeners/VendorActivityListener.php new file mode 100644 index 000000000000..181b50feb8bb --- /dev/null +++ b/app/Listeners/VendorActivityListener.php @@ -0,0 +1,55 @@ +activityRepo = $activityRepo; + } + + // Vendors + public function createdVendor(VendorWasCreated $event) + { + $this->activityRepo->create( + $event->vendor, + ACTIVITY_TYPE_CREATE_VENDOR + ); + } + + public function deletedVendor(VendorWasDeleted $event) + { + $this->activityRepo->create( + $event->vendor, + ACTIVITY_TYPE_DELETE_VENDOR + ); + } + + public function archivedVendor(VendorWasArchived $event) + { + if ($event->vendor->is_deleted) { + return; + } + + $this->activityRepo->create( + $event->vendor, + ACTIVITY_TYPE_ARCHIVE_VENDOR + ); + } + + public function restoredVendor(VendorWasRestored $event) + { + $this->activityRepo->create( + $event->vendor, + ACTIVITY_TYPE_RESTORE_VENDOR + ); + } +} diff --git a/app/Models/Account.php b/app/Models/Account.php index bf57ab820fbc..d774114a3dfa 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -30,6 +30,7 @@ class Account extends Eloquent ACCOUNT_PRODUCTS, ACCOUNT_NOTIFICATIONS, ACCOUNT_IMPORT_EXPORT, + ACCOUNT_PAYMENT_TERMS, ]; public static $advancedSettings = [ diff --git a/app/Models/Expense.php b/app/Models/Expense.php new file mode 100644 index 000000000000..d97bc5099121 --- /dev/null +++ b/app/Models/Expense.php @@ -0,0 +1,101 @@ +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 getName() + { + if($this->expense_number) + return $this->expense_number; + + return $this->public_id; + } + + public function getDisplayName() + { + return $this->getName(); + } + + public function getRoute() + { + return "/expenses/{$this->public_id}"; + } + + public function getEntityType() + { + return ENTITY_EXPENSE; + } + + public function apply($amount) + { + if ($amount > $this->balance) { + $applied = $this->balance; + $this->balance = 0; + } else { + $applied = $amount; + $this->balance = $this->balance - $amount; + } + + $this->save(); + + return $applied; + } +} + +Expense::creating(function ($expense) { + $expense->setNullValues(); +}); + +Expense::created(function ($expense) { + event(new ExpenseWasCreated($expense)); +}); + +Expense::updating(function ($expense) { + $expense->setNullValues(); +}); + +Expense::updated(function ($expense) { + event(new ExpenseWasUpdated($expense)); +}); + +Expense::deleting(function ($expense) { + $expense->setNullValues(); +}); + +Expense::deleted(function ($expense) { + event(new ExpenseWasDeleted($expense)); +}); diff --git a/app/Models/ExpenseActivity.php b/app/Models/ExpenseActivity.php new file mode 100644 index 000000000000..bd72098518c1 --- /dev/null +++ b/app/Models/ExpenseActivity.php @@ -0,0 +1,63 @@ +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 vendor() + { + return $this->belongsTo('App\Models\Vendor')->withTrashed(); + } + + public function expense() + { + return $this->belongsTo('App\Models\Expense')->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; + $expense = $this->expense; + + if($expense) + { + $route = link_to($expense->getRoute(), $expense->getDisplayName()); + } else { + $route ='no expense id'; + } + + $data = [ + 'expense' => $route, + 'user' => $isSystem ? '' . trans('texts.system') . '' : $user->getDisplayName(), + ]; + + return trans("texts.activity_{$activityTypeId}", $data); + } +} diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 3d01216629cf..d486b40c0c22 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -28,6 +28,7 @@ class Invoice extends EntityModel implements BalanceAffecting 'is_recurring' => 'boolean', 'has_tasks' => 'boolean', 'auto_bill' => 'boolean', + 'has_expenses' => 'boolean', ]; // used for custom invoice numbers @@ -82,7 +83,7 @@ class Invoice extends EntityModel implements BalanceAffecting public function getDisplayName() { - return $this->is_recurring ? trans('texts.recurring') : $this->invoice_number; + return $this->is_recurring ? trans('texts.recurring') : $this->invoice_number; } public function affectsBalance() @@ -136,7 +137,7 @@ class Invoice extends EntityModel implements BalanceAffecting return ($this->amount - $this->balance); } - + public function trashed() { if ($this->client && $this->client->trashed()) { @@ -212,7 +213,7 @@ class Invoice extends EntityModel implements BalanceAffecting $invitation->markSent($messageId); - // if the user marks it as sent rather than acually sending it + // if the user marks it as sent rather than acually sending it // then we won't track it in the activity log if (!$notify) { return; @@ -375,6 +376,7 @@ class Invoice extends EntityModel implements BalanceAffecting 'has_tasks', 'custom_text_value1', 'custom_text_value2', + 'has_expenses', ]); $this->client->setVisible([ @@ -468,7 +470,7 @@ class Invoice extends EntityModel implements BalanceAffecting // Fix for months with less than 31 days $transformerConfig = new \Recurr\Transformer\ArrayTransformerConfig(); $transformerConfig->enableLastDayOfMonthFix(); - + $transformer = new \Recurr\Transformer\ArrayTransformer(); $transformer->setConfig($transformerConfig); $dates = $transformer->transform($rule); @@ -494,7 +496,7 @@ class Invoice extends EntityModel implements BalanceAffecting if (count($schedule) < 2) { return null; } - + return $schedule[1]->getStart(); } @@ -651,7 +653,7 @@ class Invoice extends EntityModel implements BalanceAffecting if (!$nextSendDate = $this->getNextSendDate()) { return false; } - + return $this->account->getDateTime() >= $nextSendDate; } */ diff --git a/app/Models/PaymentTerm.php b/app/Models/PaymentTerm.php index de8cced5db72..dbb788aef1c2 100644 --- a/app/Models/PaymentTerm.php +++ b/app/Models/PaymentTerm.php @@ -1,8 +1,17 @@ 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; diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php new file mode 100644 index 000000000000..481967232ff6 --- /dev/null +++ b/app/Models/Vendor.php @@ -0,0 +1,248 @@ + '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->fill($data); + $contact->is_primary = $isPrimary; + + return $this->vendorContacts()->save($contact); + } + + public function getRoute() + { + return "/vendors/{$this->public_id}"; + } + + public function getName() + { + return $this->name; + } + + public function getDisplayName() + { + return $this->getName(); + } + + 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 getCurrencyId() + { + if ($this->currency_id) { + return $this->currency_id; + } + + if (!$this->account) { + $this->load('account'); + } + + return $this->account->currency_id ?: DEFAULT_CURRENCY; + } + + public function getTotalExpense() + { + return DB::table('expenses') + ->where('vendor_id', '=', $this->id) + ->whereNull('deleted_at') + ->sum('amount'); + } +} + +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)); +}); + + +Vendor::deleting(function ($vendor) { + $vendor->setNullValues(); +}); + +Vendor::deleted(function ($vendor) { + event(new VendorWasDeleted($vendor)); +}); diff --git a/app/Models/VendorActivity.php b/app/Models/VendorActivity.php new file mode 100644 index 000000000000..2776a631a677 --- /dev/null +++ b/app/Models/VendorActivity.php @@ -0,0 +1,61 @@ +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; + + if($vendor) { + $route = $vendor->getRoute(); + + $data = [ + 'vendor' => link_to($route, $vendor->getDisplayName()), + 'user' => $isSystem ? '' . trans('texts.system') . '' : $user->getDisplayName(), + 'vendorcontact' => $contactId ? $vendor->getDisplayName() : $user->getDisplayName(), + ]; + } else { + return trans("texts.invalid_activity"); + } + return trans("texts.activity_{$activityTypeId}", $data); + } +} diff --git a/app/Models/VendorContact.php b/app/Models/VendorContact.php new file mode 100644 index 000000000000..5546b27d2adb --- /dev/null +++ b/app/Models/VendorContact.php @@ -0,0 +1,68 @@ +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 ''; + } + } +} diff --git a/app/Models/VendorInvitation.php b/app/Models/VendorInvitation.php new file mode 100644 index 000000000000..ffb3776bb05f --- /dev/null +++ b/app/Models/VendorInvitation.php @@ -0,0 +1,90 @@ +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, '
') : 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(); + } +} diff --git a/app/Ninja/Import/BaseTransformer.php b/app/Ninja/Import/BaseTransformer.php index ea05584c20d1..8e17bfeec36f 100644 --- a/app/Ninja/Import/BaseTransformer.php +++ b/app/Ninja/Import/BaseTransformer.php @@ -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; + } + } \ No newline at end of file diff --git a/app/Ninja/Import/CSV/VendorTransformer.php b/app/Ninja/Import/CSV/VendorTransformer.php new file mode 100644 index 000000000000..464274e5a4fa --- /dev/null +++ b/app/Ninja/Import/CSV/VendorTransformer.php @@ -0,0 +1,35 @@ +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, + ]; + }); + } +} diff --git a/app/Ninja/Import/FreshBooks/VendorTransformer.php b/app/Ninja/Import/FreshBooks/VendorTransformer.php new file mode 100644 index 000000000000..c083360aa305 --- /dev/null +++ b/app/Ninja/Import/FreshBooks/VendorTransformer.php @@ -0,0 +1,36 @@ +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), + ]; + }); + } +} diff --git a/app/Ninja/Import/Harvest/VendorContactTransformer.php b/app/Ninja/Import/Harvest/VendorContactTransformer.php new file mode 100644 index 000000000000..3aa0b0b36aa2 --- /dev/null +++ b/app/Ninja/Import/Harvest/VendorContactTransformer.php @@ -0,0 +1,24 @@ +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, + ]; + }); + } +} diff --git a/app/Ninja/Import/Harvest/VendorTransformer.php b/app/Ninja/Import/Harvest/VendorTransformer.php new file mode 100644 index 000000000000..efab1e6b66ad --- /dev/null +++ b/app/Ninja/Import/Harvest/VendorTransformer.php @@ -0,0 +1,20 @@ +hasVendor($data->vendor_name)) { + return false; + } + + return new Item($data, function ($data) { + return [ + 'name' => $data->vendor_name, + ]; + }); + } +} diff --git a/app/Ninja/Import/Hiveage/VendorTransformer.php b/app/Ninja/Import/Hiveage/VendorTransformer.php new file mode 100644 index 000000000000..dec1b62d1ccb --- /dev/null +++ b/app/Ninja/Import/Hiveage/VendorTransformer.php @@ -0,0 +1,35 @@ +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), + ]; + }); + } +} diff --git a/app/Ninja/Import/Invoiceable/VendorTransformer.php b/app/Ninja/Import/Invoiceable/VendorTransformer.php new file mode 100644 index 000000000000..1ec4a2876884 --- /dev/null +++ b/app/Ninja/Import/Invoiceable/VendorTransformer.php @@ -0,0 +1,34 @@ +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, + ], + ], + ]; + }); + } +} diff --git a/app/Ninja/Import/Nutcache/VendorTransformer.php b/app/Ninja/Import/Nutcache/VendorTransformer.php new file mode 100644 index 000000000000..b97f0811906e --- /dev/null +++ b/app/Ninja/Import/Nutcache/VendorTransformer.php @@ -0,0 +1,35 @@ +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, + ]; + }); + } +} diff --git a/app/Ninja/Import/Ronin/VendorTransformer.php b/app/Ninja/Import/Ronin/VendorTransformer.php new file mode 100644 index 000000000000..817de03d6647 --- /dev/null +++ b/app/Ninja/Import/Ronin/VendorTransformer.php @@ -0,0 +1,28 @@ +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, + ], + ], + ]; + }); + } +} diff --git a/app/Ninja/Import/Wave/VendorTransformer.php b/app/Ninja/Import/Wave/VendorTransformer.php new file mode 100644 index 000000000000..f2fe2f43e375 --- /dev/null +++ b/app/Ninja/Import/Wave/VendorTransformer.php @@ -0,0 +1,38 @@ +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), + ]; + }); + } +} diff --git a/app/Ninja/Import/Zoho/VendorTransformer.php b/app/Ninja/Import/Zoho/VendorTransformer.php new file mode 100644 index 000000000000..811a9f7ff2d9 --- /dev/null +++ b/app/Ninja/Import/Zoho/VendorTransformer.php @@ -0,0 +1,37 @@ +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), + ]; + }); + } +} diff --git a/app/Ninja/Mailers/VendorContactMailer.php b/app/Ninja/Mailers/VendorContactMailer.php new file mode 100644 index 000000000000..267bb90cec5a --- /dev/null +++ b/app/Ninja/Mailers/VendorContactMailer.php @@ -0,0 +1,152 @@ +vendor; + $account = $invoice->account; + + if (Auth::check()) { + $user = Auth::user(); + } else { + $user = $invitation->user; + if ($invitation->user->trashed()) { + $user = $account->users()->orderBy('id')->first(); + } + } + + if (!$user->email || !$user->registered) { + return trans('texts.email_errors.user_unregistered'); + } elseif (!$user->confirmed) { + return trans('texts.email_errors.user_unconfirmed'); + } elseif (!$invitation->contact->email) { + return trans('texts.email_errors.invalid_contact_email'); + } elseif ($invitation->contact->trashed()) { + return trans('texts.email_errors.inactive_contact'); + } + + $variables = [ + 'account' => $account, + 'vendor' => $vendor, + 'invitation' => $invitation + ]; + + $data = [ + 'body' => $this->processVariables($body, $variables), + 'link' => $invitation->getLink(), + 'entityType' => $invoice->getEntityType(), + 'invitation' => $invitation, + 'account' => $account, + 'vendor' => $vendor, + 'invoice' => $invoice, + ]; + + if ($account->attatchPDF()) { + $data['pdfString'] = $pdfString; + $data['pdfFileName'] = $invoice->getFileName(); + } + + $subject = $this->processVariables($subject, $variables); + $fromEmail = $user->email; + + if ($account->email_design_id == EMAIL_DESIGN_PLAIN) { + $view = ENTITY_INVOICE; + } else { + $view = 'design' . ($account->email_design_id - 1); + } + + $response = $this->sendTo($invitation->contact->email, $fromEmail, $account->getDisplayName(), $subject, $view, $data); + + if ($response === true) { + return true; + } else { + return $response; + } + } + + public function sendLicensePaymentConfirmation($name, $email, $amount, $license, $productId) + { + $view = 'license_confirmation'; + $subject = trans('texts.payment_subject'); + + if ($productId == PRODUCT_ONE_CLICK_INSTALL) { + $license = "Softaculous install license: $license"; + } elseif ($productId == PRODUCT_INVOICE_DESIGNS) { + $license = "Invoice designs license: $license"; + } elseif ($productId == PRODUCT_WHITE_LABEL) { + $license = "White label license: $license"; + } + + $data = [ + 'vendor' => $name, + 'amount' => Utils::formatMoney($amount, DEFAULT_CURRENCY, DEFAULT_COUNTRY), + 'license' => $license + ]; + + $this->sendTo($email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data); + } + + private function processVariables($template, $data) + { + $account = $data['account']; + $vendor = $data['vendor']; + $invitation = $data['invitation']; + $invoice = $invitation->invoice; + + $variables = [ + '$footer' => $account->getEmailFooter(), + '$vendor' => $vendor->getDisplayName(), + '$account' => $account->getDisplayName(), + '$contact' => $invitation->contact->getDisplayName(), + '$firstName' => $invitation->contact->first_name, + '$amount' => $account->formatMoney($data['amount'], $vendor), + '$invoice' => $invoice->invoice_number, + '$quote' => $invoice->invoice_number, + '$link' => $invitation->getLink(), + '$dueDate' => $account->formatDate($invoice->due_date), + '$viewLink' => $invitation->getLink(), + '$viewButton' => HTML::emailViewButton($invitation->getLink(), $invoice->getEntityType()), + '$paymentLink' => $invitation->getLink('payment'), + '$paymentButton' => HTML::emailPaymentButton($invitation->getLink('payment')), + '$customClient1' => $account->custom_vendor_label1, + '$customClient2' => $account->custom_vendor_label2, + '$customInvoice1' => $account->custom_invoice_text_label1, + '$customInvoice2' => $account->custom_invoice_text_label2, + ]; + + // Add variables for available payment types + foreach (Gateway::$paymentTypes as $type) { + $camelType = Gateway::getPaymentTypeName($type); + $type = Utils::toSnakeCase($camelType); + $variables["\${$camelType}Link"] = $invitation->getLink() . "/{$type}"; + $variables["\${$camelType}Button"] = HTML::emailPaymentButton($invitation->getLink('payment') . "/{$type}"); + } + + $str = str_replace(array_keys($variables), array_values($variables), $template); + + return $str; + } +} diff --git a/app/Ninja/Presenters/ExpensePresenter.php b/app/Ninja/Presenters/ExpensePresenter.php new file mode 100644 index 000000000000..3c7237d39d64 --- /dev/null +++ b/app/Ninja/Presenters/ExpensePresenter.php @@ -0,0 +1,18 @@ +entity->vendor ? $this->entity->vendor->getDisplayName() : ''; + } + + public function expense_date() + { + return Utils::fromSqlDate($this->entity->expense_date); + } +} \ No newline at end of file diff --git a/app/Ninja/Presenters/VendorPresenter.php b/app/Ninja/Presenters/VendorPresenter.php new file mode 100644 index 000000000000..b3da402bec40 --- /dev/null +++ b/app/Ninja/Presenters/VendorPresenter.php @@ -0,0 +1,12 @@ +entity->country ? $this->entity->country->name : ''; + } +} \ No newline at end of file diff --git a/app/Ninja/Repositories/ExpenseActivityRepository.php b/app/Ninja/Repositories/ExpenseActivityRepository.php new file mode 100644 index 000000000000..1406983962b5 --- /dev/null +++ b/app/Ninja/Repositories/ExpenseActivityRepository.php @@ -0,0 +1,65 @@ +vendor_id = $entity->vendor_id; + $activity->contact_id = $entity->contact_id; + $activity->activity_type_id = $activityTypeId; + $activity->message = $activity->getMessage(); + $activity->expense_id = $entity->id; + $activity->save(); + + return $activity; + } + + private function getBlank($entity) + { + $activity = new ExpenseActivity(); + + 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; + } + + $activity->token_id = session('token_id'); + $activity->ip = Request::getClientIp(); + + + return $activity; + } + + + public function findByExpenseId($expenseId) + { + return DB::table('expense_activities') + ->join('accounts', 'accounts.id', '=', 'expense_activities.account_id') + ->join('users', 'users.id', '=', 'expense_activities.user_id') + ->join('expenses','expenses.public_id', '=', 'expense_activities.expense_id') + ->where('expense_activities.expense_id', '=', $expenseId) + ->select('*', + 'users.first_name as user_first_name', + 'users.last_name as user_last_name', + 'users.email as user_email', + 'expenses.amount' + ); + + } +} diff --git a/app/Ninja/Repositories/ExpenseRepository.php b/app/Ninja/Repositories/ExpenseRepository.php new file mode 100644 index 000000000000..d1c6cb86622f --- /dev/null +++ b/app/Ninja/Repositories/ExpenseRepository.php @@ -0,0 +1,160 @@ +with('user') + ->withTrashed() + ->where('is_deleted', '=', false) + ->get(); + } + + public function findVendor($vendorPublicId) + { + $accountid = \Auth::user()->account_id; + $query = DB::table('expenses') + ->join('accounts', 'accounts.id', '=', 'expenses.account_id') + ->where('expenses.account_id', '=', $accountid) + ->where('expenses.vendor_id','=',$vendorPublicId) + ->select('expenses.id', + 'expenses.expense_date', + 'expenses.amount', + 'expenses.public_notes', + 'expenses.public_id', + 'expenses.deleted_at','expenses.is_invoiced','expenses.should_be_invoiced','expenses.created_at'); + return $query; + } + + public function find($filter = null) + { + $accountid = \Auth::user()->account_id; + $query = DB::table('expenses') + ->join('accounts', 'accounts.id', '=', 'expenses.account_id') + ->leftjoin('vendors','vendors.public_id','=', 'expenses.vendor_id') + ->where('expenses.account_id', '=', $accountid) + ->select('expenses.account_id', + 'expenses.amount', + 'expenses.amount_cur', + 'expenses.currency_id', + 'expenses.deleted_at', + 'expenses.exchange_rate', + 'expenses.expense_date', + 'expenses.id', + 'expenses.is_deleted', + 'expenses.is_invoiced', + 'expenses.private_notes', + 'expenses.public_id', + 'expenses.public_notes', + 'expenses.should_be_invoiced', + 'expenses.vendor_id', + 'vendors.name as vendor_name', + 'vendors.public_id as vendor_public_id'); + + $showTrashed = \Session::get('show_trash:expense'); + + if (!$showTrashed) { + $query->where('expenses.deleted_at', '=', null); + } + + if ($filter) { + $query->where(function ($query) use ($filter) { + $query->where('expenses.public_notes', 'like', '%'.$filter.'%'); + }); + } + + return $query; + } + + public function save($input) + { + $publicId = isset($input['public_id']) ? $input['public_id'] : false; + + if ($publicId) { + $expense = Expense::scope($publicId)->firstOrFail(); + } else { + $expense = Expense::createNew(); + } + + // First auto fill + $expense->fill($input); + + // We can have an expense without a vendor + if(isset($input['vendor'])) { + $expense->vendor_id = $input['vendor']; + } + + $expense->expense_date = Utils::toSqlDate($input['expense_date']); + $expense->amount = Utils::parseFloat($input['amount']); + + if(isset($input['amount_cur'])) + $expense->amount_cur = Utils::parseFloat($input['amount_cur']); + + $expense->private_notes = trim($input['private_notes']); + $expense->public_notes = trim($input['public_notes']); + + if(isset($input['exchange_rate'])) + $expense->exchange_rate = Utils::parseFloat($input['exchange_rate']); + else + $expense->exchange_rate = 100; + + if($expense->exchange_rate == 0) + $expense->exchange_rate = 100; + + // set the currency + if(isset($input['currency_id'])) + $expense->currency_id = $input['currency_id']; + + if($expense->currency_id == 0) + $expense->currency_id = Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY); + + // Calculate the amount cur + $expense->amount_cur = ($expense->amount / 100) * $expense->exchange_rate; + + $expense->should_be_invoiced = isset($input['should_be_invoiced']) ? true : false; + if(isset($input['client'])) { + $expense->invoice_client_id = $input['client']; + } + $expense->save(); + + return $expense; + } + + public function bulk($ids, $action) + { + $expenses = Expense::withTrashed()->scope($ids)->get(); + + foreach ($expenses as $expense) { + if ($action == 'restore') { + $expense->restore(); + + $expense->is_deleted = false; + $expense->save(); + } else { + if ($action == 'delete') { + $expense->is_deleted = true; + $expense->save(); + } + + $expense->delete(); + } + } + + return count($tasks); + } + +} diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index 5aa5f6a9b73b..f94bafb97335 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -7,6 +7,7 @@ use App\Models\InvoiceItem; use App\Models\Invitation; use App\Models\Product; use App\Models\Task; +use App\Models\Expense; use App\Services\PaymentService; use App\Ninja\Repositories\BaseRepository; @@ -177,7 +178,7 @@ class InvoiceRepository extends BaseRepository $table->addColumn('balance', function ($model) { return $model->partial > 0 ? trans('texts.partial_remaining', [ - 'partial' => Utils::formatMoney($model->partial, $model->currency_id, $model->country_id), + 'partial' => Utils::formatMoney($model->partial, $model->currency_id, $model->country_id), 'balance' => Utils::formatMoney($model->balance, $model->currency_id, $model->country_id) ]) : Utils::formatMoney($model->balance, $model->currency_id, $model->country_id); @@ -206,6 +207,9 @@ class InvoiceRepository extends BaseRepository if (isset($data['has_tasks']) && filter_var($data['has_tasks'], FILTER_VALIDATE_BOOLEAN)) { $invoice->has_tasks = true; } + if (isset($data['has_expenses']) && filter_var($data['has_expenses'], FILTER_VALIDATE_BOOLEAN)) { + $invoice->has_expenses = true; + } } else { $invoice = Invoice::scope($publicId)->firstOrFail(); } @@ -276,7 +280,7 @@ class InvoiceRepository extends BaseRepository if (isset($data['po_number'])) { $invoice->po_number = trim($data['po_number']); } - + $invoice->invoice_design_id = isset($data['invoice_design_id']) ? $data['invoice_design_id'] : $account->invoice_design_id; if (isset($data['tax_name']) && isset($data['tax_rate']) && $data['tax_name']) { @@ -398,6 +402,14 @@ class InvoiceRepository extends BaseRepository $task->save(); } + if (isset($item['expense_public_id']) && $item['expense_public_id']) { + $expense = Expense::scope($item['expense_public_id'])->where('invoice_id', '=', null)->firstOrFail(); + $expense->invoice_id = $invoice->id; + $expense->invoice_client_id = $invoice->client_id; + $expense->is_invoiced = true; + $expense->save(); + } + if ($item['product_key']) { $productKey = trim($item['product_key']); if (\Auth::user()->account->update_products && ! strtotime($productKey)) { @@ -406,7 +418,10 @@ class InvoiceRepository extends BaseRepository $product = Product::createNew(); $product->product_key = trim($item['product_key']); } + $product->notes = $invoice->has_tasks ? '' : $item['notes']; + $product->notes = $invoice->has_expenses ? '' : $item['notes']; + $product->cost = $item['cost']; $product->save(); } @@ -642,7 +657,7 @@ class InvoiceRepository extends BaseRepository public function findNeedingReminding($account) { $dates = []; - + for ($i=1; $i<=3; $i++) { if ($date = $account->getReminderDate($i)) { $field = $account->{"field_reminder{$i}"} == REMINDER_FIELD_DUE_DATE ? 'due_date' : 'invoice_date'; diff --git a/app/Ninja/Repositories/PaymentTermRepository.php b/app/Ninja/Repositories/PaymentTermRepository.php new file mode 100644 index 000000000000..e631e9f1627a --- /dev/null +++ b/app/Ninja/Repositories/PaymentTermRepository.php @@ -0,0 +1,22 @@ +where('payment_terms.account_id', '=', $accountId) + ->where('payment_terms.deleted_at', '=', null) + ->select('payment_terms.public_id', 'payment_terms.name', 'payment_terms.num_days', 'payment_terms.deleted_at'); + } +} diff --git a/app/Ninja/Repositories/VendorActivityRepository.php b/app/Ninja/Repositories/VendorActivityRepository.php new file mode 100644 index 000000000000..7c59f80d438d --- /dev/null +++ b/app/Ninja/Repositories/VendorActivityRepository.php @@ -0,0 +1,101 @@ +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', '=', 'vendor_activities.account_id') + ->join('users', 'users.id', '=', 'vendor_activities.user_id') + ->join('vendors', 'vendors.id', '=', 'vendor_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' + + ); + } + +} \ No newline at end of file diff --git a/app/Ninja/Repositories/VendorContactRepository.php b/app/Ninja/Repositories/VendorContactRepository.php new file mode 100644 index 000000000000..242b1b9d0c54 --- /dev/null +++ b/app/Ninja/Repositories/VendorContactRepository.php @@ -0,0 +1,26 @@ +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; + } +} \ No newline at end of file diff --git a/app/Ninja/Repositories/VendorRepository.php b/app/Ninja/Repositories/VendorRepository.php new file mode 100644 index 000000000000..ae2a7f60a5e5 --- /dev/null +++ b/app/Ninja/Repositories/VendorRepository.php @@ -0,0 +1,91 @@ +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.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['vendorcontact']) && ! isset($data['vendorcontacts'])) { + return $vendor; + } + + $first = true; + $vendorcontacts = isset($data['vendorcontact']) ? [$data['vendorcontact']] : $data['vendorcontacts']; + + foreach ($vendorcontacts as $vendorcontact) { + $vendorcontact = $vendor->addVendorContact($vendorcontact, $first); + $first = false; + } + + return $vendor; + } +} diff --git a/app/Ninja/Transformers/InvoiceTransformer.php b/app/Ninja/Transformers/InvoiceTransformer.php index 09d24ae83260..8108115b2f01 100644 --- a/app/Ninja/Transformers/InvoiceTransformer.php +++ b/app/Ninja/Transformers/InvoiceTransformer.php @@ -24,7 +24,7 @@ class InvoiceTransformer extends EntityTransformer 'invoice_items', 'payments' ]; - + public function includeInvoiceItems(Invoice $invoice) { $transformer = new InvoiceItemTransformer($this->account, $this->serializer); @@ -77,6 +77,7 @@ class InvoiceTransformer extends EntityTransformer 'custom_value2' => (float) $invoice->custom_value2, 'custom_taxes1' => (bool) $invoice->custom_taxes1, 'custom_taxes2' => (bool) $invoice->custom_taxes2, + 'has_expenses' => (bool) $invoice->has_expenses, ]; } -} \ No newline at end of file +} diff --git a/app/Ninja/Transformers/VendorContactTransformer.php b/app/Ninja/Transformers/VendorContactTransformer.php new file mode 100644 index 000000000000..0166883aba4d --- /dev/null +++ b/app/Ninja/Transformers/VendorContactTransformer.php @@ -0,0 +1,24 @@ + (int) $contact->public_id, + 'first_name' => $contact->first_name, + 'last_name' => $contact->last_name, + 'email' => $contact->email, + 'updated_at' => $this->getTimestamp($contact->updated_at), + 'archived_at' => $this->getTimestamp($contact->deleted_at), + 'is_primary' => (bool) $contact->is_primary, + 'phone' => $contact->phone, + 'last_login' => $contact->last_login, + 'account_key' => $this->account->account_key, + ]; + } +} \ No newline at end of file diff --git a/app/Ninja/Transformers/VendorTransformer.php b/app/Ninja/Transformers/VendorTransformer.php new file mode 100644 index 000000000000..c02750bbaf80 --- /dev/null +++ b/app/Ninja/Transformers/VendorTransformer.php @@ -0,0 +1,92 @@ +account, $this->serializer); + return $this->includeCollection($vendor->contacts, $transformer, ENTITY_CONTACT); + } + + public function includeInvoices(Vendor $vendor) + { + $transformer = new InvoiceTransformer($this->account, $this->serializer); + return $this->includeCollection($vendor->invoices, $transformer, ENTITY_INVOICE); + } + + public function transform(Vendor $vendor) + { + return [ + 'id' => (int) $vendor->public_id, + 'name' => $vendor->name, + 'balance' => (float) $vendor->balance, + 'paid_to_date' => (float) $vendor->paid_to_date, + 'user_id' => (int) $vendor->user->public_id + 1, + 'account_key' => $this->account->account_key, + 'updated_at' => $this->getTimestamp($vendor->updated_at), + 'archived_at' => $this->getTimestamp($vendor->deleted_at), + 'address1' => $vendor->address1, + 'address2' => $vendor->address2, + 'city' => $vendor->city, + 'state' => $vendor->state, + 'postal_code' => $vendor->postal_code, + 'country_id' => (int) $vendor->country_id, + 'work_phone' => $vendor->work_phone, + 'private_notes' => $vendor->private_notes, + 'last_login' => $vendor->last_login, + 'website' => $vendor->website, + 'industry_id' => (int) $vendor->industry_id, + 'size_id' => (int) $vendor->size_id, + 'is_deleted' => (bool) $vendor->is_deleted, + 'payment_terms' => (int) $vendor->payment_terms, + 'vat_number' => $vendor->vat_number, + 'id_number' => $vendor->id_number, + 'language_id' => (int) $vendor->language_id, + 'currency_id' => (int) $vendor->currency_id + ]; + } +} \ No newline at end of file diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 74098a3966de..a094b01a6079 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -53,7 +53,11 @@ class AppServiceProvider extends ServiceProvider { $str .= '
  • '.trans("texts.credits").'
  • '.trans("texts.new_credit").'
  • '; - } + } else if ($type == ENTITY_EXPENSE) { + $str .= '
  • +
  • '.trans("texts.vendors").'
  • +
  • '.trans("texts.new_vendor").'
  • '; + } $str .= ' '; diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 37a09fbc44ca..1eb0ad9858ee 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -138,6 +138,36 @@ 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', + ], + + // Expense events + 'App\Events\ExpenseWasCreated' => [ + 'App\Listeners\ExpenseActivityListener@createdExpense', + 'App\Listeners\SubscriptionListener@createdExpense', + ], + 'App\Events\ExpenseWasArchived' => [ + 'App\Listeners\ExpenseActivityListener@archivedExpense', + ], + 'App\Events\ExpenseWasDeleted' => [ + 'App\Listeners\ExpenseActivityListener@deletedExpense', + ], + 'App\Events\ExpenseWasRestored' => [ + 'App\Listeners\ExpenseActivityListener@restoredExpense', + ], + ]; /** diff --git a/app/Services/ExpenseActivityService.php b/app/Services/ExpenseActivityService.php new file mode 100644 index 000000000000..bd75aac7d26a --- /dev/null +++ b/app/Services/ExpenseActivityService.php @@ -0,0 +1,62 @@ +activityRepo = $activityRepo; + $this->datatableService = $datatableService; + } + + public function getDatatable($expensePublicId = null) + { + $expenseId = Expense::getPrivateId($expensePublicId); + + $query = $this->activityRepo->findByExpenseId($expenseId); + + return $this->createDatatable(ENTITY_EXPENSE_ACTIVITY, $query); + } + + protected function getDatatableColumns($entityType, $hideExpense) + { + return [ + [ + 'expense_activities.id', + function ($model) { + return Utils::timestampToDateTimeString(strtotime($model->created_at)); + } + ], + [ + 'activity_type_id', + function ($model) { + $data = [ + 'expense' => link_to('/expenses/' . $model->public_id, trans('texts.view_expense',['expense' => $model->public_id])), + 'user' => $model->is_system ? '' . trans('texts.system') . '' : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email), + ]; + + return trans("texts.activity_{$model->activity_type_id}", $data); + } + ], + [ + 'amount', + function ($model) { + return Utils::formatMoney($model->amount); + } + ], + [ + 'expense_date', + function ($model) { + return Utils::fromSqlDate($model->expense_date); + } + ] + ]; + } +} \ No newline at end of file diff --git a/app/Services/ExpenseService.php b/app/Services/ExpenseService.php new file mode 100644 index 000000000000..558864798953 --- /dev/null +++ b/app/Services/ExpenseService.php @@ -0,0 +1,188 @@ +expenseRepo = $expenseRepo; + $this->datatableService = $datatableService; + } + + protected function getRepo() + { + return $this->expenseRepo; + } + + public function save($data) + { + return $this->expenseRepo->save($data); + } + + public function getDatatable($search) + { + $query = $this->expenseRepo->find($search); + + return $this->createDatatable(ENTITY_EXPENSE, $query); + } + + public function getDatatableVendor($vendorPublicId) + { + $query = $this->expenseRepo->findVendor($vendorPublicId); + return $this->datatableService->createDatatable(ENTITY_EXPENSE, + $query, + $this->getDatatableColumnsVendor(ENTITY_EXPENSE,false), + $this->getDatatableActionsVendor(ENTITY_EXPENSE), + false); + } + + protected function getDatatableColumns($entityType, $hideClient) + { + return [ + [ + 'vendor_name', + function ($model) + { + if($model->vendor_public_id) { + return link_to("vendors/{$model->vendor_public_id}", $model->vendor_name); + } else { + return 'No vendor' ; + } + } + ], + [ + 'amount', + function ($model) { + return Utils::formatMoney($model->amount, false, false); + } + ], + [ + 'expense_date', + function ($model) { + return Utils::fromSqlDate($model->expense_date); + } + ], + [ + 'public_notes', + function ($model) { + return $model->public_notes != null ? $model->public_notes : ''; + } + ], + [ + 'is_invoiced', + function ($model) { + return $model->is_invoiced ? trans('texts.yes') : trans('texts.no'); + } + ], + [ + 'should_be_invoiced', + function ($model) { + return $model->should_be_invoiced ? trans('texts.yes') : trans('texts.no'); + } + ], + ]; + } + + protected function getDatatableColumnsVendor($entityType, $hideClient) + { + return [ + /* + [ + 'expenses.id', + function ($model) { + return Utils::timestampToDateTimeString(strtotime($model->created_at)); + } + ],*/ + [ + 'expense_date', + function ($model) { + return $model->expense_date; + } + ], + [ + 'amount', + function ($model) { + return Utils::formatMoney($model->amount, false, false); + } + ], + [ + 'public_notes', + function ($model) { + return $model->public_notes != null ? $model->public_notes : ''; + } + ], + [ + 'is_invoiced', + function ($model) { + return $model->is_invoiced ? trans('texts.yes') : trans('texts.no'); + } + ], + [ + 'should_be_invoiced', + function ($model) { + return $model->should_be_invoiced ? trans('texts.yes') : trans('texts.no'); + } + ], + ]; + } + + protected function getDatatableActions($entityType) + { + return [ + [ + trans('texts.invoice_expense'), + function ($model) { + return URL::to("expense/invoice/{$model->public_id}") . '?client=1'; + } + ], + [ + trans('texts.view'), + function ($model) { + return URL::to("expenses/{$model->public_id}") ; + } + ], + [ + trans('texts.edit'), + function ($model) { + return URL::to("expenses/{$model->public_id}/edit") ; + } + ], + + ]; + } + protected function getDatatableActionsVendor($entityType) + { + return [ + [ + trans('texts.invoice_expense'), + function ($model) { + return URL::to("expense/invoice/{$model->public_id}") . '?client=1'; + } + ], + [ + trans('texts.view'), + function ($model) { + return URL::to("expenses/{$model->public_id}") ; + } + ], + [ + trans('texts.edit'), + function ($model) { + return URL::to("expenses/{$model->public_id}/edit") ; + } + ], + + ]; + } + +} diff --git a/app/Services/PaymentTermService.php b/app/Services/PaymentTermService.php new file mode 100644 index 000000000000..bfcf670475b9 --- /dev/null +++ b/app/Services/PaymentTermService.php @@ -0,0 +1,60 @@ +paymentTermRepo = $paymentTermRepo; + $this->datatableService = $datatableService; + } + + protected function getRepo() + { + return $this->paymentTermRepo; + } + + public function getDatatable($accountId = 0) + { + $query = $this->paymentTermRepo->find(); + + return $this->createDatatable(ENTITY_PAYMENT_TERM, $query, false); + } + + protected function getDatatableColumns($entityType, $hideClient) + { + return [ + [ + 'name', + function ($model) { + return link_to("payment_terms/{$model->public_id}/edit", $model->name); + } + ], + [ + 'days', + function ($model) { + return $model->num_days; + } + ] + ]; + } + + protected function getDatatableActions($entityType) + { + return [ + [ + uctrans('texts.edit_payment_terms'), + function ($model) { + return URL::to("payment_terms/{$model->public_id}/edit"); + } + ] + ]; + } +} \ No newline at end of file diff --git a/app/Services/VendorActivityService.php b/app/Services/VendorActivityService.php new file mode 100644 index 000000000000..b083279a11c6 --- /dev/null +++ b/app/Services/VendorActivityService.php @@ -0,0 +1,64 @@ +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 ? '' . trans('texts.system') . '' : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email), + '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), + 'balance' => Utils::formatMoney($model->balance, $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) : ''; + } + ] + ]; + } +} diff --git a/app/Services/VendorService.php b/app/Services/VendorService.php new file mode 100644 index 000000000000..e67384ed6b06 --- /dev/null +++ b/app/Services/VendorService.php @@ -0,0 +1,97 @@ +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)); + } + ], + [ + '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}"); + } + ] + ]; + } +} diff --git a/composer.json b/composer.json index b5bd763f6cdc..713e2aea207d 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "omnipay/omnipay": "~2.3.0", "intervention/image": "dev-master", "webpatser/laravel-countries": "dev-master", - "barryvdh/laravel-ide-helper": "2.0.x", + "barryvdh/laravel-ide-helper": "^2.1", "doctrine/dbal": "2.5.x", "jsanc623/phpbenchtime": "2.x", "lokielse/omnipay-alipay": "dev-master", diff --git a/composer.lock b/composer.lock index 4e4a12f12ea8..6a19f28aa1ee 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "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", - "content-hash": "7305e2f5d6864894aeb23ac91f3e1fe7", + "hash": "67f1ec18911e60b4f0babd8991278fda", + "content-hash": "862c124e81438b922db1afe626149ec0", "packages": [ { "name": "agmscode/omnipay-agms", @@ -123,12 +123,12 @@ "source": { "type": "git", "url": "https://github.com/alfaproject/omnipay-skrill.git", - "reference": "2fa2ba8083fd5289366660f8de1b46b5f49ac052" + "reference": "41a7a03c5b90d496720e288bebc157d898837ccd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/alfaproject/omnipay-skrill/zipball/2fa2ba8083fd5289366660f8de1b46b5f49ac052", - "reference": "2fa2ba8083fd5289366660f8de1b46b5f49ac052", + "url": "https://api.github.com/repos/alfaproject/omnipay-skrill/zipball/41a7a03c5b90d496720e288bebc157d898837ccd", + "reference": "41a7a03c5b90d496720e288bebc157d898837ccd", "shasum": "" }, "require": { @@ -169,7 +169,7 @@ "payment", "skrill" ], - "time": "2014-02-25 13:40:07" + "time": "2016-01-13 16:33:07" }, { "name": "anahkiasen/former", @@ -379,22 +379,22 @@ }, { "name": "barryvdh/laravel-ide-helper", - "version": "v2.0.6", + "version": "v2.1.2", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "037386153630a7515a1542f29410d8c267651689" + "reference": "d82e8f191fb043a0f8cbf2de64fd3027bfa4f772" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/037386153630a7515a1542f29410d8c267651689", - "reference": "037386153630a7515a1542f29410d8c267651689", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/d82e8f191fb043a0f8cbf2de64fd3027bfa4f772", + "reference": "d82e8f191fb043a0f8cbf2de64fd3027bfa4f772", "shasum": "" }, "require": { - "illuminate/console": "5.0.x|5.1.x", - "illuminate/filesystem": "5.0.x|5.1.x", - "illuminate/support": "5.0.x|5.1.x", + "illuminate/console": "5.0.x|5.1.x|5.2.x", + "illuminate/filesystem": "5.0.x|5.1.x|5.2.x", + "illuminate/support": "5.0.x|5.1.x|5.2.x", "php": ">=5.4.0", "phpdocumentor/reflection-docblock": "2.0.4", "symfony/class-loader": "~2.3" @@ -408,7 +408,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -438,7 +438,7 @@ "phpstorm", "sublime" ], - "time": "2015-06-25 08:58:59" + "time": "2015-12-21 19:48:06" }, { "name": "cardgate/omnipay-cardgate", @@ -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", @@ -1481,16 +1481,16 @@ }, { "name": "doctrine/dbal", - "version": "v2.5.3", + "version": "v2.5.4", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "2fbcea96eae34a53183377cdbb0b9bec33974648" + "reference": "abbdfd1cff43a7b99d027af3be709bc8fc7d4769" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/2fbcea96eae34a53183377cdbb0b9bec33974648", - "reference": "2fbcea96eae34a53183377cdbb0b9bec33974648", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/abbdfd1cff43a7b99d027af3be709bc8fc7d4769", + "reference": "abbdfd1cff43a7b99d027af3be709bc8fc7d4769", "shasum": "" }, "require": { @@ -1548,7 +1548,7 @@ "persistence", "queryobject" ], - "time": "2015-12-25 16:28:24" + "time": "2016-01-05 22:11:12" }, { "name": "doctrine/inflector", @@ -2254,12 +2254,12 @@ "source": { "type": "git", "url": "https://github.com/Intervention/image.git", - "reference": "e6acb1609ce89f2c1ec7864fbbda0a20a3eeca70" + "reference": "86dfe2f2a95aa9eed58faf1cc1dae6534a6ce931" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/e6acb1609ce89f2c1ec7864fbbda0a20a3eeca70", - "reference": "e6acb1609ce89f2c1ec7864fbbda0a20a3eeca70", + "url": "https://api.github.com/repos/Intervention/image/zipball/86dfe2f2a95aa9eed58faf1cc1dae6534a6ce931", + "reference": "86dfe2f2a95aa9eed58faf1cc1dae6534a6ce931", "shasum": "" }, "require": { @@ -2308,7 +2308,7 @@ "thumbnail", "watermark" ], - "time": "2015-12-04 17:09:36" + "time": "2016-01-10 11:20:02" }, { "name": "ircmaxell/password-compat", @@ -3463,12 +3463,12 @@ "source": { "type": "git", "url": "https://github.com/meebio/omnipay-secure-trading.git", - "reference": "42f97ee5ad1d28605550d816fc1893919e19e502" + "reference": "992224a3c8dd834ee18f6f253a77ecb4c87c1c1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/meebio/omnipay-secure-trading/zipball/42f97ee5ad1d28605550d816fc1893919e19e502", - "reference": "42f97ee5ad1d28605550d816fc1893919e19e502", + "url": "https://api.github.com/repos/meebio/omnipay-secure-trading/zipball/992224a3c8dd834ee18f6f253a77ecb4c87c1c1a", + "reference": "992224a3c8dd834ee18f6f253a77ecb4c87c1c1a", "shasum": "" }, "require": { @@ -3513,7 +3513,7 @@ "secure trading", "securetrading" ], - "time": "2015-12-01 10:03:20" + "time": "2016-01-05 09:26:36" }, { "name": "mfauveau/omnipay-pacnet", @@ -4363,16 +4363,16 @@ }, { "name": "omnipay/firstdata", - "version": "v2.2.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-firstdata.git", - "reference": "0853bba0ee313f5557eb1c696d3ce5538dbd4aca" + "reference": "e33826821db88d90886cad6c81a29452d3cf91a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-firstdata/zipball/0853bba0ee313f5557eb1c696d3ce5538dbd4aca", - "reference": "0853bba0ee313f5557eb1c696d3ce5538dbd4aca", + "url": "https://api.github.com/repos/thephpleague/omnipay-firstdata/zipball/e33826821db88d90886cad6c81a29452d3cf91a2", + "reference": "e33826821db88d90886cad6c81a29452d3cf91a2", "shasum": "" }, "require": { @@ -4417,7 +4417,7 @@ "pay", "payment" ], - "time": "2015-07-28 17:50:44" + "time": "2016-01-14 06:24:28" }, { "name": "omnipay/gocardless", @@ -5112,16 +5112,16 @@ }, { "name": "omnipay/paypal", - "version": "2.5.0", + "version": "v2.5.1", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-paypal.git", - "reference": "67efe5a927dec13fc7520e29bc44f15fd3a728e9" + "reference": "b546d24241725061d44e60516f0fbce202336963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-paypal/zipball/67efe5a927dec13fc7520e29bc44f15fd3a728e9", - "reference": "67efe5a927dec13fc7520e29bc44f15fd3a728e9", + "url": "https://api.github.com/repos/thephpleague/omnipay-paypal/zipball/b546d24241725061d44e60516f0fbce202336963", + "reference": "b546d24241725061d44e60516f0fbce202336963", "shasum": "" }, "require": { @@ -5166,20 +5166,20 @@ "paypal", "purchase" ], - "time": "2015-11-11 21:48:00" + "time": "2016-01-13 07:03:27" }, { "name": "omnipay/pin", - "version": "v2.1.0", + "version": "v2.2.1", "source": { "type": "git", - "url": "https://github.com/omnipay/pin.git", - "reference": "04e778e9689882d4c40419263014068b69b93168" + "url": "https://github.com/thephpleague/omnipay-pin.git", + "reference": "c2252e41f3674267b2bbe79eaeec73b6b1e4ee58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/omnipay/pin/zipball/04e778e9689882d4c40419263014068b69b93168", - "reference": "04e778e9689882d4c40419263014068b69b93168", + "url": "https://api.github.com/repos/thephpleague/omnipay-pin/zipball/c2252e41f3674267b2bbe79eaeec73b6b1e4ee58", + "reference": "c2252e41f3674267b2bbe79eaeec73b6b1e4ee58", "shasum": "" }, "require": { @@ -5210,11 +5210,11 @@ }, { "name": "Omnipay Contributors", - "homepage": "https://github.com/omnipay/pin/contributors" + "homepage": "https://github.com/thephpleague/omnipay-pin/contributors" } ], "description": "Pin Payments driver for the Omnipay payment processing library", - "homepage": "https://github.com/omnipay/pin", + "homepage": "https://github.com/thephpleague/omnipay-pin", "keywords": [ "gateway", "merchant", @@ -5223,20 +5223,20 @@ "payment", "pin" ], - "time": "2014-04-14 11:26:15" + "time": "2016-01-13 07:00:17" }, { "name": "omnipay/sagepay", - "version": "v2.2.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-sagepay.git", - "reference": "899507095428fa54276ba5ca89f11fd7f8fd78ab" + "reference": "4208d23b33b2f8a59176e44ad22d304c461ecb62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-sagepay/zipball/899507095428fa54276ba5ca89f11fd7f8fd78ab", - "reference": "899507095428fa54276ba5ca89f11fd7f8fd78ab", + "url": "https://api.github.com/repos/thephpleague/omnipay-sagepay/zipball/4208d23b33b2f8a59176e44ad22d304c461ecb62", + "reference": "4208d23b33b2f8a59176e44ad22d304c461ecb62", "shasum": "" }, "require": { @@ -5282,7 +5282,7 @@ "sage pay", "sagepay" ], - "time": "2015-04-02 17:46:20" + "time": "2016-01-12 12:43:31" }, { "name": "omnipay/securepay", @@ -5343,16 +5343,16 @@ }, { "name": "omnipay/stripe", - "version": "v2.3.0", + "version": "v2.3.1", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-stripe.git", - "reference": "54b816a5e95e34c988d71fb805b0232cfd7c1ce5" + "reference": "6c4cef5b5168a58476eef6fa73b7875e15c94b6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-stripe/zipball/54b816a5e95e34c988d71fb805b0232cfd7c1ce5", - "reference": "54b816a5e95e34c988d71fb805b0232cfd7c1ce5", + "url": "https://api.github.com/repos/thephpleague/omnipay-stripe/zipball/6c4cef5b5168a58476eef6fa73b7875e15c94b6f", + "reference": "6c4cef5b5168a58476eef6fa73b7875e15c94b6f", "shasum": "" }, "require": { @@ -5396,7 +5396,7 @@ "payment", "stripe" ], - "time": "2015-11-10 16:17:35" + "time": "2016-01-13 04:00:45" }, { "name": "omnipay/targetpay", @@ -5512,6 +5512,54 @@ ], "time": "2014-09-17 00:37:18" }, + { + "name": "paragonie/random_compat", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "dd8998b7c846f6909f4e7a5f67fabebfc412a4f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/dd8998b7c846f6909f4e7a5f67fabebfc412a4f7", + "reference": "dd8998b7c846f6909f4e7a5f67fabebfc412a4f7", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ], + "time": "2016-01-06 13:31:20" + }, { "name": "patricktalmadge/bootstrapper", "version": "5.5.3", @@ -6058,16 +6106,16 @@ }, { "name": "symfony/class-loader", - "version": "v2.8.1", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", - "reference": "ec74b0a279cf3a9bd36172b3e3061591d380ce6c" + "reference": "98e9089a428ed0e39423b67352c57ef5910a3269" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/ec74b0a279cf3a9bd36172b3e3061591d380ce6c", - "reference": "ec74b0a279cf3a9bd36172b3e3061591d380ce6c", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/98e9089a428ed0e39423b67352c57ef5910a3269", + "reference": "98e9089a428ed0e39423b67352c57ef5910a3269", "shasum": "" }, "require": { @@ -6106,11 +6154,11 @@ ], "description": "Symfony ClassLoader Component", "homepage": "https://symfony.com", - "time": "2015-12-05 17:37:59" + "time": "2016-01-03 15:33:41" }, { "name": "symfony/console", - "version": "v2.6.12", + "version": "v2.6.13", "target-dir": "Symfony/Component/Console", "source": { "type": "git", @@ -6168,16 +6216,16 @@ }, { "name": "symfony/css-selector", - "version": "v2.8.1", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "eaa3320e32f09a01dc432c6efbe8051aee59cfef" + "reference": "ac06d8173bd80790536c0a4a634a7d705b91f54f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/eaa3320e32f09a01dc432c6efbe8051aee59cfef", - "reference": "eaa3320e32f09a01dc432c6efbe8051aee59cfef", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/ac06d8173bd80790536c0a4a634a7d705b91f54f", + "reference": "ac06d8173bd80790536c0a4a634a7d705b91f54f", "shasum": "" }, "require": { @@ -6217,11 +6265,11 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2015-12-05 17:37:59" + "time": "2016-01-03 15:33:41" }, { "name": "symfony/debug", - "version": "v2.6.12", + "version": "v2.6.13", "target-dir": "Symfony/Component/Debug", "source": { "type": "git", @@ -6282,16 +6330,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v2.8.1", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc" + "reference": "ee278f7c851533e58ca307f66305ccb9188aceda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a5eb815363c0388e83247e7e9853e5dbc14999cc", - "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ee278f7c851533e58ca307f66305ccb9188aceda", + "reference": "ee278f7c851533e58ca307f66305ccb9188aceda", "shasum": "" }, "require": { @@ -6338,20 +6386,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2015-10-30 20:15:42" + "time": "2016-01-13 10:28:07" }, { "name": "symfony/filesystem", - "version": "v2.8.1", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "a7ad724530a764d70c168d321ac226ba3d2f10fc" + "reference": "637b64d0ee10f44ae98dbad651b1ecdf35a11e8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/a7ad724530a764d70c168d321ac226ba3d2f10fc", - "reference": "a7ad724530a764d70c168d321ac226ba3d2f10fc", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/637b64d0ee10f44ae98dbad651b1ecdf35a11e8c", + "reference": "637b64d0ee10f44ae98dbad651b1ecdf35a11e8c", "shasum": "" }, "require": { @@ -6387,11 +6435,11 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2015-12-22 10:25:57" + "time": "2016-01-13 10:28:07" }, { "name": "symfony/finder", - "version": "v2.6.12", + "version": "v2.6.13", "target-dir": "Symfony/Component/Finder", "source": { "type": "git", @@ -6441,7 +6489,7 @@ }, { "name": "symfony/http-foundation", - "version": "v2.6.12", + "version": "v2.6.13", "target-dir": "Symfony/Component/HttpFoundation", "source": { "type": "git", @@ -6495,17 +6543,17 @@ }, { "name": "symfony/http-kernel", - "version": "v2.6.12", + "version": "v2.6.13", "target-dir": "Symfony/Component/HttpKernel", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "498866a8ca0bcbcd3f3824b1520fa568ff7ca3b6" + "reference": "cdd991d304fed833514dc44d6aafcf19397c26cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/498866a8ca0bcbcd3f3824b1520fa568ff7ca3b6", - "reference": "498866a8ca0bcbcd3f3824b1520fa568ff7ca3b6", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/cdd991d304fed833514dc44d6aafcf19397c26cb", + "reference": "cdd991d304fed833514dc44d6aafcf19397c26cb", "shasum": "" }, "require": { @@ -6569,7 +6617,7 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2015-11-23 11:37:53" + "time": "2016-01-14 10:11:16" }, { "name": "symfony/polyfill-php56", @@ -6681,7 +6729,7 @@ }, { "name": "symfony/process", - "version": "v2.6.12", + "version": "v2.6.13", "target-dir": "Symfony/Component/Process", "source": { "type": "git", @@ -6731,7 +6779,7 @@ }, { "name": "symfony/routing", - "version": "v2.6.12", + "version": "v2.6.13", "target-dir": "Symfony/Component/Routing", "source": { "type": "git", @@ -6800,20 +6848,21 @@ }, { "name": "symfony/security-core", - "version": "v2.6.12", + "version": "v2.6.13", "target-dir": "Symfony/Component/Security/Core", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "05f58bb3814e8a853332dc448e3b7addaa87679c" + "reference": "813cf2aaacccbbe1a4705aef8d4ac0d79d993a76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/05f58bb3814e8a853332dc448e3b7addaa87679c", - "reference": "05f58bb3814e8a853332dc448e3b7addaa87679c", + "url": "https://api.github.com/repos/symfony/security-core/zipball/813cf2aaacccbbe1a4705aef8d4ac0d79d993a76", + "reference": "813cf2aaacccbbe1a4705aef8d4ac0d79d993a76", "shasum": "" }, "require": { + "paragonie/random_compat": "~1.0", "php": ">=5.3.3" }, "require-dev": { @@ -6860,11 +6909,11 @@ ], "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", - "time": "2015-07-22 10:08:40" + "time": "2016-01-14 09:04:34" }, { "name": "symfony/translation", - "version": "v2.6.12", + "version": "v2.6.13", "target-dir": "Symfony/Component/Translation", "source": { "type": "git", @@ -6923,7 +6972,7 @@ }, { "name": "symfony/var-dumper", - "version": "v2.6.12", + "version": "v2.6.13", "target-dir": "Symfony/Component/VarDumper", "source": { "type": "git", @@ -7030,16 +7079,16 @@ }, { "name": "true/punycode", - "version": "v2.0.1", + "version": "v2.0.2", "source": { "type": "git", "url": "https://github.com/true/php-punycode.git", - "reference": "b672918d992b84f8016bbe353a42516928393c63" + "reference": "74fa01d4de396c40e239794123b3874cb594a30c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/true/php-punycode/zipball/b672918d992b84f8016bbe353a42516928393c63", - "reference": "b672918d992b84f8016bbe353a42516928393c63", + "url": "https://api.github.com/repos/true/php-punycode/zipball/74fa01d4de396c40e239794123b3874cb594a30c", + "reference": "74fa01d4de396c40e239794123b3874cb594a30c", "shasum": "" }, "require": { @@ -7072,7 +7121,7 @@ "idna", "punycode" ], - "time": "2015-09-01 14:53:31" + "time": "2016-01-07 17:12:58" }, { "name": "twbs/bootstrap", @@ -7347,16 +7396,16 @@ }, { "name": "zircote/swagger-php", - "version": "2.0.4", + "version": "2.0.5", "source": { "type": "git", "url": "https://github.com/zircote/swagger-php.git", - "reference": "be5d96e56c23cbe52c5bc5e267851323d95c57cd" + "reference": "c19af4edcc13c00e82fabeee926335b1fe1d92e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/be5d96e56c23cbe52c5bc5e267851323d95c57cd", - "reference": "be5d96e56c23cbe52c5bc5e267851323d95c57cd", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/c19af4edcc13c00e82fabeee926335b1fe1d92e9", + "reference": "c19af4edcc13c00e82fabeee926335b1fe1d92e9", "shasum": "" }, "require": { @@ -7403,7 +7452,7 @@ "rest", "service discovery" ], - "time": "2015-11-13 13:50:11" + "time": "2016-01-15 09:39:28" } ], "packages-dev": [ @@ -7593,16 +7642,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 +7681,7 @@ "selenium", "webdriver" ], - "time": "2015-12-08 17:04:30" + "time": "2015-12-31 15:58:49" }, { "name": "fzaninotto/faker", @@ -7722,16 +7771,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 +7845,7 @@ "testing", "tests" ], - "time": "2015-11-29 02:03:49" + "time": "2016-01-01 10:17:54" }, { "name": "phpspec/prophecy", @@ -8599,16 +8648,16 @@ }, { "name": "symfony/browser-kit", - "version": "v2.8.1", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "dd2cfb20fabd4efca14cf3b2345d40b3dd5e9aca" + "reference": "a93dffaf763182acad12a4c42c7efc372899891e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/dd2cfb20fabd4efca14cf3b2345d40b3dd5e9aca", - "reference": "dd2cfb20fabd4efca14cf3b2345d40b3dd5e9aca", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/a93dffaf763182acad12a4c42c7efc372899891e", + "reference": "a93dffaf763182acad12a4c42c7efc372899891e", "shasum": "" }, "require": { @@ -8652,20 +8701,20 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2015-12-26 13:37:56" + "time": "2016-01-12 17:46:01" }, { "name": "symfony/dom-crawler", - "version": "v2.8.1", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "a2712aff8b250d9601ad6bd23a2ff82a12730e8e" + "reference": "650d37aacb1fa0dcc24cced483169852b3a0594e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/a2712aff8b250d9601ad6bd23a2ff82a12730e8e", - "reference": "a2712aff8b250d9601ad6bd23a2ff82a12730e8e", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/650d37aacb1fa0dcc24cced483169852b3a0594e", + "reference": "650d37aacb1fa0dcc24cced483169852b3a0594e", "shasum": "" }, "require": { @@ -8708,7 +8757,7 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2015-12-23 17:16:29" + "time": "2016-01-03 15:33:41" }, { "name": "symfony/polyfill-mbstring", @@ -8771,16 +8820,16 @@ }, { "name": "symfony/yaml", - "version": "v2.8.1", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966" + "reference": "34c8a4b51e751e7ea869b8262f883d008a2b81b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966", - "reference": "ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966", + "url": "https://api.github.com/repos/symfony/yaml/zipball/34c8a4b51e751e7ea869b8262f883d008a2b81b8", + "reference": "34c8a4b51e751e7ea869b8262f883d008a2b81b8", "shasum": "" }, "require": { @@ -8816,7 +8865,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2015-12-26 13:37:56" + "time": "2016-01-13 10:28:07" } ], "aliases": [], diff --git a/config/app.php b/config/app.php index a7e071e62898..cd55e8ee965a 100644 --- a/config/app.php +++ b/config/app.php @@ -161,6 +161,8 @@ return [ 'App\Providers\ConfigServiceProvider', 'App\Providers\EventServiceProvider', 'App\Providers\RouteServiceProvider', + + 'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider', ], /* diff --git a/config/debugbar.php b/config/debugbar.php index a389dd17da36..57ec1876f5fc 100644 --- a/config/debugbar.php +++ b/config/debugbar.php @@ -89,7 +89,7 @@ return array( 'files' => false, // Show the included files 'config' => false, // Display config settings 'auth' => false, // Display Laravel authentication status - 'session' => false, // Display session data in a separate tab + 'session' => true, // Display session data in a separate tab ), /* diff --git a/database/.gitignore b/database/.gitignore index 9b1dffd90fdc..bb55a4b89e24 100644 --- a/database/.gitignore +++ b/database/.gitignore @@ -1 +1,2 @@ *.sqlite +*-komodoproject \ No newline at end of file diff --git a/database/migrations/2016_01_04_175228_create_vendors_table.php b/database/migrations/2016_01_04_175228_create_vendors_table.php new file mode 100644 index 000000000000..1ca1df6ae6f1 --- /dev/null +++ b/database/migrations/2016_01_04_175228_create_vendors_table.php @@ -0,0 +1,60 @@ +increments('id'); + $table->timestamps(); + $table->softDeletes(); + $table->integer('user_id', false, true); + $table->integer('account_id', false, true); + $table->integer('currency_id',false, true)->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->decimal('balance',13,2); + $table->string('website'); + $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')->default(0); + $table->string('custom_value1')->nullable(); + $table->string('custom_value2')->nullable(); + $table->string('vat_number')->nullable(); + $table->string('id_number')->nullable(); + $table->integer('language_id', false, true)->nullable(); + }); + + // add relations + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('vendors'); + } + +} diff --git a/database/migrations/2016_01_05_134713_create_vendorcontacts_table.php b/database/migrations/2016_01_05_134713_create_vendorcontacts_table.php new file mode 100644 index 000000000000..b2ab1c4c0591 --- /dev/null +++ b/database/migrations/2016_01_05_134713_create_vendorcontacts_table.php @@ -0,0 +1,51 @@ +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'); + } + +} diff --git a/database/migrations/2016_01_06_101343_create_table_Vendor_Invitations.php b/database/migrations/2016_01_06_101343_create_table_Vendor_Invitations.php new file mode 100644 index 000000000000..5797c9c5c96b --- /dev/null +++ b/database/migrations/2016_01_06_101343_create_table_Vendor_Invitations.php @@ -0,0 +1,55 @@ +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'); + } + +} diff --git a/database/migrations/2016_01_06_102020_create_table_Vendor_Activities.php b/database/migrations/2016_01_06_102020_create_table_Vendor_Activities.php new file mode 100644 index 000000000000..c7557427867d --- /dev/null +++ b/database/migrations/2016_01_06_102020_create_table_Vendor_Activities.php @@ -0,0 +1,58 @@ +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'); + } + +} diff --git a/database/migrations/2016_01_06_155001_create_expenses_table.php b/database/migrations/2016_01_06_155001_create_expenses_table.php new file mode 100644 index 000000000000..6ddd62c3fe31 --- /dev/null +++ b/database/migrations/2016_01_06_155001_create_expenses_table.php @@ -0,0 +1,58 @@ +increments('id'); + $table->timestamps(); + $table->softDeletes(); + + $table->unsignedInteger('account_id')->index(); + $table->unsignedInteger('vendor_id')->nullable(); + $table->unsignedInteger('user_id'); + $table->unsignedInteger('invoice_id')->nullable(); + $table->unsignedInteger('invoice_client_id')->nullable(); + $table->boolean('is_deleted')->default(false); + $table->decimal('amount', 13, 2); + $table->decimal('amount_cur', 13, 2); + $table->decimal('exchange_rate', 13, 2); + $table->date('expense_date')->nullable(); + $table->text('private_notes'); + $table->text('public_notes'); + $table->integer('currency_id',false, true)->nullable(); + $table->boolean('is_invoiced')->default(false); + $table->boolean('should_be_invoiced')->default(true); + + // Relations + $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + + // Indexes + $table->unsignedInteger('public_id')->index(); + $table->unique( array('account_id','public_id') ); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('expenses'); + } +} diff --git a/database/migrations/2016_01_06_191912_create_expense_activities_table.php b/database/migrations/2016_01_06_191912_create_expense_activities_table.php new file mode 100644 index 000000000000..9b5981741c25 --- /dev/null +++ b/database/migrations/2016_01_06_191912_create_expense_activities_table.php @@ -0,0 +1,52 @@ +increments('id'); + $table->timestamps(); + + $table->unsignedInteger('account_id'); + $table->unsignedInteger('vendor_id'); + $table->unsignedInteger('user_id'); + $table->unsignedInteger('contact_id')->nullable(); + $table->unsignedInteger('expense_id'); + $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'); + $table->foreign('expense_id')->references('id')->on('expenses')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('expense_activities'); + } + +} diff --git a/database/migrations/2016_01_07_173015_add_fields_to_paymentterms.php b/database/migrations/2016_01_07_173015_add_fields_to_paymentterms.php new file mode 100644 index 000000000000..1756cc60b246 --- /dev/null +++ b/database/migrations/2016_01_07_173015_add_fields_to_paymentterms.php @@ -0,0 +1,50 @@ +timestamps(); + $table->softDeletes(); + $table->integer('user_id', false, true); + $table->integer('account_id', false, true); + $table->integer('public_id')->default(0); + }); + + // Update public id + $paymentTerms = DB::table('payment_terms') + ->where('public_id', '=',0) + ->select('id', 'public_id') + ->get(); + $i = 1; + foreach ($paymentTerms as $pTerm) { + $data = ['public_id' => $i]; + + DB::table('paymet_terms')->where('id', $pTerm->id)->update($data); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('payment_terms', function(Blueprint $table) + { + // + }); + } + +} diff --git a/database/migrations/2016_01_10_095302_add_invoices_has_expenses.php b/database/migrations/2016_01_10_095302_add_invoices_has_expenses.php new file mode 100644 index 000000000000..96e89d75e616 --- /dev/null +++ b/database/migrations/2016_01_10_095302_add_invoices_has_expenses.php @@ -0,0 +1,46 @@ +boolean('has_expenses')->default(false); + }); + + $invoices = DB::table('invoices') + ->join('expenses', 'expenses.invoice_id', '=', 'invoices.id') + ->selectRaw('DISTINCT invoices.id') + ->get(); + + foreach ($invoices as $invoice) { + DB::table('invoices') + ->where('id', $invoice->id) + ->update(['has_tasks' => true]); + } + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('invoices', function(Blueprint $table) + { + $table->dropColumn('has_expenses'); + }); + } + +} diff --git a/invoiceninja.komodoproject b/invoiceninja.komodoproject new file mode 100644 index 000000000000..16111c8ee84d --- /dev/null +++ b/invoiceninja.komodoproject @@ -0,0 +1,43 @@ + + + + + + + + + + + + + PHP + + + + application/x-www-form-urlencoded + GET + 1 + 0 + 0 + + + + + PHP + %25d/%25m/%25Y %25H:%25M:%25S + 1 + + default + default + PHP + Project + None + None + None + c:\wamp\bin\php\php5.6.15\php.exe + vendor;C:/webdev/invoiceninja/app + 1 + None + None + + diff --git a/public/js/built.js b/public/js/built.js index 4331c8699121..db4a29bbc067 100644 --- a/public/js/built.js +++ b/public/js/built.js @@ -30298,6 +30298,17 @@ function getClientDisplayName(client) return ''; } +function getVendorDisplayName(vendor) +{ + var contact = vendor.contacts ? vendor.vendorcontacts[0] : false; + if (vendor.name) { + return vendor.name; + } else if (contact) { + return getContactDisplayName(contact); + } + return ''; +} + function populateInvoiceComboboxes(clientId, invoiceId) { var clientMap = {}; var invoiceMap = {}; diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index f1b099fe796b..31b2d46a7a6d 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -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', @@ -884,6 +892,14 @@ return array( 'activity_27' => ':user restored payment :payment', 'activity_28' => ':user restored :credit credit', 'activity_29' => ':contact approved quote :quote', + 'activity_30' => ':user created :vendor', + 'activity_31' => ':user created :vendor', + 'activity_32' => ':user created :vendor', + 'activity_33' => ':user created :vendor', + 'activity_34' => ':user created expense :expense', + 'activity_35' => ':user created :vendor', + 'activity_36' => ':user created :vendor', + 'activity_37' => ':user created :vendor', 'payment' => 'Payment', 'system' => 'System', @@ -997,6 +1013,54 @@ 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 + 'expense' => 'Expense', + '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', + + // Expenses + 'expense_amount' => 'Expense amount', + 'expense_balance' => 'Expense balance', + 'expense_date' => 'Expense date', + 'expense_exchange_rate_100' => 'The amount for 100 in company currency', + 'expense_should_be_invoiced' => 'Should this expense be invoiced?', + 'public_notes' => 'Public notes', + 'expense_amount_in_cur' => 'Expense amount in curency', + 'is_invoiced' => 'Is invoiced', + 'expense_is_not_invoiced' => 'Expense not invoiced', + 'expense_is_invoiced' => 'Expense invoiced', + 'yes' => 'Yes', + 'no' => 'No', + 'should_be_invoiced' => 'Should be invoiced', + 'view_expense' => 'View expense # :expense', + 'edit_expense' => 'Edit expense', + 'archive_expense' => 'Archive expense', + 'delete_expense' => 'Delete expense', + 'view_expense_num' => 'Expense # :expense', + 'updated_expense' => 'Expense updated', + 'enter_expense' => 'Enter expense', + 'view' => 'View', + 'restore_expense' => 'Restore expense', + 'invoice_expense' => 'Invoice', + 'expense_error_multiple_clients' =>'The expenses can\'t belong to different clients', + 'expense_error_invoiced' => 'Expense have already been invoiced', + 'expense_error_should_not_be_invoiced' => 'Expense maked not to be invoiced', + + // Payment terms + 'num_days' => 'Number of days', + 'create_payment_term' => 'Create payment term', + 'edit_payment_terms' => 'Edit payment term', + 'edit_payment_term' => 'Edit payment term', + 'archive_payment_term' => 'Archive payment term', + // recurring due dates 'recurring_due_dates' => 'Recurring Invoice Due Dates', 'recurring_due_date_help' => '

    Automatically sets a due date for the invoice.

    diff --git a/resources/views/accounts/payment_term.blade.php b/resources/views/accounts/payment_term.blade.php new file mode 100644 index 000000000000..a1939995dcbb --- /dev/null +++ b/resources/views/accounts/payment_term.blade.php @@ -0,0 +1,47 @@ +@extends('header') + +@section('content') + @parent + + @include('accounts.nav', ['selected' => ACCOUNT_PAYMENT_TERMS]) + + {!! Former::open($url)->method($method) + ->rules([ + 'name' => 'required', + 'num_days' => 'required' + ]) + ->addClass('warn-on-exit') !!} + + +
    +
    +

    {!! $title !!}

    +
    +
    + + @if ($paymentTerm) + {{ Former::populate($paymentTerm) }} + @endif + + {!! Former::text('name')->label('texts.name') !!} + {!! Former::text('num_days')->label('texts.num_days') !!} + +
    +
    + + {!! Former::actions( + Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/settings/payment_terms'))->appendIcon(Icon::create('remove-circle')), + Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) + ) !!} + + {!! Former::close() !!} + + + +@stop \ No newline at end of file diff --git a/resources/views/accounts/payment_terms.blade.php b/resources/views/accounts/payment_terms.blade.php new file mode 100644 index 000000000000..f88016991f13 --- /dev/null +++ b/resources/views/accounts/payment_terms.blade.php @@ -0,0 +1,33 @@ +@extends('header') + +@section('content') + @parent + + @include('accounts.nav', ['selected' => ACCOUNT_PAYMENT_TERMS]) + + {!! Button::primary(trans('texts.create_payment_term')) + ->asLinkTo(URL::to('/payment_terms/create')) + ->withAttributes(['class' => 'pull-right']) + ->appendIcon(Icon::create('plus-sign')) !!} + + @include('partials.bulk_form', ['entityType' => ENTITY_PAYMENT_TERM]) + + {!! Datatable::table() + ->addColumn( + trans('texts.name'), + trans('texts.num_days'), + trans('texts.action')) + ->setUrl(url('api/payment_terms/')) + ->setOptions('sPaginationType', 'bootstrap') + ->setOptions('bFilter', false) + ->setOptions('bAutoWidth', false) + ->setOptions('aoColumns', [[ "sWidth"=> "40%" ], [ "sWidth"=> "40%" ], ["sWidth"=> "20%"]]) + ->setOptions('aoColumnDefs', [['bSortable'=>false, 'aTargets'=>[2]]]) + ->render('datatable') !!} + + + + +@stop \ No newline at end of file diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index 56f9690610ec..d678aa53669f 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -84,6 +84,18 @@ {!! $activity->getMessage() !!} @endforeach + @foreach ($vendoractivities as $activity) +
  • + {{ Utils::timestampToDateString(strtotime($activity->created_at)) }}: + {!! $activity->getMessage() !!} +
  • + @endforeach + @foreach ($expenseactivities as $activity) +
  • + {{ Utils::timestampToDateString(strtotime($activity->created_at)) }}: + {!! $activity->getMessage() !!} +
  • + @endforeach diff --git a/resources/views/expenses/edit.blade.php b/resources/views/expenses/edit.blade.php new file mode 100644 index 000000000000..0e953849d153 --- /dev/null +++ b/resources/views/expenses/edit.blade.php @@ -0,0 +1,97 @@ +@extends('header') + +@section('content') + + {!! Former::open($url)->addClass('col-md-10 col-md-offset-1 warn-on-exit')->method($method)->rules(array( + 'public_notes' => 'required', + 'amount' => 'required', + 'expense_date' => 'required', + )) !!} + + @if ($expense) + {!! Former::populate($expense) !!} + {!! Former::hidden('public_id') !!} + @endif + + +
    +
    + +
    +
    + {!! Former::select('vendor')->addOption('', '')->addGroupClass('client-select') !!} + {!! Former::text('expense_date') + ->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT)) + ->addGroupClass('expense_date')->label(trans('texts.expense_date')) + ->append('') !!} + {!! Former::text('amount')->label(trans('texts.expense_amount')) !!} + {!! Former::text('amount_cur')->label(trans('texts.expense_amount_in_cur')) !!} + {!! Former::select('currency_id')->addOption('','') + ->placeholder($account->currency ? $account->currency->name : '') + ->fromQuery($currencies, 'name', 'id') !!} + {!! Former::text('exchange_rate')->append(trans('texts.expense_exchange_rate_100')) !!} + {!! Former::textarea('private_notes') !!} + {!! Former::textarea('public_notes') !!} + {!! Former::checkbox('should_be_invoiced') !!} + {!! Former::select('client')->addOption('', '')->addGroupClass('client-select') !!} +
    +
    +
    +
    + +
    + {!! Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/dashboard'))->appendIcon(Icon::create('remove-circle')) !!} + {!! Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) !!} +
    + + {!! Former::close() !!} + + +@stop \ No newline at end of file diff --git a/resources/views/expenses/show.blade.php b/resources/views/expenses/show.blade.php new file mode 100644 index 000000000000..dd0c613b7a3c --- /dev/null +++ b/resources/views/expenses/show.blade.php @@ -0,0 +1,122 @@ +@extends('header') + +@section('head') + @parent +@stop + +@section('content') +
    + {!! Former::open('expenses/bulk')->addClass('mainForm') !!} +
    + {!! Former::text('action') !!} + {!! Former::text('public_id')->value($expense->public_id) !!} +
    + + @if ($expense->trashed()) + {!! Button::primary(trans('texts.restore_expense'))->withAttributes(['onclick' => 'onRestoreClick()']) !!} + @else + {!! DropdownButton::normal(trans('texts.edit_expense')) + ->withAttributes(['class'=>'normalDropDown']) + ->withContents([ + ['label' => trans('texts.archive_expense'), 'url' => "javascript:onArchiveClick()"], + ['label' => trans('texts.delete_expense'), 'url' => "javascript:onDeleteClick()"], + ] + )->split() !!} + + {!! DropdownButton::primary(trans('texts.new_expense')) + ->withAttributes(['class'=>'primaryDropDown']) + ->withContents($actionLinks)->split() !!} + @endif + {!! Former::close() !!} + +
    + +

    {{ trans('texts.view_expense_num', ['expense' => $expense->public_id]) }}

    +
    +
    +
    + +
    +

    {{ trans('texts.details') }}

    + +

    {{ $expense->public_notes }}

    +
    + +
    +

    {{ trans('texts.standing') }} + + + + + + + + + + @if ($credit > 0) + + + + + @endif +
    {{ trans('texts.expense_date') }}{{ Utils::fromSqlDate($expense->expense_date) }}
    {{ trans('texts.expense_amount') }}{{ Utils::formatMoney($expense->amount) }}
    {{ trans('texts.expense_amount_cur') }}{{ Utils::formatMoney($$expense->amount_cur, $expense->curency_id) }}
    +

    +
    +
    +
    +
    + + + +
    +
    + {!! Datatable::table() + ->addColumn( + trans('texts.expense_date'), + trans('texts.message'), + trans('texts.amount'), + trans('texts.public_notes')) + ->setUrl(url('api/expenseactivities/'. $expense->public_id)) + ->setOptions('sPaginationType', 'bootstrap') + ->setOptions('bFilter', false) + ->setOptions('aaSorting', [['0', 'desc']]) + ->render('datatable') !!} +
    +
    + + + +@stop diff --git a/resources/views/header.blade.php b/resources/views/header.blade.php index eb7416f1bc2c..f20244b6416c 100644 --- a/resources/views/header.blade.php +++ b/resources/views/header.blade.php @@ -375,6 +375,7 @@ {!! HTML::menu_link('task') !!} {!! HTML::menu_link('invoice') !!} {!! HTML::menu_link('payment') !!} + {!! HTML::menu_link('expense') !!} - +
    @endif @@ -72,7 +72,7 @@
    @@ -88,20 +88,20 @@    @if (Utils::isConfirmed()) - - @endif
    - +
    @@ -109,7 +109,7 @@ ->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->appendIcon('calendar')->addGroupClass('invoice_date') !!} {!! Former::text('due_date')->data_bind("datePicker: due_date, valueUpdate: 'afterkeydown'")->label(trans("texts.{$entityType}_due_date")) ->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->appendIcon('calendar')->addGroupClass('due_date') !!} - + {!! Former::text('partial')->data_bind("value: partial, valueUpdate: 'afterkeydown'")->onchange('onPartialChange()') ->rel('tooltip')->data_toggle('tooltip')->data_placement('bottom')->title(trans('texts.partial_value')) !!}
    @@ -127,6 +127,26 @@ @if ($account->showCustomField('custom_invoice_text_label1', $invoice)) {!! Former::text('custom_text_value1')->label($account->custom_invoice_text_label1)->data_bind("value: custom_text_value1, valueUpdate: 'afterkeydown'") !!} + @endif + + @if ($entityType == ENTITY_INVOICE) +
    +
    + @if ($invoice->recurring_invoice) + {!! trans('texts.created_by_invoice', ['invoice' => link_to('/invoices/'.$invoice->recurring_invoice->public_id, trans('texts.recurring_invoice'))]) !!} + @elseif ($invoice->id) + + @if (isset($lastSent) && $lastSent) + {!! trans('texts.last_sent_on', ['date' => link_to('/invoices/'.$lastSent->public_id, $invoice->last_sent_date, ['id' => 'lastSent'])]) !!}
    + @endif + @if ($invoice->is_recurring && $invoice->getNextSendDate()) + {!! trans('texts.next_send_on', ['date' => ''.$account->formatDate($invoice->getNextSendDate()). + '']) !!} + @endif +
    + @endif +
    +
    @endif
    @@ -135,7 +155,7 @@ {!! Former::text('invoice_number') ->label(trans("texts.{$entityType}_number_short")) ->data_bind("value: invoice_number, valueUpdate: 'afterkeydown'") !!} - + {!! Former::checkbox('auto_bill') ->label(trans('texts.auto_bill')) @@ -147,15 +167,15 @@ ->addGroupClass('discount-group')->type('number')->min('0')->step('any')->append( Former::select('is_amount_discount')->addOption(trans('texts.discount_percent'), '0') ->addOption(trans('texts.discount_amount'), '1')->data_bind("value: is_amount_discount")->raw() - ) !!} + ) !!} @if ($account->showCustomField('custom_invoice_text_label2', $invoice)) {!! Former::text('custom_text_value2')->label($account->custom_invoice_text_label2)->data_bind("value: custom_text_value2, valueUpdate: 'afterkeydown'") !!} @endif - + @if ($entityType == ENTITY_INVOICE)
    -
    +
    @if ($invoice->recurring_invoice) {!! trans('texts.created_by_invoice', ['invoice' => link_to('/invoices/'.$invoice->recurring_invoice->public_id, trans('texts.recurring_invoice'))]) !!} @elseif ($invoice->id) @@ -207,16 +227,17 @@ !!} - + - - @@ -228,7 +249,7 @@
    - @@ -252,7 +273,7 @@
    {!! Former::textarea('public_notes')->data_bind("value: wrapped_notes, valueUpdate: 'afterkeydown'") - ->label(null)->style('resize: none; min-width: 450px;')->rows(3) !!} + ->label(null)->style('resize: none; min-width: 450px;')->rows(3) !!}
    {!! Former::textarea('terms')->data_bind("value:wrapped_terms, placeholder: terms_placeholder, valueUpdate: 'afterkeydown'") @@ -323,7 +344,7 @@ - + @if (!$account->hide_quantity) {{ trans('texts.tax') }} @endif @@ -376,7 +397,7 @@
    - +

     

    @@ -390,6 +411,7 @@ {!! Former::text('is_quote')->data_bind('value: is_quote') !!} {!! Former::text('has_tasks')->data_bind('value: has_tasks') !!} {!! Former::text('data')->data_bind('value: ko.mapping.toJSON(model)') !!} + {!! Former::text('has_expenses')->data_bind('value: has_expenses') !!} {!! Former::text('pdfupload') !!}
    @@ -401,12 +423,12 @@ @if (!Utils::isPro() || \App\Models\InvoiceDesign::count() == COUNT_FREE_DESIGNS_SELF_HOST) {!! Former::select('invoice_design_id')->style('display:inline;width:150px;background-color:white !important')->raw()->fromQuery($invoiceDesigns, 'name', 'id')->data_bind("value: invoice_design_id")->addOption(trans('texts.more_designs') . '...', '-1') !!} - @else + @else {!! Former::select('invoice_design_id')->style('display:inline;width:150px;background-color:white !important')->raw()->fromQuery($invoiceDesigns, 'name', 'id')->data_bind("value: invoice_design_id") !!} @endif - {!! Button::primary(trans('texts.download_pdf'))->withAttributes(array('onclick' => 'onDownloadClick()'))->appendIcon(Icon::create('download-alt')) !!} - + {!! Button::primary(trans('texts.download_pdf'))->withAttributes(array('onclick' => 'onDownloadClick()'))->appendIcon(Icon::create('download-alt')) !!} + @if ($invoice->isClientTrashed()) @elseif ($invoice->trashed()) @@ -454,22 +476,24 @@ ->label('client_name') !!} + {!! Former::text('client[id_number]') ->label('id_number') ->data_bind("value: id_number, valueUpdate: 'afterkeydown'") !!} {!! Former::text('client[vat_number]') ->label('vat_number') ->data_bind("value: vat_number, valueUpdate: 'afterkeydown'") !!} - + {!! Former::text('client[website]') ->label('website') ->data_bind("value: website, valueUpdate: 'afterkeydown'") !!} {!! Former::text('client[work_phone]') ->label('work_phone') ->data_bind("value: work_phone, valueUpdate: 'afterkeydown'") !!} + - @if (Auth::user()->isPro()) + @if (Auth::user()->isPro()) @if ($account->custom_client_label1) {!! Former::text('client[custom_value1]') ->label($account->custom_client_label1) @@ -515,11 +539,11 @@ {!! Former::hidden('public_id')->data_bind("value: public_id, valueUpdate: 'afterkeydown', attr: {name: 'client[contacts][' + \$index() + '][public_id]'}") !!} - {!! Former::text('first_name')->data_bind("value: first_name, valueUpdate: 'afterkeydown', + {!! Former::text('first_name')->data_bind("value: first_name, valueUpdate: 'afterkeydown', attr: {name: 'client[contacts][' + \$index() + '][first_name]'}") !!} {!! Former::text('last_name')->data_bind("value: last_name, valueUpdate: 'afterkeydown', attr: {name: 'client[contacts][' + \$index() + '][last_name]'}") !!} - {!! Former::text('email')->data_bind("value: email, valueUpdate: 'afterkeydown', + {!! Former::text('email')->data_bind("value: email, valueUpdate: 'afterkeydown', attr: {name: 'client[contacts][' + \$index() + '][email]', id:'email'+\$index()}") ->addClass('client-email') !!} {!! Former::text('phone')->data_bind("value: phone, valueUpdate: 'afterkeydown', @@ -529,7 +553,7 @@
    {!! link_to('#', trans('texts.remove_contact').' -', array('data-bind'=>'click: $parent.removeContact')) !!} - + {!! link_to('#', trans('texts.add_contact').' +', array('data-bind'=>'click: $parent.addContact')) !!} @@ -552,7 +576,7 @@ ->placeholder($account->language ? $account->language->name : '') ->label(trans('texts.language_id')) ->data_bind('value: language_id') - ->fromQuery($languages, 'name', 'id') !!} + ->fromQuery($languages, 'name', 'id') !!} {!! Former::select('client[payment_terms]')->addOption('','')->data_bind('value: payment_terms') ->fromQuery($paymentTerms, 'name', 'num_days') ->label(trans('texts.payment_terms')) @@ -577,9 +601,9 @@   - +
    - +
    @@ -599,11 +623,11 @@ - + - + @@ -639,16 +663,16 @@ @include('invoices.knockout') \ No newline at end of file + diff --git a/resources/views/list.blade.php b/resources/views/list.blade.php index 1b7ac5b630c9..98c309a5db36 100644 --- a/resources/views/list.blade.php +++ b/resources/views/list.blade.php @@ -12,6 +12,9 @@ @if ($entityType == ENTITY_TASK) {!! Button::primary(trans('texts.invoice'))->withAttributes(['class'=>'invoice', 'onclick' =>'submitForm("invoice")'])->appendIcon(Icon::create('check')) !!} @endif + @if ($entityType == ENTITY_EXPENSE) + {!! Button::primary(trans('texts.invoice'))->withAttributes(['class'=>'invoice', 'onclick' =>'submitForm("invoice")'])->appendIcon(Icon::create('check')) !!} + @endif {!! DropdownButton::normal(trans('texts.archive'))->withContents([ ['label' => trans('texts.archive_'.$entityType), 'url' => 'javascript:submitForm("archive")'], diff --git a/resources/views/vendor.blade.php b/resources/views/vendor.blade.php new file mode 100644 index 000000000000..3248b39f4d3b --- /dev/null +++ b/resources/views/vendor.blade.php @@ -0,0 +1,99 @@ +{!!-- // vendor --!!} +
    +
    + + {!! Former::legend('Organization') !!} + {!! Former::text('name') !!} + {!! Former::text('id_number') !!} + {!! Former::text('vat_number') !!} + {!! Former::text('work_phone')->label('Phone') !!} + {!! Former::textarea('notes') !!} + + + {!! Former::legend('Address') !!} + {!! Former::text('address1')->label('Street') !!} + {!! Former::text('address2')->label('Apt/Floor') !!} + {!! Former::text('city') !!} + {!! Former::text('state') !!} + {!! Former::text('postal_code') !!} + {!! Former::select('country_id')->addOption('','')->label('Country') + ->fromQuery($countries, 'name', 'id') !!} + + +
    +
    + + {!! Former::legend('VendorContacts') !!} +
    + {!! Former::hidden('public_id')->data_bind("value: public_id, valueUpdate: 'afterkeydown'") !!} + {!! Former::text('first_name')->data_bind("value: first_name, valueUpdate: 'afterkeydown'") !!} + {!! Former::text('last_name')->data_bind("value: last_name, valueUpdate: 'afterkeydown'") !!} + {!! Former::text('email')->data_bind("value: email, valueUpdate: 'afterkeydown'") !!} + {!! Former::text('phone')->data_bind("value: phone, valueUpdate: 'afterkeydown'") !!} + +
    +
    + + {!! link_to('#', 'Remove contact', array('data-bind'=>'click: $parent.removeContact')) !!} + + + {!! link_to('#', 'Add contact', array('onclick'=>'return addContact()')) !!} + +
    +
    + +
    + +
    +
    + + +{!! Former::hidden('data')->data_bind("value: ko.toJSON(model)") !!} + + + diff --git a/resources/views/vendors/edit.blade.php b/resources/views/vendors/edit.blade.php new file mode 100644 index 000000000000..ef37f3dec67b --- /dev/null +++ b/resources/views/vendors/edit.blade.php @@ -0,0 +1,236 @@ +@extends('header') + + +@section('onReady') + $('input#name').focus(); +@stop + +@section('content') + +@if ($errors->first('vendorcontacts')) +
    {{ trans($errors->first('vendorcontacts')) }}
    +@endif + +
    + + {!! Former::open($url) + ->autocomplete('off') + ->rules( + ['email' => 'email'] + )->addClass('col-md-12 warn-on-exit') + ->method($method) !!} + + @include('partials.autocomplete_fix') + + @if ($vendor) + {!! Former::populate($vendor) !!} + {!! Former::hidden('public_id') !!} + @endif + +
    +
    + + +
    +
    +

    {!! trans('texts.organization') !!}

    +
    +
    + + {!! Former::text('name')->data_bind("attr { placeholder: placeholderName }") !!} + {!! Former::text('id_number') !!} + {!! Former::text('vat_number') !!} + {!! Former::text('website') !!} + {!! Former::text('work_phone') !!} + + @if (Auth::user()->isPro()) + @if ($customLabel1) + {!! Former::text('custom_value1')->label($customLabel1) !!} + @endif + @if ($customLabel2) + {!! Former::text('custom_value2')->label($customLabel2) !!} + @endif + @endif +
    +
    + +
    +
    +

    {!! trans('texts.address') !!}

    +
    +
    + + {!! Former::text('address1') !!} + {!! Former::text('address2') !!} + {!! Former::text('city') !!} + {!! Former::text('state') !!} + {!! Former::text('postal_code') !!} + {!! Former::select('country_id')->addOption('','') + ->fromQuery($countries, 'name', 'id') !!} + +
    +
    +
    +
    + + +
    +
    +

    {!! trans('texts.contacts') !!}

    +
    +
    + +
    + {!! Former::hidden('public_id')->data_bind("value: public_id, valueUpdate: 'afterkeydown', + attr: {name: 'vendorcontacts[' + \$index() + '][public_id]'}") !!} + {!! Former::text('first_name')->data_bind("value: first_name, valueUpdate: 'afterkeydown', + attr: {name: 'vendorcontacts[' + \$index() + '][first_name]'}") !!} + {!! Former::text('last_name')->data_bind("value: last_name, valueUpdate: 'afterkeydown', + attr: {name: 'vendorcontacts[' + \$index() + '][last_name]'}") !!} + {!! Former::text('email')->data_bind("value: email, valueUpdate: 'afterkeydown', + attr: {name: 'vendorcontacts[' + \$index() + '][email]', id:'email'+\$index()}") !!} + {!! Former::text('phone')->data_bind("value: phone, valueUpdate: 'afterkeydown', + attr: {name: 'vendorcontacts[' + \$index() + '][phone]'}") !!} + +
    +
    + + {!! link_to('#', trans('texts.remove_contact').' -', array('data-bind'=>'click: $parent.removeContact')) !!} + + + {!! link_to('#', trans('texts.add_contact').' +', array('onclick'=>'return addContact()')) !!} + +
    +
    +
    +
    +
    + + +
    +
    +

    {!! trans('texts.additional_info') !!}

    +
    +
    + + {!! Former::select('currency_id')->addOption('','') + ->placeholder($account->currency ? $account->currency->name : '') + ->fromQuery($currencies, 'name', 'id') !!} + {!! Former::select('language_id')->addOption('','') + ->placeholder($account->language ? $account->language->name : '') + ->fromQuery($languages, 'name', 'id') !!} + {!! Former::select('payment_terms')->addOption('','') + ->fromQuery($paymentTerms, 'name', 'num_days') + ->help(trans('texts.payment_terms_help')) !!} + {!! Former::select('size_id')->addOption('','') + ->fromQuery($sizes, 'name', 'id') !!} + {!! Former::select('industry_id')->addOption('','') + ->fromQuery($industries, 'name', 'id') !!} + {!! Former::textarea('private_notes') !!} + + + @if (isset($proPlanPaid)) + {!! Former::populateField('pro_plan_paid', $proPlanPaid) !!} + {!! Former::text('pro_plan_paid') + ->data_date_format('yyyy-mm-dd') + ->addGroupClass('pro_plan_paid_date') + ->append('') !!} + + @endif + +
    +
    + +
    +
    + + + {!! Former::hidden('data')->data_bind("value: ko.toJSON(model)") !!} + + + +
    + {!! Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/vendors/' . ($vendor ? $vendor->public_id : '')))->appendIcon(Icon::create('remove-circle')) !!} + {!! Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) !!} +
    + + {!! Former::close() !!} +
    +@stop diff --git a/resources/views/vendors/show.blade.php b/resources/views/vendors/show.blade.php new file mode 100644 index 000000000000..1b6b402629ed --- /dev/null +++ b/resources/views/vendors/show.blade.php @@ -0,0 +1,272 @@ +@extends('header') + +@section('head') + @parent + + @if ($vendor->hasAddress()) + + + + @endif +@stop + + +@section('content') + +
    + {!! Former::open('vendors/bulk')->addClass('mainForm') !!} +
    + {!! Former::text('action') !!} + {!! Former::text('public_id')->value($vendor->public_id) !!} +
    + + @if ($vendor->trashed()) + {!! Button::primary(trans('texts.restore_vendor'))->withAttributes(['onclick' => 'onRestoreClick()']) !!} + @else + {!! DropdownButton::normal(trans('texts.edit_vendor')) + ->withAttributes(['class'=>'normalDropDown']) + ->withContents([ + ['label' => trans('texts.archive_vendor'), 'url' => "javascript:onArchiveClick()"], + ['label' => trans('texts.delete_vendor'), 'url' => "javascript:onDeleteClick()"], + ] + )->split() !!} + + {!! DropdownButton::primary(trans('texts.new_expense')) + ->withAttributes(['class'=>'primaryDropDown']) + ->withContents($actionLinks)->split() !!} + @endif + {!! Former::close() !!} + +
    + + +

    {{ $vendor->getDisplayName() }}

    +
    +
    +
    +
    +

    {{ trans('texts.details') }}

    + @if ($vendor->id_number) +

    {{ trans('texts.id_number').': '.$vendor->id_number }}

    + @endif + @if ($vendor->vat_number) +

    {{ trans('texts.vat_number').': '.$vendor->vat_number }}

    + @endif + + @if ($vendor->address1) + {{ $vendor->address1 }}
    + @endif + @if ($vendor->address2) + {{ $vendor->address2 }}
    + @endif + @if ($vendor->getCityState()) + {{ $vendor->getCityState() }}
    + @endif + @if ($vendor->country) + {{ $vendor->country->name }}
    + @endif + + @if ($vendor->account->custom_vendor_label1 && $vendor->custom_value1) + {{ $vendor->account->custom_vendor_label1 . ': ' . $vendor->custom_value1 }}
    + @endif + @if ($vendor->account->custom_vendor_label2 && $vendor->custom_value2) + {{ $vendor->account->custom_vendor_label2 . ': ' . $vendor->custom_value2 }}
    + @endif + + @if ($vendor->work_phone) + {{ $vendor->work_phone }} + @endif + + @if ($vendor->private_notes) +

    {{ $vendor->private_notes }}

    + @endif + + @if ($vendor->vendor_industry) + {{ $vendor->vendor_industry->name }}
    + @endif + @if ($vendor->vendor_size) + {{ $vendor->vendor_size->name }}
    + @endif + + @if ($vendor->website) +

    {!! Utils::formatWebsite($vendor->website) !!}

    + @endif + + @if ($vendor->language) +

    {{ $vendor->language->name }}

    + @endif + +

    {{ $vendor->payment_terms ? trans('texts.payment_terms') . ": " . trans('texts.payment_terms_net') . " " . $vendor->payment_terms : '' }}

    +
    + +
    +

    {{ trans('texts.contacts') }}

    + @foreach ($vendor->vendorcontacts as $contact) + @if ($contact->first_name || $contact->last_name) + {{ $contact->first_name.' '.$contact->last_name }}
    + @endif + @if ($contact->email) + {!! HTML::mailto($contact->email, $contact->email) !!}
    + @endif + @if ($contact->phone) + {{ $contact->phone }}
    + @endif + @endforeach +
    + +
    +

    {{ trans('texts.standing') }} + + + + + +
    {{ trans('texts.balance') }}{{ Utils::formatMoney($totalexpense, $vendor->getCurrencyId()) }}
    +

    +
    +
    +
    +
    + + @if ($vendor->hasAddress()) +
    +
    + @endif + + + +
    +
    + {!! Datatable::table() + ->addColumn( + trans('texts.date'), + trans('texts.message')) + ->setUrl(url('api/vendoractivities/'. $vendor->public_id)) + ->setCustomValues('entityType', 'activity') + ->setOptions('sPaginationType', 'bootstrap') + ->setOptions('bFilter', false) + ->setOptions('aaSorting', [['0', 'desc']]) + ->render('datatable') !!} +
    + +
    + {!! Datatable::table() + ->addColumn( + trans('texts.expense_date'), + trans('texts.amount'), + trans('texts.public_notes')) + ->setUrl(url('api/expenseVendor/' . $vendor->public_id)) + ->setCustomValues('entityType', 'expenses') + ->setOptions('sPaginationType', 'bootstrap') + ->setOptions('bFilter', false) + ->setOptions('aaSorting', [['0', 'asc']]) + ->render('datatable') + !!} +
    +
    + + + +@stop