Add invoice display, prevent editing on anything other than incoming tier

This commit is contained in:
Ramon Gutierrez 2018-02-05 13:14:55 +08:00
parent 075c9e7dd5
commit 887823eee0
8 changed files with 205 additions and 67 deletions

View file

@ -9,6 +9,7 @@ use App\Ramcar\WarrantyClass;
use App\Ramcar\DiscountApply;
use App\Ramcar\TradeInType;
use App\Ramcar\InvoiceCriteria;
use App\Ramcar\InvoiceStatus;
use App\Entity\JobOrder;
use App\Entity\BatteryManufacturer;
use App\Entity\Customer;
@ -61,8 +62,9 @@ class JobOrderController extends BaseController
return $this->render('job-order/form.html.twig', $params);
}
public function incomingSubmit(Request $req, ValidatorInterface $validator)
public function incomingSubmit(Request $req, ValidatorInterface $validator, InvoiceCreator $ic)
{
error_log(print_r($req->request->all(), true));
$this->denyAccessUnlessGranted('jo_in.list', null, 'No access.');
// initialize error list
@ -109,6 +111,41 @@ class JobOrderController extends BaseController
->setAgentNotes($req->request->get('agent_notes'))
->setDeliveryAddress($req->request->get('delivery_address'));
// instantiate invoice criteria
$criteria = new InvoiceCriteria();
$ierror = $this->invoicePromo($em, $criteria, $req->request->get('invoice_promo'));
$invoice_items = $req->request->get('invoice_items');
if (!$ierror && !empty($invoice_items))
$ierror = $this->invoiceBatteries($em, $criteria, $invoice_items);
if ($ierror)
{
$error_array['invoice'] = $ierror;
}
else
{
// generate the invoice
$iobj = $ic->processCriteria($criteria);
$iobj->setStatus(InvoiceStatus::DRAFT)
->setCreatedBy($this->getUser());
// validate
$ierrors = $validator->validate($iobj);
// add errors to list
foreach ($ierrors as $error) {
$error_array[$error->getPropertyPath()] = $error->getMessage();
}
// add invoice to JO
$obj->setInvoice($iobj);
// save
$em->persist($iobj);
}
// validate
$errors = $validator->validate($obj);
@ -729,7 +766,7 @@ class JobOrderController extends BaseController
{
$invoice['items'][] = [
'title' => $item->getTitle(),
'quantity' => $item->getQuantity(), // TODO: quantities are always 1, hardcoded into InvoiceCreator. no way of accepting quantities on InvoiceCriteria
'quantity' => number_format($item->getQuantity()), // TODO: quantities are always 1, hardcoded into InvoiceCreator. no way of accepting quantities on InvoiceCriteria
'unit_price' => number_format($item->getPrice(), 2),
'amount' => number_format($item->getPrice() * $item->getQuantity(), 2) // TODO: should this calculation should be a saved value on InvoiceItem instead?
];

View file

@ -40,23 +40,23 @@ class Invoice
// user that created the invoice
/**
* @ORM\ManyToOne(targetEntity="User", inversedBy="invoice")
* @ORM\ManyToOne(targetEntity="User", inversedBy="invoices")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $created_by;
// the job order the invoice was created from
/**
* @ORM\ManyToOne(targetEntity="JobOrder", inversedBy="invoice")
* @ORM\OneToOne(targetEntity="JobOrder", inversedBy="invoice")
* @ORM\JoinColumn(name="job_order_id", referencedColumnName="id")
*/
protected $job_order;
// invoice items
/**
* @ORM\OneToMany(targetEntity="InvoiceItem", mappedBy="invoice")
* @ORM\OneToMany(targetEntity="InvoiceItem", mappedBy="invoice", cascade={"persist"})
*/
protected $invoice_items;
protected $items;
// total discount (amount, not %)
/**
@ -94,10 +94,17 @@ class Invoice
*/
protected $status;
// promo used by this invoice
/**
* @ORM\ManyToOne(targetEntity="Promo")
* @ORM\JoinColumn(name="promo_id", referencedColumnName="id")
*/
protected $promo;
public function __construct()
{
$this->date_create = new DateTime();
$this->invoice_items = new ArrayCollection();
$this->items = new ArrayCollection();
}
public function getID()
@ -156,20 +163,20 @@ class Invoice
public function addItem(InvoiceItem $item)
{
$this->invoice_items->add($item);
$this->items->add($item);
$item->setInvoice($this);
return $this;
}
public function clearItems()
{
$this->invoice_items->clear();
$this->items->clear();
return $this;
}
public function getItems()
{
return $this->invoice_items;
return $this->items;
}
public function setDiscount($discount)
@ -237,4 +244,15 @@ class Invoice
{
return $this->status;
}
public function setPromo(Promo $promo)
{
$this->promo = $promo;
return $this;
}
public function getPromo()
{
return $this->promo;
}
}

View file

@ -43,6 +43,13 @@ class InvoiceItem
*/
protected $price;
// battery the item is linked to
/**
* @ORM\ManyToOne(targetEntity="Battery")
* @ORM\JoinColumn(name="battery_id", referencedColumnName="id")
*/
protected $battery;
public function __construct()
{
$this->title = '';
@ -97,4 +104,15 @@ class InvoiceItem
{
return $this->price;
}
public function setBattery(Battery $battery)
{
$this->battery = $battery;
return $this;
}
public function getBattery()
{
return $this->battery;
}
}

View file

@ -162,6 +162,9 @@ class JobOrder
protected $delivery_address;
// invoice
/**
* @ORM\OneToOne(targetEntity="Invoice", mappedBy="job_order")
*/
protected $invoice;
public function __construct()
@ -397,4 +400,16 @@ class JobOrder
{
return $this->delivery_address;
}
public function setInvoice(Invoice $invoice)
{
$this->invoice = $invoice;
$invoice->setJobOrder($this);
return $this;
}
public function getInvoice()
{
return $this->invoice;
}
}

