Working on expense categories

This commit is contained in:
Hillel Coren 2016-07-06 21:35:16 +03:00
parent 965ff2e252
commit cd9aae1e22
23 changed files with 487 additions and 41 deletions

View File

@ -0,0 +1,108 @@
<?php namespace App\Http\Controllers;
use View;
use Utils;
use Input;
use Session;
use App\Services\ExpenseCategoryService;
use App\Ninja\Repositories\ExpenseCategoryRepository;
use App\Http\Requests\ExpenseCategoryRequest;
use App\Http\Requests\CreateExpenseCategoryRequest;
use App\Http\Requests\UpdateExpenseCategoryRequest;
class ExpenseCategoryController extends BaseController
{
protected $categoryRepo;
protected $categoryService;
protected $entityType = ENTITY_EXPENSE_CATEGORY;
public function __construct(ExpenseCategoryRepository $categoryRepo, ExpenseCategoryService $categoryService)
{
$this->categoryRepo = $categoryRepo;
$this->categoryService = $categoryService;
}
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
return View::make('list', [
'entityType' => ENTITY_EXPENSE_CATEGORY,
'title' => trans('texts.expense_categories'),
'sortCol' => '1',
'columns' => Utils::trans([
'checkbox',
'name',
''
]),
]);
}
public function getDatatable($expensePublicId = null)
{
return $this->categoryService->getDatatable(Input::get('sSearch'));
}
public function create(ExpenseCategoryRequest $request)
{
$data = [
'category' => null,
'method' => 'POST',
'url' => 'expense_categories',
'title' => trans('texts.new_category'),
];
return View::make('expense_categories.edit', $data);
}
public function edit(ExpenseCategoryRequest $request)
{
$category = $request->entity();
$data = [
'category' => $category,
'method' => 'PUT',
'url' => 'expense_categories/' . $category->public_id,
'title' => trans('texts.edit_category'),
];
return View::make('expense_categories.edit', $data);
}
public function store(CreateExpenseCategoryRequest $request)
{
$category = $this->categoryRepo->save($request->input());
Session::flash('message', trans('texts.created_expense_category'));
return redirect()->to($category->getRoute());
}
public function update(UpdateExpenseCategoryRequest $request)
{
$category = $this->categoryRepo->save($request->input(), $request->entity());
Session::flash('message', trans('texts.updated_expense_category'));
return redirect()->to($category->getRoute());
}
public function bulk()
{
$action = Input::get('action');
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
$count = $this->categoryService->bulk($ids, $action);
if ($count > 0) {
$field = $count == 1 ? "{$action}d_expense_category" : "{$action}d_expense_categories";
$message = trans("texts.$field", ['count' => $count]);
Session::flash('message', $message);
}
return redirect()->to('/expense_categories');
}
}

View File

@ -50,6 +50,7 @@ class ExpenseController extends BaseController
'client',
'expense_date',
'amount',
'category',
'public_notes',
'status',
''

View File

@ -0,0 +1,27 @@
<?php namespace App\Http\Requests;
class CreateExpenseCategoryRequest extends ExpenseCategoryRequest
{
// Expenses
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->is_admin;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required',
];
}
}

View File

