Expense module

This commit is contained in:
steenrabol 2016-01-10 11:25:05 +01:00
parent d8cb1b436d
commit 3e9e28f06f
11 changed files with 287 additions and 140 deletions

View File

@ -143,7 +143,51 @@ class ExpenseController extends BaseController
{ {
$action = Input::get('action'); $action = Input::get('action');
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids'); $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); $count = $this->expenseService->bulk($ids, $action);
}
if ($count > 0) { if ($count > 0) {
$message = Utils::pluralize($action.'d_expense', $count); $message = Utils::pluralize($action.'d_expense', $count);

View File

@ -380,6 +380,7 @@ class InvoiceController extends BaseController
'recurringHelp' => $recurringHelp, 'recurringHelp' => $recurringHelp,
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(), 'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null, 'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null,
'expenses' => Session::get('expenses') ? json_encode(Session::get('expenses')) : null,
]; ];
} }

View File

@ -1,8 +1,9 @@
<?php namespace app\Listeners; <?php namespace app\Listeners;
use Carbon; use Carbon;
use App\Models\Credit; use App\Models\Expense;
use App\Events\PaymentWasDeleted; use App\Events\PaymentWasDeleted;
use App\Events\InvoiceWasDeleted;
use App\Ninja\Repositories\ExpenseRepository; use App\Ninja\Repositories\ExpenseRepository;
class ExpenseListener class ExpenseListener
@ -14,4 +15,11 @@ class ExpenseListener
{ {
$this->expenseRepo = $expenseRepo; $this->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]);
}
} }

View File

@ -28,6 +28,7 @@ class Invoice extends EntityModel implements BalanceAffecting
'is_recurring' => 'boolean', 'is_recurring' => 'boolean',
'has_tasks' => 'boolean', 'has_tasks' => 'boolean',
'auto_bill' => 'boolean', 'auto_bill' => 'boolean',
'has_expenses' => 'boolean',
]; ];
// used for custom invoice numbers // used for custom invoice numbers
@ -358,6 +359,7 @@ class Invoice extends EntityModel implements BalanceAffecting
'has_tasks', 'has_tasks',
'custom_text_value1', 'custom_text_value1',
'custom_text_value2', 'custom_text_value2',
'has_expenses',
]); ]);
$this->client->setVisible([ $this->client->setVisible([

View File

@ -7,6 +7,7 @@ use App\Models\InvoiceItem;
use App\Models\Invitation; use App\Models\Invitation;
use App\Models\Product; use App\Models\Product;
use App\Models\Task; use App\Models\Task;
use App\Models\Expense;
use App\Services\PaymentService; use App\Services\PaymentService;
use App\Ninja\Repositories\BaseRepository; use App\Ninja\Repositories\BaseRepository;
@ -204,6 +205,9 @@ class InvoiceRepository extends BaseRepository
if (isset($data['has_tasks']) && filter_var($data['has_tasks'], FILTER_VALIDATE_BOOLEAN)) { if (isset($data['has_tasks']) && filter_var($data['has_tasks'], FILTER_VALIDATE_BOOLEAN)) {
$invoice->has_tasks = true; $invoice->has_tasks = true;
} }
if (isset($data['has_expenses']) && filter_var($data['has_expenses'], FILTER_VALIDATE_BOOLEAN)) {
$invoice->has_expenses = true;
}
} else { } else {
$invoice = Invoice::scope($publicId)->firstOrFail(); $invoice = Invoice::scope($publicId)->firstOrFail();
} }
@ -387,6 +391,14 @@ class InvoiceRepository extends BaseRepository
$task->save(); $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']) { if ($item['product_key']) {
$productKey = trim($item['product_key']); $productKey = trim($item['product_key']);
if (\Auth::user()->account->update_products && ! strtotime($productKey)) { if (\Auth::user()->account->update_products && ! strtotime($productKey)) {
@ -395,7 +407,10 @@ class InvoiceRepository extends BaseRepository
$product = Product::createNew(); $product = Product::createNew();
$product->product_key = trim($item['product_key']); $product->product_key = trim($item['product_key']);
} }
$product->notes = $invoice->has_tasks ? '' : $item['notes']; $product->notes = $invoice->has_tasks ? '' : $item['notes'];
$product->notes = $invoice->has_expenses ? '' : $item['notes'];
$product->cost = $item['cost']; $product->cost = $item['cost'];
$product->save(); $product->save();
} }

View File

@ -70,6 +70,7 @@ class InvoiceTransformer extends EntityTransformer
'custom_value2' => $invoice->custom_value2, 'custom_value2' => $invoice->custom_value2,
'custom_taxes1' => (bool) $invoice->custom_taxes1, 'custom_taxes1' => (bool) $invoice->custom_taxes1,
'custom_taxes2' => (bool) $invoice->custom_taxes2, 'custom_taxes2' => (bool) $invoice->custom_taxes2,
'has_expenses' => (bool) $invoice->has_expenses,
]; ];
} }
} }

View File

