Index: /changelog
===================================================================
--- /changelog	(revision 8279)
+++ /changelog	(revision 8280)
@@ -580,2 +580,6 @@
 #4.4.4
 - Bugfix: EAN bei Varianten auch ohne Produktartikelmodul anzeigbar wenn gefÃŒllt
+- Feature: Produktkombinationen fÃŒr Varianten
+- Feature: Productcodemanagement
+- Feature: Positionsnummer fÃŒr Produktgruppen
+- Feature: Markierung wenn Produktgruppe aktiven Rabatt hat in ProduktgruppenÃŒbersicht
Index: /controller/wpsg_ProduktController.class.php
===================================================================
--- /controller/wpsg_ProduktController.class.php	(revision 8279)
+++ /controller/wpsg_ProduktController.class.php	(revision 8280)
@@ -1300,5 +1300,5 @@
 
 				}
-				 
+				
 				$this->shop->callMods('produkt_save_before', array(&$data));
 				
Index: /controller/wpsg_ShopController.class.php
===================================================================
--- /controller/wpsg_ShopController.class.php	(revision 8279)
+++ /controller/wpsg_ShopController.class.php	(revision 8280)
@@ -4759,4 +4759,8 @@
 
 		/**
+		 *
+		 * DEPRECATED: Lieber direkt ÃŒber wpsg_order_product oder wpsg_product gehen
+		 * Hier ist auch kein Productkey verwendet also wird der Variantenname nicht gezogen
+		 *
 		 * Gibt den Produktnamen als HTML zurÃŒck
 		 * - Wenn Detailname, dann der Detailname
@@ -4765,9 +4769,7 @@
 		 * @param int $product_id ID des Produktes
 		 */
-		public function getProductName($product_id, $detailname = false)
-		{
+		public function getProductName($product_id, $detailname = false) {
 
 			$oProduct = wpsg_product::getInstance($product_id);
-
 			return $oProduct->getProductName($detailname);
 
Index: /model/wpsg_order_product.class.php
===================================================================
--- /model/wpsg_order_product.class.php	(revision 8279)
+++ /model/wpsg_order_product.class.php	(revision 8280)
@@ -85,8 +85,14 @@
 		} // public function getNumber()
 		
-		public function getProduct()
-		{
-			
-			return $this->shop->cache->loadProductObject($this->getProductId());
+		/**
+		 * @return wpsg_product
+		 */
+		public function getProduct() {
+			
+			$oProduct = $this->shop->cache->loadProductObject($this->getProductId());
+			
+			if ($this->getProductKey() !== null) $oProduct->setProductKey($this->getProductKey());
+			
+			return $oProduct;
 			
 		} // public function getProduct()
@@ -118,4 +124,5 @@
 	            $strName = $this->getProduct()->getProductName();
 
+				/*
 	            if ($this->shop->hasMod('wpsg_mod_productvariants') && preg_match('/pv_(.*)/', $this->getProductKey())) {
 
@@ -125,4 +132,5 @@
 
 	            }
+				*/
 
 			} catch (\Exception $e) {
@@ -165,21 +173,19 @@
         }
 
-		public function getProductKey()
-		{
+		public function getProductKey() {
 			
 			$var_key = $this->mod_vp_varkey;
 			
-			if (wpsg_isSizedString($var_key))
-			{
+			if (wpsg_isSizedString($var_key)) {
 				
 				return $this->mod_vp_varkey;
 				
-			}
-			else
-			{
+			} else {
 				
 				return $this->productkey;
 				
 			}
+			
+			return null;
 			
 		} // public function getProductKey()
Index: /model/wpsg_product.class.php
===================================================================
--- /model/wpsg_product.class.php	(revision 8279)
+++ /model/wpsg_product.class.php	(revision 8280)
@@ -81,4 +81,63 @@
 		}
 		
+		public function getCombination(): ?\wpsg\wpsg_combination {
+			
+			if ($this->getProductKey() !== null) {
+				
+				return \wpsg\wpsg_combination::findByProductAndKey($this->getId(), $this->getProductKey());
+				
+			}
+			
+			return null;
+			
+		}
+		
+		/**
+		 * Gibt den Namen des Produktes zurÃŒck
+		 */
+		public function getProductName($detailname = false) {
+
+			$oCombination = $this->getCombination();
+			
+			if ($oCombination !== null) {
+				
+				$strDetailname = $oCombination->getMeta('detailname');
+				
+				if (wpsg_isSizedString($strDetailname) && $detailname === true) {
+
+					$name_return = $strDetailname;
+	
+				} else {
+	
+					$name_return = $oCombination->getMeta('name');
+	
+				}
+				
+				if ($name_return !== '') return $name_return;
+				
+			}
+			
+			$strDetailname = $this->detailname;
+			
+			if (wpsg_isSizedString($strDetailname) && $detailname === true) {
+
+				$strName = $strDetailname;
+
+			} else {
+
+				$strName = $this->name;
+
+			}
+
+			if (strpos($strName, '^') !== false) {
+
+				$strName = preg_replace('/\^/', '<span class="wpsg_upper">', $strName).'</span>';
+
+			}
+
+			return $strName;
+
+		}
+		
 		/**
 		 * Gibt den Code zurÃŒck, fÃŒr $code_key kann folgendes verwendet werden:
@@ -113,10 +172,13 @@
 				if ($code === '') return null;
 				
-				$itemprop = null;
-				
-				switch ($code) {
-					
-					case 'sku': $itemprop = 'ean'; break;
-					case 'ean': $itemprop = 'gtin13'; break;
+				$code_config = wpsg_product::getProductCodeConfig()[$code_key]??null;
+				
+				if ($code_config !== null) {
+					
+					$itemprop = $code_config['itemprop']??null;
+					
+				} else {
+					
+					$itemprop = null;
 					
 				}
@@ -294,36 +356,4 @@
 
 		}
-
-		/**
-		 * Gibt den Namen des Produktes zurÃŒck
-		 */
-		public function getProductName($detailname = false)
-		{
-
-			$strDetailname = $this->detailname;
-
-			if (wpsg_isSizedString($strDetailname) && $detailname === true)
-			{
-
-				$strName = $strDetailname;
-
-			}
-			else
-			{
-
-				$strName = $this->name;
-
-			}
-
-			if (strpos($strName, '^') !== false)
-			{
-
-				$strName = preg_replace('/\^/', '<span class="wpsg_upper">', $strName).'</span>';
-
-			}
-
-			return $strName;
-
-		} // public function getProductName()
 
 		/**
@@ -1356,4 +1386,40 @@
 
         }
-
-	} // class wpsg_product extends wpsg_model
+		
+		public static function getProductCodeConfig(): array {
+		 
+			return [
+				'ean' => [
+					'label' => 'EAN (European Article Number)',
+					'itemprop' => 'gtin13'
+				],
+				'gtin' => [
+					'label' => 'GTIN (Global Trade Item Number)'
+				],
+				'mpn' => [
+					'label' => 'MPN (Manufacturer Part Number)'
+				],
+				'pzn' => [
+					'label' => 'PZN (Pharmazentralnummer)'
+				],
+				'sku' => [
+					'label' => 'SKU (Stock Keeping Unit)',
+					'itemprop' => 'ean'
+				],
+				'upc' => [
+					'label' => 'UPC (Universal Product Code)'
+				],
+				'isbn' => [
+					'label' => 'ISBN (International Standard Book Number)'
+				],
+				'issn' => [
+					'label' => 'ISSN (International Standard Serial Number)'
+				],
+				'isin' => [
+					'label' => 'ISIN (International Securities Identification Number)'
+				]
+			];
+			
+		}
+
+	}
Index: /mods/mod_productgroups/wpsg_productgroup.php
===================================================================
--- /mods/mod_productgroups/wpsg_productgroup.php	(revision 8279)
+++ /mods/mod_productgroups/wpsg_productgroup.php	(revision 8280)
@@ -80,4 +80,31 @@
 
         }
+		
+		public function hasActiveDiscount(): bool {
+			
+			if (wpsg_ShopController::getShop()->get_option("wpsg_mod_discount_productgroupdiscount") != "1") {
+				
+				return false;
+				
+			} else {
+				
+				$rabatt = explode("_", $this->__get('rabatt'));
+				
+				if (sizeof($rabatt) <= 0 || $rabatt[0] != '1' || !preg_match("/\d{2}\.\d{2}\.\d{4}/", $rabatt[1]) || !preg_match("/\d{2}\.\d{2}\.\d{4}/", $rabatt[2]) || !is_float((float)$rabatt[3])) {
+	
+					return false;
+	
+				}
+		
+				$rabatt_value = $rabatt[3];
+				$rabatt_start = strtotime($rabatt[1]);
+				$rabatt_end = strtotime($rabatt[2]);
+
+				// im Zeitrahmen?
+				if (time() >= $rabatt_start && time() <= $rabatt_end) { return true; } else { return false; }
+				
+			}
+			
+		}
 	    
         /* Statische Funktionen */
@@ -224,5 +251,5 @@
 
 			}
-			else $strQueryORDER = " PG.`id` ";
+			else $strQueryORDER = " PG.`pos` ";
 
 			// Richtung
Index: /mods/mod_productvariants/wpsg_combination.class.php
===================================================================
--- /mods/mod_productvariants/wpsg_combination.class.php	(revision 8279)
+++ /mods/mod_productvariants/wpsg_combination.class.php	(revision 8280)
@@ -31,4 +31,35 @@
 		}
 		
