Matrixproducts first beta

This commit is contained in:
Andreas Palm 2023-03-30 10:54:18 +02:00
parent 042a64589b
commit a0d4cd1ff3
46 changed files with 18251 additions and 12998 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ conf/user.inc.php
conf/user_defined.php
userdata
www/cache/
node_modules/

View File

@ -1,4 +1,10 @@
<?php
/*
* SPDX-FileCopyrightText: 2019 Xentral ERP Software GmbH, Fuggerstrasse 11, D-86150 Augsburg
* SPDX-FileCopyrightText: 2023 Andreas Palm
*
* SPDX-License-Identifier: LicenseRef-EGPL-3.1
*/
namespace Xentral\Components\Http;
@ -9,8 +15,8 @@ class JsonResponse extends Response
{
/**
* @param array|JsonSerializable $data
* @param int $statusCode
* @param array $headers
* @param int $statusCode
* @param array $headers
*/
public function __construct($data = [], $statusCode = self::HTTP_OK, array $headers = [])
{
@ -29,4 +35,19 @@ class JsonResponse extends Response
parent::__construct($content, $statusCode, $headers);
}
public static function NoContent(array $headers = []): JsonResponse
{
return new JsonResponse([], Response::HTTP_NO_CONTENT, $headers);
}
public static function BadRequest(array|JsonSerializable $data = [], array $headers = []): JsonResponse
{
return new JsonResponse($data, Response::HTTP_BAD_REQUEST, $headers);
}
public static function NotFound(array|JsonSerializable $data = [], array $headers = []): JsonResponse
{
return new JsonResponse($data, Response::HTTP_NOT_FOUND, $headers);
}
}

View File

@ -1,4 +1,9 @@
<?php
/*
* SPDX-FileCopyrightText: 2023 Andreas Palm
* SPDX-FileCopyrightText: 2019 Xentral ERP Software GmbH, Fuggerstrasse 11, D-86150 Augsburg
* SPDX-License-Identifier: LicenseRef-EGPL-3.1
*/
namespace Xentral\Components\Http;
@ -488,6 +493,16 @@ class Request
return !empty($this->content) ? $this->content : '';
}
/**
* Return a JSON request body as php object
*
* @return object
*/
public function getJson() : object
{
return json_decode($this->getContent());
}
/**
* Returns the content type of the request.
*

View File

@ -1,4 +1,9 @@
<?php
/*
* SPDX-FileCopyrightText: 2023 Andreas Palm
* SPDX-FileCopyrightText: 2019 Xentral (c) Xentral ERP Software GmbH, Fuggerstrasse 11, D-86150 Augsburg, Germany
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace Xentral\Modules\Article\Gateway;
@ -346,4 +351,13 @@ final class ArticleGateway
);
}
}
public function SetVariantStatus(int $articleId, ?int $variantOfId) : void {
$sql = "UPDATE artikel SET variante = :isVariant, variante_von = :variantOfId WHERE id = :articleId";
$this->db->perform($sql, [
'articleId' => $articleId,
'variantOfId' => $variantOfId > 0 ? $variantOfId : null,
'isVariant' => $variantOfId > 0
]);
}
}

View File

@ -0,0 +1,47 @@
<?php
/*
* SPDX-FileCopyrightText: 2023 Andreas Palm
* SPDX-License-Identifier: LicenseRef-EGPL-3.1
*/
namespace Xentral\Modules\MatrixProduct;
use Xentral\Core\DependencyInjection\ContainerInterface;
final class Bootstrap
{
/**
* @return array
*/
public static function registerServices()
{
return [
'MatrixProductService' => 'onInitMatrixProductService',
'MatrixProductGateway' => 'onInitMatrixProductGateway',
];
}
/**
* @param ContainerInterface $container
*
* @return MatrixProductService
*/
public static function onInitMatrixProductService(ContainerInterface $container) : MatrixProductService
{
return new MatrixProductService(
$container->get('Database'),
$container->get('MatrixProductGateway'),
$container->get('ArticleGateway')
);
}
/**
* @param ContainerInterface $container
*
* @return MatrixProductGateway
*/
public static function onInitMatrixProductGateway(ContainerInterface $container) : MatrixProductGateway
{
return new MatrixProductGateway($container->get('Database'));
}
}

View File

@ -0,0 +1,34 @@
<?php
/*
* SPDX-FileCopyrightText: 2023 Andreas Palm
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace Xentral\Modules\MatrixProduct\Data;
use JsonSerializable;
final class Group implements JsonSerializable
{
public function __construct(
public string $name,
public ?int $id = null,
public bool $active = true,
public ?string $nameExternal = null,
public int $projectId = 0,
public bool $required = false,
public ?int $articleId = null,
public int $sort = 0
)
{ }
public static function fromDbArray(array $data) : self {
return new self($data['name'], $data['id'], $data['aktiv'], $data['name_ext'], $data['projekt'], $data['pflicht'],
$data['artikel'] ?? null, $data['sort'] ?? 0);
}
public function jsonSerialize(): array
{
return (array) $this;
}
}

View File

@ -0,0 +1,38 @@
<?php
/*
* SPDX-FileCopyrightText: 2023 Andreas Palm
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace Xentral\Modules\MatrixProduct\Data;
use JsonSerializable;
final class Option implements JsonSerializable
{
public function __construct(
public string $name,
public readonly int $groupId,
public ?int $id = null,
public bool $active = true,
public string $nameExternal = '',
public int $sort = 0,
public string $articleNumber = '',
public string $articleNumberSuffix = '',
public readonly ?int $globalOptionId = null,
public readonly ?int $articleId = null
)
{
}
public static function fromDbArray(array $data) : Option {
return new self($data['name'], $data['gruppe'], $data['id'], $data['aktiv'], $data['name_ext'], $data['sort'],
$data['artikelnummer'], $data['articlenumber_suffix'], $data['matrixprodukt_eigenschaftenoptionen'] ?? null,
$data['artikel'] ?? null);
}
public function jsonSerialize(): array
{
return (array) $this;
}
}

View File

@ -0,0 +1,278 @@
<?php
/*
* SPDX-FileCopyrightText: 2023 Andreas Palm
* SPDX-License-Identifier: LicenseRef-EGPL-3.1
*/
namespace Xentral\Modules\MatrixProduct;
use Xentral\Components\Database\Database;
use Xentral\Modules\MatrixProduct\Data\Group;
use Xentral\Modules\MatrixProduct\Data\Option;
final class MatrixProductGateway
{
/**
* @param Database $db
*/
public function __construct(private readonly Database $db)
{
}
//region Groups
public function GetGlobalGroupById(int $id) : Group {
$sql = "SELECT * FROM matrixprodukt_eigenschaftengruppen WHERE id = :id";
$res = $this->db->fetchRow($sql, ['id' => $id]);
return Group::fromDbArray($res);
}
public function GetArticleGroupById(int $id) : Group {
$sql = "SELECT * FROM matrixprodukt_eigenschaftengruppen_artikel WHERE id = :id";
$res = $this->db->fetchRow($sql, ['id' => $id]);
return Group::fromDbArray($res);
}
public function GetArticleGroupIdByName(int $articleId, string $name) : int {
$sql = "SELECT id FROM matrixprodukt_eigenschaftengruppen_artikel
WHERE name = :name AND artikel = :articleId";
return $this->db->fetchValue($sql, [
'name' => $name,
'articleId' => $articleId
]);
}
public function GetArticleGroupsByArticleId(int $articleId) : array {
$sql = "SELECT * FROM matrixprodukt_eigenschaftengruppen_artikel WHERE artikel = :articleId";
$rows = $this->db->fetchAssoc($sql, ['articleId' => $articleId]);
$res = [];
foreach ($rows as $row) {
$res[] = Group::fromDbArray($row);
}
return $res;
}
public function InsertGlobalGroup(Group $obj) : Group {
$sql = "INSERT INTO matrixprodukt_eigenschaftengruppen
(aktiv, name, name_ext, projekt, pflicht)
VALUES
(:active, :name, :nameExternal, :projectId, :required)";
print_r($obj);
$this->db->perform($sql, (array)$obj);
$obj->id = $this->db->lastInsertId();
return $obj;
}
public function InsertArticleGroup(Group $obj) : Group {
$sql = "INSERT INTO matrixprodukt_eigenschaftengruppen_artikel
(artikel, aktiv, name, name_ext, projekt, sort, pflicht)
VALUES
(:articleId, :active, :name, :nameExternal, :projectId, :sort, :required)";
$this->db->perform($sql, (array)$obj);
$obj->id = $this->db->lastInsertId();
return $obj;
}
public function UpdateGlobalGroup(Group $obj) : Group {
$sql = "UPDATE matrixprodukt_eigenschaftengruppen
SET aktiv = :active, name = :name, name_ext = :nameExternal, projekt = :projectId, pflicht = :required
WHERE id = :id";
$this->db->perform($sql, (array)$obj);
return $obj;
}
public function UpdateArticleGroup(Group $obj) : Group {
$sql = "UPDATE matrixprodukt_eigenschaftengruppen_artikel
SET aktiv = :active, name = :name, name_ext = :nameExternal, projekt = :projectId, sort = :sort, pflicht = :required
WHERE id = :id";
$this->db->perform($sql, (array)$obj);
return $obj;
}
public function DeleteGlobalGroup(int $id) : void {
$sql = "UPDATE matrixprodukt_eigenschaftenoptionen_artikel moa
JOIN matrixprodukt_eigenschaftenoptionen mo ON moa.matrixprodukt_eigenschaftenoptionen=mo.id
JOIN matrixprodukt_eigenschaftengruppen mg ON mo.gruppe=mg.id
SET moa.matrixprodukt_eigenschaftenoptionen=0
WHERE mg.id = :id";
$this->db->perform($sql, ['id' => $id]);
$sql = "DELETE mg, mo
FROM matrixprodukt_eigenschaftengruppen mg
LEFT OUTER JOIN matrixprodukt_eigenschaftenoptionen mo ON mo.gruppe = mg.id
WHERE mg.id = :id";
$this->db->perform($sql, ['id' => $id]);
}
public function DeleteArticleGroup(int $id) : void {
$sql = "DELETE mga, moa, mota
FROM matrixprodukt_eigenschaftengruppen_artikel mga
LEFT OUTER JOIN matrixprodukt_eigenschaftenoptionen_artikel moa ON moa.gruppe = mga.id
LEFT OUTER JOIN matrixprodukt_optionen_zu_artikel mota ON mota.option_id = moa.id
WHERE mga.id = :id";
$this->db->perform($sql, ['id' => $id]);
}
//endregion
//region Options
public function GetGlobalOptionById(int $id) : Option {
$sql = "SELECT * FROM matrixprodukt_eigenschaftenoptionen WHERE id = :id";
$res = $this->db->fetchRow($sql, ['id' => $id]);
return Option::fromDbArray($res);
}
public function GetArticleOptionById(int $id) : Option {
$sql = "SELECT * FROM matrixprodukt_eigenschaftenoptionen_artikel WHERE id = :id";
$res = $this->db->fetchRow($sql, ['id' => $id]);
return Option::fromDbArray($res);
}
public function GetArticleOptionIdsByGroupIds(int|array $groupIds) : array {
$sql = "SELECT id FROM matrixprodukt_eigenschaftenoptionen WHERE gruppe IN (:ids)";
return $this->db->fetchCol($sql, ['ids' => $groupIds]);
}
public function GetArticleOptionIdByName(int $articleId, int $groupId, string $name) : int {
$sql = "SELECT id FROM matrixprodukt_eigenschaftenoptionen_artikel
WHERE name = :name AND artikel = :articleId AND gruppe = :groupId";
return $this->db->fetchValue($sql, [
'name' => $name,
'articleId' => $articleId,
'groupId' => $groupId
]);
}
public function GetArticleOptionsByArticleId(int $articleId) : array {
$sql = "SELECT * FROM matrixprodukt_eigenschaftenoptionen_artikel WHERE artikel = :articleId";
$rows = $this->db->fetchAssoc($sql, ['articleId' => $articleId]);
$res = [];
foreach ($rows as $row) {
$res[] = Option::fromDbArray($row);
}
return $res;
}
public function GetSelectedOptionIdsByVariantId(int $variantId) : array {
$sql = "SELECT option_id FROM matrixprodukt_optionen_zu_artikel WHERE artikel = :variantId";
return $this->db->fetchCol($sql, ['variantId' => $variantId]);
}
public function InsertGlobalOption(Option $obj) : Option {
$sql = "INSERT INTO matrixprodukt_eigenschaftenoptionen
(aktiv, name, name_ext, sort, gruppe, artikelnummer, articlenumber_suffix)
VALUES
(:active, :name, :nameExternal, :sort, :groupId, :articleNumber, :articleNumberSuffix)";
$this->db->perform($sql, (array)$obj);
$obj->id = $this->db->lastInsertId();
return $obj;
}
public function InsertArticleOption(Option $obj) : Option {
$sql = "INSERT INTO matrixprodukt_eigenschaftenoptionen_artikel
(artikel, matrixprodukt_eigenschaftenoptionen, aktiv, name, name_ext, sort, gruppe, artikelnummer, articlenumber_suffix)
VALUES
(:articleId, :globalOptionId, :active, :name, :nameExternal, :sort, :groupId, :articleNumber, :articleNumberSuffix)";
$this->db->perform($sql, (array)$obj);
$obj->id = $this->db->lastInsertId();
return $obj;
}
public function UpdateGlobalOption(Option $obj) : Option {
$sql = "UPDATE matrixprodukt_eigenschaftenoptionen
SET aktiv = :active, name = :name, name_ext = :nameExternal, sort = :sort, artikelnummer = :articleNumber,
articlenumber_suffix = :articleNumberSuffix
WHERE id = :id";
$this->db->perform($sql, (array)$obj);
return $obj;
}
public function UpdateArticleOption(Option $obj) : Option {
$sql = "UPDATE matrixprodukt_eigenschaftenoptionen_artikel
SET aktiv = :active, name = :name, name_ext = :nameExternal, sort = :sort, artikelnummer = :articleNumber,
articlenumber_suffix = :articleNumberSuffix
WHERE id = :id";
$this->db->perform($sql, (array)$obj);
return $obj;
}
public function DeleteGlobalOption(int $id) : void {
$sql = "UPDATE matrixprodukt_eigenschaftenoptionen_artikel moa
JOIN matrixprodukt_eigenschaftenoptionen mo ON moa.matrixprodukt_eigenschaftenoptionen=mo.id
SET moa.matrixprodukt_eigenschaftenoptionen=0
WHERE mo.id = :id";
$this->db->perform($sql, ['id' => $id]);
$sql = "DELETE moa
FROM matrixprodukt_eigenschaftenoptionen mo
WHERE mo.id = :id";
$this->db->perform($sql, ['id' => $id]);
}
public function DeleteArticleOption(int $id) : void {
$sql = "DELETE moa, mota
FROM matrixprodukt_eigenschaftenoptionen_artikel moa
LEFT OUTER JOIN matrixprodukt_optionen_zu_artikel mota ON mota.option_id=moa.id
WHERE moa.id = :id";
$this->db->perform($sql, ['id' => $id]);
}
//endregion
//region Variants
public function ReplaceVariant(int $oldId, int $newId) : void {
$sql = "UPDATE matrixprodukt_optionen_zu_artikel SET artikel = :newId WHERE artikel = :oldId";
$this->db->perform($sql, [
'oldId' => $oldId,
'newId' => $newId]
);
}
/**
* @param int $variantId
* @return int[]
*/
public function GetOptionIdsByVariant(int $variantId) : array {
$sql = "SELECT option_id FROM matrixprodukt_optionen_zu_artikel WHERE artikel = :id";
return $this->db->fetchCol($sql, ['id' => $variantId]);
}
public function AddOptionToVariant(int $variantId, int $optionId) : void {
$sql = "INSERT INTO matrixprodukt_optionen_zu_artikel (artikel, option_id) VALUES (:variantId, :optionId)";
$this->db->perform($sql, [
'variantId' => $variantId,
'optionId' => $optionId
]);
}
public function DeleteOptionFromVariant(int $variantId, int $optionId) : void {
$sql = "DELETE FROM matrixprodukt_optionen_zu_artikel WHERE artikel = :variantId AND option_id = :optionId";
$this->db->perform($sql, [
'variantId' => $variantId,
'optionId' => $optionId
]);
}
public function GetVariantIdByOptions(array $optionIds) : ?int {
if (empty($optionIds))
return null;
sort($optionIds);
$sql = "SELECT artikel
FROM matrixprodukt_optionen_zu_artikel
WHERE option_id IN (:ids)
GROUP BY artikel
HAVING group_concat(option_id order by option_id separator ',') = :idList";
$res = $this->db->fetchValue($sql, [
'ids' => $optionIds,
'idList' => join(',', $optionIds)
]);
return $res ?: null;
}
public function GetVariantIdsByOptions(int|array $optionIds) : array
{
$sql = "SELECT artikel FROM matrixprodukt_optionen_zu_artikel WHERE option_id IN (:ids)";
return $this->db->fetchCol($sql, ['ids' => $optionIds]);
}
public function DeleteVariantById(int $variantId) : void {
$sql = "DELETE FROM matrixprodukt_optionen_zu_artikel WHERE artikel = :id";
$this->db->perform($sql, ['id' => $variantId]);
}
//endregion
}

