Skip to content

Commit 4672cd4

Browse files
initial stock management
1 parent 5ee4e64 commit 4672cd4

File tree

4 files changed

+366
-0
lines changed

4 files changed

+366
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/**
4+
* SiteBase
5+
* PHP Version 8.3
6+
*
7+
* @category CMS / Framework
8+
* @package Degami\Sitebase
9+
* @author Mirko De Grandis <degami@github.com>
10+
* @license MIT https://opensource.org/licenses/mit-license.php
11+
* @link https://github.com/degami/sitebase
12+
*/
13+
14+
namespace App\Base\Migrations;
15+
16+
use App\Base\Abstracts\Migrations\DBMigration;
17+
use Degami\SqlSchema\Table;
18+
use Degami\SqlSchema\Index;
19+
20+
class CreateProductStockTableMigration extends DBMigration
21+
{
22+
protected string $tableName = 'product_stock';
23+
24+
public function getName(): string
25+
{
26+
return '08.1_' . parent::getName();
27+
}
28+
29+
public function addDBTableDefinition(Table $table): Table
30+
{
31+
$table
32+
->addColumn('id', 'INT', null, ['UNSIGNED'], false)
33+
->addColumn('user_id', 'INT', null, ['UNSIGNED'], false)
34+
->addColumn('website_id', 'INT', null, ['UNSIGNED'], false)
35+
->addColumn('product_class', 'VARCHAR', [255])
36+
->addColumn('product_id', 'INT', null, ['UNSIGNED'])
37+
->addColumn('quantity', 'INT', null, ['UNSIGNED'])
38+
->addColumn('created_at', 'TIMESTAMP', null, [], false, 'CURRENT_TIMESTAMP()')
39+
->addColumn('updated_at', 'TIMESTAMP', null, [], false, 'CURRENT_TIMESTAMP()')
40+
->addIndex(null, 'id', Index::TYPE_PRIMARY)
41+
->addForeignKey('fk_stock_website_id', ['website_id'], 'website', ['id'])
42+
->addForeignKey('fk_stock_owner_id', ['user_id'], 'user', ['id'])
43+
->setAutoIncrementColumn('id');
44+
45+
return $table;
46+
}
47+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
/**
4+
* SiteBase
5+
* PHP Version 8.3
6+
*
7+
* @category CMS / Framework
8+
* @package Degami\Sitebase
9+
* @author Mirko De Grandis <degami@github.com>
10+
* @license MIT https://opensource.org/licenses/mit-license.php
11+
* @link https://github.com/degami/sitebase
12+
*/
13+
14+
namespace App\Base\Migrations;
15+
16+
use App\Base\Abstracts\Migrations\DBMigration;
17+
use Degami\SqlSchema\Table;
18+
use Degami\SqlSchema\Index;
19+
20+
class CreateStockMovementTableMigration extends DBMigration
21+
{
22+
protected string $tableName = 'stock_movement';
23+
24+
public function getName(): string
25+
{
26+
return '08.2_' . parent::getName();
27+
}
28+
29+
public function addDBTableDefinition(Table $table): Table
30+
{
31+
$table
32+
->addColumn('id', 'INT', null, ['UNSIGNED'], false)
33+
->addColumn('user_id', 'INT', null, ['UNSIGNED'], false)
34+
->addColumn('website_id', 'INT', null, ['UNSIGNED'], false)
35+
->addColumn('stock_id', 'INT', null, ['UNSIGNED'])
36+
->addColumn('movement_type', 'VARCHAR', [50])
37+
->addColumn('quantity', 'INT', null, ['UNSIGNED'])
38+
->addColumn('cart_item_id', 'INT', null, ['UNSIGNED'])
39+
->addColumn('order_item_id', 'INT', null, ['UNSIGNED'])
40+
->addColumn('created_at', 'TIMESTAMP', null, [], false, 'CURRENT_TIMESTAMP()')
41+
->addColumn('updated_at', 'TIMESTAMP', null, [], false, 'CURRENT_TIMESTAMP()')
42+
->addIndex(null, 'id', Index::TYPE_PRIMARY)
43+
->addForeignKey('fk_stockmovement_website_id', ['website_id'], 'website', ['id'])
44+
->addForeignKey('fk_stockmovement_owner_id', ['user_id'], 'user', ['id'])
45+
->addForeignKey('fk_stockmovement_stock_id', ['stock_id'], 'product_stock', ['id'])
46+
->setAutoIncrementColumn('id');
47+
48+
return $table;
49+
}
50+
}

app/base/models/ProductStock.php

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<?php
2+
3+
/**
4+
* SiteBase
5+
* PHP Version 8.3
6+
*
7+
* @category CMS / Framework
8+
* @package Degami\Sitebase
9+
* @author Mirko De Grandis <degami@github.com>
10+
* @license MIT https://opensource.org/licenses/mit-license.php
11+
* @link https://github.com/degami/sitebase
12+
*/
13+
14+
namespace App\Base\Models;
15+
16+
use App\App;
17+
use App\Base\Abstracts\Models\BaseCollection;
18+
use App\Base\Abstracts\Models\BaseModel;
19+
use App\Base\Interfaces\Model\ProductInterface;
20+
use App\Base\Traits\WithOwnerTrait;
21+
use App\Base\Traits\WithWebsiteTrait;
22+
use DateTime;
23+
24+
/**
25+
* Product Stock Model
26+
*
27+
* @method int getId()
28+
* @method int getUserId()
29+
* @method int getWebsiteId()
30+
* @method string getProductClass()
31+
* @method int getProductId()
32+
* @method int getQuantity()
33+
* @method DateTime getCreatedAt()
34+
* @method DateTime getUpdatedAt()
35+
* @method self setId(int $id)
36+
* @method self setUserId(int $user_id)
37+
* @method self setWebsiteId(int $website_id)
38+
* @method self setProductClass(string $product_class)
39+
* @method self setProductId(int $product_id)
40+
* @method self setQuantity(int $quantity)
41+
* @method self setAdminCurrencyCode(string $admin_currency_code)
42+
* @method self setCreatedAt(DateTime $created_at)
43+
* @method self setUpdatedAt(DateTime $updated_at)
44+
*/
45+
class ProductStock extends BaseModel
46+
{
47+
use WithOwnerTrait, WithWebsiteTrait;
48+
49+
protected ?ProductInterface $product = null;
50+
51+
/**
52+
* Set the product for this stock item and update related properties
53+
*
54+
* @param ProductInterface $product
55+
* @return self
56+
*/
57+
public function setProduct(ProductInterface $product): self
58+
{
59+
$this->product = $product;
60+
$this->setProductClass(get_class($product));
61+
$this->setProductId($product->getId());
62+
63+
return $this;
64+
}
65+
66+
/**
67+
* Get the product associated with this stock item
68+
*
69+
* @return ProductInterface|null
70+
*/
71+
public function getProduct() : ?ProductInterface
72+
{
73+
if ($this->product) {
74+
return $this->product;
75+
}
76+
77+
if (!$this->getProductClass()) {
78+
return null;
79+
}
80+
81+
if (!$this->getProductId()) {
82+
return App::getInstance()->containerMake($this->getProductClass());
83+
}
84+
85+
/** @var ?ProductInterface $product */
86+
$product = App::getInstance()->containerCall([$this->getProductClass(), 'load'], ['id' => $this->getProductId()]);
87+
88+
if (!$product instanceof ProductInterface) {
89+
return null;
90+
}
91+
92+
$this->product = $product;
93+
94+
return $this->product;
95+
}
96+
97+
/**
98+
* Get the stock movements associated with this stock item
99+
*
100+
* @return BaseCollection
101+
*/
102+
public function getMovements() : BaseCollection
103+
{
104+
return StockMovement::getCollection()
105+
->where(['stock_id' => $this->getId()]);
106+
}
107+
108+
public function consolidateStock(): self
109+
{
110+
// @todo - check resulting query
111+
$collection = $this->getMovements()->orWhere([
112+
['movement_type' => StockMovement::MOVEMENT_TYPE_INCREASE],
113+
['movement_type' => StockMovement::MOVEMENT_TYPE_DECREASE, 'order_item_id:not' => null]
114+
]);
115+
116+
$this->setQuantity($this->getQuantity() + array_sum($collection->map(function (StockMovement $movement) {
117+
return match($movement->getMovementType()) {
118+
StockMovement::MOVEMENT_TYPE_INCREASE => 1 * $movement->getQuantity(),
119+
StockMovement::MOVEMENT_TYPE_DECREASE => -1 * $movement->getQuantity(),
120+
default => 0,
121+
};
122+
})));
123+
124+
$this->persist();
125+
126+
$collection->remove();
127+
128+
return $this;
129+
}
130+
}

app/base/models/StockMovement.php

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
<?php
2+
3+
/**
4+
* SiteBase
5+
* PHP Version 8.3
6+
*
7+
* @category CMS / Framework
8+
* @package Degami\Sitebase
9+
* @author Mirko De Grandis <degami@github.com>
10+
* @license MIT https://opensource.org/licenses/mit-license.php
11+
* @link https://github.com/degami/sitebase
12+
*/
13+
14+
namespace App\Base\Models;
15+
16+
use App\App;
17+
use App\Base\Abstracts\Models\BaseModel;
18+
use App\Base\Interfaces\Model\ProductInterface;
19+
use App\Base\Traits\WithOwnerTrait;
20+
use App\Base\Traits\WithWebsiteTrait;
21+
use DateTime;
22+
23+
/**
24+
* Stock Movement Model
25+
*
26+
* @method int getId()
27+
* @method int getUserId()
28+
* @method int getWebsiteId()
29+
* @method int getStockId()
30+
* @method string getMovementType()
31+
* @method int getQuantity()
32+
* @method int getCartItemId()
33+
* @method int getOrderItemId()
34+
* @method DateTime getCreatedAt()
35+
* @method DateTime getUpdatedAt()
36+
* @method self setId(int $id)
37+
* @method self setUserId(int $user_id)
38+
* @method self setWebsiteId(int $website_id)
39+
* @method self setStockId(int $stock_id)
40+
* @method self setMovementType(string $movement_type)
41+
* @method self setQuantity(int $quantity)
42+
* @method self setCartItemId(int $cart_item_id)
43+
* @method self setOrderItemId(int $order_item_id)
44+
* @method self setAdminCurrencyCode(string $admin_currency_code)
45+
* @method self setCreatedAt(DateTime $created_at)
46+
* @method self setUpdatedAt(DateTime $updated_at)
47+
*/
48+
class StockMovement extends BaseModel
49+
{
50+
use WithOwnerTrait, WithWebsiteTrait;
51+
52+
public const MOVEMENT_TYPE_INCREASE = 'increase';
53+
public const MOVEMENT_TYPE_DECREASE = 'decrease';
54+
55+
protected ?ProductStock $product_stock = null;
56+
protected ?CartItem $cart_item = null;
57+
protected ?OrderItem $order_item = null;
58+
59+
/**
60+
* Set the product stock for this movement item
61+
*
62+
* @param ProductStock $product_stock
63+
* @return self
64+
*/
65+
public function setProductStock(ProductStock $product_stock): self
66+
{
67+
$this->product_stock = $product_stock;
68+
$this->setStockId($product_stock->getId());
69+
70+
return $this;
71+
}
72+
73+
/**
74+
* Get the product stock associated with this movement item
75+
*
76+
* @return ProductStock|null
77+
*/
78+
public function getProductStock(): ?ProductStock
79+
{
80+
if ($this->product_stock === null) {
81+
$this->product_stock = ProductStock::load($this->getStockId());
82+
}
83+
84+
return $this->product_stock;
85+
}
86+
87+
/**
88+
* Set the cart item for this movement item
89+
*
90+
* @param CartItem $cart_item
91+
* @return self
92+
*/
93+
public function setCartItem(CartItem $cart_item): self
94+
{
95+
$this->cart_item = $cart_item;
96+
$this->setCartItemId($cart_item->getId());
97+
98+
return $this;
99+
}
100+
101+
/**
102+
* Get the cart item associated with this movement item
103+
*
104+
* @return CartItem|null
105+
*/
106+
public function getCartItem(): ?CartItem
107+
{
108+
if ($this->cart_item === null) {
109+
$this->cart_item = CartItem::load($this->getCartItemId());
110+
}
111+
return $this->cart_item;
112+
}
113+
114+
/**
115+
* Set the order item for this movement item
116+
*
117+
* @param OrderItem $order_item
118+
* @return self
119+
*/
120+
public function setOrderItem(OrderItem $order_item): self
121+
{
122+
$this->order_item = $order_item;
123+
$this->setOrderItemId($order_item->getId());
124+
return $this;
125+
}
126+
127+
/**
128+
* Get the order item associated with this movement item
129+
*
130+
* @return OrderItem|null
131+
*/
132+
public function getOrderItem(): ?OrderItem
133+
{
134+
if ($this->order_item === null) {
135+
$this->order_item = OrderItem::load($this->getOrderItemId());
136+
}
137+
return $this->order_item;
138+
}
139+
}

0 commit comments

Comments
 (0)