1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 05:02:36 +01:00

Enabled fixed amount discounts

This commit is contained in:
Hillel Coren 2014-12-04 00:05:38 +02:00
parent 6b519f6833
commit dcc852eafa
27 changed files with 171 additions and 41 deletions

View File

@ -41,7 +41,7 @@ class DashboardController extends \BaseController {
->orderBy('due_date', 'asc')->take(6)->get();
$upcoming = Invoice::scope()
->where('due_date', '>', date('Y-m-d'))
->where('due_date', '>=', date('Y-m-d'))
->where('balance', '>', 0)
->where('is_recurring', '=', false)
->where('is_quote', '=', false)

View File

@ -186,8 +186,15 @@ class InvoiceController extends \BaseController {
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
$invoice->is_pro = $client->account->isPro();
$invoice->is_pro = $client->account->isPro();
$contact = $invitation->contact;
$contact->setVisible([
'first_name',
'last_name',
'email',
'phone']);
$data = array(
'showClientHeader' => true,
'showBreadcrumbs' => false,
@ -195,6 +202,7 @@ class InvoiceController extends \BaseController {
'invoice' => $invoice->hidePrivateFields(),
'invitation' => $invitation,
'invoiceLabels' => $client->account->getInvoiceLabels(),
'contact' => $contact
);
return View::make('invoices.view', $data);
@ -347,7 +355,6 @@ class InvoiceController extends \BaseController {
}
$input = json_decode(Input::get('data'));
$invoice = $input->invoice;
if ($errors = $this->invoiceRepo->getErrors($invoice))

View File

@ -498,7 +498,7 @@ class PaymentController extends \BaseController
{
$errorMessage = trans('texts.payment_error');
Session::flash('error', $errorMessage);
Utils::logError($e->getMessage());
Utils::logError(Utils::getErrorString($e));
return Redirect::to('license')->withInput();
}
}
@ -653,7 +653,7 @@ class PaymentController extends \BaseController
{
$errorMessage = trans('texts.payment_error');
Session::flash('error', $errorMessage);
Utils::logError($e->getMessage());
Utils::logError(Utils::getErrorString($e));
return Redirect::to('payment/' . $invitationKey)
->withInput();
}

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddDiscountType extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('invoices', function($table)
{
$table->boolean('is_amount_discount')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('invoices', function($table)
{
$table->dropColumn('is_amount_discount');
});
}
}

View File

@ -490,6 +490,8 @@ return array(
'restored_credit' => 'Successfully restored credit',
'reason_for_canceling' => 'Help us improve our site by telling us why you\'re leaving.',
'discount_percent' => 'Percent',
'discount_amount' => 'Amount',
);

View File

@ -479,6 +479,9 @@ return array(
'restored_payment' => 'Successfully restored payment',
'restored_credit' => 'Successfully restored credit',
'reason_for_canceling' => 'Help us improve our site by telling us why you\'re leaving.',
'discount_percent' => 'Percent',
'discount_amount' => 'Amount',

View File

@ -488,6 +488,7 @@ return array(
'restored_credit' => 'Successfully restored credit',
'reason_for_canceling' => 'Help us improve our site by telling us why you\'re leaving.',
'discount_percent' => 'Percent',
'discount_amount' => 'Amount',
);

View File

@ -460,6 +460,8 @@ return array(
'restored_credit' => 'Successfully restored credit',
'reason_for_canceling' => 'Help us improve our site by telling us why you\'re leaving.',
'discount_percent' => 'Percent',
'discount_amount' => 'Amount',
);

View File

@ -481,6 +481,8 @@ return array(
'restored_credit' => 'Successfully restored credit',
'reason_for_canceling' => 'Help us improve our site by telling us why you\'re leaving.',
'discount_percent' => 'Percent',
'discount_amount' => 'Amount',
);

View File

@ -483,6 +483,8 @@ return array(
'restored_credit' => 'Successfully restored credit',
'reason_for_canceling' => 'Help us improve our site by telling us why you\'re leaving.',
'discount_percent' => 'Percent',
'discount_amount' => 'Amount',

View File

@ -491,6 +491,8 @@ return array(
'restored_credit' => 'Successfully restored credit',
'reason_for_canceling' => 'Help us improve our site by telling us why you\'re leaving.',
'discount_percent' => 'Percent',
'discount_amount' => 'Amount',
);

View File

@ -489,6 +489,8 @@ return array(
'restored_credit' => 'Successfully restored credit',
'reason_for_canceling' => 'Help us improve our site by telling us why you\'re leaving.',
'discount_percent' => 'Percent',
'discount_amount' => 'Amount',

View File

@ -484,7 +484,9 @@ return array(
'restored_credit' => 'Successfully restored credit',
'reason_for_canceling' => 'Help us improve our site by telling us why you\'re leaving.',
'discount_percent' => 'Percent',
'discount_amount' => 'Amount',
);
);

View File

@ -471,6 +471,8 @@ return array(
'restored_credit' => 'Successfully restored credit',
'reason_for_canceling' => 'Help us improve our site by telling us why you\'re leaving.',
'discount_percent' => 'Percent',
'discount_amount' => 'Amount',

View File

@ -140,6 +140,11 @@ class Utils
return View::make('error', $data)->with('error', $message);
}
public static function getErrorString($exception)
{
return "{$exception->getFile()} [Line {$exception->getLine()}] => {$exception->getMessage()}";
}
public static function logError($error, $context = 'PHP')
{
$count = Session::get('error_count', 0);

View File

@ -34,7 +34,7 @@ class Invoice extends EntityModel
public function invitations()
{
return $this->hasMany('Invitation');
return $this->hasMany('Invitation')->orderBy('invitations.contact_id');
}
public function getName()
@ -72,6 +72,7 @@ class Invoice extends EntityModel
$this->setVisible([
'invoice_number',
'discount',
'is_amount_discount',
'po_number',
'invoice_date',
'due_date',
@ -95,8 +96,8 @@ class Invoice extends EntityModel
$this->client->setVisible([
'name',
'id_number',
'vat_number',
'id_number',
'vat_number',
'address1',
'address2',
'city',
@ -112,8 +113,8 @@ class Invoice extends EntityModel
$this->account->setVisible([
'name',
'id_number',
'vat_number',
'id_number',
'vat_number',
'address1',
'address2',
'city',

View File

@ -214,7 +214,10 @@ class InvoiceRepository
$invoice = (array) $input;
$invoiceId = isset($invoice['public_id']) && $invoice['public_id'] ? Invoice::getPrivateId($invoice['public_id']) : null;
$rules = ['invoice_number' => 'required|unique:invoices,invoice_number,' . $invoiceId . ',id,account_id,' . \Auth::user()->account_id];
$rules = [
'invoice_number' => 'required|unique:invoices,invoice_number,' . $invoiceId . ',id,account_id,' . \Auth::user()->account_id,
'discount' => 'positive'
];
if ($invoice['is_recurring'] && $invoice['start_date'] && $invoice['end_date'])
{
@ -248,7 +251,8 @@ class InvoiceRepository
}
$invoice->client_id = $data['client_id'];
$invoice->discount = Utils::parseFloat($data['discount']);
$invoice->discount = round(Utils::parseFloat($data['discount']), 2);
$invoice->is_amount_discount = $data['is_amount_discount'] ? true : false;
$invoice->invoice_number = trim($data['invoice_number']);
$invoice->is_recurring = $data['is_recurring'] && !Utils::isDemo() ? true : false;
$invoice->invoice_date = Utils::toSqlDate($data['invoice_date']);
@ -309,7 +313,14 @@ class InvoiceRepository
if ($invoice->discount > 0)
{
$total *= (100 - $invoice->discount) / 100;
if ($invoice->is_amount_discount)
{
$total -= $invoice->discount;
}
else
{
$total *= (100 - $invoice->discount) / 100;
}
}
$invoice->custom_value1 = round($data['custom_value1'], 2);
@ -430,6 +441,7 @@ class InvoiceRepository
foreach ([
'client_id',
'discount',
'is_amount_discount',
'invoice_date',
'po_number',
'due_date',

View File

@ -56,7 +56,7 @@ class PaymentRepository
->where('clients.is_deleted', '=', false)
->where('payments.is_deleted', '=', false)
->where('invitations.deleted_at', '=', null)
->where('contacts.id', '=', $contactId)
->where('invitations.contact_id', '=', $contactId)
->select('invitations.invitation_key', 'payments.public_id', 'payments.transaction_reference', 'clients.name as client_name', 'clients.public_id as client_public_id', 'payments.amount', 'payments.payment_date', 'invoices.public_id as invoice_public_id', 'invoices.invoice_number', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'payment_types.name as payment_type', 'payments.account_gateway_id');
if ($filter)

View File

@ -395,7 +395,7 @@ function otrans($text)
Validator::extend('positive', function($attribute, $value, $parameters)
{
return Utils::parseFloat($value) > 0;
return Utils::parseFloat($value) >= 0;
});
Validator::extend('has_credit', function($attribute, $value, $parameters)

View File

@ -60,7 +60,7 @@ App::error(function(Exception $exception, $code)
{
if (Utils::isNinjaProd())
{
Utils::logError("{$code} {$exception->getFile()} [Line {$exception->getLine()}] => {$exception->getMessage()}");
Utils::logError($code . ' ' . Utils::getErrorString($exception));
return Response::view('error', ['hideHeader' => true, 'error' => "A {$code} error occurred."], $code);
}
else

View File

@ -361,7 +361,7 @@ Want something changed? We're {{ link_to('https://github.com/hillelcoren/invoice
</div>
<div style="background-color: #fff; padding:20px">
<h4>{{ trans('texts.white_label_text')}}</h4>
<p>{{ trans('texts.white_label_text')}}</p>
</div>
<div class="modal-footer" id="signUpFooter" style="margin-top: 0px">

View File

@ -19,7 +19,7 @@
{{ Former::open($url)->method($method)->addClass('warn-on-exit')->rules(array(
'client' => 'required',
'email' => 'required',
'product_key' => 'max:20',
'product_key' => 'max:20'
)) }}
<input type="submit" style="display:none" name="submitButton" id="submitButton">
@ -53,7 +53,7 @@
<div data-bind="with: client">
<div style="display:none" class="form-group" data-bind="visible: contacts().length > 0 &amp;&amp; contacts()[0].email(), foreach: contacts">
<div class="col-lg-8 col-lg-offset-4">
<label for="test" class="checkbox" data-bind="attr: {for: $index() + '_check'}">
<label class="checkbox" data-bind="attr: {for: $index() + '_check'}" onclick="refreshPDF()">
<input type="checkbox" value="1" data-bind="checked: send_invoice, attr: {id: $index() + '_check'}">
<span data-bind="html: email.display"/>
</label>
@ -94,7 +94,11 @@
<div class="col-md-4" id="col_2">
{{ Former::text('invoice_number')->label(trans("texts.{$entityType}_number_short"))->data_bind("value: invoice_number, valueUpdate: 'afterkeydown'") }}
{{ Former::text('po_number')->label(trans('texts.po_number_short'))->data_bind("value: po_number, valueUpdate: 'afterkeydown'") }}
{{ Former::text('discount')->data_bind("value: discount, valueUpdate: 'afterkeydown'")->append('%') }}
{{ Former::text('discount')->data_bind("value: discount, valueUpdate: 'afterkeydown'")
->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()
) }}
{{-- Former::select('currency_id')->addOption('', '')->fromQuery($currencies, 'name', 'id')->data_bind("value: currency_id") --}}
<div class="form-group" style="margin-bottom: 8px">
@ -175,7 +179,7 @@
<td style="text-align: right"><span data-bind="text: totals.subtotal"/></td>
</tr>
<tr style="display:none" data-bind="visible: discount() > 0">
<tr style="display:none" data-bind="visible: discount() != 0">
<td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="{{ $account->hide_quantity ? 1 : 2 }}">{{ trans('texts.discount') }}</td>
@ -558,7 +562,7 @@
});
}
$('#terms, #public_notes, #invoice_number, #invoice_date, #due_date, #po_number, #discount, #currency_id, #invoice_design_id, #recurring').change(function() {
$('#terms, #public_notes, #invoice_number, #invoice_date, #due_date, #po_number, #discount, #currency_id, #invoice_design_id, #recurring, #is_amount_discount').change(function() {
setTimeout(function() {
refreshPDF();
}, 1);
@ -639,6 +643,7 @@
var invoice = ko.toJS(model).invoice;
invoice.is_pro = {{ Auth::user()->isPro() ? 'true' : 'false' }};
invoice.is_quote = {{ $entityType == ENTITY_QUOTE ? 'true' : 'false' }};
invoice.contact = _.findWhere(invoice.client.contacts, {send_invoice: true});
@if (file_exists($account->getLogoPath()))
invoice.image = "{{ HTML::image_data($account->getLogoPath()) }}";
@ -1003,6 +1008,7 @@
self.account = {{ $account }};
this.id = ko.observable('');
self.discount = ko.observable('');
self.is_amount_discount = ko.observable(0);
self.frequency_id = ko.observable('');
//self.currency_id = ko.observable({{ $client && $client->currency_id ? $client->currency_id : Session::get(SESSION_CURRENCY) }});
self.terms = ko.observable(wordWrapText('{{ str_replace(["\r\n","\r","\n"], '\n', addslashes($account->invoice_terms)) }}', 300));
@ -1114,9 +1120,9 @@
}
this.totals = ko.observable();
self.totals = ko.observable();
this.totals.rawSubtotal = ko.computed(function() {
self.totals.rawSubtotal = ko.computed(function() {
var total = 0;
for(var p=0; p < self.invoice_items().length; ++p) {
var item = self.invoice_items()[p];
@ -1125,26 +1131,34 @@
return total;
});
this.totals.subtotal = ko.computed(function() {
self.totals.subtotal = ko.computed(function() {
var total = self.totals.rawSubtotal();
return total > 0 ? formatMoney(total, self.client().currency_id()) : '';
});
this.totals.rawDiscounted = ko.computed(function() {
return roundToTwo(self.totals.rawSubtotal() * (self.discount()/100));
self.totals.rawDiscounted = ko.computed(function() {
if (parseInt(self.is_amount_discount())) {
return roundToTwo(self.discount());
} else {
return roundToTwo(self.totals.rawSubtotal() * (self.discount()/100));
}
});
this.totals.discounted = ko.computed(function() {
self.totals.discounted = ko.computed(function() {
return formatMoney(self.totals.rawDiscounted(), self.client().currency_id());
});
self.totals.taxAmount = ko.computed(function() {
var total = self.totals.rawSubtotal();
var discount = self.totals.rawDiscounted();
total -= discount;
/*
var discount = parseFloat(self.discount());
if (discount > 0) {
total = roundToTwo(total * ((100 - discount)/100));
}
*/
var customValue1 = roundToTwo(self.custom_value1());
var customValue2 = roundToTwo(self.custom_value2());
@ -1178,11 +1192,15 @@
this.totals.total = ko.computed(function() {
var total = accounting.toFixed(self.totals.rawSubtotal(),2);
var discount = self.totals.rawDiscounted();
total -= discount;
/*
var discount = parseFloat(self.discount());
if (discount > 0) {
total = roundToTwo(total * ((100 - discount)/100));
}
*/
var customValue1 = roundToTwo(self.custom_value1());
var customValue2 = roundToTwo(self.custom_value2());
@ -1577,12 +1595,12 @@
for (var i=0; i<model.invoice().invoice_items().length; i++) {
var item = model.invoice().invoice_items()[i];
item.tax(model.getTaxRate(item.tax_name(), item.tax_rate()));
item.cost(NINJA.parseFloat(item.cost()) > 0 ? roundToTwo(item.cost(), true) : '');
item.cost(NINJA.parseFloat(item.cost()) != 0 ? roundToTwo(item.cost(), true) : '');
}
onTaxRateChange();
// display blank instead of '0'
if (!model.invoice().discount()) model.invoice().discount('');
if (!NINJA.parseFloat(model.invoice().discount())) model.invoice().discount('');
if (!model.invoice().custom_value1()) model.invoice().custom_value1('');
if (!model.invoice().custom_value2()) model.invoice().custom_value2('');

View File

@ -41,6 +41,7 @@
window.invoice = {{ $invoice->toJson() }};
invoice.is_pro = {{ $invoice->client->account->isPro() ? 'true' : 'false' }};
invoice.is_quote = {{ $invoice->is_quote ? 'true' : 'false' }};
invoice.contact = {{ $contact->toJson() }};
function getPDFString() {
var doc = generatePDF(invoice, invoice.invoice_design.javascript);

View File

@ -3343,6 +3343,15 @@ body {
transition: all 0.5s ease;
}
div.discount-group span {
padding: 0px;
border: none;
}
#is_amount_discount {
min-width: 120px;
}
/***********************************************
New/edit invoice page
************************************************/

View File

@ -31627,7 +31627,7 @@ function displayClient(doc, invoice, x, y, layout) {
concatStrings(client.address1, client.address2),
concatStrings(client.city, client.state, client.postal_code),
client.country ? client.country.name : false,
client.contacts && getClientDisplayName(client) != client.contacts[0].email ? client.contacts[0].email : false,
invoice.contact && getClientDisplayName(client) != invoice.contact.email ? invoice.contact.email : false,
invoice.client.custom_value1 ? invoice.account['custom_client_label1'] + ' ' + invoice.client.custom_value1 : false,
invoice.client.custom_value2 ? invoice.account['custom_client_label2'] + ' ' + invoice.client.custom_value2 : false,
];
@ -31686,7 +31686,7 @@ function displaySubtotals(doc, layout, invoice, y, rightAlignTitleX)
var data = [
{'subtotal': formatMoney(invoice.subtotal_amount, invoice.client.currency_id)},
{'discount': invoice.discount_amount > 0 ? formatMoney(invoice.discount_amount, invoice.client.currency_id) : false}
{'discount': invoice.discount_amount != 0 ? formatMoney(invoice.discount_amount, invoice.client.currency_id) : false}
];
if (NINJA.parseFloat(invoice.custom_value1) && invoice.custom_taxes1 == '1') {
@ -31791,6 +31791,8 @@ function displayGrid(doc, invoice, data, x, y, layout, options) {
key = invoice.account[key];
} else if (key === 'tax' && invoice.tax_rate) {
key = invoiceLabels[key] + ' ' + (invoice.tax_rate*1).toString() + '%';
} else if (key === 'discount' && NINJA.parseFloat(invoice.discount) && !parseInt(invoice.is_amount_discount)) {
key = invoiceLabels[key] + ' ' + parseFloat(invoice.discount) + '%';
} else {
key = invoiceLabels[key];
}
@ -31863,8 +31865,13 @@ function calculateAmounts(invoice) {
invoice.subtotal_amount = total;
if (invoice.discount > 0) {
var discount = roundToTwo(total * (invoice.discount/100));
var discount = 0;
if (invoice.discount != 0) {
if (parseInt(invoice.is_amount_discount)) {
discount = roundToTwo(invoice.discount);
} else {
discount = roundToTwo(total * (invoice.discount/100));
}
total -= discount;
}

View File

@ -543,6 +543,15 @@ body {
transition: all 0.5s ease;
}
div.discount-group span {
padding: 0px;
border: none;
}
#is_amount_discount {
min-width: 120px;
}
/***********************************************
New/edit invoice page
************************************************/

View File

@ -670,7 +670,7 @@ function displayClient(doc, invoice, x, y, layout) {
concatStrings(client.address1, client.address2),
concatStrings(client.city, client.state, client.postal_code),
client.country ? client.country.name : false,
client.contacts && getClientDisplayName(client) != client.contacts[0].email ? client.contacts[0].email : false,
invoice.contact && getClientDisplayName(client) != invoice.contact.email ? invoice.contact.email : false,
invoice.client.custom_value1 ? invoice.account['custom_client_label1'] + ' ' + invoice.client.custom_value1 : false,
invoice.client.custom_value2 ? invoice.account['custom_client_label2'] + ' ' + invoice.client.custom_value2 : false,
];
@ -729,7 +729,7 @@ function displaySubtotals(doc, layout, invoice, y, rightAlignTitleX)
var data = [
{'subtotal': formatMoney(invoice.subtotal_amount, invoice.client.currency_id)},
{'discount': invoice.discount_amount > 0 ? formatMoney(invoice.discount_amount, invoice.client.currency_id) : false}
{'discount': invoice.discount_amount != 0 ? formatMoney(invoice.discount_amount, invoice.client.currency_id) : false}
];
if (NINJA.parseFloat(invoice.custom_value1) && invoice.custom_taxes1 == '1') {
@ -834,6 +834,8 @@ function displayGrid(doc, invoice, data, x, y, layout, options) {
key = invoice.account[key];
} else if (key === 'tax' && invoice.tax_rate) {
key = invoiceLabels[key] + ' ' + (invoice.tax_rate*1).toString() + '%';
} else if (key === 'discount' && NINJA.parseFloat(invoice.discount) && !parseInt(invoice.is_amount_discount)) {
key = invoiceLabels[key] + ' ' + parseFloat(invoice.discount) + '%';
} else {
key = invoiceLabels[key];
}
@ -906,8 +908,13 @@ function calculateAmounts(invoice) {
invoice.subtotal_amount = total;
if (invoice.discount > 0) {
var discount = roundToTwo(total * (invoice.discount/100));
var discount = 0;
if (invoice.discount != 0) {
if (parseInt(invoice.is_amount_discount)) {
discount = roundToTwo(invoice.discount);
} else {
discount = roundToTwo(total * (invoice.discount/100));
}
total -= discount;
}