View File

@ -0,0 +1,170 @@
<?php
/*
* SPDX-FileCopyrightText: 2023 Andreas Palm
* SPDX-License-Identifier: LicenseRef-EGPL-3.1
*/
namespace Xentral\Modules\MatrixProduct;
use Xentral\Components\Database\Database;
use Xentral\Modules\Article\Gateway\ArticleGateway;
use Xentral\Modules\MatrixProduct\Data\Option;
use Xentral\Modules\MatrixProduct\Data\Group;
final class MatrixProductService
{
/**
* @param Database $db
* @param MatrixProductGateway $gateway
* @param ArticleGateway $articleGateway
*/
public function __construct(
private readonly Database $db,
private readonly MatrixProductGateway $gateway,
private readonly ArticleGateway $articleGateway)
{ }
//region Groups
public function GetGlobalGroupById(int $id) : Group {
return $this->gateway->GetGlobalGroupById($id);
}
public function GetArticleGroupById(int $id) : Group {
return $this->gateway->GetArticleGroupById($id);
}
/**
* @param int $articleId
* @return Group[]
*/
public function GetArticleGroupsByArticleId(int $articleId) : array {
return $this->gateway->GetArticleGroupsByArticleId($articleId);
}
public function SaveGlobalGroup(Group $obj) : void {
if ($obj->id > 0)
$this->gateway->UpdateGlobalGroup($obj);
else
$this->gateway->InsertGlobalGroup($obj);
}
public function SaveArticleGroup(Group $obj) : void {
if ($obj->id > 0) {
$this->gateway->UpdateArticleGroup($obj);
} else {
$this->gateway->InsertArticleGroup($obj);
}
}
public function DeleteGlobalGroup(int $id) : void {
$this->gateway->DeleteGlobalGroup($id);
}
public function DeleteArticleGroup(int $id) : bool {
$options = $this->gateway->GetArticleOptionIdsByGroupIds($id);
$variants = $this->gateway->GetVariantIdsByOptions($options);
if (!empty($variants))
return false;
$this->gateway->DeleteArticleGroup($id);
return true;
}
//endregion
//region Options
public function GetGlobalOptionById(int $id) : Option {
return $this->gateway->GetGlobalOptionById($id);
}
public function GetArticleOptionById(int $id) : Option {
return $this->gateway->GetArticleOptionById($id);
}
/**
* @param int $articleId
* @return Option[]
*/
public function GetArticleOptionsByArticleId(int $articleId) : array {
return $this->gateway->GetArticleOptionsByArticleId($articleId);
}
public function GetSelectedOptionIdsByVariantId(int $variantId) : array {
return $this->gateway->GetSelectedOptionIdsByVariantId($variantId);
}
public function SaveGlobalOption(Option $obj) : void {
if ($obj->id > 0) {
$this->gateway->UpdateGlobalOption($obj);
} else {
$this->gateway->InsertGlobalOption($obj);
}
}
public function SaveArticleOption(Option $obj) : void {
if ($obj->id > 0) {
$this->gateway->UpdateArticleOption($obj);
} else {
$this->gateway->InsertArticleOption($obj);
}
}
public function DeleteGlobalOption(int $id) : void {
$this->gateway->DeleteGlobalOption($id);
}
public function DeleteArticleOption(int $id) : bool {
$variants = $this->gateway->GetVariantIdsByOptions($id);
if (!empty($variants))
return false;
$this->gateway->DeleteArticleOption($id);
return true;
}
public function AddGlobalOptionsForArticle(int $articleId, int|array $optionIds): void
{
$sql = "SELECT mg.name groupname, mg.name_ext groupnameext, mg.projekt as groupprojekt, mg.pflicht as grouprequired, mo.*
FROM matrixprodukt_eigenschaftenoptionen mo
JOIN matrixprodukt_eigenschaftengruppen mg on mo.gruppe=mg.id
WHERE mo.id IN (:optionIds)";
$optionArr = $this->db->fetchAll($sql, ['optionIds' => $optionIds]);
foreach ($optionArr as $option) {
$groupId = $this->gateway->GetArticleGroupIdByName($articleId, $option['groupname']);
if (!$groupId) {
$obj = new Group($option['groupname'], nameExternal: $option['groupnameext'], projectId: $option['groupprojekt'], required: $option['grouprequired'], articleId: $articleId);
$group = $this->gateway->InsertArticleGroup($obj);
$groupId = $group->id;
}
$optionId = $this->gateway->GetArticleOptionIdByName($articleId, $groupId, $option['name']);
if (!$optionId) {
$obj = new Option($option['name'], $groupId, nameExternal: $option['name_ext'], sort: $option['sort'], globalOptionId: $option['id'], articleId: $articleId);
$this->gateway->InsertArticleOption($obj);
}
}
}
//endregion
//region Variants
public function SaveVariant(int $articleId, int $variantId, array $optionIds, ?int $oldVariantId = null) : bool|string {
if ($oldVariantId != null && $oldVariantId != $variantId) {
$this->gateway->ReplaceVariant($oldVariantId, $variantId);
$this->articleGateway->SetVariantStatus($oldVariantId, null);
}
$variantWithOptionSet = $this->gateway->GetVariantIdByOptions($optionIds);
if ($variantWithOptionSet != null && $variantWithOptionSet != $variantId)
return 'Diese Optionen wurden bereits einer anderen Variante zugewiesen';
$existingIds = $this->gateway->GetOptionIdsByVariant($variantId);
$toDelete = array_diff($existingIds, $optionIds);
$toCreate = array_diff($optionIds, $existingIds);
foreach ($toDelete as $item)
$this->gateway->DeleteOptionFromVariant($variantId, $item);
foreach ($toCreate as $item)
$this->gateway->AddOptionToVariant($variantId, $item);
$this->articleGateway->SetVariantStatus($variantId, $articleId);
return true;
}
public function DeleteVariant(int $variantId) : void {
$this->gateway->DeleteVariantById($variantId);
}
//endregion
}

View File

@ -0,0 +1,55 @@
<!--
SPDX-FileCopyrightText: 2023 Andreas Palm
SPDX-License-Identifier: LicenseRef-EGPL-3.1
-->
<script setup>
import {ref, onMounted} from "vue";
import axios from "axios";
import Dialog from "primevue/dialog";
import Listbox from "primevue/listbox";
import Button from "primevue/button";
import {AlertErrorHandler} from '@res/js/ajaxErrorHandler';
const props = defineProps({
articleId: String
})
const emit = defineEmits(['save', 'close']);
const model = ref(null);
const group = ref(null);
const selected = ref([]);
onMounted(async () => {
model.value = await fetch('index.php?module=matrixprodukt&action=list&cmd=selectoptions')
.then(x => x.json())
})
async function save() {
await axios.post('index.php?module=matrixprodukt&action=artikel&cmd=addoptions', {
articleId: props.articleId,
optionIds: selected.value
})
.then(() => {emit('save')})
.catch(AlertErrorHandler);
}
</script>
<template>
<Dialog visible modal header="Globale Optionen hinzufügen" style="width: 500px" @update:visible="emit('close')">
<div v-if="model" class="grid gap-1" style="grid-template-columns: 25% 75%">
<label for="matrixProductOptions" style="padding-top: 5px;">Optionen:</label>
<Listbox multiple
:options="model"
optionGroupLabel="name"
optionGroupChildren="options"
optionLabel="name"
optionValue="id"
listStyle="height: 200px"
v-model="selected" />
</div>
<template #footer>
<Button label="ABBRECHEN" @click="emit('close')" />
<Button label="HINZUFÜGEN" @click="save" :disabled="selected.length === 0"/>
</template>
</Dialog>
</template>

View File

@ -0,0 +1,70 @@
<!--
SPDX-FileCopyrightText: 2023 Andreas Palm
SPDX-License-Identifier: LicenseRef-EGPL-3.1
-->
<script setup>
import axios from "axios";
import {ref} from 'vue';
import {reloadDataTables} from "@res/js/jqueryBridge";
import AddGlobalToArticle from "./AddGlobalToArticle.vue";
import GroupEdit from "./GroupEdit.vue";
import OptionEdit from "./OptionEdit.vue";
import Variant from "./Variant.vue";
const model = ref(null);
document.getElementById('main').addEventListener('click', async (ev) => {
const target = ev.target;
if (!target || !target.classList.contains('vueAction'))
return;
const ds = target.dataset;
if (ds.action.endsWith('Delete')) {
const cnf = confirm('Wirklich löschen?');
if (!cnf)
return;
let url;
switch (ds.action) {
case 'groupDelete':
url = ds.articleId > 0
? 'index.php?module=matrixprodukt&action=artikel&cmd=groupdelete'
: 'index.php?module=matrixprodukt&action=list&cmd=delete';
await axios.post(url, {groupId: ds.groupId});
break;
case 'optionDelete':
url = ds.articleId > 0
? 'index.php?module=matrixprodukt&action=artikel&cmd=optiondelete'
: 'index.php?module=matrixprodukt&action=optionenlist&cmd=delete';
await axios.post(url, {optionId: ds.optionId});
break;
case 'variantDelete':
url = 'index.php?module=matrixprodukt&action=artikel&cmd=variantdelete';
await axios.post(url, {variantId: ds.variantId});
break;
}
onSave();
return;
}
model.value = ds;
});
function onSave() {
reloadDataTables();
onClose();
}
function onClose() {
model.value = null;
}
</script>
<template>
<template v-if="model">
<AddGlobalToArticle v-if="model.action === 'addGlobalToArticle'" v-bind="model" @close="onClose" @save="onSave" />
<GroupEdit v-else-if="model.action === 'groupEdit'" v-bind="model" @close="onClose" @save="onSave" />
<OptionEdit v-else-if="model.action === 'optionEdit'" v-bind="model" @close="onClose" @save="onSave" />
<Variant v-else-if="model.action === 'variantEdit'" v-bind="model" @close="onClose" @save="onSave" />
</template>
</template>

View File

@ -0,0 +1,72 @@
<!--
SPDX-FileCopyrightText: 2023 Andreas Palm
SPDX-License-Identifier: LicenseRef-EGPL-3.1
-->
<script setup>
import {ref, onMounted} from "vue";
import axios from "axios";
import Dialog from "primevue/dialog";
import Button from "primevue/button";
import {AlertErrorHandler} from "@res/js/ajaxErrorHandler";
import AutoComplete from "@res/vue/AutoComplete.vue";
const props = defineProps({
groupId: String,
articleId: String
});
const emit = defineEmits(['save', 'close']);
const model = ref({});
onMounted(async () => {
if (props.groupId > 0) {
const url = props.articleId > 0
? 'index.php?module=matrixprodukt&action=artikel&cmd=groupedit'
: 'index.php?module=matrixprodukt&action=list&cmd=edit';
model.value = await axios.get(url, {
params: props
}).then(response => response.data)
}
})
async function save() {
if (!parseInt(props.groupId) > 0)
model.value.groupId = 0;
const url = props.articleId > 0
? 'index.php?module=matrixprodukt&action=artikel&cmd=groupsave'
: 'index.php?module=matrixprodukt&action=list&cmd=save';
await axios.post(url, {...props, ...model.value})
.catch(AlertErrorHandler)
.then(() => {emit('save')});
}
</script>
<template>
<Dialog visible modal header="Gruppe anlegen/bearbeiten" style="width: 500px" @update:visible="emit('close')" class="p-fluid">
<div class="grid gap-1" style="grid-template-columns: 25% 75%">
<label for="matrixProduct_group_name">Name:</label>
<input type="text" v-model="model.name" required />
<label for="matrixProduct_group_nameExternal">Name Extern:</label>
<input type="text" v-model="model.nameExternal" />
<label for="matrixProduct_group_project">Projekt:</label>
<AutoComplete
v-model="model.project"
:optionLabel="item => [item.abkuerzung, item.name].join(' ')"
ajaxFilter="projektname"
forceSelection
/>
<label v-if="articleId" for="matrixProduct_group_sort">Sortierung:</label>
<input v-if="articleId" type="text" v-model="model.sort">
<label for="matrixProduct_group_required">Pflicht:</label>
<input type="checkbox" v-model="model.required" class="justify-self-start">
<label for="matrixProduct_group_active">Aktiv:</label>
<input type="checkbox" v-model="model.active" class="justify-self-start">
</div>
<template #footer>
<Button label="ABBRECHEN" @click="emit('close')" />
<Button label="SPEICHERN" @click="save" :disabled="!model.name"/>
</template>
</Dialog>
</template>

View File

@ -0,0 +1,63 @@
<!--
SPDX-FileCopyrightText: 2023 Andreas Palm
SPDX-License-Identifier: LicenseRef-EGPL-3.1
-->
<script setup>
import {ref, onMounted} from "vue";
import axios from "axios";
import Button from "primevue/button";
import Dialog from "primevue/dialog";
import {AlertErrorHandler} from "@res/js/ajaxErrorHandler";
const props = defineProps({
optionId: String,
groupId: String,
articleId: String
});
const emit = defineEmits(['save', 'close']);
const model = ref({});
onMounted(async () => {
if (props.optionId > 0) {
const url = props.articleId > 0
? 'index.php?module=matrixprodukt&action=artikel&cmd=optionedit'
: 'index.php?module=matrixprodukt&action=optionenlist&cmd=edit';
model.value = await axios.get(url, {
params: props
}).then(response => response.data)
}
})
async function save() {
const url = props.articleId > 0
? 'index.php?module=matrixprodukt&action=artikel&cmd=optionsave'
: 'index.php?module=matrixprodukt&action=optionenlist&cmd=save';
await axios.post(url, {...props, ...model.value})
.then(() => {emit('save')})
.catch(AlertErrorHandler);
}
</script>
<template>
<Dialog visible modal header="Option anlegen/bearbeiten" style="width: 500px" @update:visible="emit('close')">
<div class="grid gap-1" style="grid-template-columns: 25% 75%">
<label for="matrixProduct_option_name">Name:</label>
<input id="matrixProduct_option_name" type="text" v-model="model.name" required />
<label for="matrixProduct_option_nameExternal">Name Extern:</label>
<input id="matrixProduct_option_nameExternal" type="text" v-model="model.nameExternal" />
<label for="matrixProduct_option_articleNumberSuffix">Artikelnummer-Suffix:</label>
<input id="matrixProduct_option_articleNumberSuffix" type="text" v-model="model.articleNumberSuffix" />
<label for="matrixProduct_option_sort">Sortierung:</label>
<input id="matrixProduct_option_sort" type="text" v-model="model.sort" />
<label for="matrixProduct_option_active">Aktiv:</label>
<input id="matrixProduct_option_active" type="checkbox" v-model="model.active" class="justify-self-start" />
</div>
<template #footer>
<Button label="ABBRECHEN" @click="emit('close')" />
<Button label="SPEICHERN" @click="save" :disabled="!model.name" />
</template>
</Dialog>
</template>

View File

@ -0,0 +1,54 @@
<script setup>
import AutoComplete from "@res/vue/AutoComplete.vue";
import Button from "primevue/button";
import Dialog from "primevue/dialog";
import Dropdown from "primevue/dropdown";
import {onMounted, ref} from "vue";
import axios from "axios";
const props = defineProps({
articleId: String,
variantId: String,
});
const emit = defineEmits(['save', 'close']);
const model = ref({});
onMounted(async () => {
model.value = await axios.get('index.php?module=matrixprodukt&action=artikel&cmd=variantedit', {
params: {...props}
}).then(response => { return {...props, ...response.data}})
})
async function save() {
await axios.post('index.php?module=matrixprodukt&action=artikel&cmd=variantsave', {...props, ...model.value})
.catch(error => alert(error.response.data))
.then(() => {emit('save')});
}
const buttons = {
abbrechen: () => emit('close'),
speichern: save
}
</script>
<template>
<Dialog visible modal header="Variante" style="width: 500px" @update:visible="emit('close')">
<div class="grid gap-1" style="grid-template-columns: 25% 75%;">
<label>Artikel</label>
<AutoComplete v-model="model.variant"
:optionLabel="(item) => [item.nummer, item.name].join(' ')"
ajax-filter="artikelnummer"
forceSelection
/>
<template v-for="group in model.groups">
<label>{{ group.name }}</label>
<Dropdown v-model="group.selected" :options="group.options" optionLabel="name" optionValue="value" />
</template>
</div>
<template #footer>
<Button label="ABBRECHEN" @click="emit('close')" />
<Button label="SPEICHERN" @click="save" />
</template>
</Dialog>
</template>

View File

@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: 2023 Andreas Palm
//
// SPDX-License-Identifier: LicenseRef-EGPL-3.1
import App from "./App.vue";
import {createVueApp} from "@res/js/vue";
const app = createVueApp(App).mount('#vueapp')

View File

@ -1,86 +0,0 @@
$(document).ready(function() {
$('a.groupheadline').on('click',function(){
if(parseInt($(this).data('id')) > 0) {
$('#GroupheadlineDialogGroupId').val($(this).data('id'));
$('#GroupheadlineDialogArticleId').val($(this).data('article'));
$('#GroupheadlineDialogGroupName').val($(this).data('name'));
$('#GroupheadlineDialog').dialog('open');
}
});
});
$('#GroupheadlineDialog').dialog(
{
modal: true,
autoOpen: false,
minWidth: 940,
title:'',
buttons: {
'ABBRECHEN': function() {
$(this).dialog('close');
},
'ÄNDERN': function()
{
if($('#GroupheadlineDialogGroupName').val()+'' !== '') {
$.ajax({
url: 'index.php?module=matrixprodukt&action=artikel&cmd=changegroupname',
type: 'POST',
dataType: 'json',
data: {
groupId: $('#GroupheadlineDialogGroupId').val(),
groupName: $('#GroupheadlineDialogGroupName').val()
},
success: function (data) {
if(typeof data.status != 'undefined' && data.status == 1) {
window.location.href = window.location.href.split('#')[0];
}
},
beforeSend: function () {
}
});
}else{
alert('Bitte eine Bezeichnung angeben');
}
},
'LÖSCHEN': function() {
$.ajax({
url: 'index.php?module=matrixprodukt&action=artikel&cmd=deletegroup',
type: 'POST',
dataType: 'json',
data: {
groupId: $('#GroupheadlineDialogGroupId').val()
},
success: function(data) {
if(typeof data.status != 'undefined' && data.status == 1) {
if (typeof data.confirm != 'undefined' && data.confirm == 1) {
if(confirm('Die Gruppe enthält Option, sollen die Gruppe mit diesen Optionen wirklich gelöscht werden?'))
{
$.ajax({
url: 'index.php?module=matrixprodukt&action=artikel&cmd=deletegroup',
type: 'POST',
dataType: 'json',
data: {
force:1,groupId: $('#GroupheadlineDialogGroupId').val()
},success: function(data) {
window.location.href = window.location.href.split('#')[0];
}
});
}
} else {
window.location.href = window.location.href.split('#')[0];
}
}
},
beforeSend: function() {
}
});
}
},
close: function(event, ui){
}
});

View File

