Skip to content

Instantly share code, notes, and snippets.

@bizstation
Last active June 12, 2017 08:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bizstation/b6b4ff5be2b6e0d4209f52ac198a766b to your computer and use it in GitHub Desktop.
Save bizstation/b6b4ff5be2b6e0d4209f52ac198a766b to your computer and use it in GitHub Desktop.
Example with Transactd PHP ORM
<?php
require_once(__DIR__ . "/vendor/autoload.php");
use BizStation\Transactd\Transactd;
use BizStation\Transactd\Database;
use BizStation\Transactd\Tabledef;
use BizStation\Transactd\Benchmark;
use BizStation\Transactd\Nstable;
use Transactd\QueryExecuter;
use Transactd\IOException;
use Transactd\Model;
use Transactd\Collection;
use Transactd\JsonSerializable;
class_alias('Transactd\DatabaseManager', 'DB');
CONST masterUri = 'tdap://localhost/salesdb';
CONST slaveUri = 'tdap://localhost/salesdb';
class Customer extends Model
{
protected static $guarded = ['id'];
public $id = 0;
public function invoices()
{
return $this->hasMany('Invoice');
}
public function transactions($startDate, $endDate)
{
return Invoice::index(1)->keyValue($this->id, $startDate)
->where('customer_id', $this->id)->where('date', '<=', $endDate)->get();
}
}
class Product extends Model
{
protected static $guarded = ['id'];
public function stock()
{
return $this->hasOne('Stock', 0, 'code');
}
}
class Stock extends Model
{
protected static $guarded = [];
public function product()
{
return $this->belongsTo('Product', 'code', 1);
}
}
Trait AmountTrait
{
public $sales = 0;
public $tax = 0;
public $payment = 0;
public $balance = 0;
public function reset()
{
$this->sales = 0;
$this->tax = 0;
$this->payment = 0;
}
public function sum($amount)
{
$this->sales += $amount->sales;
$this->tax += $amount->tax;
$this->payment += $amount->payment;
}
public function total()
{
return $this->sales + $this->tax - $this->payment;
}
}
class InvoiceAmount
{
use AmountTrait;
use JsonSerializable;
private $oldBlance = null;
public function __construct()
{
$this->className = get_class($this);
}
public function difference()
{
return $this->balance - $this->oldBlance;
}
public function calc(Collection $rows, $base)
{
if ($this->oldBlance === null) {
$this->oldBlance = $this->balance;
}
$this->reset();
$ar = $rows->getNativeArray();
foreach($ar as $row) {
$this->increment($row);
}
$this->balance = $base + $this->total();
}
public function increment(InvoiceItem $row)
{
if ($row->line_type === InvoiceItem::SALES) {
$this->sales += $row->amount;
$this->tax += $row->tax;
} elseif ($row->line_type === InvoiceItem::PAYMENT) {
$this->payment += $row->amount;
}
}
public function decrement(InvoiceItem $row)
{
if ($row->line_type === InvoiceItem::SALES) {
$this->sales -= $row->amount;
$this->tax -= $row->tax;
} elseif ($row->line_type === InvoiceItem::PAYMENT) {
$this->payment -= $row->amount;
}
}
}
class InvoiceItemSaveHandler
{
private $date;
public function __construct($date)
{
$this->date = $date;
}
public function onStart()
{
$this->amount = new InvoiceAmount;
}
public function onEnd()
{
$it = DailySummary::index(0)->keyValue($this->date)->serverCursor();
if ($it->valid()) {
$daily = $it->current();
$daily->sum($this->amount);
$it->update($daily);
} else {
$daily = new DailySummary(['date' => $this->date]);
$daily->sum($this->amount);
$it->insert($daily);
}
DailySummary::resetQuery();
Stock::resetQuery();
}
public function onSaveRow(InvoiceItem $row)
{
$this->amount->increment($row);
if ($row->line_type === InvoiceItem::SALES) {
$it = Stock::keyValue($row->product_code)->serverCursor();
if ($it->valid()) {
$stock = $it->current();
$stock->quantity -= $row->quantity;
$it->update($stock);
} else {
$stock = new Stock(['code' => $row->product_code]);
$stock->quantity = 0 - $row->quantity;
$it->insert($stock);
}
$stock->updateCache();
}
}
public function onDeleteRow(InvoiceItem $row)
{
$this->amount->decrement($row);
if ($row->line_type === InvoiceItem::SALES) {
$it = Stock::keyValue($row->product_code)->serverCursor();
$it->validOrFail();
$stock = $it->current();
$stock->quantity += $row->quantity;
$it->update($stock);
$stock->updateCache();
}
}
}
class Invoice extends Model
{
public static $aliases = ['sales_amount' => 'sales', 'tax_amount' => 'tax', 'payment_amount' => 'payment', 'balance_amount' => 'balance'];
public static $transferMap = ['sales' => 'amount', 'tax' => 'amount', 'payment' => 'amount', 'balance' => 'amount'];
public static $guarded = ['id'];
public $id = 0;
public $amount = null;
public $date;
protected $update_at;
private $baseBalance = 0;
public function __construct()
{
parent::__construct();
$this->amount = new InvoiceAmount;
$this->date = date("Y/m/d");
}
public function items()
{
return $this->hasMany('InvoiceItem');
}
public function customer()
{
return $this->belongsTo('Customer');
}
private function assignInvoiceNumber()
{
$it = Invoice::serverCursor(0, QueryExecuter::SEEK_LAST);
if ($it->valid()) {
$inv = $it->current();
$this->id = $inv->id + 1;
} else {
$this->id = 1;
}
}
private function readBaseBalance()
{
$this->baseBalance = 0;
$it = $this->serverCursor(1, QueryExecuter::SEEK_LESSTHAN, Transactd::ROW_LOCK_S);
if ($it->valid()) {
$inv = $it->current();
if ($inv->customer_id === $this->customer_id) {
$this->baseBalance = $inv->amount->balance;
}
}
return $it;
}
private function updateBalanceAmount($difference)
{
if ($difference !== 0) {
$it = $this->serverCursor(1, QueryExecuter::SEEK_GREATER);
try {
$it->setTimestampMode(Transactd::TIMESTAMP_VALUE_CONTROL);
foreach($it as $inv) {
if ($inv->customer_id !== $this->customer_id) {
break;
}
$inv->amount->balance += $difference;
$it->update($inv);
$inv->updateCache();
}
$it->setTimestampMode(Transactd::TIMESTAMP_ALWAYS);
} catch (Exception $e) {
$it->setTimestampMode(Transactd::TIMESTAMP_ALWAYS);
throw $e;
}
}
}
private function conflictFail($inv)
{
if ($this->update_at !== $inv->update_at) {
throw new Exception('This invoice is already changed by other user.');
}
}
public function save($options = 0, $forceInsert = false)
{
InvoiceItem::$handler = new InvoiceItemSaveHandler($this->date);
InvoiceItem::$handler->onStart();
if ($this->id === 0) {
$this->assignInvoiceNumber();
$forceInsert = true;
}
$it = $this->readBaseBalance();
$this->amount->calc($this->items, $this->baseBalance);
if ($forceInsert) {
$this->items->renumber('row');
$it->insert($this);
} else {
$it = $this->serverCursor(1, QueryExecuter::SEEK_EQUAL);
$it->validOrFail();
$this->conflictFail($it->current());
$it->update($this);
}
$this->items->save();
$this->updateCache();
$this->updateBalanceAmount($this->amount->difference());
InvoiceItem::$handler->onEnd();
}
public function delete($options = null)
{
InvoiceItem::$handler = new InvoiceItemSaveHandler($this->date);
InvoiceItem::$handler->onStart();
$it = $this->serverCursor(1, QueryExecuter::SEEK_EQUAL);
$it->validOrFail();
$this->conflictFail($it->current());
$it->delete();
$this->items->delete();
$this->updateCache(true);
$this->updateBalanceAmount(0 - $this->amount->difference());
InvoiceItem::$handler->onEnd();
}
public function addSalesLine($code, $qty)
{
$prod = Product::index(1)->findOrFail($code);
$item = new InvoiceItem;
$item->assignSales($prod, $qty);
$this->items->add($item);
return $item;
}
public function addPaymentLine($amount, $desc)
{
$item = new InvoiceItem;
$item->assignPayment($amount, $desc);
$this->items->add($item);
return $item;
}
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;
}
}
}
class InvoiceItem extends Model
{
const SALES = 0;
const PAYMENT = 1;
const TAX_RATE = 0.08;
public static $handler = null;
protected static $table = 'invoice_items';
protected static $guarded = [];
public function invoice()
{
return $this->belongsTo('Invoice');
}
public function stock()
{
return $this->hasOne('Stock', 0, 'product_code');
}
public function product()
{
return $this->hasOne('Product', 1, 'product_code');
}
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);
}
public function assignPayment($amount, $desc)
{
$this->product_code = 'PAYMENT';
$this->product_description = $desc;
$this->line_type = self::PAYMENT;
$this->amount = $amount;
}
public static function deleting(InvoiceItem $row)
{
self::$handler->onDeleteRow($row);
return true;
}
public static function saving(InvoiceItem $row)
{
self::$handler->onSaveRow($row);
return true;
}
}
class DailySummary extends Model
{
use AmountTrait;
protected static $guarded = [];
protected static $table = 'daily_summaries';
static protected $aliases = ['slaes_amount' => 'sales', 'tax_amount' => 'tax', 'payment_amount' => 'payment'];
public function invoices()
{
return $this->hasMany('Invoice', 2, 'date');
}
}
// Please `connect` at the beginning of the application.
// DB::connect(URI, URI);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment