1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-08 12:12:48 +01:00

Creating re-usable products from invoice line items

This commit is contained in:
vagrant 2013-11-27 07:38:37 +00:00
parent 7b41fbc779
commit 426e7838c9
10 changed files with 91 additions and 28 deletions

0
LICENSE Normal file → Executable file
View File

0
README.md Normal file → Executable file
View File

View File

@ -42,7 +42,7 @@ class InvoiceController extends \BaseController {
$invoice = Invoice::with('invoice_items', 'client.account.account_gateways')->where('invoice_key', '=', $invoiceKey)->firstOrFail(); $invoice = Invoice::with('invoice_items', 'client.account.account_gateways')->where('invoice_key', '=', $invoiceKey)->firstOrFail();
$contact = null; $contact = null;
Activity::viewInvoice($invoice, $contact) Activity::viewInvoice($invoice, $contact);
return View::make('invoices.view')->with('invoice', $invoice); return View::make('invoices.view')->with('invoice', $invoice);
} }
@ -181,6 +181,7 @@ class InvoiceController extends \BaseController {
'title' => 'New', 'title' => 'New',
'client' => $client, 'client' => $client,
'account' => Auth::user()->account, 'account' => Auth::user()->account,
'products' => Product::getProducts()->get(),
'clients' => Client::where('account_id','=',Auth::user()->account_id)->get()); 'clients' => Client::where('account_id','=',Auth::user()->account_id)->get());
return View::make('invoices.edit', $data); return View::make('invoices.edit', $data);
} }
@ -241,7 +242,7 @@ class InvoiceController extends \BaseController {
$invoice->client_id = $clientId; $invoice->client_id = $clientId;
$invoice->number = Input::get('number'); $invoice->number = Input::get('number');
$invoice->discount = Input::get('discount'); $invoice->discount = 0;
$invoice->issued_on = $date->format('Y-m-d'); $invoice->issued_on = $date->format('Y-m-d');
$invoice->save(); $invoice->save();
@ -252,11 +253,17 @@ class InvoiceController extends \BaseController {
continue; continue;
} }
$product = new Product; $product = Product::findProduct($item->product_key);
$product->product_key = $item->product_key;
$product->notes = $item->notes; if (!$product)
$product->cost = $item->cost; {
$product->save(); $product = new Product;
$product->account_id = Auth::user()->account_id;
$product->product_key = $item->product_key;
$product->notes = $item->notes;
$product->cost = $item->cost;
$product->save();
}
$invoiceItem = new InvoiceItem; $invoiceItem = new InvoiceItem;
$invoiceItem->product_id = $product->id; $invoiceItem->product_id = $product->id;
@ -316,6 +323,7 @@ class InvoiceController extends \BaseController {
'url' => 'invoices/' . $id, 'url' => 'invoices/' . $id,
'title' => 'Edit', 'title' => 'Edit',
'account' => Auth::user()->account, 'account' => Auth::user()->account,
'products' => Product::getProducts()->get(),
'client' => $invoice->client, 'client' => $invoice->client,
'clients' => Client::where('account_id','=',Auth::user()->account_id)->get()); 'clients' => Client::where('account_id','=',Auth::user()->account_id)->get());
return View::make('invoices.edit', $data); return View::make('invoices.edit', $data);

View File

