diff --git a/Admin/Install/Navigation.install.json b/Admin/Install/Navigation.install.json index bd488dd..e94b747 100644 --- a/Admin/Install/Navigation.install.json +++ b/Admin/Install/Navigation.install.json @@ -34,7 +34,7 @@ "type": 3, "subtype": 1, "name": "Create", - "uri": "{/base}/warehouse/stocktaking/list?{?}", + "uri": "{/base}/warehouse/stocktaking/create?{?}", "target": "self", "icon": null, "order": 1, diff --git a/Admin/Install/db.json b/Admin/Install/db.json new file mode 100644 index 0000000..d3cbdc3 --- /dev/null +++ b/Admin/Install/db.json @@ -0,0 +1,139 @@ +{ + "stocktaking": { + "name": "stocktaking", + "fields": { + "stocktaking_id": { + "name": "stocktaking_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "stocktaking_unit": { + "name": "stocktaking_unit", + "type": "INT", + "null": true, + "default": null, + "foreignTable": "organization_unit", + "foreignKey": "organization_unit_id" + }, + "stocktaking_created_at": { + "name": "stocktaking_created_at", + "type": "DATETIME", + "null": false + } + } + }, + "stocktaking_distribution": { + "name": "stocktaking_distribution", + "fields": { + "stocktaking_distribution_id": { + "name": "stocktaking_distribution_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "stocktaking_distribution_distribution": { + "name": "stocktaking_distribution_distribution", + "type": "INT", + "null": true, + "default": null, + "foreignTable": "warehousemgmt_stock_distribution", + "foreignKey": "warehousemgmt_stock_distribution_id" + }, + "stocktaking_distribution_stocktaking": { + "name": "stocktaking_distribution_stocktaking", + "type": "INT", + "null": true, + "default": null, + "foreignTable": "stocktaking", + "foreignKey": "stocktaking_id" + } + } + }, + "stocktaking_stock": { + "name": "stocktaking_stock", + "fields": { + "stocktaking_stock_id": { + "name": "stocktaking_stock_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "stocktaking_stock_stock": { + "name": "stocktaking_stock_stock", + "type": "INT", + "null": true, + "default": null, + "foreignTable": "warehousemgmt_stock", + "foreignKey": "warehousemgmt_stock_id" + }, + "stocktaking_stock_stocktaking": { + "name": "stocktaking_stock_stocktaking", + "type": "INT", + "null": true, + "default": null, + "foreignTable": "stocktaking", + "foreignKey": "stocktaking_id" + } + } + }, + "stocktaking_type": { + "name": "stocktaking_type", + "fields": { + "stocktaking_type_id": { + "name": "stocktaking_type_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "stocktaking_type_type": { + "name": "stocktaking_type_type", + "type": "INT", + "null": true, + "default": null, + "foreignTable": "warehousemgmt_stock_type", + "foreignKey": "warehousemgmt_stock_type_id" + }, + "stocktaking_type_stocktaking": { + "name": "stocktaking_type_stocktaking", + "type": "INT", + "null": true, + "default": null, + "foreignTable": "stocktaking", + "foreignKey": "stocktaking_id" + } + } + }, + "stocktaking_item": { + "name": "stocktaking_item", + "fields": { + "stocktaking_item_id": { + "name": "stocktaking_item_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "stocktaking_item_item": { + "name": "stocktaking_item_item", + "type": "INT", + "null": true, + "default": null, + "foreignTable": "itemmgmt_item", + "foreignKey": "itemmgmt_item_id" + }, + "stocktaking_item_stocktaking": { + "name": "stocktaking_item_stocktaking", + "type": "INT", + "null": true, + "default": null, + "foreignTable": "stocktaking", + "foreignKey": "stocktaking_id" + } + } + } +} \ No newline at end of file diff --git a/Controller/ApiController.php b/Controller/ApiController.php index 7d036a7..1c99ff1 100644 --- a/Controller/ApiController.php +++ b/Controller/ApiController.php @@ -14,6 +14,18 @@ declare(strict_types=1); namespace Modules\StockTaking\Controller; +use Modules\StockTaking\Models\StockTaking; +use Modules\StockTaking\Models\StockTakingMapper; +use Modules\WarehouseManagement\Models\NullStock; +use Modules\WarehouseManagement\Models\NullStockType; +use Modules\WarehouseManagement\Models\Stock; +use Modules\WarehouseManagement\Models\StockDistributionMapper; +use Modules\WarehouseManagement\Models\StockMapper; +use Modules\WarehouseManagement\Models\StockType; +use phpOMS\Message\Http\RequestStatusCode; +use phpOMS\Message\RequestAbstract; +use phpOMS\Message\ResponseAbstract; + /** * Budgeting controller class. * @@ -24,4 +36,122 @@ namespace Modules\StockTaking\Controller; */ final class ApiController extends Controller { + /** + * Api method to create item payment type + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param array $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiStockTakingCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void + { + if (!empty($val = $this->validateStockTakingCreate($request))) { + $response->header->status = RequestStatusCode::R_400; + $this->createInvalidCreateResponse($request, $response, $val); + + return; + } + + $paymentTerm = $this->createStockTakingFromRequest($request); + $this->createModel($request->header->account, $paymentTerm, StockTakingMapper::class, 'stocktaking', $request->getOrigin()); + $this->createStandardCreateResponse($request, $response, $paymentTerm); + } + + /** + * Validate payment create request + * + * @param RequestAbstract $request Request + * + * @return array + * + * @since 1.0.0 + */ + private function validateStockTakingCreate(RequestAbstract $request) : array + { + $val = []; + if (($val['unit'] = !$request->hasData('unit'))) { + return $val; + } + + return []; + } + + /** + * Method to create payment from request. + * + * @param RequestAbstract $request Request + * + * @return StockTaking + * + * @since 1.0.0 + * @todo Implement a function which allows us to add stocks/locations/items to an existing stock taking (admin) + * @todo Implement a function which allows us to add items to a stock taking (user) + */ + private function createStockTakingFromRequest(RequestAbstract $request) : StockTaking + { + // @todo We started to find item->stock associations by using stock distributions + // In the future we MUST change this and create a separate table for item->stock->location->sodium_crypto_aead_chacha20poly1305_ietf_decrypt + // association. + // It is a little bit unfortunate that we cannot use attributes but it is what it is. + + // Get distributions based on type and stock filter + $stockTypeList = $request->getDataList('types') ?? []; + $stockList = $request->getDataList('stocks') ?? []; + + $stocks = []; + $stockMapper = StockMapper::getAll() + ->with('locations') + ->with('locations/type') + ->where('unit', (int) $request->getData('unit')); + + if (!empty($stockTypeList)) { + $stockMapper->where('locations/type', $stockTypeList); + } + + if (!empty($stockList)) { + $stockMapper->where('id', $stockList); + } + + foreach ($stocks as $idx => $stock) { + if (empty($stock->locations)) { + unset($stocks[$idx]); + } + + if (empty($stockTypeList)) { + foreach ($stock->locations as $location) { + $stockTypeList[] = $location->type->id; + } + } + + if (empty($stockList)) { + $stockList[] = $stock->id; + } + } + + $stocktaking = new StockTaking(); + $stocktaking->unit = (int) $request->getData('unit'); + + $stocktaking->stocks = \array_map(function (int $id) : Stock { + return new NullStock($id); + }, $stockList); + + $stocktaking->types = \array_map(function (int $id) : StockType { + return new NullStockType($id); + }, $stockTypeList); + + // @todo In the future create create a snapshot of distributions and reference those + // This would also allow us to continue creating bills while doing the stocktaking? + $stocktaking->distributions = StockDistributionMapper::getAll() + ->where('stock', $stockList) + ->where('stockType', $stockTypeList) + ->executeGetArray(); + + return $stocktaking; + } } diff --git a/Models/NullStockTaking.php b/Models/NullStockTaking.php new file mode 100644 index 0000000..053e48e --- /dev/null +++ b/Models/NullStockTaking.php @@ -0,0 +1,47 @@ +id = $id; + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() : mixed + { + return ['id' => $this->id]; + } +} diff --git a/Models/StockTaking.php b/Models/StockTaking.php new file mode 100644 index 0000000..1b70d51 --- /dev/null +++ b/Models/StockTaking.php @@ -0,0 +1,45 @@ +createdAt = new \DateTimeImmutable(); + } +} diff --git a/Models/StockTakingMapper.php b/Models/StockTakingMapper.php new file mode 100644 index 0000000..1d59926 --- /dev/null +++ b/Models/StockTakingMapper.php @@ -0,0 +1,75 @@ + + */ +final class StockTakingMapper extends DataMapperFactory +{ + /** + * Columns. + * + * @var array + * @since 1.0.0 + */ + public const COLUMNS = [ + 'stocktaking_id' => ['name' => 'stocktaking_id', 'type' => 'int', 'internal' => 'id'], + 'stocktaking_unit' => ['name' => 'stocktaking_unit', 'type' => 'int', 'internal' => 'unit'], + 'stocktaking_created_at' => ['name' => 'stocktaking_created_at', 'type' => 'DateTimeImmutable', 'internal' => 'createdAt'], + ]; + + /** + * Has many relation. + * + * @var array + * @since 1.0.0 + */ + public const HAS_MANY = [ + 'distributions' => [ + 'mapper' => StockDistributionMapper::class, + 'table' => 'stocktaking_distribution', + 'self' => 'stocktaking_distribution_stocktaking', + 'external' => null, + ], + ]; + + /** + * Primary table. + * + * @var string + * @since 1.0.0 + */ + public const TABLE = 'stocktaking'; + + /** + * Primary field name. + * + * @var string + * @since 1.0.0 + */ + public const PRIMARYFIELD = 'stocktaking_id'; +} diff --git a/Theme/Backend/Lang/de.lang.php b/Theme/Backend/Lang/de.lang.php index 8d7e44f..c73d277 100644 --- a/Theme/Backend/Lang/de.lang.php +++ b/Theme/Backend/Lang/de.lang.php @@ -14,6 +14,6 @@ declare(strict_types=1); return ['StockTaking' => [ 'Stocktaking' => 'Inventur', - 'Date' => 'Datum', - 'Status' => 'Status', + 'Date' => 'Datum', + 'Status' => 'Status', ]]; diff --git a/Theme/Backend/Lang/en.lang.php b/Theme/Backend/Lang/en.lang.php index 2163f07..0a19ccc 100644 --- a/Theme/Backend/Lang/en.lang.php +++ b/Theme/Backend/Lang/en.lang.php @@ -14,6 +14,6 @@ declare(strict_types=1); return ['StockTaking' => [ 'Stocktaking' => 'Stock Taking', - 'Date' => 'Date', - 'Status' => 'Status', + 'Date' => 'Date', + 'Status' => 'Status', ]]; diff --git a/info.json b/info.json index 19f4594..ef0679a 100644 --- a/info.json +++ b/info.json @@ -17,7 +17,9 @@ "description": "Stock Taking module.", "directory": "StockTaking", "dependencies": { - "Controlling": "*" + "Controlling": "*", + "ItemManagement": "*", + "WarehouseManagement": "*" }, "providing": { "Navigation": "*"