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 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.

  1. Create master data
  2. Increase and decrease in stock quantity