348 lines
17 KiB
PHP
348 lines
17 KiB
PHP
<?php
|
|
|
|
class WarehouseArticleController extends TTCrud {
|
|
protected string $headerTitle = 'Artikel';
|
|
protected $createText = 'Artikel erstellen';
|
|
|
|
// @formatter:off
|
|
protected array $columns = [
|
|
['key' => 'title', 'text' => 'Titel', 'required' => true, 'table' => ['priority' => 9]],
|
|
['key' => 'articleNumber', 'text' => 'Nr.', 'required' => true],
|
|
['key' => 'description', 'text' => 'Beschreibung', 'required' => true],
|
|
['key' => 'category', 'text' => 'Kategorie', 'required' => true],
|
|
['key' => 'unit', 'text' => 'Einheit', 'required' => true,'table' => false], // Boolean value
|
|
['key' => 'revenueAccount', 'text' => 'Erlöskonto', 'required' => true,'modal' =>
|
|
['type' => 'select', 'items' => [['value' => 0, 'text' => 'Dienstleistungen'], ['value' => 1, 'text' => 'Handelswaren']]
|
|
], 'table' => false], // Boolean value
|
|
['key' => 'cheapestPurchasePrice', 'text' => 'Einkauf', 'modal' => false, 'table' => ['class' => 'text-center', 'suffix' => ' €']],
|
|
['key' => 'cheapestSellPrice', 'text' => 'Verkauf', 'modal' => false, 'table' => ['class' => 'text-center', 'suffix' => ' €']],
|
|
['key' => 'warningAmount', 'text' => 'Warnmenge', 'required' => true,'modal' => ['type' => 'number'], 'table' => ['class' => 'text-center']], // Stock/inventory related
|
|
['key' => 'criticalAmount', 'text' => 'Kritische Menge', 'required' => true,'modal' => ['type' => 'number'], 'table' => ['class' => 'text-center']], // Stock/inventory related
|
|
['key' => 'isSerialDocumentation', 'text' => 'Seriennummern', 'required' => true,'modal' => ['type' => 'checkbox'], 'table' => false], // Boolean value
|
|
['key' => 'isEShop', 'text' => 'Ist E-Shop', 'required' => true,'modal' => ['type' => 'checkbox'], 'table' => false], // Boolean value
|
|
['key' => 'isEShopHide', 'text' => 'E-Shop Versteckt', 'required' => true,'modal' => ['type' => 'checkbox'], 'table' => false], // Boolean value
|
|
['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center', 'priority' => 8]]
|
|
];
|
|
|
|
protected array $autocompleteColumns = ['articleNumber', 'title', 'description', 'category'];
|
|
|
|
protected array $additionalActions = [
|
|
['key' => 'openHistory','title' => 'Historie','class' => 'fas fa-history text-secondary'],
|
|
['key' => 'editDistributorEntries','title' => 'Lieferanten','class' => 'fas fa-truck text-cyan'],
|
|
['key' => 'editThresholdEntries','title' => 'Schwellenwerte','class' => 'far fa-fw fa-box-full text-orange'],
|
|
['key' => 'editPricesEntries','title' => 'Preise','class' => 'fas fa-euro-sign text-green'],
|
|
['key' => 'addToCart','title' => 'Zur Bestellung hinzufügen','class' => 'fas fa-shopping-cart text-primary'],
|
|
];
|
|
// @formatter:on
|
|
|
|
protected array $additionalJSVariables = ['WAREHOUSE_ADMIN' => true];
|
|
|
|
protected array $infoMessages = ['create' => 'Artikel wurde erstellt',
|
|
'update' => 'Artikel wurde aktualisiert',
|
|
'delete' => 'Artikel wurde gelöscht',
|
|
'noChanges' => 'Keine Änderungen',];
|
|
|
|
protected function prepareCrudConfig() {
|
|
if (!$this->user->can('WarehouseAdmin')) {
|
|
// find column with key actions, cheapestPurchasePrice, warningAmount, criticalAmount and set table to false
|
|
foreach ($this->columns as $key => $column) {
|
|
if (in_array($column['key'], ['actions', 'cheapestPurchasePrice', 'warningAmount', 'criticalAmount'])) {
|
|
$this->columns[$key]['table'] = false;
|
|
}
|
|
}
|
|
$this->createText = false;
|
|
$this->additionalJSVariables['WAREHOUSE_ADMIN'] = false;
|
|
}
|
|
}
|
|
|
|
protected function beforeUpdate($postData): bool {
|
|
(new WarehouseHistoryController)->create($postData, $this->mod);
|
|
return true;
|
|
}
|
|
|
|
protected function afterUpdate($postData) {
|
|
self::updateCheapestPurchasePrice($postData['id']);
|
|
}
|
|
|
|
/**
|
|
* Updates the cheapest purchase price for a given article from WarehouseArticleDistributorModel prices.
|
|
*
|
|
* @param int $articleId The ID of the article to update.
|
|
* @return void
|
|
* @throws Exception If the article is not an instance of WarehouseArticleModel.
|
|
*/
|
|
public static function updateCheapestPurchasePrice(int $articleId): void {
|
|
$article = WarehouseArticleModel::get($articleId);
|
|
|
|
if (!$article instanceof WarehouseArticleModel) {
|
|
throw new Exception("Article is not an instance of WarehouseArticleModel");
|
|
}
|
|
|
|
$order = ['key' => 'purchasePrice', 'order' => 'ASC'];
|
|
$cheapestDistributorEntry = WarehouseArticleDistributorModel::getAll(['articleId' => $articleId], 1, 0, $order);
|
|
|
|
if (empty($cheapestDistributorEntry)) return;
|
|
|
|
$cheapestPurchasePrice = $cheapestDistributorEntry[0]->purchasePrice;
|
|
if ($article->cheapestPurchasePrice == $cheapestPurchasePrice) return;
|
|
WarehouseArticleModel::update(array_merge(get_object_vars($article), ['cheapestPurchasePrice' => $cheapestPurchasePrice]));
|
|
}
|
|
|
|
protected function afterCreate($postData) {
|
|
self::updateCheapestPurchasePrice($postData['id']);
|
|
self::updateSellPrices($postData['id']);
|
|
}
|
|
|
|
/**
|
|
* Updates the sell prices for a given article.
|
|
*
|
|
* @param int $articleId The ID of the article to update.
|
|
* @return void
|
|
* @throws Exception If the article is not an instance of WarehouseArticleModel.
|
|
*/
|
|
public static function updateSellPrices(int $articleId) {
|
|
$article = WarehouseArticleModel::get($articleId);
|
|
if (!$article instanceof WarehouseArticleModel) {
|
|
throw new Exception("Article is not an instance of WarehouseArticleModel");
|
|
}
|
|
|
|
$priceTypes = WarehouseArticlePriceTypeModel::getAll();
|
|
$articlePriceTypes = WarehouseArticlePriceModel::getAll(['articleId' => $articleId]);
|
|
|
|
$cheapestSellPrices = [];
|
|
// Calculate sell prices for each price type, use default sell multiplier if no specific price is set
|
|
foreach ($priceTypes as $priceType) {
|
|
$articlePriceType = null;
|
|
foreach ($articlePriceTypes as $apt) {
|
|
if ($apt->articlePriceTypeId == $priceType->id) {
|
|
$articlePriceType = $apt;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$sellPrice = $priceType->defaultPriceFactor * $article->cheapestPurchasePrice;
|
|
if ($articlePriceType !== null) {
|
|
$sellPrice = $articlePriceType->priceOverride ?: $articlePriceType->priceMultiplier * $article->cheapestPurchasePrice;
|
|
}
|
|
$cheapestSellPrices[$priceType->id] = ['title' => $priceType->title, 'price' => round($sellPrice, 2)];
|
|
}
|
|
|
|
$article->cheapestSellPrice = json_encode($cheapestSellPrices);
|
|
WarehouseArticleModel::update(get_object_vars($article));
|
|
}
|
|
|
|
public function updatePricesAction() {
|
|
foreach (WarehouseArticleModel::getAll() as $article) {
|
|
self::updateCheapestPurchasePrice($article->id);
|
|
self::updateSellPrices($article->id);
|
|
}
|
|
self::returnJson(['success' => true, 'message' => 'Preise wurden aktualisiert']);
|
|
}
|
|
|
|
protected function getHistoryAction() {
|
|
self::returnJson((new WarehouseHistoryController)->getHistory($this->request->id, $this->mod, $this->columns));
|
|
}
|
|
|
|
protected function importAction() {
|
|
error_reporting(E_ALL);
|
|
ini_set('display_errors', 1);
|
|
// read import.json from directory of this file
|
|
$json = fopen(dirname(__FILE__) . '/import.json', 'r') or die('Unable to open file!');
|
|
|
|
// read file content
|
|
$data = fread($json, filesize(dirname(__FILE__) . '/import.json'));
|
|
|
|
// decode json
|
|
$data = json_decode($data, true);
|
|
|
|
// close file
|
|
fclose($json);
|
|
|
|
// die with data length
|
|
// loop through data
|
|
echo 'Importing ' . count($data) . ' items' . PHP_EOL;
|
|
$count = 0;
|
|
foreach ($data as $item) {
|
|
// echo count + 1
|
|
echo ++$count . PHP_EOL;
|
|
// Check if Distributor exists
|
|
$distributor = WarehouseDistributorModel::getAll(['name' => $item['Lieferant']]);
|
|
if (empty($distributor)) {
|
|
$distributorId = WarehouseDistributorModel::create(['name' => $item['Lieferant'],
|
|
'address' => 'Missing',
|
|
'plz' => 'Missing',
|
|
'city' => 'Missing',
|
|
'countryId' => 1,
|
|
'email' => 'Missing',
|
|
'phone' => 'Missing',
|
|
'contactPerson' => 'Missing',]);
|
|
} else {
|
|
$distributorId = $distributor[0]->id;
|
|
}
|
|
|
|
// only continue if PRODUKT 1.ZEILE and PRODUKT 2.ZEILE and ARTIKEL GRUPPE and VK and EK and Lieferant/ Hersteller Artikelnr: are set
|
|
if (!isset($item['PRODUKT 1.ZEILE'])) {
|
|
echo 'Missing data for ' . $item['PRODUKT 1.ZEILE'] . PHP_EOL;
|
|
continue;
|
|
}
|
|
|
|
if (empty($item['VK'])) {
|
|
$item['VK'] = 0;
|
|
$calcSellPriceMultiplier = 0;
|
|
} else {
|
|
$item['VK'] = floatval(str_replace(',', '', $item['VK']));
|
|
}
|
|
|
|
if (empty($item['EK'])) {
|
|
$item['EK'] = 0;
|
|
$calcSellPriceMultiplier = 0;
|
|
$purchasePrice = 0;
|
|
} else {
|
|
$item['EK'] = floatval(str_replace(',', '', $item['EK']));
|
|
}
|
|
|
|
if (!empty($item['VK']) && !empty($item['EK'])) {
|
|
$calcSellPriceMultiplier = $item['VK'] / $item['EK'];
|
|
|
|
// if calcSellPriceMultiplier has more than 2 decimal places assign $calcSellPriceOverride
|
|
echo strlen(substr(strrchr($calcSellPriceMultiplier, "."), 1)) . PHP_EOL;
|
|
if (strlen(substr(strrchr($calcSellPriceMultiplier, "."), 1)) > 2) {
|
|
$calcSellPriceOverride = $item['VK'];
|
|
}
|
|
|
|
$purchasePrice = str_replace(',', '', $item['EK']);
|
|
}
|
|
|
|
if (!isset($calcSellPriceMultiplier) && !isset($calcSellPriceOverride) || !isset($purchasePrice)) {
|
|
echo 'Missing data for ' . $item['PRODUKT 1.ZEILE'] . PHP_EOL;
|
|
continue;
|
|
}
|
|
|
|
|
|
// Check if Article exists
|
|
$article = WarehouseArticleModel::getAll(['title' => ['exact' => $item['PRODUKT 1.ZEILE']]]);
|
|
if (empty($article)) {
|
|
$articleCreateData = ['title' => $item['PRODUKT 1.ZEILE'],
|
|
'description' => $item['PRODUKT 2. ZEILE'],
|
|
'category' => $item['ARTIKEL GRUPPE'],
|
|
'cheapestPurchasePrice' => 0,
|
|
'warningAmount' => 25,
|
|
'criticalAmount' => 10,
|
|
'isEShop' => 0,];
|
|
|
|
// if calcSellPriceOverride is set, add it to the $articleCreateData array
|
|
if (isset($calcSellPriceOverride)) {
|
|
$articleCreateData['sellPriceOverride'] = $calcSellPriceOverride;
|
|
} else if (isset($calcSellPriceMultiplier)) {
|
|
$articleCreateData['sellPriceMultiplier'] = $calcSellPriceMultiplier;
|
|
}
|
|
|
|
$articleId = WarehouseArticleModel::create($articleCreateData);
|
|
} else {
|
|
echo 'Article already exists with title ' . $item['PRODUKT 1.ZEILE'] . PHP_EOL;
|
|
$articleId = $article[0]->id;
|
|
}
|
|
|
|
// Check if ArticleDistributor exists
|
|
$articleDistributor = WarehouseArticleDistributorModel::getAll(['articleId' => $articleId,
|
|
'distributorId' => $distributorId]);
|
|
if (empty($articleDistributor)) {
|
|
WarehouseArticleDistributorModel::create(['articleId' => $articleId,
|
|
'distributorId' => $distributorId,
|
|
'purchasePrice' => $purchasePrice,
|
|
'externalArticleNumber' => $item['Lieferant/ Hersteller Artikelnr:'],]);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
protected function provArticleNumberImportAction() {
|
|
// if method is post and file is set read the csv and var dump json and die
|
|
// if method is get return basic html with a form to upload a file and a submit button
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_FILES['file'])) {
|
|
$file = fopen($_FILES['file']['tmp_name'], 'r');
|
|
$data = [];
|
|
// parse csv as object with first row as keys as header and use key => value
|
|
$firstRow = true;
|
|
while (($row = fgetcsv($file)) !== false) {
|
|
if ($firstRow) {
|
|
$header = $row;
|
|
$firstRow = false;
|
|
continue;
|
|
}
|
|
$data[] = array_combine($header, $row);
|
|
}
|
|
|
|
// loop through all the data and if PRODUKT 1.ZEILE is set and articleNumber is not set push the last 4 numbers of "EAN 13 Code" to articleNumber of the found article
|
|
|
|
foreach ($data as $item) {
|
|
if (isset($item['PRODUKT 1.ZEILE'])) {
|
|
$articles = WarehouseArticleModel::getAll(['title' => $item['PRODUKT 1.ZEILE']]);
|
|
if (!empty($articles)) {
|
|
$article = (array) WarehouseArticleModel::get($articles[0]->id);
|
|
|
|
$article['articleNumber'] = substr($item['EAN 13 Code'], -4);
|
|
WarehouseArticleModel::update($article);
|
|
}
|
|
}
|
|
}
|
|
|
|
fclose($file);
|
|
die(json_encode(['success' => true]));
|
|
}
|
|
|
|
$html = '<html><head></head><body><form method="post" enctype="multipart/form-data"><input type="file" name="file"><input type="submit"></form></body></html>';
|
|
echo $html;
|
|
die();
|
|
|
|
}
|
|
|
|
protected function prepareOrderAction() {
|
|
// inside post json it will look like
|
|
// [
|
|
// {
|
|
// "amount": "5",
|
|
// "itemId": 441,
|
|
// "title": "RT-FB-7590AX"
|
|
// },
|
|
// {
|
|
// "amount": "5",
|
|
// "itemId": 421,
|
|
// "title": "RT-FB-7590"
|
|
// }
|
|
//]
|
|
// get the json from the post request
|
|
// then create a array containing each order we need to make, so search through WarehouseArticleDistributorModel to get the distributorId and purchasePrice (use lowest purchasePrice)
|
|
// then get the WarehouseDistributorModel and then create a summary of the orders we need to make for each distributor
|
|
|
|
$postData = json_decode(file_get_contents('php://input'), true);
|
|
$orders = [];
|
|
foreach ($postData as $order) {
|
|
$articleDistributors = WarehouseArticleDistributorModel::getAll(['articleId' => $order['itemId']]);
|
|
$cheapestArticleDistributor = $articleDistributors[0];
|
|
foreach ($articleDistributors as $articleDistributor) {
|
|
if ($articleDistributor->purchasePrice < $cheapestArticleDistributor->purchasePrice) {
|
|
$cheapestArticleDistributor = $articleDistributor;
|
|
}
|
|
}
|
|
$distributor = WarehouseDistributorModel::get($cheapestArticleDistributor->distributorId);
|
|
|
|
if (!isset($orders[$distributor->id])) {
|
|
$orders[$distributor->id] = ['distributor' => array($distributor),
|
|
'orderAmount' => 0,
|
|
'orders' => []];
|
|
}
|
|
|
|
$orders[$distributor->id]['orders'][] = ['articleId' => $order['itemId'],
|
|
'amount' => $order['amount'],
|
|
'sum' => $order['amount'] * $cheapestArticleDistributor->purchasePrice,
|
|
'purchasePrice' => $cheapestArticleDistributor->purchasePrice,
|
|
'externalArticleNumber' => $cheapestArticleDistributor->externalArticleNumber,
|
|
'title' => $order['title'],];
|
|
$orders[$distributor->id]['orderAmount'] += $order['amount'] * $cheapestArticleDistributor->purchasePrice;
|
|
}
|
|
|
|
self::returnJson($orders);
|
|
}
|
|
} |