@ -1,513 +0,0 @@
var MatrixproductListview = function ($) {
"use strict";
var me = {
storage:{
selectedArticles: '',
optionTableloaded: false,
groupTableloaded: false,
articleTableloaded: false,
},
selector: {
divlistview: 'div.listview',
popupGroup: '#popupgroup',
popupOption: '#popupoption',
popupArticleCreate: '#popuparcticlecreate',
popupArticle: '#popuparticle',
btnNewGroup: '#newGroup',
btnNewOption: '#newOption',
articleTable: '#matrixprodukt_list_view',
groupTable: '#matrixprodukt_list_view_group',
optionTable: '#matrixprodukt_list_view_options',
},
editArticle: function(id) {
if(id > 0) {
$.ajax({
type: 'POST',
dataType: 'json',
url: 'index.php?module=matrixprodukt&action=artikel&cmd=listgetarticle',
data: {
articleid: $(me.selector.popupGroup).data('articleid'),
listid: id
},
success: function (data) {
$('div#options').html(data.optionhtml);
$('#articlelistid').val(data.id);
$(me.selector.popupArticle).dialog('open');
}
});
}
},
editGroup: function(id){
$('#groupid').val(id);
if(id > 0) {
$.ajax({
type: 'POST',
dataType: 'json',
url: 'index.php?module=matrixprodukt&action=artikel&cmd=listgetgroup',
data: {
articleid: $(me.selector.popupGroup).data('articleid'),
groupid: $('#groupid').val()
},
success: function (data) {
$('#groupname').val(data.name);
$(me.selector.popupGroup).dialog('open');
}
});
return;
}
$(me.selector.popupGroup).dialog('open');
},
deleteGroup: function(id) {
$.ajax({
type: 'POST',
dataType: 'json',
url: 'index.php?module=matrixprodukt&action=artikel&cmd=listdeletegroupcheck',
data: {
articleid: $(me.selector.popupGroup).data('articleid'),
groupid: id
},
success: function (data) {
if(typeof data.message) {
if(!confirm(data.message)) {
return;
}
$.ajax({
type: 'POST',
dataType: 'json',
url: 'index.php?module=matrixprodukt&action=artikel&cmd=listdeletegroup',
data: {
articleid: $(me.selector.popupGroup).data('articleid'),
groupid: data.groupid
},
success: function (data) {
if(typeof data.url !== 'undefined') {
window.location.href=data.url;
return;
}
$(me.selector.optionTable).DataTable().ajax.reload();
$(me.selector.groupTable).DataTable().ajax.reload();
$(me.selector.articleTable).DataTable().ajax.reload();
},
error: function()
{
$(me.selector.optionTable).DataTable().ajax.reload();
$(me.selector.groupTable).DataTable().ajax.reload();
$(me.selector.articleTable).DataTable().ajax.reload();
}
});
}
}
});
},
deleteOption: function(id) {
$.ajax({
type: 'POST',
dataType: 'json',
url: 'index.php?module=matrixprodukt&action=artikel&cmd=listdeleteoptioncheck',
data: {
articleid: $(me.selector.popupGroup).data('articleid'),
optionid: id
},
success: function (data) {
if(typeof data.message) {
if(!confirm(data.message)) {
return;
}
$.ajax({
type: 'POST',
dataType: 'json',
url: 'index.php?module=matrixprodukt&action=artikel&cmd=listdeleteoption',
data: {
articleid: $(me.selector.popupGroup).data('articleid'),
optionid: data.optionid
},
success: function () {
$(me.selector.optionTable).DataTable().ajax.reload();
$(me.selector.articleTable).DataTable().ajax.reload();
},
error: function() {
$(me.selector.optionTable).DataTable().ajax.reload();
$(me.selector.articleTable).DataTable().ajax.reload();
}
});
}
}
});
},
generateList: function()
{
$('#tabs-1').loadingOverlay('show');
$.ajax({
type: 'POST',
dataType: 'json',
url: 'index.php?module=matrixprodukt&action=artikel&cmd=generatelist',
data: {
articleid: $(me.selector.popupGroup).data('articleid')
},
success: function (data) {
if(typeof data.url != 'undefined') {
window.location.href=data.url;
return;
}
$('#tabs-1').loadingOverlay('remove');
},
error:function()
{
$('#tabs-1').loadingOverlay('remove');
}
});
},
massedit: function(){
me.storage.selectedArticles= '';
$(me.selector.articleTable).find('input:checked').each(function(){
if($(this).data('articleid') > 0) {
if (me.storage.selectedArticles !== '') {
me.storage.selectedArticles
= me.storage.selectedArticles + ';';
}
me.storage.selectedArticles
= me.storage.selectedArticles
+ $(this).data('articleid')
}
});
if(me.storage.selectedArticles !== '') {
matrixproduktedit_open(me.storage.selectedArticles);
}
else {
alert('Kein Artikel ausgwählt');
}
},
createallmissingarticles: function() {
me.storage.selectedArticles= 'ALL';
$('#listids').val(me.storage.selectedArticles);
$(me.selector.popupArticleCreate).dialog('open');
},
createMissingArticles: function(){
me.storage.selectedArticles= '';
$(me.selector.articleTable).find('input:checked').each(function(){
if(me.storage.selectedArticles !== '') {
me.storage.selectedArticles
=me.storage.selectedArticles + ';';
}
me.storage.selectedArticles
=me.storage.selectedArticles
+$(this).data('id')
});
if(me.storage.selectedArticles !== '') {
$('#listids').val(me.storage.selectedArticles);
$(me.selector.popupArticleCreate).dialog('open');
}
},
editOption: function(id){
$('#optionid').val(id);
if(id > 0) {
$.ajax({
type: 'POST',
dataType: 'json',
url: 'index.php?module=matrixprodukt&action=artikel&cmd=listgetoption',
data: {
articleid: $(me.selector.popupGroup).data('articleid'),
optionid: $('#optionid').val()
},
success: function (data) {
$('#optionname').val(data.name);
$('#optiongroup').html(data.groups);
$('#optiongroup').val(data.gruppe);
$(me.selector.popupOption).dialog('open');
}
});
return;
}
else {
$('#optionname').val('');
}
$(me.selector.popupOption).dialog('open');
},
articeTableAfterReload:function(){
$(me.selector.articleTable).find('img.editarticle').on('click',function(){
me.editArticle($(this).data('id'));
});
me.storage.articleTableloaded = true;
},
groupTableAfterReload:function(){
$(me.selector.groupTable).find('img.editgroup').on('click',function(){
me.editGroup($(this).data('id'));
});
$(me.selector.groupTable).find('img.deletegroup').on('click',function(){
me.deleteGroup($(this).data('id'));
});
me.storage.groupTableloaded = true;
},
optionTableAfterReload:function(){
$(me.selector.optionTable).find('img.editoption').on('click',function(){
me.editOption($(this).data('id'));
});
$(me.selector.optionTable).find('img.deleteoption').on('click',function(){
me.deleteOption($(this).data('id'));
});
me.storage.optionTableloaded = true;
},
createMissingAriclesSave: function()
{
$(me.selector.popupArticleCreate).parent().loadingOverlay('show');
$.ajax({
type: 'POST',
dataType: 'json',
url: 'index.php?module=matrixprodukt&action=artikel&cmd=createarticles',
data: {
listids:$('#listids').val(),
articleid: $(me.selector.popupGroup).data('articleid'),
fromcategory: $('#fromcategory').prop('checked')?1:0,
fromoption: $('#fromoption').prop('checked')?1:0,
fromsuffix: $('#fromsuffix').prop('checked')?1:0,
fromprefix: $('#fromprefix').prop('checked')?1:0,
prefixseparator: $('#prefixseparator').val(),
prefixcount: $('#prefixcount').val(),
prefixnextnumber: $('#prefixnextnumber').val(),
appendname: $('#prefixseparator').prop('checked')?1:0,
nextprefixnumber: $('#nextprefixnumber').val(),
},
success: function (data) {
$(me.selector.popupArticleCreate).parent().loadingOverlay('remove');
if(typeof data.continue != 'undefined' && data.continue == 1) {
if(typeof data.nextprefixnumber != 'undefined') {
$('#nextprefixnumber').val(data.nextprefixnumber);
}
me.createMissingAriclesSave();
return;
}
$('#nextprefixnumber').val('');
$(me.selector.articleTable).DataTable().ajax.reload();
$(me.selector.popupArticleCreate).dialog('close');
},
error: function()
{
$(me.selector.popupArticleCreate).parent().loadingOverlay('remove');
$('#nextprefixnumber').val('');
$(me.selector.articleTable).DataTable().ajax.reload();
$(me.selector.popupArticleCreate).dialog('close');
}
});
},
initListView: function(){
$(me.selector.popupGroup).dialog(
{
modal: true,
autoOpen: false,
minWidth: 940,
title: '',
buttons: {
'ABBRECHEN':function(){
$(me.selector.popupGroup).dialog('close');
},
'SPEICHERN': function () {
$(me.selector.popupGroup).parent().loadingOverlay('show');
$.ajax({
type: 'POST',
dataType: 'json',
url: 'index.php?module=matrixprodukt&action=artikel&cmd=listsavegroup',
data: {
articleid: $(me.selector.popupGroup).data('articleid'),
name: $('#groupname').val(),
groupid: $('#groupid').val()
},
success: function (data) {
if(typeof data.url != 'undefined') {
window.location.href=data.url;
return;
}
$(me.selector.groupTable).DataTable().ajax.reload();
$(me.selector.articleTable).DataTable().ajax.reload();
$(me.selector.popupGroup).parent().loadingOverlay('remove');
},
error: function()
{
$(me.selector.groupTable).DataTable().ajax.reload();
$(me.selector.articleTable).DataTable().ajax.reload();
$(me.selector.popupGroup).parent().loadingOverlay('remove');
}
});
},
}
}
);
$(me.selector.popupArticleCreate).dialog(
{
modal: true,
autoOpen: false,
minWidth: 940,
title: '',
buttons: {
'ABBRECHEN':function(){
$(me.selector.popupArticleCreate).dialog('close');
},
'SPEICHERN': function () {
me.createMissingAriclesSave();
},
}
}
);
$(me.selector.popupArticle).dialog(
{
modal: true,
autoOpen: false,
minWidth: 940,
title: '',
buttons: {
'ABBRECHEN':function(){
$(me.selector.popupArticle).dialog('close');
},
'SPEICHERN': function () {
$(me.selector.popupArticle).parent().loadingOverlay('show');
$.ajax({
type: 'POST',
dataType: 'json',
url: 'index.php?module=matrixprodukt&action=artikel&cmd=listsavearticle',
data: {
articleid: $(me.selector.popupGroup).data('articleid'),
listid:$('#articlelistid').val(),
option1: (typeof $('#option1').val() != 'undefined'?$('#option1').val():0),
option2: (typeof $('#option2').val() != 'undefined'?$('#option2').val():0),
option3: (typeof $('#option3').val() != 'undefined'?$('#option3').val():0),
option4: (typeof $('#option4').val() != 'undefined'?$('#option4').val():0),
option5: (typeof $('#option5').val() != 'undefined'?$('#option5').val():0),
option6: (typeof $('#option6').val() != 'undefined'?$('#option6').val():0),
option7: (typeof $('#option7').val() != 'undefined'?$('#option7').val():0),
option8: (typeof $('#option8').val() != 'undefined'?$('#option8').val():0),
option9: (typeof $('#option9').val() != 'undefined'?$('#option9').val():0),
option10: (typeof $('#option10').val() != 'undefined'?$('#option10').val():0),
option11: (typeof $('#option11').val() != 'undefined'?$('#option11').val():0),
option12: (typeof $('#option12').val() != 'undefined'?$('#option12').val():0),
option13: (typeof $('#option13').val() != 'undefined'?$('#option13').val():0),
option14: (typeof $('#option14').val() != 'undefined'?$('#option14').val():0),
option15: (typeof $('#option15').val() != 'undefined'?$('#option15').val():0),
option16: (typeof $('#option16').val() != 'undefined'?$('#option16').val():0),
option17: (typeof $('#option17').val() != 'undefined'?$('#option17').val():0),
option18: (typeof $('#option18').val() != 'undefined'?$('#option18').val():0),
option19: (typeof $('#option19').val() != 'undefined'?$('#option19').val():0),
option20: (typeof $('#option20').val() != 'undefined'?$('#option20').val():0),
},
success: function () {
$(me.selector.articleTable).DataTable().ajax.reload();
$(me.selector.popupArticle).parent().loadingOverlay('remove');
$(me.selector.popupArticle).dialog('close');
},
error: function() {
$(me.selector.articleTable).DataTable().ajax.reload();
$(me.selector.popupArticle).parent().loadingOverlay('remove');
$(me.selector.popupArticle).dialog('close');
}
});
},
}
}
);
$(me.selector.popupOption).dialog(
{
modal: true,
autoOpen: false,
minWidth: 940,
title: '',
buttons: {
'ABBRECHEN':function(){
$(me.selector.popupOption).dialog('close');
},
'SPEICHERN': function () {
$(me.selector.popupOption).parent().loadingOverlay('show');
$.ajax({
type: 'POST',
dataType: 'json',
url: 'index.php?module=matrixprodukt&action=artikel&cmd=listsaveoption',
data: {
articleid: $(me.selector.popupGroup).data('articleid'),
name: $('#optionname').val(),
groupid: $('#optiongroup').val(),
optionid: $('#optionid').val()
},
success: function () {
$(me.selector.optionTable).DataTable().ajax.reload();
$(me.selector.articleTable).DataTable().ajax.reload();
$(me.selector.popupOption).parent().loadingOverlay('remove');
$(me.selector.popupOption).dialog('close');
},
error: function(){
$(me.selector.optionTable).DataTable().ajax.reload();
$(me.selector.articleTable).DataTable().ajax.reload();
$(me.selector.popupOption).parent().loadingOverlay('remove');
$(me.selector.popupOption).dialog('close');
}
});
},
}
}
);
$(me.selector.popupGroup).toggleClass('hidden', false);
$(me.selector.popupOption).toggleClass('hidden', false);
$(me.selector.popupArticle).toggleClass('hidden', false);
$(me.selector.popupArticleCreate).toggleClass('hidden', false);
$(me.selector.articleTable).on('afterreload', function(){
me.articeTableAfterReload();
});
$(me.selector.optionTable).on('afterreload', function(){
me.optionTableAfterReload();
});
$(me.selector.groupTable).on('afterreload', function(){
me.groupTableAfterReload();
});
$(me.selector.btnNewGroup).on('click',function(){
me.editGroup(0);
});
$(me.selector.btnNewOption).on('click',function(){
me.editOption(0);
});
$('#changeall').on('change',function(){
$(me.selector.articleTable).find('input.select').prop('checked', $('#changeall').prop('checked'));
});
$('#createmissingarticles').on('click',function () {
me.createMissingArticles();
});
$('#createallmissingarticles').on('click',function () {
me.createallmissingarticles();
});
$('#massedit').on('click',function () {
me.massedit();
});
$('#generatelist').on('click',function () {
me.generateList();
});
if(!me.storage.optionTableloaded) {
$(me.selector.optionTable).DataTable().ajax.reload();
}
if(!me.storage.groupTableloaded) {
$(me.selector.groupTable).DataTable().ajax.reload();
}
if(!me.storage.articleTableloaded) {
$(me.selector.articleTable).DataTable().ajax.reload();
}
},
init: function () {
if($(me.selector.divlistview).length) {
me.initListView();
}
}
};
return {
init: me.init,
}
}(jQuery);
$(document).ready(function () {
MatrixproductListview.init();
});

27296
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -11,15 +11,19 @@
"production": "./node_modules/cross-env/src/bin/cross-env-shell.js NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
},
"devDependencies": {
"raml2html": "^7.6.0",
"raml2html-werk-theme": "^1.1.0",
"cross-env": "^7.0",
"laravel-mix": "^5.0.1",
"raml2html": "^7.6.0",
"raml2html-werk-theme": "^1.1.0",
"resolve-url-loader": "^3.1.0",
"sass": "^1.15.2",
"sass-loader": "^8.0.0"
},
"dependencies": {
"vue": "^2.6.12"
"@vitejs/plugin-vue": "^4.3.4",
"axios": "^1.5.0",
"primevue": "^3.35.0",
"vite": "^4.4.9",
"vue": "^3.3.4"
}
}

View File

