├── .github └── FUNDING.yml ├── LICENSE.md ├── README.md ├── composer.json ├── config └── stock.php ├── database └── migrations │ └── 2020_02_12_100000_create_stock_mutations_table.php └── src ├── HasStock.php ├── ReferencedByStockMutations.php ├── StockMutation.php └── StockServiceProvider.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: appstract 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) Appstract 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Stock 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/appstract/laravel-stock.svg?style=flat-square)](https://packagist.org/packages/appstract/laravel-stock) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/appstract/laravel-stock.svg?style=flat-square)](https://packagist.org/packages/appstract/laravel-stock) 5 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 6 | [![Build Status](https://img.shields.io/travis/appstract/laravel-stock/master.svg?style=flat-square)](https://travis-ci.org/appstract/laravel-stock) 7 | 8 | Keep stock for Eloquent models. This package will track stock mutations for your models. You can increase, decrease, clear and set stock. It's also possible to check if a model is in stock (on a certain date/time). 9 | 10 | ## Installation 11 | 12 | You can install the package via composer: 13 | 14 | ``` bash 15 | composer require appstract/laravel-stock 16 | ``` 17 | 18 | By running `php artisan vendor:publish --provider="Appstract\Stock\StockServiceProvider"` in your project all files for this package will be published. Run `php artisan migrate` to migrate the table. There will now be a `stock_mutations` table in your database. 19 | 20 | ## Usage 21 | 22 | Adding the `HasStock` trait will enable stock functionality on the Model. 23 | 24 | ``` php 25 | use Appstract\Stock\HasStock; 26 | 27 | class Book extends Model 28 | { 29 | use HasStock; 30 | } 31 | ``` 32 | 33 | ### Basic mutations 34 | 35 | ```php 36 | $book->increaseStock(10); 37 | $book->decreaseStock(10); 38 | $book->mutateStock(10); 39 | $book->mutateStock(-10); 40 | ``` 41 | 42 | ### Clearing stock 43 | 44 | It's also possible to clear the stock and directly setting a new value. 45 | 46 | ```php 47 | $book->clearStock(); 48 | $book->clearStock(10); 49 | ``` 50 | 51 | ### Setting stock 52 | 53 | It is possible to set stock. This will create a new mutation with the difference between the old and new value. 54 | 55 | ```php 56 | $book->setStock(10); 57 | ``` 58 | 59 | ### Check if model is in stock 60 | 61 | It's also possible to check if a product is in stock (with a minimal value). 62 | 63 | ```php 64 | $book->inStock(); 65 | $book->inStock(10); 66 | ``` 67 | 68 | ### Current stock 69 | 70 | Get the current stock value (on a certain date). 71 | 72 | ```php 73 | $book->stock; 74 | $book->stock(Carbon::now()->subDays(10)); 75 | ``` 76 | 77 | ### Stock arguments 78 | 79 | Add a description and/or reference model to de StockMutation. 80 | 81 | ```php 82 | $book->increaseStock(10, [ 83 | 'description' => 'This is a description', 84 | 'reference' => $otherModel, 85 | ]); 86 | ``` 87 | 88 | ### Query Scopes 89 | 90 | It is also possible to query based on stock. 91 | 92 | ```php 93 | Book::whereInStock()->get(); 94 | Book::whereOutOfStock()->get(); 95 | ``` 96 | 97 | ## Testing 98 | 99 | ``` bash 100 | composer test 101 | ``` 102 | 103 | ## Contributing 104 | 105 | Contributions are welcome, [thanks to y'all](https://github.com/appstract/laravel-stock/graphs/contributors) :) 106 | 107 | ## About Appstract 108 | 109 | Appstract is a small team from The Netherlands. We create (open source) tools for Web Developers and write about related subjects on [Medium](https://medium.com/appstract). You can [follow us on Twitter](https://twitter.com/appstractnl), [buy us a beer](https://www.paypal.me/appstract/10) or [support us on Patreon](https://www.patreon.com/appstract). 110 | 111 | ## License 112 | 113 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 114 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "appstract/laravel-stock", 3 | "description": "Keep stock for Eloquent models", 4 | "keywords": [ 5 | "appstract", 6 | "laravel-stock" 7 | ], 8 | "homepage": "https://github.com/appstract/laravel-stock", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Gijs Jorissen", 13 | "email": "gijs@appstract.nl", 14 | "homepage": "https://appstract.nl", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^7.2|^8.0" 20 | }, 21 | "require-dev": { 22 | "orchestra/testbench": "^4.6", 23 | "phpunit/phpunit": "^8.3" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Appstract\\Stock\\": "src" 28 | } 29 | }, 30 | "autoload-dev": { 31 | "psr-4": { 32 | "Appstract\\Stock\\Tests\\": "tests" 33 | } 34 | }, 35 | "scripts": { 36 | "test": "vendor/bin/phpunit" 37 | }, 38 | "config": { 39 | "sort-packages": true 40 | }, 41 | "extra": { 42 | "laravel": { 43 | "providers": [ 44 | "Appstract\\Stock\\StockServiceProvider" 45 | ], 46 | "aliases": { 47 | "Alias": "Appstract\\Stock\\StockFacade" 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /config/stock.php: -------------------------------------------------------------------------------- 1 | 'stock_mutations', 15 | 16 | ]; 17 | -------------------------------------------------------------------------------- /database/migrations/2020_02_12_100000_create_stock_mutations_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->morphs('stockable'); 19 | $table->string('reference_type')->nullable(); 20 | $table->unsignedBigInteger('reference_id')->nullable(); 21 | $table->integer('amount'); 22 | $table->text('description')->nullable(); 23 | $table->timestamps(); 24 | 25 | $table->index(['reference_type', 'reference_id']); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::dropIfExists(config('stock.table')); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/HasStock.php: -------------------------------------------------------------------------------- 1 | stock(); 25 | } 26 | 27 | /* 28 | |-------------------------------------------------------------------------- 29 | | Methods 30 | |-------------------------------------------------------------------------- 31 | */ 32 | 33 | public function stock($date = null) 34 | { 35 | $date = $date ?: Carbon::now(); 36 | 37 | if (! $date instanceof DateTimeInterface) { 38 | $date = Carbon::create($date); 39 | } 40 | 41 | return (int) $this->stockMutations() 42 | ->where('created_at', '<=', $date->format('Y-m-d H:i:s')) 43 | ->sum('amount'); 44 | } 45 | 46 | public function increaseStock($amount = 1, $arguments = []) 47 | { 48 | return $this->createStockMutation($amount, $arguments); 49 | } 50 | 51 | public function decreaseStock($amount = 1, $arguments = []) 52 | { 53 | return $this->createStockMutation(-1 * abs($amount), $arguments); 54 | } 55 | 56 | public function mutateStock($amount = 1, $arguments = []) 57 | { 58 | return $this->createStockMutation($amount, $arguments); 59 | } 60 | 61 | public function clearStock($newAmount = null, $arguments = []) 62 | { 63 | $this->stockMutations()->delete(); 64 | 65 | if (! is_null($newAmount)) { 66 | $this->createStockMutation($newAmount, $arguments); 67 | } 68 | 69 | return true; 70 | } 71 | 72 | public function setStock($newAmount, $arguments = []) 73 | { 74 | $currentStock = $this->stock; 75 | 76 | if ($deltaStock = $newAmount - $currentStock) { 77 | return $this->createStockMutation($deltaStock, $arguments); 78 | } 79 | } 80 | 81 | public function inStock($amount = 1) 82 | { 83 | return $this->stock > 0 && $this->stock >= $amount; 84 | } 85 | 86 | public function outOfStock() 87 | { 88 | return $this->stock <= 0; 89 | } 90 | 91 | /** 92 | * Function to handle mutations (increase, decrease). 93 | * 94 | * @param int $amount 95 | * @param array $arguments 96 | * @return bool 97 | */ 98 | protected function createStockMutation($amount, $arguments = []) 99 | { 100 | $reference = Arr::get($arguments, 'reference'); 101 | 102 | $createArguments = collect([ 103 | 'amount' => $amount, 104 | 'description' => Arr::get($arguments, 'description'), 105 | ])->when($reference, function ($collection) use ($reference) { 106 | return $collection 107 | ->put('reference_type', $reference->getMorphClass()) 108 | ->put('reference_id', $reference->getKey()); 109 | })->toArray(); 110 | 111 | return $this->stockMutations()->create($createArguments); 112 | } 113 | 114 | /* 115 | |-------------------------------------------------------------------------- 116 | | Scopes 117 | |-------------------------------------------------------------------------- 118 | */ 119 | 120 | public function scopeWhereInStock($query) 121 | { 122 | return $query->where(function ($query) { 123 | return $query->whereHas('stockMutations', function ($query) { 124 | return $query->select('stockable_id') 125 | ->groupBy('stockable_id') 126 | ->havingRaw('SUM(amount) > 0'); 127 | }); 128 | }); 129 | } 130 | 131 | public function scopeWhereOutOfStock($query) 132 | { 133 | return $query->where(function ($query) { 134 | return $query->whereHas('stockMutations', function ($query) { 135 | return $query->select('stockable_id') 136 | ->groupBy('stockable_id') 137 | ->havingRaw('SUM(amount) <= 0'); 138 | })->orWhereDoesntHave('stockMutations'); 139 | }); 140 | } 141 | 142 | /* 143 | |-------------------------------------------------------------------------- 144 | | Relations 145 | |-------------------------------------------------------------------------- 146 | */ 147 | 148 | /** 149 | * Relation with StockMutation. 150 | * 151 | * @return \Illuminate\Database\Eloquent\Relations\morphMany 152 | */ 153 | public function stockMutations() 154 | { 155 | return $this->morphMany(StockMutation::class, 'stockable'); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/ReferencedByStockMutations.php: -------------------------------------------------------------------------------- 1 | morphMany(StockMutation::class, 'reference'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/StockMutation.php: -------------------------------------------------------------------------------- 1 | setTable(config('stock.table', 'stock_mutations')); 26 | } 27 | 28 | /** 29 | * Relation. 30 | * 31 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo 32 | */ 33 | public function stockable() 34 | { 35 | return $this->morphTo(); 36 | } 37 | 38 | /** 39 | * Relation. 40 | * 41 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo 42 | */ 43 | public function reference() 44 | { 45 | return $this->morphTo(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/StockServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 15 | $this->publishes([ 16 | __DIR__.'/../config/stock.php' => config_path('stock.php'), 17 | ], 'config'); 18 | 19 | $this->publishes([ 20 | __DIR__.'/../database/migrations' => database_path('migrations'), 21 | ], 'migrations'); 22 | } 23 | } 24 | 25 | /** 26 | * Register the application services. 27 | */ 28 | public function register() 29 | { 30 | $this->mergeConfigFrom(__DIR__.'/../config/stock.php', 'stock'); 31 | } 32 | } 33 | --------------------------------------------------------------------------------