Add invoice display, prevent editing on anything other than incoming tier
This commit is contained in:
parent
075c9e7dd5
commit
887823eee0
8 changed files with 205 additions and 67 deletions
|
|
@ -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?
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
18
src/Ramcar/InvoiceStatus.php
Normal file
18
src/Ramcar/InvoiceStatus.php
Normal 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',
|
||||
];
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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> </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> </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> </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> </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
|
||||
|
|
|
|||
Loading…
Reference in a new issue