| Server IP : 210.245.233.93 / Your IP : 216.73.216.226 Web Server : Apache/2.2.15 (CentOS) System : Linux webserver2.onesolution.com.hk 2.6.32-754.35.1.el6.x86_64 #1 SMP Sat Nov 7 12:42:14 UTC 2020 x86_64 User : apache ( 48) PHP Version : 5.3.3 Disable Function : exec, shell_exec, system, passthru, popen, proc_open, pcntl_exec MySQL : ON | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /var/www/hkosl.com/m.musiccircle/webadmin/models/ |
Upload File : |
<?php
use Illuminate\Database\Capsule\Manager as DB;
use Carbon\Carbon as Carbon;
class Inventory extends BaseModel{
protected $table = "inventory";
public function invetoryTxns(){
return $this->hasMany('InventoryTxn');
}
public function proProductSku(){
return $this->belongsTo('ProProductSku', 'productsku_id');
}
public function warehouse(){
return $this->belongsTo('Warehouse', 'warehouse_id');
}
//----------------------------static functions--------------------------------------
public static function stockin($productsku_id, $qty, $warehouse_id, $settings=array()){
global $dbh;
// insert into (optional)
// insert into inventory
$inventory = Inventory::create(array(
'company_id' => 1,
'productsku_id' => $productsku_id,
'warehouse_id' => $warehouse_id,
'org_qty' => $qty,
'qty' => $qty,
'docdate' => date('Y-m-d'),
'unit_cost' => $settings['unit_cost'] ?: 0,
));
$inventory_id = $inventory->id;
// insert into inventory_txn
$inventory_txn = InventoryTxn::create(array(
'company_id' => 1,
'productsku_id' => $productsku_id,
'inventory_id' => $inventory_id,
'docdate' => date('Y-m-d'),
'doc_type' => $settings['doc_type'] ?: 'StockAdjustment',
'doc_id' => $settings['doc_id'] ?: false,
'from_warehouse_id' => false,
'to_warehouse_id' => $warehouse_id,
'qty' => $qty,
));
// update productsku.qty
self::update_productsku_qty($productsku_id);
return true;
}
public static function stockout_reserved($productsku_id, $qty, $settings=array()){
return self::stockout($productsku_id, $qty, array('deduct_reserved_qty'=>true)+$settings);
}
public static function stockout($productsku_id, $qty, $settings=array()){
$productsku = ProProductSku::find($productsku_id);
if($qty>$productsku['qty']){
throw new Exception("Insufficient stock", 1);
}
$qty_remaining = $qty;
$inventoryTxns = array();
while($qty_remaining>0){
$inventory = self::stockout_inventory($productsku_id, $settings);
if(!$inventory){
// roll back
throw new Exception("Insufficient stock", 1);
}
$qty_to_deduct = min($inventory['qty'], $qty_remaining);
$qty_remaining -= $qty_to_deduct;
// var_dump("Stock out {$qty_to_deduct} from inventory {$inventory['id']}");
// insert into inventory_txn
$inventoryTxns[] = InventoryTxn::create(array(
'company_id' => 1,
'order_item_id' => $settings['order_item_id'],
'productsku_id' => $productsku_id,
'inventory_id' => $inventory['id'],
'docdate' => date('Y-m-d'),
'doc_type' => $settings['doc_type'] ?: 'StockAdjustment',
'doc_id' => $settings['doc_id'],
'from_warehouse_id' => $inventory['warehouse_id'],
'to_warehouse_id' => false,
'qty' => $qty_to_deduct,
));
// update inventory.qty
self::update_inventory_qty($inventory['id'], $inventory['warehouse_id']);
// decrease pro_product_sku.reserved_qty
if($qty && $settings['deduct_reserved_qty']!==false){
// reserve qty in negative qty
self::reserve($productsku_id, $qty, false);
}
}
// update productsku.qty
self::update_productsku_qty($productsku_id);
return $inventoryTxns;
}
// $settings = [
// 'createby' => 'user1',
// 'stockout_doc' => [
// 'doc_type' => 'DeliveryNote',
// 'doc_id' => '1000001',
// ],
// 'stockin_doc' => [
// 'doc_type' => 'TransferNote',
// 'doc_id' => '50000000',
// ]
// ]
public static function stockTransfer($productsku_id, $qty, $from_warehouse_id, $to_warehouse_id, $settings=array()){
$inventoryTxns = self::stockout($productsku_id, $qty, $settings['stockout_doc']+$settings);
foreach($inventoryTxns as $inventoryTxn){
// var_dump("Stockout for Txn:{$inventoryTxn->id} qty:{$inventoryTxn->qty} cost:{$inventoryTxn->inventory->unit_cost}");
self::stockin($productsku_id, $inventoryTxn->qty, $to_warehouse_id, array(
'doc_type' => $settings['stockin_doc']['doc_type'] ?: 'STOCK_TRANSFER',
'doc_id' => $settings['stockin_doc']['doc_id'] ?: 0,
'unit_cost' => $inventoryTxn->inventory->unit_cost,
));
}
return count($inventoryTxns)>0;
}
// reverse txn with AVERAGE unit cost
public static function stockReturn($inventory_txn_id, $qty, $settings=array()){
// txn lookup
$old_txn = InventoryTxn::find($inventory_txn_id);
// var_dump($old_txn);
if($old_txn){
if(!$old_txn['from_warehouse_id'] && !$settings['to_warehouse_id']){
// default stock return warehouse
$settings['to_warehouse_id'] = Warehouse::stockin_warehouse($old_txn->proProductSku->supplier_id)->id;
}
if($settings['to_warehouse_id']){
// stockin to specific warehouse with orginal cost
self::stockin($productsku_id, $qty, $warehouse_id, array(
'doc_type' => $settings['doc_type'],
'doc_id' => $settings['doc_id'],
'unit_cost' => $settings['unit_cost'] ?: $old_txn->inventory->unit_cost,
));
}else{
// insert to txn
$inventroyTxn = InventoryTxn::create(array(
'company_id' => 1,
'productsku_id' => $old_txn['productsku_id'],
'inventory_id' => $old_txn['inventory_id'],
'docdate' => date('Y-m-d'),
'doc_type' => 'StockAdjustment',
'doc_id' => 0,
'from_warehouse_id' => $old_txn['to_warehouse_id'],
'to_warehouse_id' => $old_txn['from_warehouse_id'],
'qty' => $qty,
));
// update inventory.qty for both in and out
if($old_txn['from_warehouse_id']){
self::update_inventory_qty($old_txn['inventory_id'], $old_txn['from_warehouse_id']);
}
if($old_txn['to_warehouse_id']){
self::update_inventory_qty($old_txn['inventory_id'], $old_txn['to_warehouse_id']);
}
// update productsku.qty
self::update_productsku_qty($old_txn->inventory->productsku_id);
}
}else{
// TODO: refactor flow control
// transaction/source warehouse not found
// somehow we managed to stockin unknown source product to the inventory with provided warehouse_id and unitcost
// self::stockin()
}
}
public static function reserve($productsku_id, $qty, $is_reserve=true){
$proProductSku = ProProductSku::find($productsku_id);
if(!$is_reserve){
$proProductSku->reserved_qty = max($proProductSku->reserved_qty - $qty, 0);
}else{
$proProductSku->reserved_qty = max($proProductSku->reserved_qty + $qty, 0);
}
$proProductSku->save();
}
protected static function stockout_inventory($productsku_id, $settings = array()){
if(isset($settings['from_warehouse_id'])){
// specified stockout from warehouse
$inventory = Inventory::whereDeleted(0)
->where('qty', '>', 0)
->where('productsku_id', $productsku_id)
->where('warehouse_id', $settings['from_warehouse_id'])
->orderBy('id', 'asc')
->first();
return $inventory;
}else{
// best strategy
// stockout based on warehouse priority
$inventory = Inventory::select('inventory.*')
->join('warehouse', 'warehouse.id', '=', 'inventory.warehouse_id')
->where('inventory.deleted', 0)
->where('warehouse.deleted', 0)
->where('inventory.qty', '>', 0)
->where('productsku_id', $productsku_id)
->where('is_eshop_warehouse', 1)
->orderBy('warehouse.priority', 'asc')
->orderBy('inventory.id', 'asc')
->first();
return $inventory;
}
}
protected static function update_productsku_qty($productsku_id){
$sum = Inventory::whereDeleted(0)
->where('productsku_id', $productsku_id)
->where('qty', '>', 0)
->sum('qty');
$proProductSku = ProProductSku::find($productsku_id);
$proProductSku->qty = $sum;
$proProductSku->save();
}
protected static function update_inventory_qty($inventory_id, $warehouse_id){
$sum_to_inventory = InventoryTxn::whereDeleted(0)
->where('inventory_id', $inventory_id)
->where('qty', '>', 0)
->where('to_warehouse_id', $warehouse_id)
->sum('qty');
$sum_from_inventory = InventoryTxn::whereDeleted(0)
->where('inventory_id', $inventory_id)
->where('qty', '>', 0)
->where('from_warehouse_id', $warehouse_id)
->sum('qty');
$inventory = Inventory::find($inventory_id);
$inventory->qty = $sum_to_inventory - $sum_from_inventory;
$inventory->save();
}
// test cases =======================================================================================
public static function test_reserve(){
global $dbh;
var_dump(__FUNCTION__, '==START==');
$dbh->beginTransaction();
Inventory::reserve(4, 2);
$dbh->commit();
var_dump('==ENDED==');
}
public static function test_stockin(){
global $dbh;
var_dump(__FUNCTION__, '==START==');
$dbh->beginTransaction();
Inventory::stockin(4, 5, 7);
$dbh->commit();
var_dump('==ENDED==');
}
public static function test_stockout(){
global $dbh;
var_dump(__FUNCTION__, '==START==');
$dbh->beginTransaction();
$txns = Inventory::stockout(4, 3);
$dbh->commit();
var_dump($txns);
var_dump('==ENDED==');
}
public static function test_stockout_reserved(){
global $dbh;
var_dump(__FUNCTION__, '==START==');
$dbh->beginTransaction();
Inventory::stockout_reserved(4, 1, array('deduct_reserved_qty'=>1));
$dbh->commit();
var_dump('==ENDED==');
}
public static function test_selectInventory(){
var_dump(__FUNCTION__, '==START==');
var_dump( Inventory::stockout_inventory(1) );
// var_dump( Inventory::stockout_inventory(1, ['from_warehouse_id'=>2]) );
}
public static function test_stockTransfer(){
Inventory::stockTransfer(4, 3, 7, 15, array(
'stockout_doc' => array(
'doc_type' => 'TransferNote',
'doc_id' => '50000000',
),
'stockin_doc' => array(
'doc_type' => 'TransferNote',
'doc_id' => '50000000',
)
));
}
}