@ -23,6 +23,7 @@ class CreateExpensesTable extends Migration
$table->unsignedInteger('account_id')->index(); $table->unsignedInteger('account_id')->index();
$table->unsignedInteger('vendor_id')->nullable(); $table->unsignedInteger('vendor_id')->nullable();
$table->unsignedInteger('user_id'); $table->unsignedInteger('user_id');
$table->unsignedInteger('invoice_id')->nullable();
$table->unsignedInteger('invoice_client_id')->nullable(); $table->unsignedInteger('invoice_client_id')->nullable();
$table->boolean('is_deleted')->default(false); $table->boolean('is_deleted')->default(false);
$table->decimal('amount', 13, 2); $table->decimal('amount', 13, 2);

View File

@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddInvoicesHasExpenses extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('invoices', function(Blueprint $table)
{
$table->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');
});
}
}

View File

@ -1051,11 +1051,14 @@ return array(
'view' => 'View', 'view' => 'View',
'restore_expense' => 'Restore expense', 'restore_expense' => 'Restore expense',
'invoice_expense' => 'Invoice', '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 // Payment terms
'num_days' => 'Number of days', 'num_days' => 'Number of days',
'create_payment_term' => 'Create payment term', 'create_payment_term' => 'Create payment term',
'edit_payment_terms' => 'Edit payment term', 'edit_payment_terms' => 'Edit payment term',
'edit_payment_term' => 'Edit payment term', 'edit_payment_term' => 'Edit payment term',
'archive_payment_term' => 'Archive payment term', 'archive_payment_term' => 'Archive payment term',
); );

View File

@ -203,6 +203,7 @@
<textarea data-bind="value: wrapped_notes, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][notes]'}" <textarea data-bind="value: wrapped_notes, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][notes]'}"
rows="1" cols="60" style="resize: vertical" class="form-control word-wrap"></textarea> rows="1" cols="60" style="resize: vertical" class="form-control word-wrap"></textarea>
<input type="text" data-bind="value: task_public_id, attr: {name: 'invoice_items[' + $index() + '][task_public_id]'}" style="display: none"/> <input type="text" data-bind="value: task_public_id, attr: {name: 'invoice_items[' + $index() + '][task_public_id]'}" style="display: none"/>
<input type="text" data-bind="value: expense_public_id, attr: {name: 'invoice_items[' + $index() + '][expense_public_id]'}" style="display: none"/>
</td> </td>
<td> <td>
<input data-bind="value: prettyCost, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][cost]'}" <input data-bind="value: prettyCost, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][cost]'}"
@ -383,6 +384,7 @@
{!! Former::text('is_quote')->data_bind('value: is_quote') !!} {!! Former::text('is_quote')->data_bind('value: is_quote') !!}
{!! Former::text('has_tasks')->data_bind('value: has_tasks') !!} {!! Former::text('has_tasks')->data_bind('value: has_tasks') !!}
{!! Former::text('data')->data_bind('value: ko.mapping.toJSON(model)') !!} {!! Former::text('data')->data_bind('value: ko.mapping.toJSON(model)') !!}
{!! Former::text('has_expenses')->data_bind('value: has_expenses') !!}
{!! Former::text('pdfupload') !!} {!! Former::text('pdfupload') !!}
</div> </div>
@ -685,6 +687,24 @@
model.invoice().invoice_items.push(blank); model.invoice().invoice_items.push(blank);
model.invoice().has_tasks(true); model.invoice().has_tasks(true);
@endif @endif
@if (isset($expenses) && $expenses)
// move the blank invoice line item to the end
var blank = model.invoice().invoice_items.pop();
var expenses = {!! $expenses !!};
for (var i=0; i<expenses.length; i++) {
var expense = expenses[i];
var item = model.invoice().addItem();
item.notes(expense.description);
item.qty(expense.qty);
item.expense_public_id(expense.publicId);
item.cost(expense.cost);
}
model.invoice().invoice_items.push(blank);
model.invoice().has_expenses(true);
@endif
@endif @endif
model.invoice().tax(model.getTaxRate(model.invoice().tax_name(), model.invoice().tax_rate())); model.invoice().tax(model.getTaxRate(model.invoice().tax_name(), model.invoice().tax_rate()));
@ -806,6 +826,10 @@
NINJA.formIsChanged = true; NINJA.formIsChanged = true;
@endif @endif
@if (isset($expenses) && $expenses)
NINJA.formIsChanged = true;
@endif
applyComboboxListeners(); applyComboboxListeners();
}); });

View File

@ -229,6 +229,7 @@ function InvoiceModel(data) {
self.invoice_design_id = ko.observable(1); self.invoice_design_id = ko.observable(1);
self.partial = ko.observable(0); self.partial = ko.observable(0);
self.has_tasks = ko.observable(); self.has_tasks = ko.observable();
self.has_expenses = ko.observable();
self.custom_value1 = ko.observable(0); self.custom_value1 = ko.observable(0);
self.custom_value2 = ko.observable(0); self.custom_value2 = ko.observable(0);
@ -712,6 +713,7 @@ function ItemModel(data) {
self.tax_name = ko.observable(''); self.tax_name = ko.observable('');
self.tax_rate = ko.observable(0); self.tax_rate = ko.observable(0);
self.task_public_id = ko.observable(''); self.task_public_id = ko.observable('');
self.expense_public_id = ko.observable('');
self.actionsVisible = ko.observable(false); self.actionsVisible = ko.observable(false);
self._tax = ko.observable(); self._tax = ko.observable();