Create and save invoices
First, we implement simple creation and saving of invoice. We will implement functions such as increasing and decreasing stock later.
In this chapter, you can learn about the following contents:
- Save relationship objects at once
- Start and end transaction
- Error handling
Save invoices
Following is the sample code which sell a product APPLE1
to the customer id = 1
. Search the price of APPLE1
from Product
master.
const TAX_RATE = 0.08;
const SALES = 0;
$cust = Customer::find(1);
$inv = new Invoice;
// Set date
$inv->date = date("Y/m/d"); // Today
// Set customer
$inv->customer()->associate($cust); // Copy $cust->id to $inv->customer_id
// Search the product
$prod = Product::index(1)->findOrFail('APPLE1');
// Create invoice item
$item = new InvoiceItem;
$qty = 1;
$item->product_code = $prod->code;
$item->product_description = $prod->description;
$item->line_type = SALES;
$item->price = $prod->price;
$item->quantity = $qty;
$item->amount = $prod->price * $qty;
$item->tax = (int)(($item->amount * TAX_RATE) + 0.5);
// Add it to invoice
$inv->items->add($item);
// Save items in collection at same time
$inv->save(Model::SAVE_WITH_RELATIONS);
To use date
function in PHP, set timezone in php.ini
beforehand.
[Date]
date.timezone = "Asia/Tokyo"
Make it easier
Above code is too long. Let's make it easier and shorter.
Separate above process into methods in Invoice
and InvoiceItem
. In addition, add method to record payment.
class Invoice extends Model
{
// (omitted)
public $date;
public function __construct()
{
parent::__construct();
$this->amount = new InvoiceAmount;
// Default is Today.
$this->date = date("Y/m/d");
}
// Add sales
public function addSalesLine($code, $qty)
{
$prod = Product::index(1)->findOrFail($code);
$item = new InvoiceItem;
$item->assignSales($prod, $qty);
$this->items->add($item);
return $item;
}
// Add payment
public function addPaymentLine($amount, $desc)
{
$item = new InvoiceItem;
$item->assignPayment($amount, $desc);
$this->items->add($item);
return $item;
}
}
class InvoiceItem extends Model
{
const SALES = 0;
const PAYMENT = 1;
const TAX_RATE = 0.08;
// (omitted)
// Assign sales with specifying the product and its quantity
public function assignSales($prod, $qty)
{
$this->product_code = $prod->code;
$this->product_description = $prod->description;
$this->line_type = self::SALES;
$this->price = $prod->price;
$this->quantity = $qty;
$this->amount = $prod->price * $qty;
$this->tax = (int)(($this->amount * self::TAX_RATE) + 0.5);
}
// Assign payment with specifying amount and description
public function assignPayment($amount, $desc)
{
$this->product_code = 'PAYMENT';
$this->product_description = $desc;
$this->line_type = self::PAYMENT;
$this->amount = $amount;
}
}
With these methods, you can make the same invoice as follows:
$cust = Customer::find(1);
$inv = new Invoice;
$inv->customer()->associate($cust); // Copy $cust->id to $inv->customer_id
$inv->addSalesLine('APPLE1', 1);
$inv->save(Model::SAVE_WITH_RELATIONS);
Model::SAVE_WITH_RELATIONS
specifies that the items in $inv->items
collection will be saved at the same time.
Note: Saving items in $inv->items
collection to database will be done after deleting all records which match the condition of relationship. It prevents inconsistencies such as records not in the collection.
Use transaction to save
Next, we use transaction to save Invoice
and InvoiceItem
, to prevents inconsistencies.
Define saveWithTransaction
method which wraps save
with transaction. And define deleteWithTransaction
in the same way.
save
may throws exceptions. You must catch it and rollBack
transaction.
class Invoice extends Model
{
// (omitted)
// Prepare tables
private function prepareTables()
{
Stock::prepareTable();
DailySummary::prepareTable();
InvoiceItem::prepareTable();
}
public function saveWithTransaction()
{
try {
$this->prepareTables();
DB::beginTrn(Transactd::MULTILOCK_GAP);
$this->save();
DB::commit();
} catch (Exception $e) {
DB::rollBack();
throw $e;
}
}
public function deleteWithTransaction()
{
try {
$this->prepareTables();
DB::beginTrn(Transactd::MULTILOCK_GAP);
$this->delete();
DB::commit();
} catch (Exception $e) {
DB::rollBack();
throw $e;
}
}
The table to be used must be opened before starting transaction. Use prepareTable
to open them. This method does not open table which has already opened. It does not open the table in duplicate.
From now on, we will use saveWithTransaction
instead of save
.
Source code
The source code of this chapter can be downloaded from GitHub Gist.