+		public function getVariantUrl(): string {
+			
+			$product_url = $this->getProduct()->getProductURL();
+			
+			if (strpos($product_url, '?') === false) $product_url .= '?set_product_key=';
+			else $product_url .= '&set_product_key=';
+			
+			$product_url .= 'pv_'.$this->getProductId().'|'.$this->getVarKey();
+			
+			return $product_url;
+			
+		}
+		
+		public function getVarKey(): string {
+			
+			return $this->__get('var_key');
+			
+		}
+		
+		public function getProductId(): int {
+			
+			return intval($this->__get('product_id'));
+			
+		}
+		
+		public function getProduct(): \wpsg_product {
+			
+			return \wpsg_product::getInstance($this->getProductId());
+			
+		}
+		
 		public function toJSON(): array {
 			
@@ -39,15 +70,22 @@
 			$json['label'] = $this->getLabel();
 			$json['loaded'] = true;
+			$json['url'] = $this->getVariantUrl();
+			$json['anr'] = $this->getMeta('anr');
+			$json['name'] = $this->getMeta('name');
+			$json['detailname'] = $this->getMeta('detailname');
+			$json['arProductCodes'] = [];
 			
-			$json['anr'] = $this->getMeta('anr', false, '');
-			$json['ean'] = $this->getMeta('ean', false, '');
-			$json['mpn'] = $this->getMeta('mpn', false, '');
-			$json['pzn'] = $this->getMeta('pzn', false, '');
-			$json['sku'] = $this->getMeta('sku', false, '');
-			$json['gtin'] = $this->getMeta('gtin', false, '');
-			$json['upc'] = $this->getMeta('upc', false, '');
-			$json['isbn'] = $this->getMeta('isbn', false, '');
-			$json['issn'] = $this->getMeta('issn', false, '');
-			$json['isin'] = $this->getMeta('isin', false, '');
+			foreach (\wpsg_product::getProductCodeConfig() as $code_key => $code_data) {
+				
+				$product_code = $this->getMeta($code_key, false, '');
+				
+				$json['arProductCodes'][$code_key] = [
+					'label' => $code_data['label'],
+					'code_key' => $code_key,
+					'code' => trim($product_code),
+					'set' => trim($product_code) !== ''
+				];
+				
+			}
 			
 			return $json;
Index: /mods/wpsg_mod_export.class.php
===================================================================
--- /mods/wpsg_mod_export.class.php	(revision 8279)
+++ /mods/wpsg_mod_export.class.php	(revision 8280)
@@ -276,4 +276,6 @@
             {
 
+				//die('<pre>'.nl2br(file_get_contents($arFiles[0][0])));
+				
                 if ($arFiles[0][3] == self::FORMAT_CSV) header("Content-Type: text/csv");
                 else header("Content-Type: text/xml");
@@ -911,5 +913,5 @@
 							
 							foreach ($oOrder->getOrderProducts() as $oOrderProduct) {
-								
+					 
 								$arValues[] = $this->getValue($f, $profil['field_delimiter'], $order_id, $oOrderProduct->getProductId(), $oOrder->getCustomer()->getId(), $oOrderProduct->getId());
 								
@@ -1674,7 +1676,5 @@
                         'produkt_weight' => __('Gewicht', 'wpsg'),
                         'produkt_anr' => __('Artikelnummer', 'wpsg'),
-                        'produkt_menge' => __('Anzahl', 'wpsg'),
-                        'produkt_ean' => __('EAN', 'wpsg'),
-                        'produkt_gtin' => __('GTIN', 'wpsg')
+                        'produkt_menge' => __('Anzahl', 'wpsg')
                     )
                 ),
@@ -1703,4 +1703,10 @@
                 ),
             );
