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.