@ -1,4 +1,12 @@
<?php
/*
* SPDX-FileCopyrightText: 2019 Xentral ERP Software GmbH, Fuggerstrasse 11, D-86150 Augsburg
* SPDX-FileCopyrightText: 2023 Andreas Palm
*
* SPDX-License-Identifier: LicenseRef-EGPL-3.1
*/
/*
**** COPYRIGHT & LICENSE NOTICE *** DO NOT REMOVE ****
*
@ -317,9 +325,11 @@ class Player {
$moduleClassName = strtoupper($module[0]) . substr($module, 1);
$this->app->ModuleScriptCache->IncludeModule($moduleClassName);
$this->app->ModuleScriptCache->IncludeJavascriptModules('_theme_', ['www/themes/new/js/main.js']);
$this->app->Tpl->Add('MODULESTYLESHEET', $this->app->ModuleScriptCache->GetStylesheetHtmlTags());
$this->app->Tpl->Add('MODULEJAVASCRIPTHEAD', $this->app->ModuleScriptCache->GetJavascriptHtmlTags('head'));
$this->app->Tpl->Add('MODULEJAVASCRIPTBODY', $this->app->ModuleScriptCache->GetJavascriptHtmlTags('body'));
$this->app->Tpl->Set('JAVASCRIPTMODULES', $this->app->ModuleScriptCache->GetJavascriptModulesHtmlTags());
$permission = true;
if(isset($myApp) && method_exists($myApp,'CheckRights'))$permission = $myApp->CheckRights();
@ -412,7 +422,7 @@ class Player {
else{
$this->app->Tpl->Set('VUEJS', 'vue.min.js');
$this->app->erp->RunHook('before_final_parse_page');
$this->app->erp->RunHook('before_final_parse_page');
echo $this->app->Tpl->FinalParse('page.tpl');
}
}

View File

@ -1,4 +1,12 @@
<?php
<?php
/*
* SPDX-FileCopyrightText: 2019 Xentral ERP Software GmbH, Fuggerstrasse 11, D-86150 Augsburg
* SPDX-FileCopyrightText: 2023 Andreas Palm
*
* SPDX-License-Identifier: LicenseRef-EGPL-3.1
*/
/*
**** COPYRIGHT & LICENSE NOTICE *** DO NOT REMOVE ****
*
@ -10,8 +18,8 @@
* to obtain the text of the corresponding license version.
*
**** END OF COPYRIGHT & LICENSE NOTICE *** DO NOT REMOVE ****
*/
?>
*/
?>
<?php
/**
@ -34,12 +42,19 @@ class ModuleScriptCache
/** @var string $relativeCacheDir Relativer Pfad zum Cache-Ordner (ausgehend von www) */
protected $relativeCacheDir;
protected $assetDir;
/** @var object $assetManifest Parsed manifest.json from vite */
protected $assetManifest;
/** @var array $javascriptFiles Absolute Pfade zu Javascript-Dateien die gecached werden sollen */
protected $javascriptFiles = [
'head' => [],
'body' => [],
];
protected $javascriptModules = [];
/** @var array $stylesheetFiles Absolute Pfade zu Stylesheet-Dateien die gecached werden sollen */
protected $stylesheetFiles = [];
@ -48,6 +63,8 @@ class ModuleScriptCache
$this->baseDir = dirname(dirname(__DIR__));
$this->absoluteCacheDir = $this->baseDir . '/www/cache';
$this->relativeCacheDir = './cache';
$this->assetDir = '/dist';
$this->assetManifest = json_decode(file_get_contents($this->baseDir. '/www' . $this->assetDir . '/manifest.json'));
// Cache-Ordner anzulegen, falls nicht existent
if (!is_dir($this->absoluteCacheDir)) {
@ -74,6 +91,7 @@ class ModuleScriptCache
// Javascript- und Stylesheet-Dateien sind als Eigenschaft im Modul definiert
$javascript = $this->GetClassProperty($legacyModuleClassName, 'javascript');
$stylesheet = $this->GetClassProperty($legacyModuleClassName, 'stylesheet');
$jsmodules = $this->GetClassProperty($legacyModuleClassName, 'jsmodules');
// Falls nicht im Modul definiert > Defaults verwenden
if (empty($javascript)) {
@ -82,9 +100,13 @@ class ModuleScriptCache
if (empty($stylesheet)) {
$stylesheet = [$this->GetDefaultModuleStylesheetFile($newModuleName)];
}
if (empty($jsmodules)) {
$jsmodules = $this->GetDefaultModuleJavascriptModules($newModuleName);
}
$this->IncludeJavascriptFiles($newModuleName, $javascript);
$this->IncludeStylesheetFiles($newModuleName, $stylesheet);
$this->IncludeJavascriptModules($newModuleName, $jsmodules);
}
/**
@ -193,6 +215,20 @@ class ModuleScriptCache
}
}
public function IncludeJavascriptModules(string $moduleName, array $files) : void
{
foreach ($files as $file) {
$realPath = realpath($this->baseDir . '/' . $file);
if (!is_file($realPath))
continue;
if (isset($this->assetManifest->$file))
$this->javascriptModules[] = $this->assetManifest->$file;
else
$this->javascriptModules[] = $realPath;
}
}
/**
* @param string $cacheName Name unter dem die Cache-Datei zusammengefasst werden
* @param array $files Array mit relativen Pfaden zur Xentral-Installation
@ -258,6 +294,36 @@ class ModuleScriptCache
return $html;
}
public function GetJavascriptModulesHtmlTags() : string
{
if (empty($this->javascriptModules))
return '';
$html = '';
foreach ($this->javascriptModules as $module) {
if (is_object($module)) {
if (defined('VITE_DEV_SERVER')) {
$url = VITE_DEV_SERVER . $module->src;
} else {
$url = $this->assetDir . '/' . $module->file;
if (isset($module->css)) {
foreach ($module->css as $css)
$html .= sprintf('<link rel="stylesheet" type="text/css" href="%s" />', $this->assetDir.'/'.$css);
$html .= "\r\n";
}
}
} elseif (str_starts_with($module,$this->baseDir.'/www')) {
$url = substr($module, strlen($this->baseDir)+4);
}
if (isset($url)) {
$html .= sprintf('<script type="module" src="%s"></script>', $url);
$html .= "\r\n";
}
}
return $html;
}
/**
* @return string
*/
@ -389,6 +455,18 @@ class ModuleScriptCache
return sprintf('./classes/Modules/%s/www/js/%s.js', $moduleName, strtolower($moduleName));
}
/**
* @param string $moduleName
* @return string relative path to default Javascript-Module-File
*/
protected function GetDefaultModuleJavascriptModules(string $moduleName): array
{
return [
sprintf('classes/Modules/%s/www/js/entry.js', $moduleName),
sprintf('classes/Modules/%s/www/js/entry.jsx', $moduleName)
];
}
/**
* @param string $moduleName
*

View File

@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2023 Andreas Palm
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@import "autocomplete.css";
@import "dialog.css";
@import "dropdown.css";
@import "listbox.css";
.p-component-overlay {
background-color: rgba(170,170,170,0.7);
}
.p-button {
background-color: var(--button-primary-background);
padding: 6px;
margin: 3px;
color: white;
border-radius: 3px;
border: 1px solid var(--button-primary-border-color);
font-size: 14px;
}
.p-icon {
width: 1rem;
height: 1rem;
}

View File

@ -0,0 +1,94 @@
/*
* SPDX-FileCopyrightText: 2023 Andreas Palm
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
.p-autocomplete .p-autocomplete-loader {
right: 0.75rem;
}
.p-autocomplete.p-autocomplete-dd .p-autocomplete-loader {
right: 3.107rem;
}
.p-autocomplete:not(.p-disabled):hover .p-autocomplete-multiple-container {
border-color: #ced4da;
}
.p-autocomplete:not(.p-disabled).p-focus .p-autocomplete-multiple-container {
outline: 0 none;
outline-offset: 0;
box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5);
border-color: #007bff;
}
.p-autocomplete .p-autocomplete-multiple-container {
padding: 0.25rem 0.75rem;
gap: 0.5rem;
}
.p-autocomplete .p-autocomplete-multiple-container .p-autocomplete-input-token {
padding: 0.25rem 0;
}
.p-autocomplete .p-autocomplete-multiple-container .p-autocomplete-input-token input {
font-family: inherit;
font-feature-settings: inherit;
font-size: inherit;
color: #212529;
padding: 0;
margin: 0;
}
.p-autocomplete .p-autocomplete-multiple-container .p-autocomplete-token {
padding: 0.25rem 0.75rem;
background: #dee2e6;
color: #212529;
border-radius: 16px;
}
.p-autocomplete .p-autocomplete-multiple-container .p-autocomplete-token .p-autocomplete-token-icon {
margin-left: 0.5rem;
}
.p-autocomplete .p-autocomplete-multiple-container .p-autocomplete-token.p-focus {
background: #ced4da;
color: #212529;
}
.p-autocomplete.p-invalid.p-component > .p-inputtext {
border-color: #dc3545;
}
.p-autocomplete-panel {
background: #ffffff;
color: var(--grey);
border: 1px solid var(--fieldset-dark);
border-radius: 4px;
box-shadow: none;
}
.p-autocomplete-panel .p-autocomplete-items {
padding: 0.5rem 0;
}
.p-autocomplete-panel .p-autocomplete-items .p-autocomplete-item {
margin: 0;
padding: 3px;
border: 0 none;
color: #212529;
background: transparent;
transition: box-shadow 0.15s;
border-radius: 0;
}
.p-autocomplete-panel .p-autocomplete-items .p-autocomplete-item.p-highlight {
color: #ffffff;
background: #007bff;
}
.p-autocomplete-panel .p-autocomplete-items .p-autocomplete-item.p-highlight.p-focus {
background: #0067d6;
}
.p-autocomplete-panel .p-autocomplete-items .p-autocomplete-item:not(.p-highlight):not(.p-disabled).p-focus {
color: #212529;
background: #dee2e6;
}
.p-autocomplete-panel .p-autocomplete-items .p-autocomplete-item:not(.p-highlight):not(.p-disabled):hover {
color: #212529;
background: #e9ecef;
}
.p-autocomplete-panel .p-autocomplete-items .p-autocomplete-item-group {
margin: 0;
padding: 0.75rem 1rem;
color: #212529;
background: #ffffff;
font-weight: 600;
}

View File

@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2023 Andreas Palm
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
.p-dialog {
background-color: var(--body-background);
opacity: 1;
}
.p-dialog-header {
color: #6d6d6f;
font-size: 14px;
font-weight: bold;
padding: .4em 1em;
}
.p-dialog .p-dialog-header .p-dialog-header-icon {
width: 2rem;
height: 2rem;
}
.p-dialog .p-dialog-content {
padding: 0.5em 1em;
}
.p-dialog .p-dialog-footer {
border-top: 1px solid var(--fieldset-dark);
padding: 0.3em 1em 0.5em 0.4em;
text-align: right;
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
}

View File

@ -0,0 +1,129 @@
/*
* SPDX-FileCopyrightText: 2023 Andreas Palm
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
.p-dropdown {
background: #ffffff;
border: 1px solid #ced4da;
transition: background-color 0.15s, border-color 0.15s, box-shadow 0.15s;
border-radius: 4px;
padding: 4px 5px;
font-size: 11px;
}
.p-dropdown:not(.p-disabled):hover {
border-color: #ced4da;
}
.p-dropdown:not(.p-disabled).p-focus {
outline: 0 none;
outline-offset: 0;
box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5);
border-color: #007bff;
}
.p-dropdown.p-dropdown-clearable .p-dropdown-label {
padding-right: 1.75rem;
}
.p-dropdown .p-dropdown-label {
background: transparent;
border: 0 none;
color: #6d6d6f;
}
.p-dropdown .p-dropdown-label.p-placeholder {
color: #6c757d;
}
.p-dropdown .p-dropdown-label:focus, .p-dropdown .p-dropdown-label:enabled:focus {
outline: 0 none;
box-shadow: none;
}
.p-dropdown .p-dropdown-trigger {
background: transparent;
color: #495057;
width: 2.357rem;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.p-dropdown .p-dropdown-clear-icon {
color: #495057;
right: 2.357rem;
}
.p-dropdown.p-invalid.p-component {
border-color: #dc3545;
}
.p-dropdown-panel {
background: #ffffff;
color: var(--grey);
border: 1px solid var(--fieldset-dark);
border-radius: 4px;
box-shadow: none;
}
.p-dropdown-panel .p-dropdown-header {
padding: 0.75rem 1.5rem;
border-bottom: 1px solid #dee2e6;
color: #212529;
background: #efefef;
margin: 0;
border-top-right-radius: 4px;
border-top-left-radius: 4px;
}
.p-dropdown-panel .p-dropdown-header .p-dropdown-filter {
padding-right: 1.75rem;
margin-right: -1.75rem;
}
.p-dropdown-panel .p-dropdown-header .p-dropdown-filter-icon {
right: 0.75rem;
color: #495057;
}
.p-dropdown-panel .p-dropdown-items {
padding: 0.5rem 0;
}
.p-dropdown-panel .p-dropdown-items .p-dropdown-item {
margin: 0;
padding: 3px;
border: 0 none;
color: #212529;
background: transparent;
transition: box-shadow 0.15s;
border-radius: 0;
}
.p-dropdown-panel .p-dropdown-items .p-dropdown-item.p-highlight {
color: #ffffff;
background: #007bff;
}
.p-dropdown-panel .p-dropdown-items .p-dropdown-item.p-highlight.p-focus {
background: #0067d6;
}
.p-dropdown-panel .p-dropdown-items .p-dropdown-item:not(.p-highlight):not(.p-disabled).p-focus {
color: #212529;
background: #dee2e6;
}
.p-dropdown-panel .p-dropdown-items .p-dropdown-item:not(.p-highlight):not(.p-disabled):hover {
color: #212529;
background: #e9ecef;
}
.p-dropdown-panel .p-dropdown-items .p-dropdown-item-group {
margin: 0;
padding: 0.75rem 1rem;
color: #212529;
background: #ffffff;
font-weight: 600;
}
.p-dropdown-panel .p-dropdown-items .p-dropdown-empty-message {
padding: 0.5rem 1.5rem;
color: #212529;
background: transparent;
}
.p-input-filled .p-dropdown {
background: #efefef;
}
.p-input-filled .p-dropdown:not(.p-disabled):hover {
background-color: #efefef;
}
.p-input-filled .p-dropdown:not(.p-disabled).p-focus {
background-color: #efefef;
}
.p-input-filled .p-dropdown:not(.p-disabled).p-focus .p-inputtext {
background-color: transparent;
}

View File

@ -0,0 +1,75 @@
/*
* SPDX-FileCopyrightText: 2023 Andreas Palm
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
.p-listbox {
background: #ffffff;
color: #6d6d6f;
border: 1px solid #ced4da;
border-radius: 4px;
transition: background-color 0.15s, border-color 0.15s, box-shadow 0.15s;
}
.p-listbox .p-listbox-header {
padding: 0.75rem 1.5rem;
border-bottom: 1px solid #dee2e6;
color: #212529;
background: #efefef;
margin: 0;
border-top-right-radius: 4px;
border-top-left-radius: 4px;
}
.p-listbox .p-listbox-header .p-listbox-filter {
padding-right: 1.75rem;
}
.p-listbox .p-listbox-header .p-listbox-filter-icon {
right: 0.75rem;
color: #495057;
}
.p-listbox .p-listbox-list {
padding: 0.5rem 0;
outline: 0 none;
}
.p-listbox .p-listbox-list .p-listbox-item {
margin: 0;
padding: 0.5rem 1.5rem;
border: 0 none;
color: #212529;
transition: box-shadow 0.15s;
border-radius: 0;
}
.p-listbox .p-listbox-list .p-listbox-item.p-highlight {
color: #ffffff;
background: var(--green);
}
.p-listbox .p-listbox-list .p-listbox-item-group {
margin: 0;
padding: 0.75rem 1rem;
color: #212529;
background: #ffffff;
font-weight: 600;
}
.p-listbox .p-listbox-list .p-listbox-empty-message {
padding: 0.5rem 1.5rem;
color: #212529;
background: transparent;
}
.p-listbox:not(.p-disabled) .p-listbox-item.p-highlight.p-focus {
background: var(--green);
}
.p-listbox:not(.p-disabled) .p-listbox-item:not(.p-highlight):not(.p-disabled).p-focus {
color: #212529;
background: #dee2e6;
}
.p-listbox:not(.p-disabled) .p-listbox-item:not(.p-highlight):not(.p-disabled):hover {
color: #212529;
background: #e9ecef;
}
.p-listbox.p-focus {
outline: 0 none;
outline-offset: 0;
}
.p-listbox.p-invalid {
border-color: #dc3545;
}

37
resources/css/vue.css Normal file
View File

@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: 2023 Andreas Palm
*
* SPDX-License-Identifier: LicenseRef-EGPL-3.1
*/
.vueAction {
cursor: pointer;
}
.flex {
display: flex;
}
.grid {
display: grid;
}
.flex-align-center {
align-items: center;
}
.justify-self-start {
justify-self: start;
}
.gap-1 {
gap: 0.25rem;
}
.grid label {
padding-top: 5px;
}
.grid input {
width: 100%;
}

View File

@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: 2023 Andreas Palm
//
// SPDX-License-Identifier: AGPL-3.0-only
import {AxiosError} from 'axios';
export function AlertErrorHandler(error: AxiosError) {
if (error.response === undefined || error.response.status >= 500) {
console.log('Unknown error on axios request', error);
alert('Unerwarteter Fehler, weitere Hinweise ggf. in der JavaScript-Konsole');
} else {
console.log('ClientError on axios request', error);
alert(error.response.data);
}
}

7
resources/js/jqueryBridge.js vendored Normal file
View File

@ -0,0 +1,7 @@
// SPDX-FileCopyrightText: 2023 Andreas Palm
//
// SPDX-License-Identifier: AGPL-3.0-only
export function reloadDataTables() {
window.$('#main .dataTable').DataTable().ajax.reload();
}

12
resources/js/vue.js Normal file
View File

@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: 2023 Andreas Palm
//
// SPDX-License-Identifier: LicenseRef-EGPL-3.1
import '@res/css/vue.css';
import '@res/css/primevue/_base.css';
import {createApp} from "vue";
import PrimeVue from "primevue/config";
export function createVueApp(rootComponent, rootProps) {
return createApp(rootComponent, rootProps).use(PrimeVue);
}

View File

@ -0,0 +1,44 @@
<!--
SPDX-FileCopyrightText: 2023 Andreas Palm
SPDX-License-Identifier: AGPL-3.0-only
-->
<script setup>
import {ref} from "vue";
import AutoComplete from "primevue/autocomplete";
import axios from "axios";
const props = defineProps({
ajaxFilter: String,
modelValue: null,
forceSelection: Boolean
});
const emit = defineEmits(['update:modelValue']);
const items = ref([]);
async function search(event) {
await axios.get('index.php',
{
params: {
module: 'ajax',
action: 'filter',
filtername: props.ajaxFilter,
term: event.query,
object: true
}
})
.then(response => items.value = response.data)
}
</script>
<template>
<AutoComplete
:modelValue="modelValue"
@update:modelValue="value => emit('update:modelValue', value)"
:suggestions="items"
@complete="search"
dataKey="id"
:forceSelection="forceSelection"
/>
</template>

31
vite.config.js Normal file
View File

@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2023 Andreas Palm
//
// SPDX-License-Identifier: AGPL-3.0-only
import glob from 'glob';
import path from 'path';
import vue from '@vitejs/plugin-vue';
const moduleInputs = glob.sync('classes/Modules/*/www/js/entry.{js,jsx}')
.map(file => ['modules/'+file.split('/')[2], file]);
/** @type {import('vite').UserConfig} */
export default {
build: {
rollupOptions: {
input: {
...Object.fromEntries(moduleInputs)
}
},
manifest: true,
outDir: 'www/dist',
},
plugins: [vue()],
mode: 'development',
resolve: {
alias: {
'@theme': path.resolve(__dirname, 'www/themes/new/js'),
'@res': path.resolve(__dirname, 'resources')
}
}
}