+			
+			foreach (wpsg_product::getProductCodeConfig() as $code_key => $code_data) {
+				
+				$this->fields[20]['fields']['product_code_'.$code_key] = $code_data['label'];
+				
+			}
 
             if ($this->shop->hasMod('wpsg_mod_stock')) {
@@ -1869,4 +1875,7 @@
         public function getValue($f, $profil_separator, $o_id, $p_id = null, $customer_id = null, $order_product_id = null) {
  
+			$oProduct = null;
+			$oOrderProduct = null;
+			
             $field_value = $f['value_key'];
 
@@ -1965,5 +1974,5 @@
 			 
 			if ($order_product_id !== null) {
-				 
+		
 				if (isset($this->arCache['order_product_id'][$order_product_id])) {
 					
@@ -2222,5 +2231,5 @@
                 case 'produkt_id': $return = $oProduct->getId(); break;
                 case 'produkt_index': $return = $oOrderProduct->getProductIndex(); break;
-                case 'produkt_name': $return = $oProduct->getProductName(); break;
+				case 'produkt_name': if (!isset($oProduct)) throw new \Exception(); $return = $oProduct->getProductName(); break;
                 case 'product_url': $return = $oProduct->getProductURL(); break;
                 case 'product_picture_url':
@@ -2365,5 +2374,33 @@
 
                 default:
-
+					
+					$return = null;
+					
+					foreach (wpsg_product::getProductCodeConfig() as $code_key => $code_data) {
+						
+						if ($field_value === 'product_code_'.$code_key) {
+							
+							if ($oOrderProduct !== null) {
+							 
+								$pc_data = $oOrderProduct->getProduct()->getProductCode($code_key);
+								
+								if ($pc_data === null) $return = '';
+								else $return = $pc_data['code'];
+							
+							} else if (isset($oProduct)) {
+								
+								$pc_data = $oProduct->getProductCode($code_key);
+								
+								if ($pc_data === null) $return = '';
+								else $return = $pc_data['code'];
+								
+							} else throw new \Exception();
+							
+						}
+						
+					}
+					
+					if ($return !== null) break;
+					
                     if (preg_match('/ov_\d+/', $field_value)) {
 
@@ -2377,13 +2414,17 @@
                         
                         // Produktvariable (index kann auch 0 sein)
-                        if (isset($order['pvars'][$oOrderProduct->getProductIndex()])) {
-
-                            $return = $order['pvars'][$oOrderProduct->getProductIndex()][$produktvarid]??'';
-
-                        } else {
-
-                            $return = $order['pvars'][$oOrderProduct->getProductKey()][$produktvarid]??'';
-
-                        }
+						if (isset($oOrderProduct)) {
+							
+							if (isset($order['pvars'][$oOrderProduct->getProductIndex()])) {
+								
+								$return = $order['pvars'][$oOrderProduct->getProductIndex()][$produktvarid] ?? '';
+								
+							} else {
+								
+								$return = $order['pvars'][$oOrderProduct->getProductKey()][$produktvarid] ?? '';
+								
+							}
+							
+						}
 
                     } else if (preg_match('/pa_\d+/', $field_value)) {
@@ -2438,5 +2479,5 @@
             }
  
-            $return = substr($return, 0, intval($f['size']));
+            $return = substr($return??'', 0, intval($f['size']));
 
             return $return;
Index: /mods/wpsg_mod_productgroups.class.php
===================================================================
--- /mods/wpsg_mod_productgroups.class.php	(revision 8279)
+++ /mods/wpsg_mod_productgroups.class.php	(revision 8280)
@@ -45,4 +45,5 @@
 		   		stock_value INT(11) NOT NULL,
 				lang TEXT NOT NULL,
+				pos INT (11) NOT NULL DEFAULT 0,
 		   		PRIMARY KEY  (id)
 		   	) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;";
@@ -701,4 +702,5 @@
 			
 			wpsg_checkRequest('name', [WPSG_SANITIZE_TEXTFIELD], __('Produktgruppenname'), $data);
+			wpsg_checkRequest('pos', [WPSG_SANITIZE_INT], __('Position'), $data);
 			wpsg_checkRequest('template_file', [WPSG_SANITIZE_VALUES, $arTemplateFiles], __('Template'), $data);
 			wpsg_checkRequest('infopage', [WPSG_SANITIZE_PAGEID], __('1nfo Seite'), $data);
Index: /mods/wpsg_mod_productvariants.class.php
===================================================================
--- /mods/wpsg_mod_productvariants.class.php	(revision 8279)
+++ /mods/wpsg_mod_productvariants.class.php	(revision 8280)
@@ -146,5 +146,5 @@
 
 		public function be_ajax() {
-
+			
 			$action = $_REQUEST['subaction'].'Action';
 
@@ -1286,4 +1286,116 @@
 		}
 
+		private function convert_1Action(): void {
+			
+			$arProductCol = $this->db->fetchAssocField("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '".WPSG_TBL_PRODUCTS."' ");
+			
+			// EAN / GTIN aus dem Produktartikel Modul in die fixen Spalten
+			if ($this->shop->hasMod('wpsg_mod_produktartikel')) {
+			 
+				if (in_array('ean', $arProductCol)) {
+					
+					$arProductData = $this->db->fetchAssoc("SELECT `id`, `ean` FROM `".WPSG_TBL_PRODUCTS."` WHERE `ean` != '' ");
+					
+					if (sizeof($arProductData) > 0) {
+						
+						foreach ($arProductData as $product_data) {
+							
+							$oProduct = wpsg_product::getInstance(intval($product_data['id']));
+							$oProduct->setMeta('ean', $product_data['ean']);
+							
+							$this->db->UpdateQuery(WPSG_TBL_PRODUCTS, [
+								'ean' => ''
+							], " `id` = '".intval($oProduct->getId())."' ");
+							
+						}
+						
+					}
+					
+				}
+				
+				if (in_array('gtin', $arProductCol)) {
+					
+					$arProductData = $this->db->fetchAssoc("SELECT `id`, `gtin` FROM `".WPSG_TBL_PRODUCTS."` WHERE `gtin` != '' ");
+					
+					if (sizeof($arProductData) > 0) {
+						
+						foreach ($arProductData as $product_data) {
+							
+							$oProduct = wpsg_product::getInstance(intval($product_data['id']));
+							$oProduct->setMeta('gtin', $product_data['gtin']);
+							
+							$this->db->UpdateQuery(WPSG_TBL_PRODUCTS, [
+								'gtin' => ''
+							], " `id` = '".intval($oProduct->getId())."' ");
+							
+						}
+						
+					}
+					
+				}
+				
+			}
+			
+			// ANR, EAN aus den Varianten
+			$arProductVariationCol = $this->db->fetchAssocField("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '".WPSG_TBL_PRODUCTS_VARIATION."' ");
+				
+			if (in_array('anr', $arProductVariationCol) && in_array('ean', $arProductVariationCol)) {
+				
+				$arProductVariationData = $this->db->fetchAssoc("SELECT DISTINCT `product_id` FROM `".WPSG_TBL_PRODUCTS_VARIATION."` WHERE `anr` != '' OR `ean` != '' ");
+				if (sizeof($arProductVariationData) > 0) {
+					
+					foreach ($arProductVariationData as $product_variation_data) {
+						
+						try {
+							
+							$oProduct = wpsg_product::getInstance(intval($product_variation_data['product_id']));
+							
+							$arPossibleKeys = $oProduct->getPossibleProductVariantKeys();
+							
+							foreach ($arPossibleKeys as $k => $v) {
+								
+								$oCombination = \wpsg\wpsg_combination::getByProductAndKey($oProduct->getId(), $v['var_key']);
+								
+								$arKeyCombi = explode('|', $v['var_key']);
+								$arVarVari = [];
+								
+								foreach ($arKeyCombi as $var_vari) {
+									
+									list($var_id, $vari_id) = array_map('intval', explode(':', $var_vari));
+									
+									$arVarVari[$var_id] = $vari_id;
+									
+								}
+								
+								$arAnr = [];
+								
+								foreach ($this->db->fetchAssocField("SELECT `variant_id` FROM `".WPSG_TBL_PRODUCTS_VARIANT."` WHERE `variant_id` IN (".implode(',', array_keys($arVarVari)).") AND `product_id` = '".intval($oProduct->getId())."' ORDER BY `pos` ASC ") as $var_id) {
+									
+									$arAnr[] = $this->db->fetchOne("SELECT `anr` FROM `".WPSG_TBL_PRODUCTS_VARIATION."` WHERE `variation_id` = '".intval($arVarVari[intval($var_id)])."' AND `product_id` = '".intval($oProduct->getId())."' ");
+									
+								}
+								
+								if ($oCombination->getMeta('anr', false, '') === '') $oCombination->setMeta('anr', trim(implode(' / ', $arAnr), '/ '));
+								
+							}
+							
+							$this->db->UpdateQuery(WPSG_TBL_PRODUCTS_VARIATION, [
+								'anr' => '',
+								'ean' => ''
+							], " `product_id` = '".intval($product_variation_data['product_id'])."' ");
+							
+						} catch (\Exception $e) { }
+						
+					}
+					
+				}
+				
+			}
+			
+			$this->shop->addBackendMessage(__('Es wurden alte Artikelnummern und EAN Nummern ÃŒberfÃŒhrt.', 'wpsg'));
+            $this->shop->redirect(WPSG_URL_WP.'wp-admin/admin.php?page=wpsg-Admin'); exit;
+			
+		}
+		
 		private function combinationAction(): void {
 			
@@ -1305,13 +1417,20 @@
 				
 					$oCombination->setMeta('anr', $data['combination']['anr']);
-					$oCombination->setMeta('ean', $data['combination']['ean']);
-					$oCombination->setMeta('mpn', $data['combination']['mpn']);
-					$oCombination->setMeta('pzn', $data['combination']['pzn']);
-					$oCombination->setMeta('sku', $data['combination']['sku']);
-					$oCombination->setMeta('gtin', $data['combination']['gtin']);
-					$oCombination->setMeta('upc', $data['combination']['upc']);
-					$oCombination->setMeta('isbn', $data['combination']['isbn']);
-					$oCombination->setMeta('issn', $data['combination']['issn']);
-					$oCombination->setMeta('isin', $data['combination']['isin']);
+					$oCombination->setMeta('name', $data['combination']['name']);
+					$oCombination->setMeta('detailname', $data['combination']['detailname']);
+					
+					foreach (($data['combination']['arProductCodes']??[]) as $code_key => $code_data) {
+						
+						if ($code_data['set'] === true) {
+							
+							$oCombination->setMeta($code_key, $code_data['code']);
+							
+						} else {
+							
+							$oCombination->setMeta($code_key, '');
+							
+						}
+						
+					}
 					
 				}
@@ -1332,4 +1451,5 @@
 						
 						$arPossibleKeys[$k]['id'] = intval(\wpsg\wpsg_combination::findByProductAndKey($oProduct->getId(), $v['var_key'])->getId());
+						$arPossibleKeys[$k]['url'] = \wpsg\wpsg_combination::getInstance($arPossibleKeys[$k]['id'])->getVariantUrl();
 						
 					} catch (\Throwable $e) { $arPossibleKeys[$k]['id'] = 0; }
@@ -2409,61 +2529,4 @@
 
 		} // public function getVariantenInfoArray($vari_key)
-
-		/**
-		 * Wird nach dem speichern des Produktes aus der saveAction des 
-		 * Produktcontrollers aufgerufen
-		 * 
-		*/
-		public function produkt_save(&$produkt_id) {
-			
-			$varis = $GLOBALS['wpsg_sc']->db->fetchAssoc("SELECT * FROM `".WPSG_TBL_PRODUCTS_VARIATION."` WHERE `product_id`='".wpsg_q($produkt_id)."' ");
-			
-			return;
-			//TODO
-			foreach ($varis as $v)
-			{
-				$im0 = unserialize($v['images']);
-                $im0['pic'] = trim($im0['pic'], ',');
-                $v['images'] = serialize($im0);
-				var_dump('ALT:'.$v['images'].'<br/>');
-                //die($v['images']);
-				$im1 = array();
-				$im1 = $im0;
-				//$im1['pic'] = array();
-				//$im1['picOrder'] = $im0['picOrder'];
-				//$im1['postid'] = array();
-				
-				$pids = explode(',', $im0['postid']);
-				$postid = array();
-				$pic = array();
-				foreach ($pids as $pid)
-				{
-					
-					$post = $GLOBALS['wpsg_sc']->db->fetchRow("SELECT * FROM `".$GLOBALS['wpdb']->prefix."posts` WHERE `ID` = '".wpsg_q($pid)."' ");
-					if (isset($post['ID']))
-					{
-                        //die($postid[count($postid) - 1].'/'.$post['ID']);
-                        if (($postid[count($postid) - 1]) != $post['ID']) {
-                            $postid[] = $post['ID'];
-                            $arrf = pathinfo($post['guid']);
-                            $file = $arrf['basename'];
-                            var_dump($file.'<br/>');
-                            $file = preg_replace('/\-(\d+)x(\d+)\./', '.', $file);
-                            $pic[] = $file;
-                        }
-					}
-				}
-				$im1['pic'] = implode(',', $pic);
-				$im1['postid'] = implode(',', $postid);
-				$images = serialize($im1);
-                var_dump('NEU:'.$images.'<br/>');
-                //die($images);
-				// Update WPSG_TBL_PRODUCTS_VARIATION
-				$data = array('images' => $images);
-				$GLOBALS['wpsg_sc']->db->UpdateQuery(WPSG_TBL_PRODUCTS_VARIATION, $data, "`id` = '".wpsg_q($v['id'])."'");
-				
-			}
-            //die();
-		}	// public function produkt_save(&$produkt_id)
 
         public function wpsg_mod_export_loadFields(&$arFields) { 
@@ -2588,5 +2651,59 @@
 
         }
-
-    } // class wpsg_mod_productvariants extends wpsg_mod_basic
-
+		
+		public function systemcheck(&$arData) {
+			
+			$bNeedConvert = false;
+
+			$arProductCol = $this->db->fetchAssocField("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '".WPSG_TBL_PRODUCTS."' ");
+			
+			// EAN / GTIN aus dem Produktartikel Modul in die fixen Spalten
+			if ($this->shop->hasMod('wpsg_mod_produktartikel')) {
+			 
+				if ($bNeedConvert === false && in_array('ean', $arProductCol)) {
+					
+					$arProductIDs = $this->db->fetchAssocField("SELECT `id` FROM `".WPSG_TBL_PRODUCTS."` WHERE `ean` != '' ");
+					if (sizeof($arProductIDs) > 0) $bNeedConvert = true;
+					
+				}
+					
+				if ($bNeedConvert === false && in_array('gtin', $arProductCol)) {
+					
+					$arProductIDs = $this->db->fetchAssocField("SELECT `id` FROM `".WPSG_TBL_PRODUCTS."` WHERE `gtin` != '' ");
+					if (sizeof($arProductIDs) > 0) $bNeedConvert = true;
+					
+				}
+				
+			}
+			
+			// ANR, EAN aus den Varianten
+			if (!$bNeedConvert === false) {
+			
+				$arProductVariationCol = $this->db->fetchAssocField("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '".WPSG_TBL_PRODUCTS_VARIATION."' ");
+				
+				if (in_array('anr', $arProductVariationCol) && in_array('ean', $arProductVariationCol)) {
+					
+					$arProductIDs = $this->db->fetchAssocField("SELECT `id` FROM `".WPSG_TBL_PRODUCTS_VARIATION."` WHERE `anr` != '' OR `ean` != '' ");
+					if (sizeof($arProductIDs) > 0) $bNeedConvert = true;
+					
+				}
+				
+			}
+			
+			if ($bNeedConvert === true) {
+			
+				$arData[] = [
+					'wpsg_mod_productvariants_convert_1',
+					wpsg_ShopController::CHECK_ERROR,
+					wpsg_translate(
+						__('Es gibt alte Artikelnummern / EAN in Varianten die falsch zugeordnet sind. Klicken Sie <a href="#1#">hier</a> um diese in die neue Struktur zu ÃŒberfÃŒhren.', 'wpsg'),
+						WPSG_URL_WP.'wp-admin/admin.php?page=wpsg-Admin&modul=wpsg_mod_productvariants&action=module&noheader=1&subaction=convert_1'
+					)
+				];
+				
+			}
+
+        }
+
+    }
+
Index: /mods/wpsg_mod_produktartikel.class.php
===================================================================
--- /mods/wpsg_mod_produktartikel.class.php	(revision 8279)
+++ /mods/wpsg_mod_produktartikel.class.php	(revision 8280)
@@ -697,12 +697,5 @@
 
         } // public function produkt_save_translation(&$produkt_id, &$trans_id)