@ -18,7 +18,7 @@ class Activity extends Eloquent
$activity->user_id = $user->id; $activity->user_id = $user->id;
$activity->account_id = $user->account_id; $activity->account_id = $user->account_id;
return $user; return $activity;
} }
public static function createClient($client) public static function createClient($client)
@ -26,7 +26,7 @@ class Activity extends Eloquent
$activity = Activity::getBlank(); $activity = Activity::getBlank();
$activity->client_id = $client->id; $activity->client_id = $client->id;
$activity->activity_type_id = ACTIVITY_TYPE_CREATE_CLIENT; $activity->activity_type_id = ACTIVITY_TYPE_CREATE_CLIENT;
$activity->message = $user->getFullName() . ' created client ' . $client->name; $activity->message = Auth::user()->getFullName() . ' created client ' . $client->name;
$activity->save(); $activity->save();
} }
@ -35,7 +35,7 @@ class Activity extends Eloquent
$activity = Activity::getBlank(); $activity = Activity::getBlank();
$activity->client_id = $client->id; $activity->client_id = $client->id;
$activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_CLIENT; $activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_CLIENT;
$activity->message = $user->getFullName() . ' archived client ' . $client->name; $activity->message = Auth::user()->getFullName() . ' archived client ' . $client->name;
$activity->save(); $activity->save();
} }
@ -45,7 +45,7 @@ class Activity extends Eloquent
$activity->invoice_id = $invoice->id; $activity->invoice_id = $invoice->id;
$activity->client_id = $invoice->client_id; $activity->client_id = $invoice->client_id;
$activity->activity_type_id = ACTIVITY_TYPE_CREATE_INVOICE; $activity->activity_type_id = ACTIVITY_TYPE_CREATE_INVOICE;
$activity->message = $user->getFullName() . ' created invoice ' . $invoice->number; $activity->message = Auth::user()->getFullName() . ' created invoice ' . $invoice->number;
$activity->save(); $activity->save();
} }
@ -55,7 +55,7 @@ class Activity extends Eloquent
$activity->invoice_id = $invoice->id; $activity->invoice_id = $invoice->id;
$activity->client_id = $invoice->client_id; $activity->client_id = $invoice->client_id;
$activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_INVOICE; $activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_INVOICE;
$activity->message = $user->getFullName() . ' archived invoice ' . $invoice->number; $activity->message = Auth::user()->getFullName() . ' archived invoice ' . $invoice->number;
$activity->save(); $activity->save();
} }
@ -65,7 +65,7 @@ class Activity extends Eloquent
$activity->invoice_id = $invoice->id; $activity->invoice_id = $invoice->id;
$activity->client_id = $invoice->client_id; $activity->client_id = $invoice->client_id;
$activity->activity_type_id = ACTIVITY_TYPE_EMAIL_INVOICE; $activity->activity_type_id = ACTIVITY_TYPE_EMAIL_INVOICE;
$activity->message = $user->getFullName() . ' emailed invoice ' . $invoice->number . ' to ' . $contact->getFullName(); $activity->message = Auth::user()->getFullName() . ' emailed invoice ' . $invoice->number . ' to ' . $contact->getFullName();
$activity->save(); $activity->save();
} }
@ -74,7 +74,7 @@ class Activity extends Eloquent
if (Auth::check()) if (Auth::check())
{ {
$activity = Activity::getBlank(); $activity = Activity::getBlank();
$activity->message = $user->getFullName() . ' created invoice ' . $payment->transaction_reference; $activity->message = Auth::user()->getFullName() . ' created invoice ' . $payment->transaction_reference;
} }
else else
{ {
@ -96,7 +96,7 @@ class Activity extends Eloquent
$activity->invoice_id = $invoice->id; $activity->invoice_id = $invoice->id;
$activity->client_id = $invoice->client_id; $activity->client_id = $invoice->client_id;
$activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_PAYMENT; $activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_PAYMENT;
$activity->message = $user->getFullName() . ' archived payment ' . $invoice->number; $activity->message = Auth::user()->getFullName() . ' archived payment ' . $invoice->number;
$activity->save(); $activity->save();
} }

View File

@ -3,4 +3,22 @@
class Product extends Eloquent class Product extends Eloquent
{ {
protected $softDelete = true; protected $softDelete = true;
public static function getProducts()
{
return Product::where('account_id','=',Auth::user()->account_id);
}
public static function findProduct($key)
{
return Product::getProducts()->where('product_key','=',$key)->first();
}
public static function getProductKeys($products)
{
$products = array_pluck($products, 'product_key');
$products = array_combine($products, $products);
return $products;
}
} }

View File

@ -97,6 +97,10 @@ function toArray($data)
return json_decode(json_encode((array) $data), true); return json_decode(json_encode((array) $data), true);
} }
define("ENV_DEVELOPMENT", "local");
define("ENV_STAGING", "staging");
define("ENV_PRODUCTION", "production");
define("ACCOUNT_DETAILS", "details"); define("ACCOUNT_DETAILS", "details");
define("ACCOUNT_SETTINGS", "settings"); define("ACCOUNT_SETTINGS", "settings");
define("ACCOUNT_IMPORT", "import"); define("ACCOUNT_IMPORT", "import");

View File

@ -197,7 +197,7 @@
} }
.invoice-table td { .invoice-table td {
padding: 0px !important; padding: 2px !important;
} }
.invoice-table td input, .invoice-table td input,
@ -225,6 +225,7 @@
<body> <body>
@if (App::environment() != ENV_DEVELOPMENT)
<script> <script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
@ -235,6 +236,8 @@
ga('send', 'pageview'); ga('send', 'pageview');
</script> </script>
@endif
<div class="container"> <div class="container">
<p/> <p/>

View File