1
www/dist/assets/entry-597722a1.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

14
www/dist/manifest.json vendored Normal file
View File

@ -0,0 +1,14 @@
{
"classes/Modules/MatrixProduct/www/js/entry.css": {
"file": "assets/entry-597722a1.css",
"src": "classes/Modules/MatrixProduct/www/js/entry.css"
},
"classes/Modules/MatrixProduct/www/js/entry.jsx": {
"css": [
"assets/entry-597722a1.css"
],
"file": "assets/modules/MatrixProduct-1c46dcc9.js",
"isEntry": true,
"src": "classes/Modules/MatrixProduct/www/js/entry.jsx"
}
}

View File

@ -1875,7 +1875,6 @@ class Remote
if(is_numeric($matrixPseudoStorage) && $matrixPseudoStorage < 0) {
$matrixPseudoStorage = 0;
}
$matrixStock = (float)$this->app->erp->ArtikelAnzahlVerkaufbar($eigenschaft['artikel'], 0, $projektlager, $id, $lagergrundlage);
if($matrixStock < 0) {
$matrixStock = 0;
}

7
www/manifest.json Normal file
View File

@ -0,0 +1,7 @@
{
"classes/Modules/MatrixProduct/www/js/entry.js": {
"file": "assets/modules/MatrixProduct-4ed993c7.js",
"isEntry": true,
"src": "classes/Modules/MatrixProduct/www/js/entry.js"
}
}

View File

