Files
thetool/application/WarehouseArticle/WarehouseArticleController.php
2025-02-04 19:06:08 +01:00

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);
}
}