mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-06-23 20:00:33 -04:00
Expense module
This commit is contained in:
parent
d8cb1b436d
commit
3e9e28f06f
@ -143,7 +143,51 @@ class ExpenseController extends BaseController
|
||||
{
|
||||
$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);
|
||||
|
@ -380,6 +380,7 @@ class InvoiceController extends BaseController
|
||||
'recurringHelp' => $recurringHelp,
|
||||
'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,
|
||||
];
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
<?php namespace app\Listeners;
|
||||
|
||||
use Carbon;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Expense;
|
||||
use App\Events\PaymentWasDeleted;
|
||||
use App\Events\InvoiceWasDeleted;
|
||||
use App\Ninja\Repositories\ExpenseRepository;
|
||||
|
||||
class ExpenseListener
|
||||
@ -14,4 +15,11 @@ class ExpenseListener
|
||||
{
|
||||
$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]);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
@ -358,6 +359,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
'has_tasks',
|
||||
'custom_text_value1',
|
||||
'custom_text_value2',
|
||||
'has_expenses',
|
||||
]);
|
||||
|
||||
$this->client->setVisible([
|
||||
|
@ -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;
|
||||
|
||||
@ -204,6 +205,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();
|
||||
}
|
||||
@ -387,6 +391,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)) {
|
||||
@ -395,7 +407,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();
|
||||
}
|
||||
|
@ -70,6 +70,7 @@ class InvoiceTransformer extends EntityTransformer
|
||||
'custom_value2' => $invoice->custom_value2,
|
||||
'custom_taxes1' => (bool) $invoice->custom_taxes1,
|
||||
'custom_taxes2' => (bool) $invoice->custom_taxes2,
|
||||
'has_expenses' => (bool) $invoice->has_expenses,
|
||||
];
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ class CreateExpensesTable extends Migration
|
||||
$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);
|
||||
|
@ -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');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -1051,11 +1051,14 @@ return array(
|
||||
'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',
|
||||
|
||||
);
|
||||
|
@ -203,6 +203,7 @@
|
||||
<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>
|
||||
<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>
|
||||
<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('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') !!}
|
||||
</div>
|
||||
|
||||
@ -685,6 +687,24 @@
|
||||
model.invoice().invoice_items.push(blank);
|
||||
model.invoice().has_tasks(true);
|
||||
@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
|
||||
|
||||
model.invoice().tax(model.getTaxRate(model.invoice().tax_name(), model.invoice().tax_rate()));
|
||||
@ -806,6 +826,10 @@
|
||||
NINJA.formIsChanged = true;
|
||||
@endif
|
||||
|
||||
@if (isset($expenses) && $expenses)
|
||||
NINJA.formIsChanged = true;
|
||||
@endif
|
||||
|
||||
applyComboboxListeners();
|
||||
});
|
||||
|
||||
|
@ -229,6 +229,7 @@ function InvoiceModel(data) {
|
||||
self.invoice_design_id = ko.observable(1);
|
||||
self.partial = ko.observable(0);
|
||||
self.has_tasks = ko.observable();
|
||||
self.has_expenses = ko.observable();
|
||||
|
||||
self.custom_value1 = ko.observable(0);
|
||||
self.custom_value2 = ko.observable(0);
|
||||
@ -712,6 +713,7 @@ function ItemModel(data) {
|
||||
self.tax_name = ko.observable('');
|
||||
self.tax_rate = ko.observable(0);
|
||||
self.task_public_id = ko.observable('');
|
||||
self.expense_public_id = ko.observable('');
|
||||
self.actionsVisible = ko.observable(false);
|
||||
|
||||
self._tax = ko.observable();
|
||||
|
Loading…
x
Reference in New Issue
Block a user