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
@ -111,7 +111,7 @@ class ExpenseController extends BaseController
|
||||
$data['proPlanPaid'] = $account['pro_plan_paid'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return View::make('expenses.edit', $data);
|
||||
}
|
||||
|
||||
@ -124,26 +124,70 @@ class ExpenseController extends BaseController
|
||||
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');
|
||||
$count = $this->expenseService->bulk($ids, $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);
|
||||
@ -152,7 +196,7 @@ class ExpenseController extends BaseController
|
||||
|
||||
return Redirect::to('expenses');
|
||||
}
|
||||
|
||||
|
||||
private static function getViewModel()
|
||||
{
|
||||
return [
|
||||
@ -172,7 +216,7 @@ class ExpenseController extends BaseController
|
||||
public function show($publicId)
|
||||
{
|
||||
$expense = Expense::withTrashed()->scope($publicId)->firstOrFail();
|
||||
|
||||
|
||||
if($expense) {
|
||||
Utils::trackViewed($expense->getDisplayName(), 'expense');
|
||||
}
|
||||
@ -191,5 +235,5 @@ class ExpenseController extends BaseController
|
||||
);
|
||||
|
||||
return View::make('expenses.show', $data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,11 +119,11 @@ class InvoiceController extends BaseController
|
||||
Session::put('invitation_key', $invitationKey); // track current invitation
|
||||
|
||||
$account->loadLocalizationSettings($client);
|
||||
|
||||
|
||||
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
|
||||
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
|
||||
$invoice->is_pro = $account->isPro();
|
||||
|
||||
|
||||
if ($invoice->invoice_design_id == CUSTOM_DESIGN) {
|
||||
$invoice->invoice_design->javascript = $account->custom_design;
|
||||
} else {
|
||||
@ -204,7 +204,7 @@ class InvoiceController extends BaseController
|
||||
->withTrashed()
|
||||
->firstOrFail();
|
||||
$entityType = $invoice->getEntityType();
|
||||
|
||||
|
||||
$contactIds = DB::table('invitations')
|
||||
->join('contacts', 'contacts.id', '=', 'invitations.contact_id')
|
||||
->where('invitations.invoice_id', '=', $invoice->id)
|
||||
@ -282,7 +282,7 @@ class InvoiceController extends BaseController
|
||||
'actions' => $actions,
|
||||
'lastSent' => $lastSent);
|
||||
$data = array_merge($data, self::getViewModel());
|
||||
|
||||
|
||||
if ($clone) {
|
||||
$data['formIsChanged'] = true;
|
||||
}
|
||||
@ -318,14 +318,14 @@ class InvoiceController extends BaseController
|
||||
$account = Auth::user()->account;
|
||||
$entityType = $isRecurring ? ENTITY_RECURRING_INVOICE : ENTITY_INVOICE;
|
||||
$clientId = null;
|
||||
|
||||
|
||||
if ($clientPublicId) {
|
||||
$clientId = Client::getPrivateId($clientPublicId);
|
||||
}
|
||||
|
||||
|
||||
$invoice = $account->createInvoice($entityType, $clientId);
|
||||
$invoice->public_id = 0;
|
||||
|
||||
|
||||
$data = [
|
||||
'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(),
|
||||
'entityType' => $invoice->getEntityType(),
|
||||
@ -335,7 +335,7 @@ class InvoiceController extends BaseController
|
||||
'title' => trans('texts.new_invoice'),
|
||||
];
|
||||
$data = array_merge($data, self::getViewModel());
|
||||
|
||||
|
||||
return View::make('invoices.edit', $data);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
];
|
||||
|
||||
}
|
||||
@ -413,7 +414,7 @@ class InvoiceController extends BaseController
|
||||
if ($action == 'email') {
|
||||
return $this->emailInvoice($invoice, Input::get('pdfupload'));
|
||||
}
|
||||
|
||||
|
||||
return redirect()->to($invoice->getRoute());
|
||||
}
|
||||
|
||||
@ -440,7 +441,7 @@ class InvoiceController extends BaseController
|
||||
} elseif ($action == 'email') {
|
||||
return $this->emailInvoice($invoice, Input::get('pdfupload'));
|
||||
}
|
||||
|
||||
|
||||
return redirect()->to($invoice->getRoute());
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
@ -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()) {
|
||||
@ -207,7 +208,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;
|
||||
@ -358,6 +359,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
'has_tasks',
|
||||
'custom_text_value1',
|
||||
'custom_text_value2',
|
||||
'has_expenses',
|
||||
]);
|
||||
|
||||
$this->client->setVisible([
|
||||
@ -451,7 +453,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);
|
||||
@ -477,7 +479,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
if (count($schedule) < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return $schedule[1]->getStart();
|
||||
}
|
||||
|
||||
@ -539,7 +541,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
if (!$nextSendDate = $this->getNextSendDate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return $this->account->getDateTime() >= $nextSendDate;
|
||||
}
|
||||
*/
|
||||
|
@ -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;
|
||||
|
||||
@ -175,7 +176,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);
|
||||
@ -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();
|
||||
}
|
||||
@ -265,7 +269,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']) {
|
||||
@ -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();
|
||||
}
|
||||
@ -639,7 +654,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';
|
||||
|
@ -23,7 +23,7 @@ class InvoiceTransformer extends EntityTransformer
|
||||
protected $defaultIncludes = [
|
||||
'invoice_items',
|
||||
];
|
||||
|
||||
|
||||
public function includeInvoiceItems(Invoice $invoice)
|
||||
{
|
||||
$transformer = new InvoiceItemTransformer($this->account, $this->serializer);
|
||||
@ -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);
|
||||
@ -38,7 +39,7 @@ class CreateExpensesTable extends Migration
|
||||
// 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') );
|
||||
|
@ -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');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -263,7 +263,7 @@ return array(
|
||||
'deleted_vendor' => 'Successfully deleted vendor',
|
||||
'deleted_vendors' => 'Successfully deleted :count vendors',
|
||||
|
||||
|
||||
|
||||
// Emails
|
||||
'confirmation_subject' => 'Invoice Ninja Account Confirmation',
|
||||
'confirmation_header' => 'Account Confirmation',
|
||||
@ -911,7 +911,7 @@ return array(
|
||||
'default_invoice_footer' => 'Default Invoice Footer',
|
||||
'quote_footer' => 'Quote Footer',
|
||||
'free' => 'Free',
|
||||
|
||||
|
||||
'quote_is_approved' => 'This quote is approved',
|
||||
'apply_credit' => 'Apply Credit',
|
||||
'system_settings' => 'System Settings',
|
||||
@ -1026,7 +1026,7 @@ return array(
|
||||
'archive_vendor' => 'Archive vendor',
|
||||
'delete_vendor' => 'Delete vendor',
|
||||
'view_vendor' => 'View vendor',
|
||||
|
||||
|
||||
// Expenses
|
||||
'expense_amount' => 'Expense amount',
|
||||
'expense_balance' => 'Expense balance',
|
||||
@ -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',
|
||||
|
||||
);
|
||||
|
@ -28,7 +28,7 @@
|
||||
<li>{!! link_to(($entityType == ENTITY_QUOTE ? 'quotes' : 'invoices'), trans('texts.' . ($entityType == ENTITY_QUOTE ? 'quotes' : 'invoices'))) !!}</li>
|
||||
<li class="active">{{ $invoice->invoice_number }}</li>
|
||||
@endif
|
||||
</ol>
|
||||
</ol>
|
||||
@endif
|
||||
|
||||
{!! Former::open($url)
|
||||
@ -39,7 +39,7 @@
|
||||
'client' => 'required',
|
||||
'invoice_number' => 'required',
|
||||
'product_key' => 'max:255'
|
||||
)) !!}
|
||||
)) !!}
|
||||
|
||||
@include('partials.autocomplete_fix')
|
||||
|
||||
@ -60,7 +60,7 @@
|
||||
<a id="editClientLink" class="pointer" data-bind="click: $root.showClientForm">{{ trans('texts.edit_client') }}</a> |
|
||||
{!! link_to('/clients/'.$invoice->client->public_id, trans('texts.view_client'), ['target' => '_blank']) !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:none">
|
||||
@endif
|
||||
|
||||
@ -69,7 +69,7 @@
|
||||
<div class="form-group" style="margin-bottom: 8px">
|
||||
<div class="col-lg-8 col-sm-8 col-lg-offset-4 col-sm-offset-4">
|
||||
<a id="createClientLink" class="pointer" data-bind="click: $root.showClientForm, html: $root.clientLinkText"></a>
|
||||
<span data-bind="visible: $root.invoice().client().public_id() > 0" style="display:none">|
|
||||
<span data-bind="visible: $root.invoice().client().public_id() > 0" style="display:none">|
|
||||
<a data-bind="attr: {href: '{{ url('/clients') }}/' + $root.invoice().client().public_id()}" target="_blank">{{ trans('texts.view_client') }}</a>
|
||||
</span>
|
||||
</div>
|
||||
@ -85,20 +85,20 @@
|
||||
<label class="checkbox" data-bind="attr: {for: $index() + '_check'}" onclick="refreshPDF(true)">
|
||||
<input type="hidden" value="0" data-bind="attr: {name: 'client[contacts][' + $index() + '][send_invoice]'}">
|
||||
<input type="checkbox" value="1" data-bind="checked: send_invoice, attr: {id: $index() + '_check', name: 'client[contacts][' + $index() + '][send_invoice]'}">
|
||||
<span data-bind="html: email.display"></span>
|
||||
<span data-bind="html: email.display"></span>
|
||||
</label>
|
||||
<span data-bind="html: $data.view_as_recipient"></span>
|
||||
@if (Utils::isConfirmed())
|
||||
<span style="vertical-align:text-top;color:red" class="fa fa-exclamation-triangle"
|
||||
<span style="vertical-align:text-top;color:red" class="fa fa-exclamation-triangle"
|
||||
data-bind="visible: $data.email_error, tooltip: {title: $data.email_error}"></span>
|
||||
<span style="vertical-align:text-top" class="glyphicon glyphicon-info-sign"
|
||||
data-bind="visible: $data.invitation_status, tooltip: {title: $data.invitation_status, html: true},
|
||||
<span style="vertical-align:text-top" class="glyphicon glyphicon-info-sign"
|
||||
data-bind="visible: $data.invitation_status, tooltip: {title: $data.invitation_status, html: true},
|
||||
style: {color: $data.hasOwnProperty('invitation_viewed') && $data.invitation_viewed() ? '#57D172':'#B1B5BA'}"></span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-md-4" id="col_2">
|
||||
<div data-bind="visible: !is_recurring()">
|
||||
@ -106,7 +106,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')) !!}
|
||||
</div>
|
||||
@ -127,7 +127,7 @@
|
||||
|
||||
@if ($entityType == ENTITY_INVOICE)
|
||||
<div class="form-group" style="margin-bottom: 8px">
|
||||
<div class="col-lg-8 col-sm-8 col-sm-offset-4" style="padding-top: 10px">
|
||||
<div class="col-lg-8 col-sm-8 col-sm-offset-4" style="padding-top: 10px">
|
||||
@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)
|
||||
@ -151,7 +151,7 @@
|
||||
{!! Former::text('invoice_number')
|
||||
->label(trans("texts.{$entityType}_number_short"))
|
||||
->data_bind("value: invoice_number, valueUpdate: 'afterkeydown'") !!}
|
||||
</span>
|
||||
</span>
|
||||
<span data-bind="visible: is_recurring()" style="display: none">
|
||||
{!! Former::checkbox('auto_bill')
|
||||
->label(trans('texts.auto_bill'))
|
||||
@ -163,7 +163,7 @@
|
||||
->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'") !!}
|
||||
@ -200,16 +200,17 @@
|
||||
!!}
|
||||
</td>
|
||||
<td>
|
||||
<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>
|
||||
<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]'}"
|
||||
<input data-bind="value: prettyCost, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][cost]'}"
|
||||
style="text-align: right" class="form-control invoice-item"/>
|
||||
</td>
|
||||
<td style="{{ $account->hide_quantity ? 'display:none' : '' }}">
|
||||
<input data-bind="value: prettyQty, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][qty]'}"
|
||||
<input data-bind="value: prettyQty, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][qty]'}"
|
||||
style="text-align: right" class="form-control invoice-item" name="quantity"/>
|
||||
</td>
|
||||
<td style="display:none;" data-bind="visible: $root.invoice_item_taxes.show">
|
||||
@ -221,7 +222,7 @@
|
||||
<div class="line-total" data-bind="text: totals.total"></div>
|
||||
</td>
|
||||
<td style="cursor:pointer" class="hide-border td-icon">
|
||||
<i style="padding-left:2px" data-bind="click: $parent.removeItem, visible: actionsVisible() &&
|
||||
<i style="padding-left:2px" data-bind="click: $parent.removeItem, visible: actionsVisible() &&
|
||||
$index() < ($parent.invoice_items().length - 1) &&
|
||||
$parent.invoice_items().length > 1" class="fa fa-minus-circle redlink" title="Remove item"/>
|
||||
</td>
|
||||
@ -245,7 +246,7 @@
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="notes" style="padding-bottom:44px">
|
||||
{!! 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) !!}
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="terms">
|
||||
{!! Former::textarea('terms')->data_bind("value:wrapped_terms, placeholder: terms_placeholder, valueUpdate: 'afterkeydown'")
|
||||
@ -316,7 +317,7 @@
|
||||
|
||||
<tr style="display:none" data-bind="visible: $root.invoice_taxes.show">
|
||||
<td class="hide-border" colspan="3"/>
|
||||
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
|
||||
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
|
||||
@if (!$account->hide_quantity)
|
||||
<td>{{ trans('texts.tax') }}</td>
|
||||
@endif
|
||||
@ -369,7 +370,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<p> </p>
|
||||
<div class="form-actions">
|
||||
|
||||
@ -383,18 +384,19 @@
|
||||
{!! 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>
|
||||
|
||||
|
||||
@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())
|
||||
<!-- do nothing -->
|
||||
@elseif ($invoice->trashed())
|
||||
@ -448,7 +450,7 @@
|
||||
{!! 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'") !!}
|
||||
@ -457,7 +459,7 @@
|
||||
->data_bind("value: work_phone, valueUpdate: 'afterkeydown'") !!}
|
||||
</span>
|
||||
|
||||
@if (Auth::user()->isPro())
|
||||
@if (Auth::user()->isPro())
|
||||
@if ($account->custom_client_label1)
|
||||
{!! Former::text('client[custom_value1]')
|
||||
->label($account->custom_client_label1)
|
||||
@ -503,11 +505,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',
|
||||
@ -517,7 +519,7 @@
|
||||
<div class="col-lg-8 col-lg-offset-4">
|
||||
<span class="redlink bold" data-bind="visible: $parent.contacts().length > 1">
|
||||
{!! link_to('#', trans('texts.remove_contact').' -', array('data-bind'=>'click: $parent.removeContact')) !!}
|
||||
</span>
|
||||
</span>
|
||||
<span data-bind="visible: $index() === ($parent.contacts().length - 1)" class="pull-right greenlink bold">
|
||||
{!! link_to('#', trans('texts.add_contact').' +', array('data-bind'=>'click: $parent.addContact')) !!}
|
||||
</span>
|
||||
@ -540,7 +542,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'))
|
||||
@ -565,9 +567,9 @@
|
||||
<span class="error-block" id="emailError" style="display:none;float:left;font-weight:bold">{{ trans('texts.provide_name_or_email') }}</span><span> </span>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('texts.cancel') }}</button>
|
||||
<button type="button" class="btn btn-default" data-bind="click: $root.showMoreFields, text: $root.showMore() ? '{{ trans('texts.less_fields') }}' : '{{ trans('texts.more_fields') }}'"></button>
|
||||
<button id="clientDoneButton" type="button" class="btn btn-primary" data-bind="click: $root.clientFormComplete">{{ trans('texts.done') }}</button>
|
||||
<button id="clientDoneButton" type="button" class="btn btn-primary" data-bind="click: $root.clientFormComplete">{{ trans('texts.done') }}</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -587,7 +589,7 @@
|
||||
<div class="modal-footer" style="margin-top: 0px">
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">{{ trans('texts.close') }}</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -607,9 +609,9 @@
|
||||
@include('invoices.knockout')
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
|
||||
var products = {!! $products !!};
|
||||
var clients = {!! $clients !!};
|
||||
var clients = {!! $clients !!};
|
||||
var account = {!! Auth::user()->account !!};
|
||||
|
||||
var clientMap = {};
|
||||
@ -628,17 +630,17 @@
|
||||
contact.send_invoice = true;
|
||||
}
|
||||
if (clientName != contactName) {
|
||||
$clientSelect.append(new Option(contactName, client.public_id));
|
||||
$clientSelect.append(new Option(contactName, client.public_id));
|
||||
}
|
||||
}
|
||||
clientMap[client.public_id] = client;
|
||||
$clientSelect.append(new Option(clientName, client.public_id));
|
||||
$clientSelect.append(new Option(clientName, client.public_id));
|
||||
}
|
||||
|
||||
@if ($data)
|
||||
// this means we failed so we'll reload the previous state
|
||||
window.model = new ViewModel({!! $data !!});
|
||||
@else
|
||||
@else
|
||||
// otherwise create blank model
|
||||
window.model = new ViewModel();
|
||||
|
||||
@ -674,7 +676,7 @@
|
||||
// move the blank invoice line item to the end
|
||||
var blank = model.invoice().invoice_items.pop();
|
||||
var tasks = {!! $tasks !!};
|
||||
|
||||
|
||||
for (var i=0; i<tasks.length; i++) {
|
||||
var task = tasks[i];
|
||||
var item = model.invoice().addItem();
|
||||
@ -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()));
|
||||
@ -702,7 +722,7 @@
|
||||
|
||||
ko.applyBindings(model);
|
||||
onItemChange();
|
||||
|
||||
|
||||
|
||||
$('#country_id').combobox().on('change', function(e) {
|
||||
var countryId = $('input[name=country_id]').val();
|
||||
@ -723,15 +743,15 @@
|
||||
@if ($invoice->client && !$invoice->id)
|
||||
$('input[name=client]').val({{ $invoice->client->public_id }});
|
||||
@endif
|
||||
|
||||
|
||||
var $input = $('select#client');
|
||||
$input.combobox().on('change', function(e) {
|
||||
var oldId = model.invoice().client().public_id();
|
||||
var clientId = parseInt($('input[name=client]').val(), 10) || 0;
|
||||
if (clientId > 0) {
|
||||
if (clientId > 0) {
|
||||
var selected = clientMap[clientId];
|
||||
model.loadClient(selected);
|
||||
// we enable searching by contact but the selection must be the client
|
||||
// we enable searching by contact but the selection must be the client
|
||||
$('.client-input').val(getClientDisplayName(selected));
|
||||
// if there's an invoice number pattern we'll apply it now
|
||||
setInvoiceNumber(selected);
|
||||
@ -748,7 +768,7 @@
|
||||
$('.client_select input.form-control').on('click', function() {
|
||||
model.showClientForm();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$('#invoice_footer, #terms, #public_notes, #invoice_number, #invoice_date, #due_date, #start_date, #po_number, #discount, #currency_id, #invoice_design_id, #recurring, #is_amount_discount, #partial, #custom_text_value1, #custom_text_value2').change(function() {
|
||||
setTimeout(function() {
|
||||
@ -766,7 +786,7 @@
|
||||
(function (_field) {
|
||||
$('.' + _field + ' .input-group-addon').click(function() {
|
||||
toggleDatePicker(_field);
|
||||
});
|
||||
});
|
||||
})(field);
|
||||
}
|
||||
|
||||
@ -775,7 +795,7 @@
|
||||
@else
|
||||
$('.client_select input.form-control').focus();
|
||||
@endif
|
||||
|
||||
|
||||
$('#clientModal').on('shown.bs.modal', function () {
|
||||
$('#client\\[name\\]').focus();
|
||||
}).on('hidden.bs.modal', function () {
|
||||
@ -784,30 +804,34 @@
|
||||
refreshPDF(true);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
$('#relatedActions > button:first').click(function() {
|
||||
onPaymentClick();
|
||||
});
|
||||
|
||||
$('label.radio').addClass('radio-inline');
|
||||
|
||||
|
||||
@if ($invoice->client->id)
|
||||
$input.trigger('change');
|
||||
@else
|
||||
@else
|
||||
refreshPDF(true);
|
||||
@endif
|
||||
|
||||
var client = model.invoice().client();
|
||||
setComboboxValue($('.client_select'),
|
||||
client.public_id(),
|
||||
client.name.display());
|
||||
setComboboxValue($('.client_select'),
|
||||
client.public_id(),
|
||||
client.name.display());
|
||||
|
||||
@if (isset($tasks) && $tasks)
|
||||
NINJA.formIsChanged = true;
|
||||
@endif
|
||||
|
||||
@if (isset($expenses) && $expenses)
|
||||
NINJA.formIsChanged = true;
|
||||
@endif
|
||||
|
||||
applyComboboxListeners();
|
||||
});
|
||||
});
|
||||
|
||||
function applyComboboxListeners() {
|
||||
var selectorStr = '.invoice-table input, .invoice-table textarea';
|
||||
@ -980,7 +1004,7 @@
|
||||
var invoice = createInvoiceModel();
|
||||
var design = getDesignJavascript();
|
||||
if (!design) return;
|
||||
|
||||
|
||||
doc = generatePDF(invoice, design, true);
|
||||
doc.getDataUrl( function(pdfString){
|
||||
$('#pdfupload').val(pdfString);
|
||||
@ -1016,7 +1040,7 @@
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
|
||||
function isEmailValid() {
|
||||
var isValid = true;
|
||||
var sendTo = false;
|
||||
@ -1046,7 +1070,7 @@
|
||||
}
|
||||
|
||||
function onConvertClick() {
|
||||
submitAction('convert');
|
||||
submitAction('convert');
|
||||
}
|
||||
|
||||
@if ($invoice->id)
|
||||
@ -1058,7 +1082,7 @@
|
||||
window.location = '{{ URL::to('credits/create/' . $invoice->client->public_id . '/' . $invoice->public_id ) }}';
|
||||
}
|
||||
@endif
|
||||
|
||||
|
||||
function onArchiveClick() {
|
||||
submitBulkAction('archive');
|
||||
}
|
||||
@ -1066,7 +1090,7 @@
|
||||
function onDeleteClick() {
|
||||
if (confirm('{!! trans("texts.are_you_sure") !!}')) {
|
||||
submitBulkAction('delete');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function formEnterClick(event) {
|
||||
@ -1084,9 +1108,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
function clientModalEnterClick(event) {
|
||||
function clientModalEnterClick(event) {
|
||||
if (event.keyCode === 13){
|
||||
event.preventDefault();
|
||||
event.preventDefault();
|
||||
model.clientFormComplete();
|
||||
return false;
|
||||
}
|
||||
@ -1113,7 +1137,7 @@
|
||||
var oldVal = val;
|
||||
val = Math.max(Math.min(val, model.invoice().totals.rawTotal()), 0);
|
||||
model.invoice().partial(val || '');
|
||||
|
||||
|
||||
if (!silent && val != oldVal) {
|
||||
$('#partial').tooltip('show');
|
||||
setTimeout(function() {
|
||||
|
@ -29,8 +29,8 @@ function ViewModel(data) {
|
||||
if (paymentTerms == -1) paymentTerms = 0;
|
||||
var dueDate = $('#invoice_date').datepicker('getDate');
|
||||
dueDate.setDate(dueDate.getDate() + paymentTerms);
|
||||
self.invoice().due_date(dueDate);
|
||||
// We're using the datepicker to handle the date formatting
|
||||
self.invoice().due_date(dueDate);
|
||||
// We're using the datepicker to handle the date formatting
|
||||
self.invoice().due_date($('#due_date').val());
|
||||
}
|
||||
@endif
|
||||
@ -51,7 +51,7 @@ function ViewModel(data) {
|
||||
return new TaxRateModel(options.data);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (data) {
|
||||
ko.mapping.fromJS(data, self.mapping, self);
|
||||
@ -63,7 +63,7 @@ function ViewModel(data) {
|
||||
}
|
||||
if (self.invoice().tax_rate() > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
@ -82,7 +82,7 @@ function ViewModel(data) {
|
||||
|
||||
self.addTaxRate = function(data) {
|
||||
var itemModel = new TaxRateModel(data);
|
||||
self.tax_rates.push(itemModel);
|
||||
self.tax_rates.push(itemModel);
|
||||
applyComboboxListeners();
|
||||
}
|
||||
|
||||
@ -200,7 +200,7 @@ function InvoiceModel(data) {
|
||||
self.id = ko.observable('');
|
||||
self.discount = ko.observable('');
|
||||
self.is_amount_discount = ko.observable(0);
|
||||
self.frequency_id = ko.observable(4); // default to monthly
|
||||
self.frequency_id = ko.observable(4); // default to monthly
|
||||
self.terms = ko.observable('');
|
||||
self.default_terms = ko.observable(account.{{ $entityType }}_terms);
|
||||
self.terms_placeholder = ko.observable({{ !$invoice->id && $account->{"{$entityType}_terms"} ? "account.{$entityType}_terms" : false}});
|
||||
@ -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);
|
||||
@ -260,7 +261,7 @@ function InvoiceModel(data) {
|
||||
@if ($account->hide_quantity)
|
||||
itemModel.qty(1);
|
||||
@endif
|
||||
self.invoice_items.push(itemModel);
|
||||
self.invoice_items.push(itemModel);
|
||||
applyComboboxListeners();
|
||||
return itemModel;
|
||||
}
|
||||
@ -286,11 +287,11 @@ function InvoiceModel(data) {
|
||||
},
|
||||
write: function(value) {
|
||||
if (value) {
|
||||
self._tax(value);
|
||||
self._tax(value);
|
||||
self.tax_name(value.name());
|
||||
self.tax_rate(value.rate());
|
||||
} else {
|
||||
self._tax(false);
|
||||
self._tax(false);
|
||||
self.tax_name('');
|
||||
self.tax_rate(0);
|
||||
}
|
||||
@ -310,7 +311,7 @@ function InvoiceModel(data) {
|
||||
|
||||
|
||||
self.wrapped_notes = ko.computed({
|
||||
read: function() {
|
||||
read: function() {
|
||||
return this.public_notes();
|
||||
},
|
||||
write: function(value) {
|
||||
@ -361,7 +362,7 @@ function InvoiceModel(data) {
|
||||
if (parseInt(self.is_amount_discount())) {
|
||||
return roundToTwo(self.discount());
|
||||
} else {
|
||||
return roundToTwo(self.totals.rawSubtotal() * (self.discount()/100));
|
||||
return roundToTwo(self.totals.rawSubtotal() * (self.discount()/100));
|
||||
}
|
||||
});
|
||||
|
||||
@ -416,7 +417,7 @@ function InvoiceModel(data) {
|
||||
} else {
|
||||
taxes[key] = {name:item.tax_name(), rate:item.tax_rate(), amount:taxAmount};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return taxes;
|
||||
});
|
||||
@ -432,24 +433,24 @@ function InvoiceModel(data) {
|
||||
return count > 0;
|
||||
});
|
||||
|
||||
self.totals.itemTaxRates = ko.computed(function() {
|
||||
self.totals.itemTaxRates = ko.computed(function() {
|
||||
var taxes = self.totals.itemTaxes();
|
||||
var parts = [];
|
||||
var parts = [];
|
||||
for (var key in taxes) {
|
||||
if (taxes.hasOwnProperty(key)) {
|
||||
parts.push(taxes[key].name + ' ' + (taxes[key].rate*1) + '%');
|
||||
}
|
||||
}
|
||||
}
|
||||
return parts.join('<br/>');
|
||||
});
|
||||
|
||||
self.totals.itemTaxAmounts = ko.computed(function() {
|
||||
var taxes = self.totals.itemTaxes();
|
||||
var parts = [];
|
||||
var parts = [];
|
||||
for (var key in taxes) {
|
||||
if (taxes.hasOwnProperty(key)) {
|
||||
parts.push(self.formatMoney(taxes[key].amount));
|
||||
}
|
||||
}
|
||||
}
|
||||
return parts.join('<br/>');
|
||||
});
|
||||
@ -464,7 +465,7 @@ function InvoiceModel(data) {
|
||||
});
|
||||
|
||||
self.totals.rawTotal = ko.computed(function() {
|
||||
var total = accounting.toFixed(self.totals.rawSubtotal(),2);
|
||||
var total = accounting.toFixed(self.totals.rawSubtotal(),2);
|
||||
var discount = self.totals.rawDiscounted();
|
||||
total -= discount;
|
||||
|
||||
@ -509,7 +510,7 @@ function InvoiceModel(data) {
|
||||
|
||||
self.totals.total = ko.computed(function() {
|
||||
return self.formatMoney(self.partial() ? self.partial() : self.totals.rawTotal());
|
||||
});
|
||||
});
|
||||
|
||||
self.onDragged = function(item) {
|
||||
refreshPDF(true);
|
||||
@ -569,7 +570,7 @@ function ClientModel(data) {
|
||||
}
|
||||
|
||||
self.removeContact = function() {
|
||||
self.contacts.remove(this);
|
||||
self.contacts.remove(this);
|
||||
}
|
||||
|
||||
self.name.display = ko.computed(function() {
|
||||
@ -577,13 +578,13 @@ function ClientModel(data) {
|
||||
return self.name();
|
||||
}
|
||||
if (self.contacts().length == 0) return;
|
||||
var contact = self.contacts()[0];
|
||||
var contact = self.contacts()[0];
|
||||
if (contact.first_name() || contact.last_name()) {
|
||||
return contact.first_name() + ' ' + contact.last_name();
|
||||
return contact.first_name() + ' ' + contact.last_name();
|
||||
} else {
|
||||
return contact.email();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
self.name.placeholder = ko.computed(function() {
|
||||
if (self.contacts().length == 0) return '';
|
||||
@ -593,13 +594,13 @@ function ClientModel(data) {
|
||||
} else {
|
||||
return contact.email();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (data) {
|
||||
ko.mapping.fromJS(data, {}, this);
|
||||
} else {
|
||||
self.addContact();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ContactModel(data) {
|
||||
@ -608,7 +609,7 @@ function ContactModel(data) {
|
||||
self.first_name = ko.observable('');
|
||||
self.last_name = ko.observable('');
|
||||
self.email = ko.observable('');
|
||||
self.phone = ko.observable('');
|
||||
self.phone = ko.observable('');
|
||||
self.send_invoice = ko.observable(false);
|
||||
self.invitation_link = ko.observable('');
|
||||
self.invitation_status = ko.observable('');
|
||||
@ -623,10 +624,10 @@ function ContactModel(data) {
|
||||
var str = '';
|
||||
if (self.first_name() || self.last_name()) {
|
||||
str += self.first_name() + ' ' + self.last_name() + '\n';
|
||||
}
|
||||
}
|
||||
if (self.email()) {
|
||||
str += self.email() + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
});
|
||||
@ -635,9 +636,9 @@ function ContactModel(data) {
|
||||
var str = '';
|
||||
if (self.first_name() || self.last_name()) {
|
||||
str += self.first_name() + ' ' + self.last_name() + '<br/>';
|
||||
}
|
||||
}
|
||||
if (self.email()) {
|
||||
str += self.email() + '<br/>';
|
||||
str += self.email() + '<br/>';
|
||||
}
|
||||
return str;
|
||||
});
|
||||
@ -651,7 +652,7 @@ function ContactModel(data) {
|
||||
@endif
|
||||
|
||||
return str;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function TaxRateModel(data) {
|
||||
@ -675,7 +676,7 @@ function TaxRateModel(data) {
|
||||
this.rate(value);
|
||||
},
|
||||
owner: this
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
self.displayName = ko.computed({
|
||||
@ -687,8 +688,8 @@ function TaxRateModel(data) {
|
||||
write: function (value) {
|
||||
// do nothing
|
||||
},
|
||||
owner: this
|
||||
});
|
||||
owner: this
|
||||
});
|
||||
|
||||
self.hideActions = function() {
|
||||
self.actionsVisible(false);
|
||||
@ -696,15 +697,15 @@ function TaxRateModel(data) {
|
||||
|
||||
self.showActions = function() {
|
||||
self.actionsVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
self.isEmpty = function() {
|
||||
return !self.rate() && !self.name();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ItemModel(data) {
|
||||
var self = this;
|
||||
var self = this;
|
||||
self.product_key = ko.observable('');
|
||||
self.notes = ko.observable('');
|
||||
self.cost = 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();
|
||||
@ -720,7 +722,7 @@ function ItemModel(data) {
|
||||
return self._tax();
|
||||
},
|
||||
write: function(value) {
|
||||
self._tax(value);
|
||||
self._tax(value);
|
||||
self.tax_name(value.name());
|
||||
self.tax_rate(value.rate());
|
||||
}
|
||||
@ -734,7 +736,7 @@ function ItemModel(data) {
|
||||
this.qty(value);
|
||||
},
|
||||
owner: this
|
||||
});
|
||||
});
|
||||
|
||||
this.prettyCost = ko.computed({
|
||||
read: function () {
|
||||
@ -744,7 +746,7 @@ function ItemModel(data) {
|
||||
this.cost(value);
|
||||
},
|
||||
owner: this
|
||||
});
|
||||
});
|
||||
|
||||
self.mapping = {
|
||||
'tax': {
|
||||
@ -755,7 +757,7 @@ function ItemModel(data) {
|
||||
}
|
||||
|
||||
if (data) {
|
||||
ko.mapping.fromJS(data, self.mapping, this);
|
||||
ko.mapping.fromJS(data, self.mapping, this);
|
||||
}
|
||||
|
||||
self.wrapped_notes = ko.computed({
|
||||
@ -775,7 +777,7 @@ function ItemModel(data) {
|
||||
this.totals.rawTotal = ko.computed(function() {
|
||||
var cost = roundToTwo(NINJA.parseFloat(self.cost()));
|
||||
var qty = roundToTwo(NINJA.parseFloat(self.qty()));
|
||||
var value = cost * qty;
|
||||
var value = cost * qty;
|
||||
return value ? roundToTwo(value) : 0;
|
||||
});
|
||||
|
||||
@ -806,4 +808,4 @@ function ItemModel(data) {
|
||||
this.onSelect = function() {}
|
||||
}
|
||||
|
||||
</script>
|
||||
</script>
|
||||
|
Loading…
x
Reference in New Issue
Block a user