View file

@ -91,6 +91,12 @@ class User implements AdvancedUserInterface, Serializable
*/
protected $tickets;
// invoices made by this user
/**
* @ORM\OneToMany(targetEntity="Invoice", mappedBy="created_by")
*/
protected $invoices;
public function __construct()
{
$this->roles = new ArrayCollection();
@ -290,4 +296,9 @@ class User implements AdvancedUserInterface, Serializable
{
return $this->tickets;
}
public function getInvoices()
{
return $this->invoices;
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace App\Ramcar;
class InvoiceStatus extends NameValue
{
const DRAFT = 'draft';
const FINALIZED = 'finalized';
const PAID = 'paid';
const CANCELLED = 'cancelled';
const COLLECTION = [
'draft' => 'Draft',
'finalized' => 'Finalized',
'paid' => 'Paid',
'cancelled' => 'Cancelled',
];
}

View file

@ -85,7 +85,8 @@ class InvoiceCreator
$item->setInvoice($invoice)
->setTitle($batt->getModel()->getName() . ' ' . $batt->getSize()->getName())
->setQuantity($qty)
->setPrice($sell_price);
->setPrice($sell_price)
->setBattery($batt);
$invoice->addItem($item);
}
@ -149,6 +150,9 @@ class InvoiceCreator
$total['discount'] = $discount;
$total['total_price'] -= $discount;
// process
$invoice->setPromo($promo);
}
public function processCriteria(InvoiceCriteria $criteria)

View file