@ -16,10 +16,14 @@ class EntityRequest extends Request {
// The entity id can appear as invoices, invoice_id, public_id or id
$publicId = false;
foreach (['_id', 's'] as $suffix) {
$field = $this->entityType . $suffix;
if ($this->$field) {
$publicId= $this->$field;
$field = $this->entityType . '_id';
if ( ! empty($this->$field)) {
$publicId = $this->$field;
}
if ( ! $publicId) {
$field = Utils::pluralizeEntityType($this->entityType);
if ( ! empty($this->$field)) {
$publicId = $this->$field;
}
}
if ( ! $publicId) {

View File

@ -0,0 +1,7 @@
<?php namespace App\Http\Requests;
class ExpenseCategoryRequest extends EntityRequest {
protected $entityType = ENTITY_EXPENSE_CATEGORY;
}

View File

@ -0,0 +1,26 @@
<?php namespace App\Http\Requests;
class UpdateExpenseCategoryRequest extends ExpenseCategoryRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->is_admin;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required',
];
}
}

View File

@ -195,6 +195,13 @@ Route::group(['middleware' => 'auth:user'], function() {
Route::get('api/expense', ['as'=>'api.expenses', 'uses'=>'ExpenseController@getDatatable']);
Route::get('api/vendor_expense/{id}', ['as'=>'api.expense', 'uses'=>'ExpenseController@getDatatableVendor']);
Route::post('expenses/bulk', 'ExpenseController@bulk');
Route::get('expense_categories', 'ExpenseCategoryController@index');
Route::get('api/expense_categories', ['as'=>'api.expense_categories', 'uses'=>'ExpenseCategoryController@getDatatable']);
Route::get('expense_categories/create', 'ExpenseCategoryController@create');
Route::post('expense_categories', 'ExpenseCategoryController@store');
Route::put('expense_categories/{expense_categories}', 'ExpenseCategoryController@update');
Route::get('expense_categories/{expense_categories}/edit', 'ExpenseCategoryController@edit');
Route::post('expense_categories/bulk', 'ExpenseCategoryController@bulk');
});
Route::group([
@ -365,7 +372,7 @@ if (!defined('CONTACT_EMAIL')) {
define('ENTITY_EXPENSE_ACTIVITY', 'expense_activity');
define('ENTITY_BANK_ACCOUNT', 'bank_account');
define('ENTITY_BANK_SUBACCOUNT', 'bank_subaccount');
define('ENTITY_EXPENSE_CATEGORIES', 'expense_categories');
define('ENTITY_EXPENSE_CATEGORY', 'expense_category');
define('INVOICE_TYPE_STANDARD', 1);
define('INVOICE_TYPE_QUOTE', 2);

View File

@ -374,6 +374,15 @@ class Utils
return $string;
}
public static function pluralizeEntityType($type)
{
if ($type === ENTITY_EXPENSE_CATEGORY) {
return 'expense_categories';
} else {
return $type . 's';
}
}
public static function maskAccountNumber($value)
{
$length = strlen($value);
@ -849,6 +858,7 @@ class Utils
}
// nouns in German and French should be uppercase
// TODO remove this
public static function transFlowText($key)
{
$str = trans("texts.$key");

View File

@ -47,7 +47,7 @@ class Expense extends EntityModel
*/
public function expense_category()
{
return $this->belongsTo('App\Models\ExpenseCategory');
return $this->belongsTo('App\Models\ExpenseCategory')->withTrashed();
}
/**

View File

@ -17,6 +17,14 @@ class ExpenseCategory extends EntityModel
'name',
];
/**
* @return mixed
*/
public function getEntityType()
{
return ENTITY_EXPENSE_CATEGORY;
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
@ -25,4 +33,12 @@ class ExpenseCategory extends EntityModel
return $this->belongsTo('App\Models\Expense');
}
/**
* @return string
*/
public function getRoute()
{
return "/expense_categories/{$this->public_id}/edit";
}
}

View File

@ -0,0 +1,39 @@
<?php namespace App\Ninja\Datatables;
use Utils;
use URL;
use Auth;
class ExpenseCategoryDatatable extends EntityDatatable
{
public $entityType = ENTITY_EXPENSE_CATEGORY;
public function columns()
{
return [
[
'name',
function ($model)
{
return link_to("expense_categories/{$model->public_id}/edit", $model->category ?: '')->toHtml();
}
],
];
}
public function actions()
{
return [
[
trans('texts.edit_category'),
function ($model) {
return URL::to("expense_categories/{$model->public_id}/edit") ;
},
function ($model) {
return Auth::user()->is_admin;
}
],
];
}
}

View File

@ -66,6 +66,12 @@ class ExpenseDatatable extends EntityDatatable
}
}
],
[
'category',
function ($model) {
return $model->category != null ? substr($model->category, 0, 100) : '';
}
],
[
'public_notes',
function ($model) {

View File

@ -176,6 +176,7 @@ class AccountRepository
ENTITY_QUOTE,
ENTITY_TASK,
ENTITY_EXPENSE,
ENTITY_EXPENSE_CATEGORY,
ENTITY_VENDOR,
ENTITY_RECURRING_INVOICE,
ENTITY_PAYMENT,
@ -185,11 +186,11 @@ class AccountRepository
foreach ($entityTypes as $entityType) {
$features[] = [
"new_{$entityType}",
"/{$entityType}s/create",
Utils::pluralizeEntityType($entityType) . '/create'
];
$features[] = [
"list_{$entityType}s",
"/{$entityType}s",
'list_' . Utils::pluralizeEntityType($entityType),
Utils::pluralizeEntityType($entityType)
];
}

View File

@ -0,0 +1,51 @@
<?php namespace App\Ninja\Repositories;
use DB;
use Utils;
use Auth;
use App\Models\ExpenseCategory;
class ExpenseCategoryRepository extends BaseRepository
{
public function getClassName()
{
return 'App\Models\ExpenseCategory';
}
public function find($filter = null)
{
$query = DB::table('expense_categories')
->where('expense_categories.account_id', '=', Auth::user()->account_id)
->select(
'expense_categories.name as category',
'expense_categories.public_id',
'expense_categories.deleted_at'
);
if (!\Session::get('show_trash:expense_category')) {
$query->where('expense_categories.deleted_at', '=', null);
}
if ($filter) {
$query->where(function ($query) use ($filter) {
$query->where('expense_categories.name', 'like', '%'.$filter.'%');
});
}
return $query;
}
public function save($input, $category = false)
{
$publicId = isset($data['public_id']) ? $data['public_id'] : false;
if ( ! $category) {
$category = ExpenseCategory::createNew();
}
$category->fill($input);
$category->save();
return $category;
}
}

View File

@ -49,6 +49,7 @@ class ExpenseRepository extends BaseRepository
->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
->leftjoin('vendors', 'vendors.id', '=', 'expenses.vendor_id')
->leftJoin('invoices', 'invoices.id', '=', 'expenses.invoice_id')
->leftJoin('expense_categories', 'expenses.expense_category_id', '=', 'expense_categories.id')
->where('expenses.account_id', '=', $accountid)
->where('contacts.deleted_at', '=', null)
->where('vendors.deleted_at', '=', null)
@ -75,6 +76,7 @@ class ExpenseRepository extends BaseRepository
'expenses.expense_currency_id',
'expenses.invoice_currency_id',
'expenses.user_id',
'expense_categories.name as category',
'invoices.public_id as invoice_public_id',
'invoices.user_id as invoice_user_id',
'invoices.balance',
@ -100,7 +102,8 @@ class ExpenseRepository extends BaseRepository
$query->where(function ($query) use ($filter) {
$query->where('expenses.public_notes', 'like', '%'.$filter.'%')
->orWhere('clients.name', 'like', '%'.$filter.'%')
->orWhere('vendors.name', 'like', '%'.$filter.'%');
->orWhere('vendors.name', 'like', '%'.$filter.'%')
->orWhere('expense_categories.name', 'like', '%'.$filter.'%');;
});
}

View File

@ -33,7 +33,7 @@ class AccountTransformer extends EntityTransformer
public function includeExpenseCategories(Account $account)
{
$transformer = new ExpenseCategoryTransformer($account, $this->serializer);
return $this->includeCollection($account->expense_categories, $transformer, ENTITY_EXPENSE_CATEGORIES);
return $this->includeCollection($account->expense_categories, $transformer, 'expense_categories');
}
/**

View File

@ -0,0 +1,5 @@
<?php
namespace App\Policies;
class ExpenseCategoryPolicy extends EntityPolicy {}

View File

@ -17,6 +17,7 @@ class AuthServiceProvider extends ServiceProvider
\App\Models\Credit::class => \App\Policies\CreditPolicy::class,
\App\Models\Document::class => \App\Policies\DocumentPolicy::class,
\App\Models\Expense::class => \App\Policies\ExpensePolicy::class,
\App\Models\ExpenseCategory::class => \App\Policies\ExpenseCategoryPolicy::class,
\App\Models\Invoice::class => \App\Policies\InvoicePolicy::class,
\App\Models\Payment::class => \App\Policies\PaymentPolicy::class,
\App\Models\Task::class => \App\Policies\TaskPolicy::class,

View File

@ -0,0 +1,66 @@
<?php namespace App\Services;
use Utils;
use Auth;
use App\Ninja\Repositories\ExpenseCategoryRepository;
use App\Ninja\Datatables\ExpenseCategoryDatatable;
/**
* Class ExpenseCategoryService
*/
class ExpenseCategoryService extends BaseService
{
/**
* @var ExpenseCategoryRepository
*/
protected $categoryRepo;
/**
* @var DatatableService
*/
protected $datatableService;
/**
* CreditService constructor.
*
* @param ExpenseCategoryRepository $creditRepo
* @param DatatableService $datatableService
*/
public function __construct(ExpenseCategoryRepository $categoryRepo, DatatableService $datatableService)
{
$this->categoryRepo = $categoryRepo;
$this->datatableService = $datatableService;
}
/**
* @return CreditRepository
*/
protected function getRepo()
{
return $this->categoryRepo;
}
/**
* @param $data
* @return mixed|null
*/
public function save($data)
{
return $this->categoryRepo->save($data);
}
/**
* @param $clientPublicId
* @param $search
* @return \Illuminate\Http\JsonResponse
*/
public function getDatatable($search)
{
// we don't support bulk edit and hide the client on the individual client page
$datatable = new ExpenseCategoryDatatable();
$query = $this->categoryRepo->find($search);
return $this->datatableService->createDatatable($datatable, $query);
}
}

View File

@ -60,7 +60,7 @@ class ExpenseService extends BaseService
if ( ! empty($data['category'])) {
$name = trim($data['category']);
$category = ExpenseCategory::scope()->whereName($name)->first();
$category = ExpenseCategory::scope()->withTrashed()->whereName($name)->first();
if ( ! $category) {
$category = ExpenseCategory::createNew();
$category->name = $name;

View File

@ -2010,6 +2010,18 @@ $LANG = array(
'all' => 'All',
'selected' => 'Selected',
'category' => 'Category',
'categories' => 'Categories',
'new_expense_category' => 'New Expense Category',
'edit_category' => 'Edit Category',
'archive_expense_category' => 'Archive Category',
'expense_categories' => 'Expense Categories',
'list_expense_categories' => 'List Expense Categories',
'updated_expense_category' => 'Successfully updated expense category',
'created_expense_category' => 'Successfully created expense category',
'archived_expense_category' => 'Successfully archived expense category',
'archived_expense_categories' => 'Successfully archived :count expense category',
'restore_expense_category' => 'Restore expense category',
'restored_expense_category' => 'Successfully restored expense category',
);

View File

@ -0,0 +1,51 @@
@extends('header')
@section('content')
{!! Former::open($url)
->addClass('col-md-10 col-md-offset-1 warn-on-exit')
->method($method)
->rules([
'name' => 'required',
]) !!}
@if ($category)
{!! Former::populate($category) !!}
@endif
<span style="display:none">
{!! Former::text('public_id') !!}
</span>
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.category') !!}</h3>
</div>
<div class="panel-body">
{!! Former::text('name') !!}
</div>
</div>
</div>
</div>
<center class="buttons">
{!! Button::normal(trans('texts.cancel'))->large()->asLinkTo(url('/expense_categories'))->appendIcon(Icon::create('remove-circle')) !!}
{!! Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) !!}
</center>
{!! Former::close() !!}
<script>
$(function() {
$('#name').focus();
});
</script>
@stop

View File

@ -2,7 +2,7 @@
@section('content')
{!! Former::open($entityType . 's/bulk')->addClass('listForm') !!}
{!! Former::open(Utils::pluralizeEntityType($entityType) . '/bulk')->addClass('listForm') !!}
<div style="display:none">
{!! Former::text('action') !!}
@ -18,14 +18,18 @@
@endif
@endcan
@if ($entityType == ENTITY_EXPENSE_CATEGORY)
{!! Button::normal(trans('texts.archive'))->asLinkTo('javascript:submitForm("archive")')->appendIcon(Icon::create('trash')) !!}
@else
{!! DropdownButton::normal(trans('texts.archive'))->withContents([
['label' => trans('texts.archive_'.$entityType), 'url' => 'javascript:submitForm("archive")'],
['label' => trans('texts.delete_'.$entityType), 'url' => 'javascript:submitForm("delete")'],
])->withAttributes(['class'=>'archive'])->split() !!}
@endif
&nbsp;<label for="trashed" style="font-weight:normal; margin-left: 10px;">
<input id="trashed" type="checkbox" onclick="setTrashVisible()"
{{ Session::get("show_trash:{$entityType}") ? 'checked' : ''}}/>&nbsp; {{ trans('texts.show_archived_deleted')}} {{ Utils::transFlowText($entityType.'s') }}
{{ Session::get("show_trash:{$entityType}") ? 'checked' : ''}}/>&nbsp; {{ trans('texts.show_archived_deleted')}}
</label>
<div id="top_right_buttons" class="pull-right">
@ -35,20 +39,21 @@
{!! Button::normal(trans('texts.quotes'))->asLinkTo(URL::to('/quotes'))->appendIcon(Icon::create('list')) !!}
{!! Button::normal(trans('texts.recurring'))->asLinkTo(URL::to('/recurring_invoices'))->appendIcon(Icon::create('list')) !!}
@elseif ($entityType == ENTITY_EXPENSE)
{!! Button::normal(trans('texts.categories'))->asLinkTo(URL::to('/expense_categories'))->appendIcon(Icon::create('list')) !!}
{!! Button::normal(trans('texts.vendors'))->asLinkTo(URL::to('/vendors'))->appendIcon(Icon::create('list')) !!}
@elseif ($entityType == ENTITY_CLIENT)
{!! Button::normal(trans('texts.credits'))->asLinkTo(URL::to('/credits'))->appendIcon(Icon::create('list')) !!}
@endif
@if (Auth::user()->hasPermission('create_all'))
{!! Button::primary(trans("texts.new_$entityType"))->asLinkTo(URL::to("/{$entityType}s/create"))->appendIcon(Icon::create('plus-sign')) !!}
{!! Button::primary(trans("texts.new_{$entityType}"))->asLinkTo(url(Utils::pluralizeEntityType($entityType) . '/create'))->appendIcon(Icon::create('plus-sign')) !!}
@endif
</div>
{!! Datatable::table()
->addColumn($columns)
->setUrl(route('api.' . $entityType . 's'))
->setUrl(route('api.' . Utils::pluralizeEntityType($entityType)))
->setCustomValues('rightAlign', isset($rightAlign) ? $rightAlign : [])
->setOptions('sPaginationType', 'bootstrap')
->setOptions('aaSorting', [[isset($sortCol) ? $sortCol : '1', 'desc']])