-
-        public function produkt_save_before(&$produkt_data) {
-
-            $produkt_data['ean'] = wpsg_tf(wpsg_sinput("key", wpsg_getStr($_REQUEST['wpsg_mod_produktartikel']['ean']), "isFloat"));
-            $produkt_data['gtin'] = wpsg_tf(wpsg_sinput("key", wpsg_getStr($_REQUEST['wpsg_mod_produktartikel']['gtin']), "isFloat"));
-
-        } // public function produkt_save_before(&$produkt_data)
-
+ 
         public function produkt_save(&$product_id) {
 
Index: /mods/wpsg_mod_rechnungen.class.php
===================================================================
--- /mods/wpsg_mod_rechnungen.class.php	(revision 8279)
+++ /mods/wpsg_mod_rechnungen.class.php	(revision 8280)
@@ -1731,21 +1731,20 @@
 					 */
 					if (file_exists($FilePath.'/'.$r_id.'.pdf')) { 
-wpsg_debug('1'); 
-						$file = $FilePath.'/'.$r_id.'.pdf'; }
-					else if (file_exists($FilePath.'/'.$rnr.'.pdf')) { 
-wpsg_debug('2'); 
-						$file = $FilePath.'/'.$rnr.'.pdf'; }
-					else if (file_exists($FilePath.'/'.'R'.$r_id.'.pdf')) { 
-wpsg_debug('3'); 
-						$file = $FilePath.'/'.'R'.$r_id.'.pdf'; }
-					else { 
-wpsg_debug('4'); 
-						$file = $FilePath.'/'.'R'.$rnr.'.pdf'; }  	
-
-
-wpsg_debug($FilePath);
-wpsg_debug($file);
-wpsg_debug($rnr);
-
+ 
+						$file = $FilePath.'/'.$r_id.'.pdf';
+					
+					} else if (file_exists($FilePath.'/'.$rnr.'.pdf')) {
+ 
+						$file = $FilePath.'/'.$rnr.'.pdf';
+					
+					} else if (file_exists($FilePath.'/'.'R'.$r_id.'.pdf')) {
+						
+						$file = $FilePath.'/'.'R'.$r_id.'.pdf';
+						
+					} else {
+						
+						$file = $FilePath.'/'.'R'.$rnr.'.pdf';
+					
+					}
 
 					$file = $this->copyTemp($file, $rnr);
@@ -1772,9 +1771,4 @@
 					
 					}
-
-
-wpsg_debug($arFiles);
-die();
-			
 
 					$this->shop->sendMail($mail_text, $empfaenger, 'invoicemail', $arFiles, $order_id, false, $mail_html);
@@ -2067,15 +2061,9 @@
 		 * Kopiert eine Datei temporÃ€r und benennt sie anhand der Rechnugnsnummer um
 		 */