@ -265,35 +265,39 @@
<div class="form-group m-form__group row">
<div class="col-lg-6">
<label>Discount Type</label>
<select class="form-control m-input" id="invoice-promo" name="invoice-promo">
<option value="">None</option>
{% for promo in promos %}
<option value="{{ promo.getID() }}">{{ promo.getName() ~ ' (' ~ promo.getDiscountRate * 100 ~ '% applied to ' ~ discount_apply[promo.getDiscountApply] ~ ')' }}</option>
{% endfor %}
</select>
<div class="form-control-feedback hide" data-field="invoice_promo"></div>
{% if mode == 'create' %}
<select class="form-control m-input" id="invoice-promo" name="invoice_promo">
<option value="">None</option>
{% for promo in promos %}
<option value="{{ promo.getID() }}">{{ promo.getName() ~ ' (' ~ promo.getDiscountRate * 100 ~ '% applied to ' ~ discount_apply[promo.getDiscountApply] ~ ')' }}</option>
{% endfor %}
</select>
<div class="form-control-feedback hide" data-field="invoice_promo"></div>
{% else %}
<input type="text" id="invoice-promo" class="form-control m-input" value="{{ obj.getInvoice ? obj.getInvoice.getPromo.getName : 'None' }}" disabled>
{% endif %}
</div>
<div class="col-lg-3">
<label>Promo Discount</label>
<input type="text" id="invoice-promo-discount" class="form-control m-input text-right" value="0.00" disabled>
<input type="text" id="invoice-promo-discount" class="form-control m-input text-right" value="{{ obj.getInvoice ? obj.getInvoice.getDiscount|number_format(2) : '0.00' }}" disabled>
</div>
<div class="col-lg-3">
<label>Trade In</label>
<input type="text" id="invoice-trade-in" class="form-control m-input text-right" value="0.00" disabled>
<input type="text" id="invoice-trade-in" class="form-control m-input text-right" value="{{ obj.getInvoice ? obj.getInvoice.getTradeIn|number_format(2) : '0.00' }}" disabled>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-3">
<label>Price</label>
<input type="text" id="invoice-price" class="form-control m-input text-right" value="0.00" disabled>
<input type="text" id="invoice-price" class="form-control m-input text-right" value="{{ obj.getInvoice ? obj.getInvoice.getVATExclusivePrice|number_format(2) : '0.00' }}" disabled>
</div>
<div class="col-lg-3">
<label>VAT</label>
<input type="text" id="invoice-vat" class="form-control m-input text-right" value="0.00" disabled>
<input type="text" id="invoice-vat" class="form-control m-input text-right" value="{{ obj.getInvoice ? obj.getInvoice.getVAT|number_format : '0.00' }}" disabled>
</div>
<div class="col-lg-6">
<label>Total Amount</label>
<input type="text" id="invoice-total-amount" class="form-control m-input text-right" value="0.00" disabled>
<input type="text" id="invoice-total-amount" class="form-control m-input text-right" value="{{ obj.getInvoice ? obj.getInvoice.getTotalPrice|number_format(2) : '0.00' }}" disabled>
</div>
</div>
<div class="form-group m-form__group row">
@ -309,52 +313,65 @@
</tr>
</thead>
<tbody>
<tr class="placeholder-row">
<td colspan="4">
No items to display.
</td>
</tr>
{% if not obj.getInvoice or (obj.getInvoice and obj.getInvoice.getItems|length == 0) %}
<tr class="placeholder-row">
<td colspan="4">
No items to display.
</td>
</tr>
{% else %}
{% for item in obj.getInvoice.getItems %}
<tr data-key="{{ loop.index0 }}">
<td>{{ item.getTitle }}</td>
<td class="text-right">{{ item.getPrice|number_format(2) }}</td>
<td class="text-right">{{ item.getQuantity|number_format }}</td>
<td class="text-right">{{ (item.getPrice * item.getQuantity)|number_format(2) }}</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
</div>
</div>
<div class="form-group m-form__group row">
<div class="col-lg-2">
<label for="invoice-bmfg">Manufacturer</label>
<select class="form-control m-input" id="invoice-bmfg">
{% for manufacturer in bmfgs %}
<option value="{{ manufacturer.getID() }}">{{ manufacturer.getName() }}</option>
{% endfor %}
</select>
{% if mode == 'create' %}
<div class="form-group m-form__group row">
<div class="col-lg-2">
<label for="invoice-bmfg">Manufacturer</label>
<select class="form-control m-input" id="invoice-bmfg">
{% for manufacturer in bmfgs %}
<option value="{{ manufacturer.getID() }}">{{ manufacturer.getName() }}</option>
{% endfor %}
</select>
</div>
<div class="col-lg-2">
<label for="invoice-battery">Battery</label>
<select class="form-control m-input" id="invoice-battery" data-value="" disabled>
<option value="">Select a manufacturer first</option>
</select>
</div>
<div class="col-lg-2">
<label for="invoice-trade-in-type">Trade In</label>
<select class="form-control m-input" id="invoice-trade-in-type">
<option value="">None</option>
{% for key, type in trade_in_types %}
<option value="{{ key }}">{{ type }}</option>
{% endfor %}
</select>
</div>
<div class="col-lg-2">
<label for="invoice-quantity">Quantity</label>
<input type="text" id="invoice-quantity" class="form-control m-input text-right" value="1">
</div>
<div class="col-lg-2">
<div><label>&nbsp;</label></div>
<button type="button" class="btn btn-primary" id="btn-add-to-invoice">Add to Invoice</button>
</div>
<div class="col-lg-2 text-right">
<div><label>&nbsp;</label></div>
<button type="button" class="btn btn-danger" id="btn-reset-invoice">Reset Invoice</button>
</div>
</div>
<div class="col-lg-2">
<label for="invoice-battery">Battery</label>
<select class="form-control m-input" id="invoice-battery" data-value="" disabled>
<option value="">Select a manufacturer first</option>
</select>
</div>
<div class="col-lg-2">
<label for="invoice-trade-in-type">Trade In</label>
<select class="form-control m-input" id="invoice-trade-in-type">
<option value="">None</option>
{% for key, type in trade_in_types %}
<option value="{{ key }}">{{ type }}</option>
{% endfor %}
</select>
</div>
<div class="col-lg-2">
<label for="invoice-quantity">Quantity</label>
<input type="text" id="invoice-quantity" class="form-control m-input text-right" value="1">
</div>
<div class="col-lg-2">
<div><label>&nbsp;</label></div>
<button type="button" class="btn btn-primary" id="btn-add-to-invoice">Add to Invoice</button>
</div>
<div class="col-lg-2 text-right">
<div><label>&nbsp;</label></div>
<button type="button" class="btn btn-danger" id="btn-reset-invoice">Reset Invoice</button>
</div>
</div>
{% endif %}
</div>
{% if mode == 'update-processing' %}
@ -916,9 +933,9 @@ $(function() {
// add to invoice array
invoiceItems.push({
'battery': battery,
'quantity': qty,
'trade_in': tradeIn,
battery: battery,
quantity: qty,
trade_in: tradeIn,
});
// regenerate the invoice