@ -1,4 +1,12 @@
<?php
/*
* SPDX-FileCopyrightText: 2019 Xentral ERP Software GmbH, Fuggerstrasse 11, D-86150 Augsburg
* SPDX-FileCopyrightText: 2023 Andreas Palm
*
* SPDX-License-Identifier: LicenseRef-EGPL-3.1
*/
/*
**** COPYRIGHT & LICENSE NOTICE *** DO NOT REMOVE ****
*
@ -1218,6 +1226,7 @@ class Ajax {
$rmodule = $this->app->Secure->GetGET('rmodule');
$raction = $this->app->Secure->GetGET('raction');
$rid = (int)$this->app->Secure->GetGET('rid');
$asObject = $this->app->Secure->GetGET('object');
$pruefemodule = array('artikel','auftrag','angebot','rechnung','lieferschein','gutschrift','bestellung','produktion');
$filter_projekt = 0;
if($raction === 'edit' && $rid && in_array($rmodule, $pruefemodule))
@ -2529,15 +2538,19 @@ select a.kundennummer, (SELECT name FROM adresse a2 WHERE a2.kundennummer = a.ku
//if($checkprojekt > 0 && $eigenernummernkreis=="1") $tmp_where = " AND projekt='$checkprojekt' ";
//else $tmp_where = "";
$selectfields = $asObject ? 'art.id, art.nummer, art.name_de name' : "CONCAT(nummer,' ',name_de) as `name`";
$arr = $this->app->DB->SelectArr(
"SELECT CONCAT(nummer,' ',name_de) as `name`
FROM artikel AS art WHERE geloescht=0 AND ($subwhere) AND geloescht=0 AND intern_gesperrt!=1 $tmp_where ".
"SELECT $selectfields
FROM artikel AS art WHERE geloescht=0 AND ($subwhere) AND intern_gesperrt!=1 $tmp_where ".
$this->app->erp->ProjektRechte('art.projekt'). ' LIMIT 20'
);
$carr = !empty($arr)?count($arr):0;
for($i = 0; $i < $carr; $i++) {
$newarr[] = $arr[$i]['name'];
if ($asObject) {
$newarr = $arr;
} else {
$carr = !empty($arr) ? count($arr) : 0;
for ($i = 0; $i < $carr; $i++) {
$newarr[] = $arr[$i]['name'];
}
}
break;
@ -3867,11 +3880,16 @@ select a.kundennummer, (SELECT name FROM adresse a2 WHERE a2.kundennummer = a.ku
}
break;
case "projektname":
$arr = $this->app->DB->SelectArr("SELECT CONCAT(p.abkuerzung,' ',p.name) as name FROM projekt p WHERE p.geloescht=0 AND status <> 'abgeschlossen' AND (p.name LIKE '%$term%' OR p.name LIKE '%$term2%' OR p.name LIKE '%$term3%' OR p.abkuerzung LIKE '%$term%' OR p.abkuerzung LIKE '%$term2%' OR p.abkuerzung LIKE '%$term3%') ".$this->app->erp->ProjektRechte());
$carr = !empty($arr)?count($arr):0;
for($i = 0; $i < $carr; $i++) {
$newarr[] = $arr[$i]['name'];
case "projektname":
$fields = $asObject ? 'p.id, p.abkuerzung, p.name' : "CONCAT(p.abkuerzung,' ',p.name) as name";
$arr = $this->app->DB->SelectArr("SELECT $fields FROM projekt p WHERE p.geloescht=0 AND status <> 'abgeschlossen' AND (p.name LIKE '%$term%' OR p.name LIKE '%$term2%' OR p.name LIKE '%$term3%' OR p.abkuerzung LIKE '%$term%' OR p.abkuerzung LIKE '%$term2%' OR p.abkuerzung LIKE '%$term3%') ".$this->app->erp->ProjektRechte());
if ($asObject) {
$newarr = $arr;
} else {
$carr = !empty($arr) ? count($arr) : 0;
for ($i = 0; $i < $carr; $i++) {
$newarr[] = $arr[$i]['name'];
}
}
break;
@ -4185,7 +4203,16 @@ select a.kundennummer, (SELECT name FROM adresse a2 WHERE a2.kundennummer = a.ku
}else{
$cnewarr = !empty($newarr)?count($newarr):0;
for($i=0;$i<$cnewarr;$i++) {
$tmp[] = $this->app->erp->ClearDataBeforeOutput(html_entity_decode($newarr[$i], ENT_QUOTES, 'UTF-8'));
$row = $newarr[$i];
if (is_string($row))
$tmp[] = $this->app->erp->ClearDataBeforeOutput(html_entity_decode($newarr[$i], ENT_QUOTES, 'UTF-8'));
else if (is_array($row)) {
$tmprow = [];
foreach ($row as $key => $value) {
$tmprow[$key] = $this->app->erp->ClearDataBeforeOutput(html_entity_decode($value, ENT_QUOTES, 'UTF-8'));
}
$tmp[] = $tmprow;
}
}
}

View File

@ -1,4 +1,12 @@
<?php
/*
* SPDX-FileCopyrightText: 2019 Xentral ERP Software GmbH, Fuggerstrasse 11, D-86150 Augsburg
* SPDX-FileCopyrightText: 2023 Andreas Palm
*
* SPDX-License-Identifier: LicenseRef-EGPL-3.1
*/
/*
**** COPYRIGHT & LICENSE NOTICE *** DO NOT REMOVE ****
*
@ -7320,6 +7328,10 @@ class Artikel extends GenArtikel {
$this->app->erp->MenuEintrag('index.php?module=artikel&action=eigenschaften&id='.$id, 'Eigenschaften');
}
if ($tmp[0]['matrixprodukt']==1) {
$this->app->erp->MenuEintrag("index.php?module=matrixprodukt&action=artikel&id=$id", 'Matrixprodukt');
}
if($rabatt!='1'){
$this->app->erp->MenuEintrag("index.php?module=artikel&action=einkauf&id=$id",'Einkauf');
if($this->app->erp->RechteVorhanden('einkaufabgleich','einkaufapi'))

View File

@ -0,0 +1,58 @@
<!--
SPDX-FileCopyrightText: 2023 Andreas Palm
SPDX-License-Identifier: LicenseRef-EGPL-3.1
-->
<div id="tabs">
<ul>
<li><a href="#tabs-1">Gruppen/Optionen</a></li>
<li><a href="#tabs-2">Variantenzuordnung</a></li>
</ul>
<div id="tabs-1">
<div class="row">
<div class="col-xs-12 col-md-10 col-md-height">
<div class="inside-white inside-full-height">
[MESSAGE]
[TAB1]
</div>
</div>
<div class="col-xs-12 col-md-2 col-md-height">
<div class="inside inside-full-height">
<fieldset>
<legend>{|Aktionen|}</legend>
<input type="button" class="btnGreenNew vueAction" data-action="addGlobalToArticle" data-article-id="[ID]" value="&#10010; Optionen übernehmen">
<input type="button" class="btnGreenNew vueAction" data-action="[ADDEDITFUNCTION]" data-group-id="[SID]" data-article-id="[ID]" value="&#10010; Neuer Eintrag">
<input type="button" class="btnGreenNew" name="matrixprodukt_module"
value="&#10140; Vordefinierte Gruppen/Optionen" onclick="window.location.href='index.php?module=matrixprodukt&action=list';">
</fieldset>
</div>
</div>
</div>
</div>
<div id="tabs-2">
<div class="row">
<div class="row-height">
<div class="col-xs-12 col-md-10 col-md-height">
<div class="inside-white inside-full-height">
[MESSAGE]
[TAB2]
</div>
</div>
<div class="col-xs-12 col-md-2 col-md-height">
<div class="inside inside-full-height">
<fieldset>
<legend>{|Aktionen|}</legend>
<input type="button" class="btnGreenNew vueAction" data-action="variantEdit" data-article-id="[ID]" value="&#10010; Neue Variante">
<!--<input type="button" class="btnGreenNew vueAction" data-action="createMissing" data-article-id="[ID]" value="&#10010; Erzeuge fehlende Varianten">-->
</fieldset>
</div>
</div>
</div>
</div>
</div>
<!-- tab view schließen -->
</div>
<!-- ende tab view schließen -->
<div id="vueapp"></div>

View File

@ -1,5 +1,8 @@
<!-- gehort zu tabview -->
<!--
SPDX-FileCopyrightText: 2023 Andreas Palm
SPDX-FileCopyrightText: 2019 Xentral (c) Xentral ERP Software GmbH, Fuggerstrasse 11, D-86150 Augsburg, Germany
SPDX-License-Identifier: LicenseRef-EGPL-3.1
-->
<div id="tabs">
<ul>
<li><a href="#tabs-1">[TABTEXT]</a></li>
@ -29,33 +32,7 @@
</div>
<!-- ende tab view schließen -->
<div id="editMatrixprodukt" style="display:none;" title="Bearbeiten">
<form action="" method="post" name="eprooform">
<input type="hidden" id="matrixprodukt_id">
<fieldset>
<legend>{|Einstellungen|}</legend>
<table>
<tr>
<td width="100">{|Name|}:</td>
<td><input type="text" size="40" name="matrixprodukt_name" id="matrixprodukt_name"></td>
</tr>
<tr[STYLEEXT]>
<td>{|Name Extern|}:</td>
<td><input type="text" size="40" name="matrixprodukt_name_ext" id="matrixprodukt_name_ext"></td>
</tr>
<tr>
<td>{|Projekt|}:</td>
<td><input type="text" size="40" name="matrixprodukt_projekt" id="matrixprodukt_projekt"></td>
</tr>
<tr>
<td>{|Aktiv|}:</td>
<td><input type="checkbox" name="matrixprodukt_aktiv" id="matrixprodukt_aktiv" value="1"></td>
</tr>
</table>
</fieldset>
</form>
</div>
<div id="vueapp"></div>
<div id="editMatrixproduktUebersetzung" style="display:none;" title="Bearbeiten">
<form action="" method="post" name="eprooform">
@ -115,56 +92,6 @@
</div>
<script type="text/javascript">
$(document).ready(function() {
$('#matrixprodukt_name').focus();
$("#editMatrixprodukt").dialog({
modal: true,
bgiframe: true,
closeOnEscape:false,
minWidth:500,
maxHeight:800,
autoOpen: false,
buttons: {
ABBRECHEN: function() {
MatrixproduktReset();
$(this).dialog('close');
},
SPEICHERN: function() {
MatrixproduktEditSave();
}
}
});
$("#editMatrixprodukt").dialog({
close: function( event, ui ) {MatrixproduktReset();}
});
$("#editMatrixproduktUebersetzung").dialog({
modal: true,
bgiframe: true,
closeOnEscape:false,
minWidth:500,
maxHeight:800,
autoOpen: false,
buttons: {
ABBRECHEN: function() {
MatrixproduktUebersetzungReset();
$(this).dialog('close');
},
SPEICHERN: function() {
MatrixproduktUebersetzungEditSave();
}
}
});
$("#editMatrixproduktUebersetzung").dialog({
close: function( event, ui ) {MatrixproduktUebersetzungReset();}
});
});
function MatrixproduktUebersetzungReset(){
$('#editMatrixproduktUebersetzung').find('#matrixprodukt_uebersetzung_id').val('');
$('#editMatrixproduktUebersetzung').find('#matrixprodukt_sprache_von').val('');
@ -277,111 +204,5 @@ function MatrixproduktUebersetzungDelete(id) {
return false;
}
function MatrixproduktReset(){
$('#editMatrixprodukt').find('#matrixprodukt_id').val('');
$('#editMatrixprodukt').find('#matrixprodukt_name').val('');
$('#editMatrixprodukt').find('#matrixprodukt_name_ext').val('');
$('#editMatrixprodukt').find('#matrixprodukt_projekt').val('');
$('#editMatrixprodukt').find('#matrixprodukt_aktiv').prop("checked", true);
}
function MatrixproduktEditSave() {
$.ajax({
url: 'index.php?module=matrixprodukt&action=list&cmd=save',
data: {
//Alle Felder die fürs editieren vorhanden sind
id: $('#matrixprodukt_id').val(),
name: $('#matrixprodukt_name').val(),
name_ext: $('#matrixprodukt_name_ext').val(),
projekt: $('#matrixprodukt_projekt').val(),
aktiv: $('#matrixprodukt_aktiv').prop("checked")?1:0
},
method: 'post',
dataType: 'json',
beforeSend: function() {
App.loading.open();
},
success: function(data) {
App.loading.close();
if (data.status == 1) {
MatrixproduktReset();
updateLiveTable();
$("#editMatrixprodukt").dialog('close');
} else {
alert(data.statusText);
}
}
});
}
function MatrixproduktEdit(id) {
if(id > 0)
{
$.ajax({
url: 'index.php?module=matrixprodukt&action=list&cmd=edit',
data: {
id: id
},
method: 'post',
dataType: 'json',
beforeSend: function() {
App.loading.open();
},
success: function(data) {
$('#editMatrixprodukt').find('#matrixprodukt_id').val(data.id);
$('#editMatrixprodukt').find('#matrixprodukt_name').val(data.name);
$('#editMatrixprodukt').find('#matrixprodukt_name_ext').val(data.name_ext);
$('#editMatrixprodukt').find('#matrixprodukt_projekt').val(data.projekt);
$('#editMatrixprodukt').find('#matrixprodukt_aktiv').prop("checked",data.aktiv==1?true:false);
App.loading.close();
$("#editMatrixprodukt").dialog('open');
}
});
} else {
MatrixproduktReset();
$("#editMatrixprodukt").dialog('open');
}
}
function updateLiveTable(i) {
var oTableL = $('#matrixprodukt_eigenschaftengruppen').dataTable();
var tmp = $('.dataTables_filter input[type=search]').val();
oTableL.fnFilter('%');
//oTableL.fnFilter('');
oTableL.fnFilter(tmp);
}
function MatrixproduktDelete(id) {
var conf = confirm('Wirklich löschen?');
if (conf) {
$.ajax({
url: 'index.php?module=matrixprodukt&action=list&cmd=delete',
data: {
id: id
},
method: 'post',
dataType: 'json',
beforeSend: function() {
App.loading.open();
},
success: function(data) {
if(data.status == 1){
updateLiveTable();
}else{
alert(data.statusText);
}
App.loading.close();
}
});
}
return false;
}
</script>

View File

@ -1,12 +1,14 @@
<!-- gehort zu tabview -->
<!--
SPDX-FileCopyrightText: 2023 Andreas Palm
SPDX-FileCopyrightText: 2019 Xentral (c) Xentral ERP Software GmbH, Fuggerstrasse 11, D-86150 Augsburg, Germany
SPDX-License-Identifier: LicenseRef-EGPL-3.1
-->
<div id="tabs">
<ul>
<li><a href="#tabs-1">[TABTEXT]</a></li>
</ul>
<!-- erstes tab -->
<div id="tabs-1">
<div class="row">
<div class="row-height">
<div class="col-xs-12 col-md-10 col-md-height">
@ -19,7 +21,7 @@
<div class="inside inside-full-height">
<fieldset>
<legend>{|Aktionen|}</legend>
<input type="button" class="btnGreenNew" name="neueoption" value="&#10010; Neuer Eintrag" onclick="MatrixproduktOptionenEdit(0);">
<input type="button" class="btnGreenNew" name="neueoption" data-action="optionEdit" data-option-id="0" value="&#10010; Neuer Eintrag" onclick="MatrixproduktOptionenEdit(0);">
<input type="button" class="btnGreenNew" name="neueuebersetzung" value="&#10010; Neue &Uuml;bersetzung" onclick="MatrixproduktOptionenUebersetzungEdit(0);">
</fieldset>
</div>
@ -32,37 +34,7 @@
</div>
<!-- ende tab view schließen -->
<div id="editMatrixproduktOptionen" style="display:none;" title="Bearbeiten">
<form action="" method="post" name="eprooform" >
<input type="hidden" id="matrixprodukt_optionen_id">
<input type="hidden" name = "matrixprodukt_eintragid" id="matrixprodukt_eintragid" value="[ID]">
<fieldset>
<legend>{|Einstellungen|}</legend>
<table>
<tr>
<td width="100">{|Name|}:</td>
<td><input type="text" size="40" name="matrixprodukt_optionen_name" id="matrixprodukt_optionen_name"></td>
</tr>
<tr>
<td width="100">{|Anhang an Artikelnummer|}:</td>
<td><input type="text" size="40" name="matrixprodukt_optionen_articlenumber_suffix" id="matrixprodukt_optionen_articlenumber_suffix"></td>
</tr>
<tr[STYLEEXT]>
<td>{|Name Extern|}:</td>
<td><input type="text" size="40" name="matrixprodukt_optionen_name_ext" id="matrixprodukt_optionen_name_ext"></td>
</tr>
<tr>
<td>{|Sortierung|}:</td>
<td><input type="text" size="8" name="matrixprodukt_optionen_sortierung" id="matrixprodukt_optionen_sortierung"></td>
</tr>
<tr>
<td>{|Aktiv|}:</td>
<td><input type="checkbox" name="matrixprodukt_optionen_aktiv" id="matrixprodukt_optionen_aktiv" value="1"></td>
</tr>
</table>
</fieldset>
</form>
</div>
<div id="vueapp"></div>
<div id="editMatrixproduktOptionenUebersetzung" style="display:none;" title="Bearbeiten">
<form action="" method="post" name="eprooform">
@ -119,33 +91,7 @@
</div>
<script type="text/javascript">
$(document).ready(function() {
$('#matrixprodukt_optionen_name').focus();
$("#editMatrixproduktOptionen").dialog({
modal: true,
bgiframe: true,
closeOnEscape:false,
minWidth:500,
maxHeight:800,
autoOpen: false,
buttons: {
ABBRECHEN: function() {
MatrixproduktOptionenReset();
$(this).dialog('close');
},
SPEICHERN: function() {
MatrixproduktOptionenEditSave();
}
}
});
$("#editMatrixproduktOptionen").dialog({
close: function( event, ui ) {MatrixproduktOptionenReset();}
});
$("#editMatrixproduktOptionenUebersetzung").dialog({
modal: true,
bgiframe: true,
@ -282,117 +228,5 @@ function MatrixproduktOptionenUebersetzungDelete(id) {
return false;
}
function MatrixproduktOptionenReset(){
$('#editMatrixproduktOptionen').find('#matrixprodukt_optionen_id').val('');
$('#editMatrixproduktOptionen').find('#matrixprodukt_optionen_name').val('');
$('#editMatrixproduktOptionen').find('#matrixprodukt_optionen_name_ext').val('');
$('#editMatrixproduktOptionen').find('#matrixprodukt_optionen_articlenumber_suffix').val('');
$('#editMatrixproduktOptionen').find('#matrixprodukt_optionen_sortierung').val('');
$('#editMatrixproduktOptionen').find('#matrixprodukt_optionen_aktiv').prop("checked", true);
}
function MatrixproduktOptionenEditSave() {
$.ajax({
url: 'index.php?module=matrixprodukt&action=optionenlist&cmd=optionensave',
data: {
//Alle Felder die fürs editieren vorhanden sind
optionenid: $('#matrixprodukt_optionen_id').val(),
matrixproduktid: $('#matrixprodukt_eintragid').val(),
optionenname: $('#matrixprodukt_optionen_name').val(),
optionenarticlenumber_suffix: $('#matrixprodukt_optionen_articlenumber_suffix').val(),
optionenname_ext: $('#matrixprodukt_optionen_name_ext').val(),
optionensortierung: $('#matrixprodukt_optionen_sortierung').val(),
optionenaktiv: $('#matrixprodukt_optionen_aktiv').prop("checked")?1:0
},
method: 'post',
dataType: 'json',
beforeSend: function() {
App.loading.open();
},
success: function(data) {
App.loading.close();
if (data.status == 1) {
MatrixproduktOptionenReset();
updateLiveTableOptionen();
$("#editMatrixproduktOptionen").dialog('close');
} else {
alert(data.statusText);
}
}
});
}
function MatrixproduktOptionenEdit(id) {
if(id > 0)
{
$.ajax({
url: 'index.php?module=matrixprodukt&action=optionenlist&cmd=optionenedit',
data: {
id: id
},
method: 'post',
dataType: 'json',
beforeSend: function() {
App.loading.open();
},
success: function(data) {
$('#editMatrixproduktOptionen').find('#matrixprodukt_optionen_id').val(data.opt_id);
$('#editMatrixproduktOptionen').find('#matrixprodukt_optionen_name').val(data.opt_name);
$('#editMatrixproduktOptionen').find('#matrixprodukt_optionen_articlenumber_suffix').val(data.opt_articlenumber_suffix);
$('#editMatrixproduktOptionen').find('#matrixprodukt_optionen_name_ext').val(data.opt_name_ext);
$('#editMatrixproduktOptionen').find('#matrixprodukt_optionen_sortierung').val(data.opt_sortierung);
$('#editMatrixproduktOptionen').find('#matrixprodukt_optionen_aktiv').prop("checked",data.opt_aktiv==1?true:false);
App.loading.close();
$("#editMatrixproduktOptionen").dialog('open');
}
});
} else {
MatrixproduktOptionenReset();
$("#editMatrixproduktOptionen").dialog('open');
}
}
function updateLiveTableOptionen(i) {
var oTableL = $('#matrixprodukt_eigenschaftenoptionen').dataTable();
var tmp = $('.dataTables_filter input[type=search]').val();
oTableL.fnFilter('%');
//oTableL.fnFilter('');
oTableL.fnFilter(tmp);
}
function MatrixproduktOptionenDelete(id) {
var conf = confirm('Wirklich löschen?');
if (conf) {
$.ajax({
url: 'index.php?module=matrixprodukt&action=optionenlist&cmd=optionendelete',
data: {
id: id
},
method: 'post',
dataType: 'json',
beforeSend: function() {
App.loading.open();
},
success: function(data) {
if(data.status == 1){
updateLiveTableOptionen();
}else{
alert(data.statusText);
}
App.loading.close();
}
});
}
return false;
}
</script>

346
www/pages/matrixprodukt.php Normal file
View File

@ -0,0 +1,346 @@
<?php
/*
* SPDX-FileCopyrightText: 2023 Andreas Palm
* SPDX-License-Identifier: LicenseRef-EGPL-3.1
*/
use Xentral\Components\Http\JsonResponse;
use Xentral\Components\Http\Request;
use Xentral\Components\Http\Response;
use Xentral\Modules\MatrixProduct\Data\Group;
use Xentral\Modules\MatrixProduct\Data\Option;
use Xentral\Modules\MatrixProduct\MatrixProductService;
class Matrixprodukt
{
private ApplicationCore $app;
private MatrixProductService $service;
private Request $request;
const MODULE_NAME = 'MatrixProduct';
public function __construct(ApplicationCore $app, bool $intern = false)
{
$this->app = $app;
if ($intern)
return;
if (!$this->app instanceof Application)
return;
$this->service = $this->app->Container->get('MatrixProductService');
$this->request = $this->app->Container->get('Request');
$this->app->ActionHandlerInit($this);
$this->app->ActionHandler("list", "ActionList");
$this->app->ActionHandler("optionenlist", "ActionOptionList");
$this->app->ActionHandler("artikel", "ActionArticle");
$this->app->ActionHandlerListen($app);
}
private function createMenu(): void
{
$this->app->erp->MenuEintrag("index.php?module=matrixprodukt&action=list", "&Uuml;bersicht");
}
public function Install()
{
}
public function TableSearch(&$app, $name, $erlaubtevars)
{
switch ($name) {
case "matrixprodukt_groups":
$allowed['matrixprodukt_list'] = array('list');
$heading = array('', 'Name', 'Name (extern)', 'Men&uuml;');
$width = array('1%', '30%', '30%', '1%'); // Fill out manually later
$findcols = array('mg.id', 'mg.name', 'mg.name_ext');
$searchsql = array('mg.name', 'mg.name_ext');
$menu = "<table><tr><td nowrap>"
. "<img class=\"vueAction\" data-action=\"groupEdit\" data-group-id=\"%value%\" src=\"./themes/{$app->Conf->WFconf['defaulttheme']}/images/edit.svg\">&nbsp;"
. "<a href=\"index.php?module=matrixprodukt&action=optionenlist&id=%value%\">"
. "<img src=\"themes/{$app->Conf->WFconf['defaulttheme']}/images/forward.svg\"></a>&nbsp;"
. "<img class=\"vueAction\" data-action=\"groupDelete\" data-group-id=\"%value%\" src=\"themes/{$app->Conf->WFconf['defaulttheme']}/images/delete.svg\">"
. "</td></tr></table>";
$sql = "SELECT SQL_CALC_FOUND_ROWS mg.id, mg.id, mg.name, mg.name_ext, mg.id FROM matrixprodukt_eigenschaftengruppen mg";
$where = "1";
$count = "SELECT count(DISTINCT id) FROM matrixprodukt_eigenschaftengruppen WHERE $where";
break;
case "matrixprodukt_options":
$id = $this->app->Secure->GetGET('id');
$heading = array('', 'Name', 'Name (extern)', 'Men&uuml;');
$width = array('1%', '30%', '30%', '1%');
$findcols = array('mo.id', 'mo.name', 'mo.name_ext');
$searchsql = array('mo.name', 'mo.name_ext');
$menu = "<table><tr><td nowrap>"
. "<img class=\"vueAction\" data-action=\"optionEdit\" data-option-id=\"%value%\" src=\"./themes/{$app->Conf->WFconf['defaulttheme']}/images/edit.svg\">&nbsp;"
. "<img class=\"vueAction\" data-action=\"optionDelete\" data-option-id=\"%value%\" src=\"themes/{$app->Conf->WFconf['defaulttheme']}/images/delete.svg\">"
. "</td></tr></table>";
$sql = "SELECT SQL_CALC_FOUND_ROWS mo.id, mo.id, mo.name, mo.name_ext, mo.id FROM matrixprodukt_eigenschaftenoptionen mo";
$where = "mo.gruppe = $id";
$count = "SELECT count(DISTINCT mo.id) FROM matrixprodukt_eigenschaftenoptionen mo WHERE $where";
break;
case "matrixprodukt_article_groups":
$id = $this->app->Secure->GetGET('id');
$heading = array('', 'Name', 'Name (extern)', 'Men&uuml;');
$width = array('1%', '30%', '30%', '1%'); // Fill out manually later
$findcols = array('mga.id', 'mga.name', 'mga.name_ext');
$searchsql = array('mga.name', 'mga.name_ext');
$menu = "<table><tr><td nowrap>"
. "<img class=\"vueAction\" data-action=\"groupEdit\" data-group-id=\"%value%\" data-article-id=\"$id\" src=\"./themes/{$app->Conf->WFconf['defaulttheme']}/images/edit.svg\">&nbsp;"
. "<a href=\"index.php?module=matrixprodukt&action=artikel&id=$id&sid=%value%\">"
. "<img src=\"themes/{$app->Conf->WFconf['defaulttheme']}/images/forward.svg\"></a>&nbsp;"
. "<img class=\"vueAction\" data-action=\"groupDelete\" data-group-id=\"%value%\" data-article-id=\"$id\" src=\"themes/{$app->Conf->WFconf['defaulttheme']}/images/delete.svg\">"
. "</td></tr></table>";
$sql = "SELECT SQL_CALC_FOUND_ROWS mga.id, mga.id, mga.name, mga.name_ext, mga.id FROM matrixprodukt_eigenschaftengruppen_artikel mga";
$where = "mga.artikel = $id";
$count = "SELECT count(DISTINCT mga.id) FROM matrixprodukt_eigenschaftengruppen_artikel mga WHERE $where";
break;
case "matrixprodukt_article_options":
$id = $this->app->Secure->GetGET('id');
$groupId = $this->app->Secure->GetGET('sid');
$heading = array('', 'Name', 'Name (extern)', 'Men&uuml;');
$width = array('1%', '30%', '30%', '1%');
$findcols = array('moa.id', 'moa.name', 'moa.name_ext');
$searchsql = array('moa.name', 'moa.name_ext');
$menu = "<table><tr><td nowrap>"
. "<img class=\"vueAction\" data-action=\"optionEdit\" data-option-id=\"%value%\" data-article-id=\"$id\" src=\"./themes/{$app->Conf->WFconf['defaulttheme']}/images/edit.svg\">&nbsp;"
. "<img class=\"vueAction\" data-action=\"optionDelete\" data-option-id=\"%value%\" data-article-id=\"$id\" src=\"themes/{$app->Conf->WFconf['defaulttheme']}/images/delete.svg\">"
. "</td></tr></table>";
$sql = "SELECT SQL_CALC_FOUND_ROWS moa.id, moa.id, moa.name, moa.name_ext, moa.id FROM matrixprodukt_eigenschaftenoptionen_artikel moa";
$where = "moa.gruppe = $groupId";
$count = "SELECT count(DISTINCT moa.id) FROM matrixprodukt_eigenschaftenoptionen_artikel moa WHERE $where";
break;
case "matrixprodukt_variants":
$id = $this->app->Secure->GetGET('id');
$groups = $this->app->DB->SelectPairs("SELECT id, name FROM matrixprodukt_eigenschaftengruppen_artikel WHERE artikel = $id");
$heading[] = 'Artikel';
$width[] = '5%';
$nameColumns = [];
$optIdColumns = [];
$optNameColumns = [];
$optFrom = [];
$optWhere = [];
foreach ($groups as $groupId => $groupName) {
$heading[] = $groupName;
$width[] = '5%';
$nameColumns[] = "name_$groupId";
$optIdColumns[] = "moa_$groupId.id";
$optNameColumns[] = "moa_$groupId.name as name_$groupId";
$optFrom[] = "matrixprodukt_eigenschaftenoptionen_artikel moa_$groupId";
$optWhere[] = "moa_$groupId.artikel = $id AND moa_$groupId.gruppe = $groupId";
}
$heading[] = 'Men&uuml;';
$width[] = '1%';
$findcols = array_merge($nameColumns, ['nummer']);
$searchsql = $nameColumns;
$menu = "<table><tr><td nowrap>"
. "<img class=\"vueAction\" data-action=\"variantEdit\" data-variant-id=\"%value%\" data-article-id=\"$id\" src=\"./themes/{$app->Conf->WFconf['defaulttheme']}/images/edit.svg\">&nbsp;"
. "<img class=\"vueAction\" data-action=\"variantDelete\" data-variant-id=\"%value%\" data-article-id=\"$id\" src=\"themes/{$app->Conf->WFconf['defaulttheme']}/images/delete.svg\">"
. "</td></tr></table>";
$optsqlIdCols = join(',', $optIdColumns);
$optsqlNameCols = join(',', $optNameColumns);
$optsqlFrom = join(' JOIN ', $optFrom);
$optsqlWhere = join(' AND ', $optWhere);
$optsql = "SELECT CONCAT_WS(',', $optsqlIdCols) idlist, $optsqlNameCols
FROM $optsqlFrom WHERE $optsqlWhere";
$artsql = "SELECT a.id, a.nummer, group_concat(mota.option_id order by mota.option_id separator ',') idlist
FROM matrixprodukt_optionen_zu_artikel mota
JOIN artikel a ON mota.artikel = a.id WHERE a.variante_von = $id group by mota.artikel";
$sqlNameCols = join(',', $nameColumns);
$sql = "SELECT SQL_CALC_FOUND_ROWS art.id, art.nummer, $sqlNameCols, art.id
FROM ($artsql) art
LEFT OUTER JOIN ($optsql) opts ON opts.idlist = art.idlist";
$where = "1";
//$count = "SELECT count(DISTINCT moa.id)
// FROM matrixprodukt_eigenschaftengruppen_artikel mga
// LEFT OUTER JOIN matrixprodukt_eigenschaftenoptionen_artikel moa on moa.gruppe = mga.id WHERE $where";
break;
}
$erg = false;
foreach ($erlaubtevars as $k => $v) {
if (isset($$v)) {
$erg[$v] = $$v;
}
}
return $erg;
}
public function ActionList()
{
$cmd = $this->app->Secure->GetGET('cmd');
switch ($cmd) {
case "edit":
$id = intval($this->app->Secure->GetGET('groupId'));
$group = $this->service->GetGlobalGroupById($id);
if (!$group)
return JsonResponse::NotFound();
$group->project = $this->app->DB->SelectRow("SELECT id, abkuerzung, name FROM projekt WHERE id = $group->projectId");
return new JsonResponse($group);
case "save":
$json = $this->request->getJson();
$group = new Group($json->name, $json->id ?? null, $json->active ?? false, $json->nameExternal ?? '', $json->project->id ?? 0, $json->required ?? false);
$this->service->SaveGlobalGroup($group);
return JsonResponse::NoContent();
case "delete":
$json = $this->request->getJson();
$this->service->DeleteGlobalGroup($json->groupId);
return JsonResponse::NoContent();
case "selectoptions":
$result = [];
$sql = "SELECT mg.id groupid, mg.name groupname, mo.id optionid, mo.name optionname FROM matrixprodukt_eigenschaftengruppen mg JOIN matrixprodukt_eigenschaftenoptionen mo ON mo.gruppe = mg.id WHERE mg.aktiv = 1 AND mo.aktiv = 1";
foreach ($this->app->DB->SelectArr($sql) as $row) {
$groupid = $row['groupid'];
if (!in_array($groupid, $result)) {
$result[$groupid]['id'] = $groupid;
$result[$groupid]['name'] = $row['groupname'];
}
$result[$groupid]['options'][] = ['id' => $row['optionid'], 'name' => $row['optionname']];
}
return new JsonResponse(array_values($result));
}
$this->createMenu();
$this->app->Tpl->Set('TABSADD', '<input type="button" class="neubutton vueAction" value="NEU" data-action="groupEdit">');
$this->app->YUI->TableSearch('TAB1', 'matrixprodukt_groups', "show", "", "", basename(__FILE__), __CLASS__);
$this->app->Tpl->Parse('PAGE', "matrixprodukt_list.tpl");
}
public function ActionOptionList()
{
$id = $this->request->get->getInt('id');
$cmd = $this->app->Secure->GetGET('cmd');
switch ($cmd) {
case "edit":
$id = intval($this->app->Secure->GetGET('optionId'));
$option = $this->service->GetGlobalOptionById($id);
if (!$option)
return JsonResponse::NotFound();
return new JsonResponse($option);
case "save":
$json = $this->request->getJson();
$option = new Option($json->name, $json->groupId, $json->id, $json->active ?? false,
$json->nameExternal ?? '', $json->sort ?? 0, $json->articleNumber ?? '',
$json->articleNumberSuffix ?? '');
$this->service->SaveGlobalOption($option);
return JsonResponse::NoContent();
case "delete":
$json = $this->request->getJson();
$this->service->DeleteGlobalOption($json->optionId);
return JsonResponse::NoContent();
}
$this->app->Tpl->Set('TABSADD', '<input type="button" class="neubutton vueAction" value="NEU" data-action="optionEdit" data-group-id="' . $id . '">');
$this->app->YUI->TableSearch('TAB1', 'matrixprodukt_options', "show", "", "", basename(__FILE__), __CLASS__);
$this->app->Tpl->Parse('PAGE', "matrixprodukt_optionen_list.tpl");
}
public function ActionArticle()
{
$cmd = $this->app->Secure->GetGET('cmd');
$articleModule = $this->app->erp->LoadModul('artikel');
$articleModule?->ArtikelMenu();
switch ($cmd) {
case "addoptions":
$json = $this->request->getJson();
$this->service->AddGlobalOptionsForArticle($json->articleId, $json->optionIds);
return JsonResponse::NoContent();
case "groupedit":
$groupId = intval($this->app->Secure->GetGET('groupId'));
if (!$groupId)
return JsonResponse::NotFound();
$group = $this->service->GetArticleGroupById($groupId);
$group->project = $this->app->DB->SelectRow("SELECT id, abkuerzung, name FROM projekt WHERE id = $group->projectId");
return new JsonResponse($group);
case "groupsave":
$json = $this->request->getJson();
$group = new Group($json->name, $json->groupId, $json->active ?? false, $json->nameExternal ?? '',
$json->project->id ?? 0, $json->required ?? false, $json->articleId, $json->sort ?? 0);
$this->service->SaveArticleGroup($group);
return JsonResponse::NoContent();
case "groupdelete":
$json = $this->request->getJson();
if (!$this->service->DeleteArticleGroup($json->groupId))
return JsonResponse::BadRequest(['error' => 'Die Gruppe wird noch von Variantenartikeln verwendet.']);
return JsonResponse::NoContent();
case "optionedit":
$optionId = intval($this->app->Secure->GetGET('optionId'));
$option = $this->service->GetArticleOptionById($optionId);
if (!$option)
return JsonResponse::NotFound();
return new JsonResponse($option);
case "optionsave":
$json = $this->request->getJson();
$option = new Option($json->name, $json->groupId, $json->optionId, $json->active ?? false,
$json->nameExternal ?? '', $json->sort ?? 0, '',
$json->articleNumberSuffix ?? '', 0, $json->articleId);
$this->service->SaveArticleOption($option);
return JsonResponse::NoContent();
case "optiondelete":
$json = $this->request->getJson();
if (!$this->service->DeleteArticleOption($json->optionId))
return JsonResponse::BadRequest(['error' => 'Die Option wird noch von Variantenartikeln verwendet.']);
return JsonResponse::NoContent();
case "variantedit":
$articleId = $this->request->get->getInt('articleId');
$variantId = $this->request->get->getInt('variantId');
$groups = $this->service->GetArticleGroupsByArticleId($articleId);
$options = $this->service->GetArticleOptionsByArticleId($articleId);
$selected = $this->service->GetSelectedOptionIdsByVariantId($variantId);
$result = [];
foreach ($groups as $group) {
$result[$group->id] = [
'name' => $group->name,
'selected' => 0,
'options' => []
];
}
foreach ($options as $option) {
$result[$option->groupId]['options'][] = ['value' => $option->id, 'name' => $option->name];
if (in_array($option->id, $selected))
$result[$option->groupId]['selected'] = $option->id;
}
$variant = $this->app->DB->SelectRow("SELECT id, nummer, name_de FROM artikel WHERE id = $variantId");
return new JsonResponse([
'groups' => $result,
'variant' => $variant
]);
case "variantsave":
$json = $this->request->getJson();
$optionIds = [];
foreach ($json->groups as $group) {
if ($group->selected > 0)
$optionIds[] = intval($group->selected);
}
if (empty($optionIds))
return JsonResponse::BadRequest();
$res = $this->service->SaveVariant($json->articleId, $json->variant->id, $optionIds, $json->variantId);
if ($res === true)
return JsonResponse::NoContent();
return new JsonResponse([$res], Response::HTTP_BAD_REQUEST);
case "variantdelete":
$json = $this->request->getJson();
$this->service->DeleteVariant($json->variantId);
return JsonResponse::NoContent();
case "acarticles":
$query = $this->app->Secure->GetGET('query');
$result = $this->app->DB->SelectArr("SELECT id, nummer, name_de FROM artikel WHERE (nummer LIKE '%$query%' OR name_de LIKE '%$query%') AND geloescht = 0");
return new JsonResponse($result);
}
$groupId = $this->app->Secure->GetGET('sid');
if (empty($groupId)) {
$this->app->YUI->TableSearch('TAB1', 'matrixprodukt_article_groups', "show", "", "", basename(__FILE__), __CLASS__);
$this->app->Tpl->Set('ADDEDITFUNCTION', 'groupEdit');
} else {
$articleId = $this->app->Secure->GetGET('id');
$this->app->erp->MenuEintrag("index.php?module=matrixprodukt&action=artikel&id=$articleId", 'Zur&uuml;ck zur Gruppen&uuml;bersicht');
$this->app->YUI->TableSearch('TAB1', 'matrixprodukt_article_options', "show", "", "", basename(__FILE__), __CLASS__);
$this->app->Tpl->Set('SID', $groupId);
$this->app->Tpl->Set('ADDEDITFUNCTION', 'optionEdit');
}
$this->app->YUI->TableSearch('TAB2', 'matrixprodukt_variants', "show", "", "", basename(__FILE__), __CLASS__);
$this->app->Tpl->Parse('PAGE', "matrixprodukt_article.tpl");
}
}