@ -32,7 +32,7 @@
<p>&nbsp;</p> <p>&nbsp;</p>
<input type="text" name="items" data-bind="value: ko.toJSON(items)" style="display:none"/> <input type="text" name="items" data-bind="value: ko.toJSON(items)" style="display:none"/>
<table class="table invoice-table" style="margin-bottom: 0px !important;"> <table class="table invoice-table" style="margin-bottom: 0px !important">
<thead> <thead>
<tr> <tr>
<th class="hide-border"></th> <th class="hide-border"></th>
@ -51,23 +51,23 @@
<i data-bind="visible: actionsVisible" class="fa fa-sort"></i> <i data-bind="visible: actionsVisible" class="fa fa-sort"></i>
</td> </td>
<td style="width:120px"> <td style="width:120px">
<input data-bind="value: product_key, valueUpdate: 'afterkeydown'" onchange="refreshPDF()"/> {{ Former::text('product_key')->useDatalist(Product::getProductKeys($products), 'product_key')->raw()->data_bind("value: product_key, valueUpdate: 'afterkeydown'")->addClass('datalist') }}
</td> </td>
<td style="width:300px"> <td style="width:300px">
<textarea data-bind="value: notes, valueUpdate: 'afterkeydown'" rows="1" cols="60" onchange="refreshPDF()"></textarea> <textarea data-bind="value: notes, valueUpdate: 'afterkeydown'" rows="1" cols="60" class="form-control" onchange="refreshPDF()"></textarea>
</td> </td>
<td style="width:100px"> <td style="width:100px">
<input data-bind="value: cost, valueUpdate: 'afterkeydown'" style="text-align: right" onchange="refreshPDF()"//> <input data-bind="value: cost, valueUpdate: 'afterkeydown'" style="text-align: right" class="form-control" onchange="refreshPDF()"//>
</td> </td>
<td style="width:80px"> <td style="width:80px">
<input data-bind="value: qty, valueUpdate: 'afterkeydown'" style="text-align: right" onchange="refreshPDF()"//> <input data-bind="value: qty, valueUpdate: 'afterkeydown'" style="text-align: right" class="form-control" onchange="refreshPDF()"//>
</td> </td>
<!-- <!--
<td style="width:100px"> <td style="width:100px">
<input data-bind="value: tax, valueUpdate: 'afterkeydown'"/> <input data-bind="value: tax, valueUpdate: 'afterkeydown'"/>
</td> </td>
--> -->
<td style="width:100px;background-color: #FFFFFF;text-align: right"> <td style="width:100px;background-color: #FFFFFF;text-align: right;padding-top:9px !important">
<span data-bind="text: total"></span> <span data-bind="text: total"></span>
</td> </td>
<td style="width:20px; cursor:pointer" class="hide-border"> <td style="width:20px; cursor:pointer" class="hide-border">
@ -106,9 +106,8 @@
<p>&nbsp;</p> <p>&nbsp;</p>
<!-- <textarea rows="20" cols="120" id="pdfText" onkeyup="runCode()"></textarea> --> <!-- <textarea rows="20" cols="120" id="pdfText" onkeyup="runCode()"></textarea> -->
<!-- <iframe frameborder="1" width="600" height="600" style="display:block;margin: 0 auto"></iframe> --> <!-- <iframe frameborder="1" width="100%" height="600" style="display:block;margin: 0 auto"></iframe> -->
<iframe frameborder="1" width="92%" height="600" style="display:block;margin: 0 auto"></iframe> <iframe frameborder="1" width="100%" height="500"></iframe>
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
@ -159,6 +158,7 @@
} }
*/ */
$(function() { $(function() {
$('#issued_on').datepicker({ $('#issued_on').datepicker({
@ -192,10 +192,31 @@
$('#number').change(refreshPDF); $('#number').change(refreshPDF);
refreshPDF(); applyComboboxListeners();
refreshPDF();
}); });
function applyComboboxListeners() {
var value;
$('.datalist').on('focus', function() {
value = $(this).val();
}).on('blur', function() {
if (value != $(this).val()) refreshPDF();
}).on('input', function() {
var key = $(this).val();
for (var i=0; i<products.length; i++) {
var product = products[i];
if (product.product_key == key) {
var model = ko.dataFor(this);
console.log(model);
model.notes(product.notes);
model.cost(product.cost);
break;
}
}
});
}
function runCode() { function runCode() {
var text = $('#pdfText').val(); var text = $('#pdfText').val();
eval(text); eval(text);
@ -317,6 +338,7 @@
self.addItem = function() { self.addItem = function() {
self.items.push(new ItemModel()); self.items.push(new ItemModel());
applyComboboxListeners();
} }
this.rawSubtotal = ko.computed(function() { this.rawSubtotal = ko.computed(function() {
@ -389,9 +411,12 @@
} }
} }
var products = {{ $products }};
window.model = new InvoiceModel(); window.model = new InvoiceModel();
ko.applyBindings(model); ko.applyBindings(model);
</script> </script>

View File

@ -21,6 +21,8 @@
</head> </head>
<body> <body>
@if (App::environment() != ENV_DEVELOPMENT)
<script> <script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
@ -29,8 +31,9 @@
ga('create', 'UA-46031341-1', 'sketch-out.com'); ga('create', 'UA-46031341-1', 'sketch-out.com');
ga('send', 'pageview'); ga('send', 'pageview');
</script> </script>
@endif
<div class="navbar navbar-inverse navbar-fixed-top"> <div class="navbar navbar-inverse navbar-fixed-top">
<div class="container"> <div class="container">
<div class="navbar-header"> <div class="navbar-header">

View File

@ -302,3 +302,5 @@ function isStorageSupported() {
} }
} }