-		private function copyTemp($file, $rnr)
-		{
+		private function copyTemp($file, $rnr) {
 			
 			$path = $this->shop->getTempDir();
 
 			wpsg_copy($file, $path.'/'.$rnr.'.pdf');
-
-
-wpsg_debug('copyTemp');
-die();
-
 
 			return $path.'/'.$rnr.'.pdf';
Index: /views/mods/mod_productgroups/add.phtml
===================================================================
--- /views/mods/mod_productgroups/add.phtml	(revision 8279)
+++ /views/mods/mod_productgroups/add.phtml	(revision 8280)
@@ -70,4 +70,5 @@
                         <?php echo wpsg_drawForm_Select('template_file', __('Template', 'wpsg'), $this->view['templates'], $this->view['data']['template_file']); ?>
                         <?php echo wpsg_drawForm_Select('infopage', __('Info Seite', 'wpsg'), $this->view['pages'], $this->view['data']['infopage']); ?>
+                        <?php echo wpsg_drawForm_Input('pos', __('Positionsnummer (Sortierung, aufsteigend)', 'wpsg'), $this->view['data']['pos']); ?>
 
                         <div class="form-group form-group-sm ">
Index: /views/mods/mod_productgroups/index.phtml
===================================================================
--- /views/mods/mod_productgroups/index.phtml	(revision 8279)
+++ /views/mods/mod_productgroups/index.phtml	(revision 8280)
@@ -84,4 +84,5 @@
                         <th class="wpsg_order col_name" data-order="nr"><?php echo __("Name", 'wpsg'); ?></th>
                         <th class="wpsg_order col_template" data-order="template"><?php echo __("Template", 'wpsg'); ?></th>
+                        <th class="wpsg_order col_pos" data-order="template"><?php echo __("Position", 'wpsg'); ?></th>
                         <th class="wpsg_order col_products" data-order="products"><?php echo __("Produkte", 'wpsg'); ?></th>
                     </tr>
@@ -123,4 +124,7 @@
                         <td class="col_template">
                             <?php echo (($pg->template_file == "0")?'Individuelle Produkttemplates':$pg->template_file); ?>
+                        </td>
+						<td class="col_pos">
+                            <?php echo $pg->pos; ?>
                         </td>
                         <td class="col_products">
Index: /views/mods/mod_productgroups/list.phtml
===================================================================
--- /views/mods/mod_productgroups/list.phtml	(revision 8279)
+++ /views/mods/mod_productgroups/list.phtml	(revision 8280)
@@ -12,7 +12,15 @@
 ?>
 
-<?php $c = 0; foreach ((array)$this->view['data'] as $k => $g) { ?>
+<?php $c = 0; foreach ((array)$this->view['data'] as $k => $g) {
+
+	$oProductgroup = wpsg_productgroup::getInstance(intval($k));
+	
+	?>
 <?php //wpsg_debug($g) ?>
-<div class="wpsg_pgruppen_list_row">
+<div class="wpsg_pgruppen_list_row <?php
+			
+		echo (($oProductgroup->hasActiveDiscount())?'active_discount':'');
+
+	?>">
 	<div class="wpsg_pgruppen_list_titel">
 		<a href="<?php echo WPSG_URL_WP ?>?page_id=<?php echo get_the_ID(); ?>&show=<?php echo $k ?>">
@@ -36,15 +44,17 @@
             <?php } else { ?>
 
-                <?php $image = array_rand($g['produkte'][$i]['bilder']); ?>
-                <?php if ($image >= 0 && is_int($image)) { ?>
-                <div class="wpsg_pgruppen_list_image">
-
-                    <a href="<?php echo $g['produkte'][$i]['url'] ?>">
-                        <?php echo wp_get_attachment_image($g['produkte'][$i]['bilder'][0], 'thumbnail', false, array()); ?>
-                    </a>
-
-                </div>
-                <?php } ?>
-
+				<?php if (sizeof($g['produkte'][$i]['bilder']??[]) > 0) { ?>
+					<?php $image = array_rand($g['produkte'][$i]['bilder']); ?>
+					<?php if ($image >= 0 && is_int($image)) { ?>
+					<div class="wpsg_pgruppen_list_image">
+	
+						<a href="<?php echo $g['produkte'][$i]['url'] ?>">
+							<?php echo wp_get_attachment_image($g['produkte'][$i]['bilder'][0], 'thumbnail', false, array()); ?>
+						</a>
+	
+					</div>
+					<?php } ?>
+				<?php } ?>
+				
             <?php } ?>
 
Index: /views/mods/mod_productgroups/show_2.phtml
===================================================================
--- /views/mods/mod_productgroups/show_2.phtml	(revision 8279)
+++ /views/mods/mod_productgroups/show_2.phtml	(revision 8280)
@@ -51,5 +51,9 @@
                     else echo $page_url.'show='.$oProductgroup->getId();
 
-                    ?>" class="productgroup">
+                    ?>" class="productgroup <?php
+			
+						echo (($oProductgroup->hasActiveDiscount())?'active_discount':'');
+			
+					?>">
 
                     <div class="title"><?php echo $oProductgroup->getLabel(); ?></div>
Index: /views/mods/mod_productvariants/admin_edit.phtml
===================================================================
--- /views/mods/mod_productvariants/admin_edit.phtml	(revision 8279)
+++ /views/mods/mod_productvariants/admin_edit.phtml	(revision 8280)
@@ -38,5 +38,5 @@
         		
         		<?php if (wpsg_isSizedInt($this->view['product_id'])) { $colspan += 2; ?>
-        		<th class="col_artnr"><?php echo __('Artikelnummer', 'wpsg'); ?></th>
+        		<!--<th class="col_artnr"><?php echo __('Artikelnummer', 'wpsg'); ?></th>-->
         		<th class="col_price"><?php echo __('Preis', 'wpsg'); ?></th>
         		
@@ -171,5 +171,5 @@
     			
     			<?php if (wpsg_isSizedInt($this->view['product_id'])) { ?>
-    			<td class="col_artnr"><span class="wpsg_editable" id="productvariation_anr_<?php echo $vari['id']; ?>"><?php echo wpsg_hspc($vari['anr']); ?></span></td>    			
+    			<!--<td class="col_artnr"><span class="wpsg_editable" id="productvariation_anr_<?php echo $vari['id']; ?>"><?php echo wpsg_hspc($vari['anr']); ?></span></td>-->
     			<td class="col_price"><span class="wpsg_editable" id="productvariation_price_<?php echo $vari['id']; ?>"><?php echo wpsg_hspc(wpsg_ff($vari['price'], $this->get_option('wpsg_currency'))); ?></span></td>
 
@@ -317,5 +317,5 @@
                     <?php } ?>
 					
-					<?php if (wpsg_isSizedInt($this->view['product_id'])) { ?>
+					<?php /* if (wpsg_isSizedInt($this->view['product_id'])) { ?>
 					<div style="margin-top:0.25rem;">
 						<span>EAN:
@@ -334,5 +334,5 @@
 						</script>
 					</div>
-					<?php } ?>
+					<?php } */ ?>
 					
 					<script>
Index: /views/mods/mod_productvariants/produkt_addedit_content.phtml
===================================================================
--- /views/mods/mod_productvariants/produkt_addedit_content.phtml	(revision 8279)
+++ /views/mods/mod_productvariants/produkt_addedit_content.phtml	(revision 8280)
@@ -52,6 +52,11 @@
 					<h3 class="panel-title">
 						{{combination.label}}
-						<span v-if="combination.id > 0" class="glyphicon glyphicon-pencil" @click.prevent="edit(combination)"></span>
-						<span v-else class="glyphicon glyphicon-plus" @click.prevent="add(combination)"></span>
+						<div class="right">
+							<a :href="combination.url" target="_blank" class="url" title="<?php echo __('Produktvariante im Frontend ansehen', 'wpsg'); ?>">
+								<span class="glyphicon glyphicon-new-window"></span>
+							</a>
+							<span v-if="combination.id > 0" class="glyphicon glyphicon-pencil" @click.prevent="edit(combination)"></span>
+							<span v-else class="glyphicon glyphicon-plus" @click.prevent="add(combination)"></span>
+						</div>
 					</h3>
 				</div>
@@ -63,8 +68,10 @@
 					
 					<div class="form-group form-group-sm ">
-						<label class="col-sm-6 control-label" for="detailname">Artikelnummer</label>
+						<label class="col-sm-6 control-label" for="name">Produktname</label>
 						<div class="col-sm-6">
 							<div class="wpsg_field_wrap">
-								<input type="text" class="form-control input-sm " autocomplete="off" placeholder="" v-model="combination.anr" />
+								<input type="text"
+									@keydown.enter.prevent
+									class="form-control input-sm " autocomplete="off" placeholder="" v-model="combination.name" />
 							</div>
 						</div>
@@ -72,10 +79,11 @@
 					</div>
 					
-					<!-- EAN -->
 					<div class="form-group form-group-sm ">
-						<label class="col-sm-6 control-label" for="detailname">EAN (European Article Number)</label>
+						<label class="col-sm-6 control-label" for="detailname">Produktname (Detail)</label>
 						<div class="col-sm-6">
 							<div class="wpsg_field_wrap">
-								<input type="text" class="form-control input-sm " autocomplete="off" placeholder="" v-model="combination.ean" maxlength="13" />
+								<input type="text"
+									@keydown.enter.prevent
+									class="form-control input-sm " autocomplete="off" placeholder="" v-model="combination.detailname" />
 							</div>
 						</div>
@@ -83,91 +91,51 @@
 					</div>
 					
-					<!-- GTIN -->
 					<div class="form-group form-group-sm ">
-						<label class="col-sm-6 control-label" for="detailname">GTIN (Global Trade Item Number)</label>
+						<label class="col-sm-6 control-label" for="anr">Artikelnummer</label>
 						<div class="col-sm-6">
 							<div class="wpsg_field_wrap">
-								<input type="text" class="form-control input-sm " autocomplete="off" placeholder="" v-model="combination.gtin" />
+								<input type="text"
+									@keydown.enter.prevent
+									class="form-control input-sm" autocomplete="off" placeholder="" v-model="combination.anr" />
 							</div>
 						</div>
 						<div class="clearfix wpsg_clear"></div>
 					</div>
-					
-					<!-- MPN -->
-					<div class="form-group form-group-sm ">
-						<label class="col-sm-6 control-label" for="detailname">MPN (Manufacturer Part Number)</label>
-						<div class="col-sm-6">
-							<div class="wpsg_field_wrap">
-								<input type="text" class="form-control input-sm " autocomplete="off" placeholder="" v-model="combination.mpn" />
+			 
+					<!-- Productcodes -->
+					<template v-if="codeKeyNotSet(combination).length > 0">
+						<div class="form-group form-group-sm has-feedback">
+							<label class="col-sm-6 control-label" for="metaean">Code hinzufÃŒgen</label>
+							<div class="col-sm-6">
+								<div class="wpsg_field_wrap">
+									<select class="form-control input-sm" v-model="combination.add_code_key">
+										<option v-for="(code_key, i) of codeKeyNotSet(combination)" :value="code_key">{{combination.arProductCodes[code_key].label}}</option>
+									</select>
+									<a @click.stop.prevent="codeKeyAdd(combination)" href="#" class="glyphicon glyphicon glyphicon-plus form-control-feedback" aria-hidden="true" style="pointer-events: auto;"></a>
+								</div>
 							</div>
+							<div class="clearfix wpsg_clear"></div>
 						</div>
-						<div class="clearfix wpsg_clear"></div>
-					</div>
-					
-					<!-- PZN -->
-					<div class="form-group form-group-sm ">
-						<label class="col-sm-6 control-label" for="detailname">PZN (Pharmazentralnummer)</label>
-						<div class="col-sm-6">
-							<div class="wpsg_field_wrap">
-								<input type="text" class="form-control input-sm " autocomplete="off" placeholder="" v-model="combination.pzn" />
+						<hr />
+					</template>
+					
+					<div class="code_wrap">
+						<template v-for="(code, i) of combination.arProductCodes">
+							<div class="form-group form-group-sm has-feedback" v-if="code.set === true">
+								<label class="col-sm-6 control-label" for="metaean">{{code.label}}</label>
+								<div class="col-sm-6">
+									<div class="wpsg_field_wrap">
+										<input type="text" class="form-control input-sm" v-model="code.code"
+											@keydown.enter.prevent
+										/>
+										<a @click.stop.prevent="codeKeyRemove(combination, code.code_key)" href="#" class="glyphicon glyphicon glyphicon-trash form-control-feedback" aria-hidden="true" style="pointer-events: auto;"></a>
+									</div>
+								</div>
+								<div class="clearfix wpsg_clear"></div>
 							</div>
-						</div>
-						<div class="clearfix wpsg_clear"></div>
-					</div>
-					
-					<!-- SKU -->
-					<div class="form-group form-group-sm ">
-						<label class="col-sm-6 control-label" for="detailname">SKU (Stock Keeping Unit)</label>
-						<div class="col-sm-6">
-							<div class="wpsg_field_wrap">
-								<input type="text" class="form-control input-sm " autocomplete="off" placeholder="" v-model="combination.sku" />
-							</div>
-						</div>
-						<div class="clearfix wpsg_clear"></div>
-					</div>
-					
-					<!-- UPC -->
-					<div class="form-group form-group-sm ">
-						<label class="col-sm-6 control-label" for="detailname">UPC (Universal Product Code)</label>
-						<div class="col-sm-6">
-							<div class="wpsg_field_wrap">
-								<input type="text" class="form-control input-sm " autocomplete="off" placeholder="" v-model="combination.upc" />
-							</div>
-						</div>
-						<div class="clearfix wpsg_clear"></div>
-					</div>
-					
-					<!-- ISBN -->
-					<div class="form-group form-group-sm ">
-						<label class="col-sm-6 control-label" for="detailname">ISBN (International Standard Book Number)</label>
-						<div class="col-sm-6">
-							<div class="wpsg_field_wrap">
-								<input type="text" class="form-control input-sm " autocomplete="off" placeholder="" v-model="combination.isbn" />
-							</div>
-						</div>
-						<div class="clearfix wpsg_clear"></div>
-					</div>
-					
-					<!-- ISSN -->
-					<div class="form-group form-group-sm ">
-						<label class="col-sm-6 control-label" for="detailname">ISSN (International Standard Serial Number)</label>
-						<div class="col-sm-6">
-							<div class="wpsg_field_wrap">
-								<input type="text" class="form-control input-sm " autocomplete="off" placeholder="" v-model="combination.issn" />
-							</div>
-						</div>
-						<div class="clearfix wpsg_clear"></div>
-					</div>
-					
-					<!-- ISIN -->
-					<div class="form-group form-group-sm ">
-						<label class="col-sm-6 control-label" for="detailname">ISIN (International Securities Identification Number)</label>
-						<div class="col-sm-6">
-							<div class="wpsg_field_wrap">
-								<input type="text" class="form-control input-sm " autocomplete="off" placeholder="" v-model="combination.isin" />
-							</div>
-						</div>
-						<div class="clearfix wpsg_clear"></div>
-					</div>
+						</template>
+					</div>
+					
+					<!--<pre>{{combination}}</pre>-->
 					
 					<div class="mt-8 flex w-full justify-end">
@@ -181,224 +149,275 @@
 	</div>
 	
+	<?php $GLOBALS['wpsg_vue_3.2.47_loaded'] = ($GLOBALS['wpsg_vue_3.2.47_loaded']??0) + 1; if ($GLOBALS['wpsg_vue_3.2.47_loaded'] <= 1) { ?>
 	<script src="<?php echo WPSG_URL_CONTENT.'plugins/'.WPSG_FOLDERNAME.'/views/js/vue-3.2.47.global.min.js'; ?>"></script>
+	<?php } ?>
+	
 	<script>
 		
-		const ref = Vue.ref;
-		const computed = Vue.computed;
-		const onMounted = Vue.onMounted;
-	 
-		const app = Vue.createApp({
-			setup() {
-				
-				const product_id = ref(<?php echo $oProduct->getId(); ?>);
-				const loading = ref(true);
-				const arFilter = ref(undefined);
-				const arPossibleKeys = ref(undefined);
-				const arVariant = ref(undefined);
-				const arVariantLabel = ref(undefined);
-				const arVariationen = ref(undefined);
-				const arVariationLabel = ref(undefined);
-				
-				const arPossibleKeysFiltered = computed(() => {
-					
-					let r = [];
-					
-					if (!arPossibleKeys.value) return r;
-					
-					for (let var_key of arPossibleKeys.value) {
-						
-						const arKey = var_key.var_key.split('|');
-						const typedKey = {};
-						
-						for (let i of arKey) {
+		(() => {
+
+			const ref = Vue.ref;
+			const computed = Vue.computed;
+			const onMounted = Vue.onMounted;
+
+			const app = Vue.createApp({
+				setup() {
+
+					const product_id = ref(<?php echo $oProduct->getId(); ?>);
+					const loading = ref(true);
+					const arFilter = ref(undefined);
+					const arPossibleKeys = ref(undefined);
+					const arVariant = ref(undefined);
+					const arVariantLabel = ref(undefined);
+					const arVariationen = ref(undefined);
+					const arVariationLabel = ref(undefined);
+
+					const arPossibleKeysFiltered = computed(() => {
+
+						let r = [];
+
+						if (!arPossibleKeys.value) return r;
+
+						for (let var_key of arPossibleKeys.value) {
+
+							const arKey = var_key.var_key.split('|');
+							const typedKey = {};
+
+							for (let i of arKey) {
+
+								const [var_id, vari_id] = i.split(':');
+
+								typedKey[var_id] = parseInt(vari_id);
+
+							}
+
+							let set = true;
+
+							for (let f in arFilter.value) {
+
+								if (arFilter.value[f] !== 0 && typedKey[f] !== arFilter.value[f]) {
+
+									set = false;
+									break;
+
+								}
+
+							}
+
+							if (set) r.push(var_key);
+
+						}
+
+						return r;
+
+					});
+
+					const getVariantLabel = (var_id) => {
+
+						if (arVariantLabel.value[var_id.toString()] !== undefined) return arVariantLabel.value[var_id.toString()];
+						else '';
+
+					};
+					const getVariationLabel = (var_id) => {
+
+						if (arVariationLabel.value[var_id.toString()] !== undefined) return arVariationLabel.value[var_id.toString()];
+						else '';
+
+					};
+					const xhr = (action_do, data, cb_success) => {
+
+						const request = new XMLHttpRequest();
+
+						data.action_do = action_do;
+
+						request.open('POST', '<?php echo WPSG_URL_WP; ?>wp-admin/?page=wpsg-Admin&action=module&modul=wpsg_mod_productvariants&subaction=combination&noheader=1', true);
+						request.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
+						request.onload = () => {
+
+							if (request.status >= 200 && request.status < 400) {
+
+								const response = JSON.parse(request.responseText);
+
+								cb_success(response);
+
+							} else {
+
+								console.error('Error: ' + request.statusText);
+
+							}
+
+						};
+
+						request.onerror = function () {
+
+							console.error('Error: ' + request.statusText);
+
+						};
+
+						request.send(JSON.stringify(data));
+
+					};
+					const save = (combination) => {
+
+						xhr('save', {
+							var_key: combination.var_key,
+							product_id: combination.product_id,
+							combination: combination
+						}, (response) => {
+
+							for (let i = 0; i < arPossibleKeys.value.length; i++) {
+
+								const var_key = arPossibleKeys.value[i];
+
+								if (var_key.var_key === response.combination.var_key) {
+
+									arPossibleKeys.value[i] = response.combination;
+
+								}
+
+							}
+
+						});
+
+						combination.loading = true;
+
+					}
+					const updateCombinationData = () => {
+
+						xhr('getCombination', {
+							product_id: product_id.value
+						}, (response) => {
+
+							arFilter.value = response.arFilter;
+							arPossibleKeys.value = response.arPossibleKeys;
+							arVariant.value = response.arVariant;
+							arVariantLabel.value = response.arVariantLabel;
+							arVariationen.value = response.arVariationen;
+							arVariationLabel.value = response.arVariationLabel;
+
+							loading.value = false;
+
+						});
+
+					};
+					const add = (combination) => {
+
+						combination.loaded = true;
+						combination.loading = true;
+
+						xhr('add', {
+							var_key: combination.var_key,
+							product_id: product_id.value
+						}, (response) => {
+
+							for (let i = 0; i < arPossibleKeys.value.length; i++) {
+
+								const var_key = arPossibleKeys.value[i];
+
+								if (var_key.var_key === response.combination.var_key) {
+
+									arPossibleKeys.value[i] = response.combination;
+
+								}
+
+							}
+
+						});
+
+					};
+					const edit = (combination) => {
+
+						combination.loaded = true;
+						combination.loading = true;
+
+						xhr('edit', {
+							var_key: combination.var_key,
+							product_id: product_id.value
+						}, (response) => {
+
+							for (let i = 0; i < arPossibleKeys.value.length; i++) {
+
+								const var_key = arPossibleKeys.value[i];
+
+								if (var_key.var_key === response.combination.var_key) {
+
+									arPossibleKeys.value[i] = response.combination;
+
+								}
+
+							}
+
+						});
+
+					};
+					const codeKeyNotSet = (combination) => {
+						
+						let r = [];
+						
+						for (let code_key in combination.arProductCodes) {
+						
+							if (combination.arProductCodes[code_key].set !== true) r.push(code_key);
+						
+						}
+						
+						return r;
+						
+					};
+					const codeKeyAdd = (combination) => {
+
+						const add_code_key = combination.add_code_key;
+						
+						if (add_code_key !== undefined && codeKeyNotSet(combination).includes(add_code_key)) {
+
+							combination.arProductCodes[add_code_key].set = true;
 							
-							const [var_id, vari_id] = i.split(':');
-							
-							typedKey[var_id] = parseInt(vari_id);
-							
+							combination.add_code_key = undefined;
+
 						}
 						
-						let set = true;
-						
-						for (let f in arFilter.value) {
-	
-							if (arFilter.value[f] !== 0 && typedKey[f] !== arFilter.value[f]) {
-								
-								set = false; break;
-								
-							}
-							
-						}
-						
-						if (set) r.push(var_key);
+					};
+					const codeKeyRemove = (combination, code_key_remove) => {
+						
+						combination.arProductCodes[code_key_remove].set = false;
 						
 					}
-					
-					return r;
-					
-				});
-				
-				const getVariantLabel = (var_id) => {
-					
-					if (arVariantLabel.value[var_id.toString()] !== undefined) return arVariantLabel.value[var_id.toString()];
-					else '';
-					
-				};
-				const getVariationLabel = (var_id) => {
-					
-					if (arVariationLabel.value[var_id.toString()] !== undefined) return arVariationLabel.value[var_id.toString()];
-					else '';
-					
-				};
-				const xhr = (action_do, data, cb_success) => {
-				
-					const request = new XMLHttpRequest();
-					
-					data.action_do = action_do;
-					
-					request.open('POST', '<?php echo WPSG_URL_WP; ?>wp-admin/?page=wpsg-Admin&action=module&modul=wpsg_mod_productvariants&subaction=combination&noheader=1', true);
-					request.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
-					request.onload = () => {
-						
-						if (request.status >= 200 && request.status < 400) {
-							
-							const response = JSON.parse(request.responseText);
-		  
-							cb_success(response);
-		
-						} else {
-		  
-							console.error('Error: ' + request.statusText);
-		
-						}
-	  
-					};
-	  
-					request.onerror = function() {
-		
-						console.error('Error: ' + request.statusText);
-	  
-					};
-	  
-					request.send(JSON.stringify(data));
-				
-				};
-				const save = (combination) => {
-					
-					xhr('save', {
-						var_key: combination.var_key,
-						product_id: combination.product_id,
-						combination: combination
-					}, (response) => {
-						
-						for (let i = 0; i < arPossibleKeys.value.length; i ++) {
-							
-							const var_key = arPossibleKeys.value[i];
-							
-							if (var_key.var_key === response.combination.var_key) {
-								
-								arPossibleKeys.value[i] = response.combination;
-	
-							}
-							
-						}
-						
+
+					onMounted(() => {
+
+						// updateCombinationData();
+
 					});
-					
-					combination.loading = true;
-					
+
+					return {
+						arFilter,
+						arPossibleKeys,
+						arVariant,
+						arVariantLabel,
+						arVariationen,
+						arVariationLabel,
+						product_id,
+						loading,
+						getVariantLabel,
+						getVariationLabel,
+						codeKeyNotSet, codeKeyAdd, codeKeyRemove,
+						arPossibleKeysFiltered,
+						add,
+						edit,
+						save,
+						updateCombinationData
+					};
+
 				}
-				const updateCombinationData = () => {
-
-					xhr('getCombination', {
-						product_id: product_id.value
-					}, (response) => {
-						
-						arFilter.value = response.arFilter;
-						arPossibleKeys.value = response.arPossibleKeys;
-						arVariant.value = response.arVariant;
-						arVariantLabel.value = response.arVariantLabel;
-						arVariationen.value = response.arVariationen;
-						arVariationLabel.value = response.arVariationLabel;
-						
-						loading.value = false;
-						
-					});
-					
-				};
-				const add = (combination) => {
-	
-					combination.loaded = true;
-					combination.loading = true;
-					
-					xhr('add', {
-						var_key: combination.var_key,
-						product_id: product_id.value
-					}, (response) => {
-						
-						for (let i = 0; i < arPossibleKeys.value.length; i ++) {
-							
-							const var_key = arPossibleKeys.value[i];
-							
-							if (var_key.var_key === response.combination.var_key) {
-								
-								arPossibleKeys.value[i] = response.combination;
-	
-							}
-							
-						}
-						
-					});
-					
-				};
-				const edit = (combination) => {
-				
-					combination.loaded = true;
-					combination.loading = true;
-					
-					xhr('edit', {
-						var_key: combination.var_key,
-						product_id: product_id.value
-					}, (response) => {
-	
-						for (let i = 0; i < arPossibleKeys.value.length; i ++) {
-							
-							const var_key = arPossibleKeys.value[i];
-							
-							if (var_key.var_key === response.combination.var_key) {
-								
-								arPossibleKeys.value[i] = response.combination;
-	
-							}
-							
-						}
-						
-					});
-					
-				}
-				
-				onMounted(() => {
-	
-					// updateCombinationData();
-					
-				});
-				
-				return {
-					arFilter, arPossibleKeys, arVariant, arVariantLabel, arVariationen, arVariationLabel, product_id, loading,
-					getVariantLabel, getVariationLabel,
-					arPossibleKeysFiltered,
-					add, edit, save, updateCombinationData
-				};
-				
-			}
-		}).mount('#wpsg_mod_productvars_combination');
-	
-		window.wpsg_mod_productvariants = app;
-		
-		jQuery(document).ready(function() {
-	
-			wpsg_mod_productvariants_show(<?php echo $this->view['data']['id']; ?>);
-			
-		} );
+			}).mount('#wpsg_mod_productvars_combination');
+
+			window.wpsg_mod_productvariants = app;
+
+			jQuery(document).ready(function () {
+
+				wpsg_mod_productvariants_show(<?php echo $this->view['data']['id']; ?>);
+
+			});
+
+		})();
 		
 	</script>
@@ -410,4 +429,5 @@
 		#wpsg_mod_productvars_combination .top_filter select { width:100%; }
 		#wpsg_mod_productvars_combination .panel-title { display:flex; justify-content:space-between; align-items:center; }
+		#wpsg_mod_productvars_combination .panel-title a.url { color:inherit; margin-right:1rem; display:inline-block; }
 		#wpsg_mod_productvars_combination .panel-title .glyphicon { cursor:pointer; }
 		#wpsg_mod_productvars_combination .panel { margin-bottom:10px; }
Index: /views/mods/mod_rechnungen/invoice_pdf.phtml
===================================================================
--- /views/mods/mod_rechnungen/invoice_pdf.phtml	(revision 8279)
+++ /views/mods/mod_rechnungen/invoice_pdf.phtml	(revision 8280)
@@ -217,5 +217,7 @@
 	foreach ($this->view['basket']['arCalculation']['product'] as $p)
 	{
+		
 		$product = wpsg_product::getInstance($p['product_id']);
+		if (($p['product_key']??'') !== '') $product->setProductKey($p['product_key']);
 
 		if (!$bKopf)
@@ -257,6 +259,6 @@
 		else $pdf->Cell(10, 8, $pnr.".", 0, 0, 'C');
 		
-		if ($this->get_option('wpsg_rechnungen_pdetailname') == '1') $produkt_text = $this->getProductName($this->getProduktID($p['product_id']), true);
-		else $produkt_text = $this->getProductName($this->getProduktID($p['product_id']), false);
+		if ($this->get_option('wpsg_rechnungen_pdetailname') == '1') $produkt_text = $product->getProductName(true);
+		else $produkt_text = $product->getProductName(false);
 
 		$produktBeschreibung = trim(strip_tags($product->getShortDescription()));
Index: /views/order/product_table.phtml
===================================================================
--- /views/order/product_table.phtml	(revision 8279)
+++ /views/order/product_table.phtml	(revision 8280)
@@ -50,5 +50,10 @@
 
 		<tr><td colspan="<?php echo $this->view['colspan'] + 1; ?>" style="line-height:2px; height:2px; background-color:#999999; padding:0px;"></td></tr>
-		<?php $i = 0; foreach ($arCalculation['product'] as $p) { $i ++; $product_price = $p[$display.'_single']; ?>
+		<?php $i = 0; foreach ($arCalculation['product'] as $p) { $i ++; $product_price = $p[$display.'_single'];
+			
+			$oProduct = wpsg_product::getInstance(intval($p['product_id']));
+			if (($p['product_key']??'') !== '') $oProduct->setProductKey($p['product_key']);
+			
+			?>
 			<tr class="wpsg_<?php echo (($i % 2 == 0)?'odd':'even'); ?>">
 				<td colspan="<?php echo $this->view['colspan'] + 1; ?>">
@@ -63,5 +68,5 @@
 						                
 					?>" title="<?php echo __('Zum Produkt', 'wpsg'); ?>">
-						<?php echo wpsg_hspc($this->getProductName($p['product_id'])); ?>
+						<?php echo wpsg_hspc($oProduct->getProductName(false)); ?>
 					</a>
 					<a href="#" class="wpsg-glyphlink-td" title="<?php echo __('Produkt bearbeiten', 'wpsg'); ?>" onclick="return WPSG_BE_Product.editProduct(<?php echo $p['order_product_id']; ?>);"><span class="glyphicon glyphicon-pencil"></span></a>
Index: /views/produkt/addedit_productcodes.phtml
===================================================================
--- /views/produkt/addedit_productcodes.phtml	(revision 8279)
+++ /views/produkt/addedit_productcodes.phtml	(revision 8280)
@@ -11,5 +11,5 @@
 		
 	namespace wpsg;
-	
+
 ?>
 
@@ -23,35 +23,125 @@
 				$oProduct = \wpsg_product::getInstance(intval($this->view['data']['id']));
 				
+				$arData = [];
+				
+				foreach (\wpsg_product::getProductCodeConfig() as $code_key => $code_data) {
+					
+					$product_code = $oProduct->getMeta($code_key, false, '');
+					
+					$arData[$code_key] = [
+						'label' => $code_data['label'],
+						'code_key' => $code_key,
+						'code' => trim($product_code),
+						'set' => trim($product_code) !== ''
+					];
+					
+				}
+				
 			?>
-		
-				<?php echo wpsg_drawForm_Select('meta[code_mode]', __('Modus', 'wpsg'), [
-					'0' => __('Erster definierter Code', 'wpsg'),
-					'ean' => __('EAN als Standard', 'wpsg'),
-					'gtin' => __('GTIN als Standard', 'wpsg'),
-					'mpn' => __('MPN als Standard', 'wpsg'),
-					'pzn' => __('PZN als Standard', 'wpsg'),
-					'sku' => __('SKU als Standard', 'wpsg'),
-					'upc' => __('UPC als Standard', 'wpsg'),
-					'isbn' => __('ISBN als Standard', 'wpsg'),
-					'issn' => __('ISSN als Standard', 'wpsg'),
-					'isin' => __('ISIN als Standard', 'wpsg')
-				], $oProduct->getMeta('code_mode', false, '0'), ['help' => 'code_mode']); ?>
-				<?php echo wpsg_drawForm_Input('meta[ean]', __('EAN', 'wpsg'),  $oProduct->getMeta('ean'), ['help' => 'ean']); ?>
-				<?php echo wpsg_drawForm_Input('meta[gtin]', __('GTIN', 'wpsg'),  $oProduct->getMeta('gtin'), ['help' => 'gtin']); ?>
-				<?php echo wpsg_drawForm_Input('meta[mpn]', __('MPN', 'wpsg'), $oProduct->getMeta('mpn'), ['help' => 'mpn']); ?>
-				<?php echo wpsg_drawForm_Input('meta[pzn]', __('PZN', 'wpsg'), $oProduct->getMeta('pzn'), ['help' => 'pzn']); ?>
-				<?php echo wpsg_drawForm_Input('meta[sku]', __('SKU', 'wpsg'), $oProduct->getMeta('sku'), ['help' => 'sku']); ?>
-				<?php echo wpsg_drawForm_Input('meta[upc]', __('UPC', 'wpsg'), $oProduct->getMeta('upc'), ['help' => 'upc']); ?>
-				<?php echo wpsg_drawForm_Input('meta[isbn]', __('ISBN', 'wpsg'), $oProduct->getMeta('isbn'), ['help' => 'isbn']); ?>
-				<?php echo wpsg_drawForm_Input('meta[issn]', __('ISSN', 'wpsg'), $oProduct->getMeta('issn'), ['help' => 'issn']); ?>
-				<?php echo wpsg_drawForm_Input('meta[isin]', __('ISIN', 'wpsg'), $oProduct->getMeta('isin'), ['help' => 'isin']); ?>
+				
+				<div id="wpsg_mod_productcodes_app">
+					
+					<template v-if="codeKeyNotSet.length > 0">
+						<div class="form-group form-group-sm has-feedback">
+							<label class="col-sm-6 control-label" for="metaean">Code hinzufÃŒgen</label>
+							<div class="col-sm-6">
+								<div class="wpsg_field_wrap">
+									<select class="form-control input-sm" v-model="add_code_key">
+										<option v-for="(code_key, i) of codeKeyNotSet" :value="code_key">{{data[code_key].label}}</option>
+									</select>
+									<a @click.stop.prevent="add()" href="#" class="glyphicon glyphicon glyphicon-plus form-control-feedback" aria-hidden="true" style="pointer-events: auto;"></a>
+								</div>
+							</div>
+							<div class="clearfix wpsg_clear"></div>
+						</div>
+						<hr />
+					</template>
+					
+					<div class="code_wrap">
+						<template v-for="(code, i) of data">
+							<div class="form-group form-group-sm has-feedback" v-if="code.set === true">
+								<label class="col-sm-6 control-label" for="metaean">{{code.label}}</label>
+								<div class="col-sm-6">
+									<div class="wpsg_field_wrap">
+										<input type="text" class="form-control input-sm" :value="code.code" :name="'meta[' + code.code_key + ']'" />
+										<a @click.stop.prevent="remove(code.code_key)" href="#" class="glyphicon glyphicon glyphicon-trash form-control-feedback" aria-hidden="true" style="pointer-events: auto;"></a>
+									</div>
+								</div>
+								<div class="clearfix wpsg_clear"></div>
+							</div>
+							<input v-else type="hidden" value="" :name="'meta[' + code.code_key + ']'" />
+						</template>
+					</div>
+					
+				</div>
+				
+				<?php $GLOBALS['wpsg_vue_3.2.47_loaded'] = ($GLOBALS['wpsg_vue_3.2.47_loaded']??0) + 1; if ($GLOBALS['wpsg_vue_3.2.47_loaded'] <= 1) { ?>
+				<script src="<?php echo WPSG_URL_CONTENT.'plugins/'.WPSG_FOLDERNAME.'/views/js/vue-3.2.47.global.min.js'; ?>"></script>
+				<?php } ?>
+			
+				<script>
 
-				<?php if (\wpsg_ShopController::getShop()->hasMod('wpsg_mod_productvariants')) { ?>
-				
-					<br />
+					(() => {
+
+						const ref = Vue.ref;
+						const computed = Vue.computed;
+						const onMounted = Vue.onMounted;
+
+						const app = Vue.createApp({
+							setup() {
+
+								const data = ref(<?php echo json_encode($arData); ?>);
+								const add_code_key = ref(undefined);
+
+								const codeKeyNotSet = computed(() => {
+
+									let r = [];
+									
+									for (let c of Object.values(data.value)) {
+
+										if (c.set !== true) r.push(c.code_key);
+										
+									}
+									
+									return r;
+
+								});
+								
+								const add = () => {
+
+									if (add_code_key.value !== undefined && codeKeyNotSet.value.includes(add_code_key.value)) {
+
+										data.value[add_code_key.value].set = true;
+
+									}
+									
+								};
+								const remove = (code_key) => {
+
+									data.value[code_key].set = false;
+									
+								};
+
+								return {
+									data, add_code_key,
+									codeKeyNotSet,
+									add, remove
+								};
+
+							}
+						}).mount('#wpsg_mod_productcodes_app');
+
+					})();
 					
-					<p class="wpsg_hinweis">Ist das Produkt ein Variantenprodukt, kÃ¶nnen die Codes unter Produktvarianten fÃŒr einzelne Variationen ÃŒberschrieben werden.</p>
+				</script>
+		  
+				<br />
 					
-				<?php } ?>
+				<p class="wpsg_hinweis">
+					<?php if (\wpsg_ShopController::getShop()->hasMod('wpsg_mod_productvariants')) { ?>
+					Ist das Produkt ein Variantenprodukt, kÃ¶nnen die Codes unter Produktvarianten fÃŒr einzelne Variationen ÃŒberschrieben werden.<br />
+					<?php } ?>
+					Ãnderungen mÃŒssen mit speichern bestÃ€tigt werden.
+				</p>
 				
 			<?php } else { ?>
@@ -61,4 +151,5 @@
 			<?php } ?>
 			
+			
 		<?php echo wpsg_drawForm_AdminboxEnd(); ?>
 	</div>
Index: /views/produkttemplates/standard.phtml
===================================================================
--- /views/produkttemplates/standard.phtml	(revision 8279)
+++ /views/produkttemplates/standard.phtml	(revision 8280)
@@ -13,5 +13,6 @@
 	/** @var wpsg_product $oProduct */
 	$oProduct = $this->view['oProduct'];
-
+	if (($this->view['data']['product_key']??'') !== '') $oProduct->setProductKey($this->view['data']['product_key']);
+	
 ?>
 
@@ -24,5 +25,5 @@
 
 		<?php if (!$this->titleDisplayed) { ?>
-			<h1 class="wpsg_producttitle"><?php echo $this->getProductName($this->view['data']['id']); ?></h1>
+			<h1 class="wpsg_producttitle"><?php echo $oProduct->getProductName(); ?></h1>
 			<h2 class="wpsg_productdetailtitle"><?php echo $this->view['data']['detailname']; ?></h2>
 		<?php }  ?>
@@ -36,5 +37,5 @@
 	<div itemscope itemtype="http://schema.org/Product" class="wpsg_produkt wpsg_produkt_<?php echo $this->view['data']['id']; ?>">
 
-		<meta itemprop="name" content="<?php echo wpsg_hspc($this->getProductName($this->view['data']['id'])); ?>" />
+		<meta itemprop="name" content="<?php echo wpsg_hspc($oProduct->getProductName()); ?>" />
 		<meta itemprop="detailname" content="<?php echo wpsg_hspc($this->view['data']['detailname']); ?>" />
 		
@@ -68,5 +69,5 @@
                     <meta itemprop="image" content="<?php echo $att[0]; ?>" />
 
-					<a <?php if ($arAttachmentIDsAll[$i] != $arAttachmentID) echo 'style="display:none;"'; ?> rel="gallery-wpsg-<?php echo $this->getTemplateIndex(); ?>" title="<?php echo $this->getProductName($this->view['data']['id']); ?>" href="<?php echo $att[0]; ?>" class="thickbox">
+					<a <?php if ($arAttachmentIDsAll[$i] != $arAttachmentID) echo 'style="display:none;"'; ?> rel="gallery-wpsg-<?php echo $this->getTemplateIndex(); ?>" title="<?php echo $oProduct->getProductName(); ?>" href="<?php echo $att[0]; ?>" class="thickbox">
 
 						<?php echo wp_get_attachment_image($arAttachmentIDsAll[$i], array(300, 300), false, array()); ?>