View File

@ -1,2 +1,4 @@
@layer reset {
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}
/*# sourceMappingURL=normalize.min.css.map */
}

View File

@ -1418,7 +1418,7 @@ ul.menu.merged[data-items='25'] > li { width: 4%; }
}
/* Clear Floated Elements
/* Clear Floated Elements
- - - - - - - - - - - - - - - - - - - - - - */
.clear {
@ -1725,7 +1725,7 @@ fieldset.usersave div.filter-item > label {
}
/* Calendar
/* Calendar
- - - - - - - - - - - - - - - - - - - - - - */

View File

@ -1,3 +1,10 @@
<!--
SPDX-FileCopyrightText: 2023 Andreas Palm
SPDX-FileCopyrightText: 2019 Xentral ERP Software GmbH, Fuggerstrasse 11, D-86150 Augsburg
SPDX-License-Identifier: LicenseRef-EGPL-3.1
-->
<!DOCTYPE html>
<html lang="de">
<head>
@ -97,6 +104,7 @@ $(document).ready(function() {
</style>
[FINALCSSLINKS]
[MODULEJAVASCRIPTHEAD]
[JAVASCRIPTMODULES]
[MODULESTYLESHEET]
</head>
<body class="[LAYOUTFIXMARKERCLASS]" data-module="[MODULE]" data-action="[ACTION]" data-version="[REVISION]">