Trigger scripts are scripts that can "hook" into events at certain points in the software and make decisions based on individual considerations, e.g. adjusting properties of an order or changing the further path through the company's processes.
Trigger scripts exist in two forms:
-
Event trigger
-
Timed trigger
Event triggers are attached to specific events, as can be seen in the following examples:
-
OnDocumentChange → If, for example, a change is made to an order or an invoice
-
OnDocumentOfferPositionChange → When in an invoice the items of the items are changed
-
OnDocumentAddressChange → When the address is changed in a document
-
OnDeliverynoteStockSwapOut → When a delivery note is swapped out
-
AfterAddressCreate → After a new address has been created
-
OnProductLabelPrint → When an item label is printed out
-
Dynamic adjustment of the shipping method according to the number of items in an order and the postal code of the delivery address → OnDocumentChange triggers a script that examines the number of items, the postal code and the cheapest shipping provider
-
Granting a promotional discount when the total order value reaches €100 → OnDocumentOfferPositionChange is called as soon as the items of an order are changed. When the total discount threshold is reached, all items and articles can be accessed, the total sum can be calculated and a discount article can be added. Under example total discount you can find an example for this
-
Automatic assignment of the "Customer" role when a new address is created by an internal sales employee and of the "Supplier" role for employees from Purchasing → AfterAdressCreate adds a role in addition to the new address, depending on the active user of the system (Sales or Purchasing)
For example, shipping settings can be changed according to the items in an order or the shipping address, items in an order can be split off when pre-booked, or the special discounts can be calculated when an order is created or imported.
Triggers exist in all parts of xentral and trigger scripts allow easy customization of almost any process, such as OnDocumentChange.
Working with receipts (documents)
-
BeforeDocumentChange, AfterDocumentChange
-
OnDocumentPositionChange
-
OnDocumentAddressChange
-
OnDocumentAttributeChange
-
OnDocumentStateChange / OnDocumentStatusChange
Working with master data
-
BeforeProductCreate, BeforeProductChange, BeforeProductDelete, AfterProductCreate, AfterProductChange, AfterProductDelete
-
BeforeAdressCreate, BeforeAdressChange, BeforeAdressDelete, AfterAdressCreate, AfterAdressChange, AfterAdressDelete
-
in future: BeforeUserCreate, BeforeUserChange, BeforeUserDelete, AfterUserCreate, AfterUserChange, AfterUserDelete
Working with productions
-
BeforeProductionCreate, BeforeProductionEdit, BeforeProductionDelete, AfterProductionCreate, AfterProductionEdit, AfterProductionDelete
-
OnProductionStart, OnProductionFinish
-
OnProductionStockSwapOut, OnProductionStockSwapIn
Process trigger
-
Shipping Center: OnShipment, OnAutoShipmentManuelStart, OnAutoShipmentPlusStart
-
Franking: PreStampCreate, PostStampCreate
-
Tracking number scan: PreTrackingScan, PostTrackingScan,
-
Stock: OnStockChange, OnStockSwapOut, OnStockSwapIn, OnDeliverynoteStockSwapOut
-
Printing: OnPrint, OnLabelPrint, OnDocumentPrint, OnDocumentDeliveryNotePrint, OnDocumentInvoicePrint, OnDocumentOfferPrint, OnDocumentOrderPrint
-
Email sending: OnEmailSend, OnDocumentEmailSend, OnDocumentDeliveryNoteEmailSend, OnDocumentInvoiceEmailSend, OnDocumentOfferEmailSend, OnDocumentOrderEmailSend
-
User actions: (future) OnUserAction
-
User session: (future) OnSessionStart, OnSessionEnd / OnSessionClose
-
Goods receipt: OnIncomingGoodsDocumentStart, OnIncomingGoodsDocumentEnd, OnIncomingGoodsArticleStart, OnIncomingGoodsArticleEnd
Timed triggers (also process starters) make it possible to perform time-controlled actions. These can be executed periodically (e.g. every 15 minutes, every hour, every 3 days) or at specific times (e.g. Sundays, 8:30 am).
-
Every night a list of addresses that are neither marked as a customer nor as a supplier is created and a task, e.g. checking and processing the cases, is created for the administration.
-
Once a week, a list is created showing all orders that have not yet been fully invoiced. This list is sent as an e-mail.
With the help of trigger scripts, all data of a document (e.g. an order) can be changed. This includes, for example, the change of positions, prices, descriptions, discounts and shipping or payment options.
The basis in this example is the OnDocumentPositionChange event. Here, the data is adjusted and then the normal functionality of the function is executed to use the default behavior of xentral for the processes that have not been changed.
Procedure before version 20.1: Override the ANABREGSRecalculate()) function in the ERPApi class, which can be overloaded for this purpose.
This function is called when the document is not read-only and is modified. This applies, among other things, when:
-
A receipt is created, even when importing from a store interface or via EDI / Transfer module
-
An API call changes the receipt
-
Items of the receipt are changed
-
Master data of the receipt is changed
The function receives two parameters:
-
$type: The type of receipt → quotation (offer), order (order), invoice (invoice), credit memo (credit memo)
-
$id: The database ID of the receipt
It is possible to work freely on all values of the document and manipulate all data in this method. For this purpose, the ERP's own functions should preferably be used (see Programming questions). However, working directly on the database is also possible.
Note
The method is used to adjust the corresponding document and should therefore only be used for this purpose in order to avoid unexpected behavior of the software. For example, it is not comprehensible for a user if the master data of the customer address would change by working on an order. For document data, the DB tables should be limited, especially with regard to write accesses. The corresponding tables are:
-
auftrag
-
auftrag_positionen
-
rechnung
-
rechnung_positionen
-
lieferschein
-
lieferschein_positionen
-
gutschrift
-
gutschrift_positionen
An overview of the most important DB tables from this area can be found under Database Diagram Documents4 and Most Important Relations.
To transfer the function, simply create a file class.erpapi_custom.php and place it in the directory www/lib/ next to the file class.erpapi.php.
You only need this information for independent hosting. For cloud customers, Xentral stores the file on the server.
For this you need the PHP of the script, the location where it should be placed and the valid license of the instance.
The following example shows a simple application: From a total sum of EUR 100,- ($threshold, value net) a discount article ($rebateId) should be inserted automatically. This can only be deleted again if the total drops below EUR 100,-, otherwise it will be created again with every calculation. The discount should not be included in the total amount, so this can also fall below EUR 100.
The example shows the transfer of the function ANABREGSNeuberechnen()
-
the restriction to receipt types (see Sample 1)
-
the use of item positions with item and price
-
the use of database (see Sample 2)
-
the use of ERP functions (see Sample 3 and Sample 4)
Various methods can be used for debugging, see Debugging during development in xentral.
<?php
class erpAPICustom extends erpAPI
{
public function __construct(&$app)
{
$this->app = $app;
parent::__construct($app);
} // end of constructor
public function ANABREGSNeuberechnen($id, $art, $bool = false)
{
/* Choose the documents you want to manipulate - Sample 1 */
if($art === 'auftrag') {
/* Custom definitions here */
$rebateId = 15; /* Set rebate article ID % */
$threshold = 100; /* Activate rebate for orders above EUR 100,- */
$rebateExists = false; /* Assume rebate article not yet added */
/* Get some information about positions from database - Sample 2 */
$positionenSQL = sprintf('SELECT ap.* FROM %s_position ap WHERE %s = %s', $art, $art, $id);
$positionen = $this->app->DB->SelectArr($positionenSQL);
/* Walk through positions and compute the price */
$sum = 0;
foreach ($positionen as $p) {
$artikelId = $p['artikel'];
$menge = $p['menge'];
if ((int)$artikelId !== $rebateId) {
$sum += $this->app->erp->GetVerkaufspreis($artikelId, $menge) * $menge; /* Sample 3 */
} else {
$rebateExists = true;
}
} // end of foreach
if ($sum >= $threshold && !$rebateExists) {
/* Sample 4 */
$this->app->erp->AddAuftragPositionManuell($id, $rebateId, 0, 1, 'Rabatt ab € '.$this->app->erp->formatMoney($threshold, 'EUR'));
} // end of if - add rebate if not exists
} // end of if - Document type: Offer / Auftrag
/* Call the main routine from parent class erpAPI and return */
return parent::ANABREGSNeuberechnen($id, $art);
} // end of function
} // end of class
As a result, the position table then adjusts as follows:

And the document also has a different appearance:

In Switzerland, 1 and 2 centime coins are no longer in circulation. Therefore, retailers round up or down to 5 or 10 centimes, with mathematical rounding rules applying. In this case, the rounding normally refers to the total amount.
With the help of the function ANABREGSNeuberechnen() this rounding can be implemented. For this purpose, the total amount must be stored in two fields:
-
Extsoll
-
Gesamtsumme
Additional ideas for projects:
-
Special price or discount from a defined total quantity
-
Combination of different items or whole item categories in a discount scale
-
"Pay x get y"
-
Total discount from x Euro (see example above)
-
Free shipping from x Euro
-
Use of special revenue accounts according to specific criteria
-
Adjustment of shipping type or cost according to destination, items, total weight, or the like
-
Special roundings (see example Swiss centime rounding)
With the method LieferscheinAuslagern the warehouse stock is adjusted. The algorithm for this can be developed by the user. The ID of the delivery note appears primarily as a parameter. Then the positions from the data base can be queried (article ID and quantity), in order to take the correct quantities in the warehouse in the table lager_platz_inhalt.
function LieferscheinAuslagern($lieferschein,$anzeige_lagerplaetze_in_lieferschein=false, $standardlager = 0, $belegtyp = 'lieferschein', $chargenmhdnachprojekt = 0, $forceseriennummerngeliefertsetzen = false,$nurrestmenge = false, $lager_platz_vpe = 0, $lpiid = 0)
The function is passed values from the system that were set during setup by the setup person or the xentral onboarding team. These passed values can be evaluated within the function:
-
$lieferschein: ID of the delivery bill
-
$anzeige_lagerplaetze_in_lieferschein: Display of the storage bins in the delivery bill (system setting)
-
$standardlager: standard warehouse (system setting), default value is 0
-
$belegtyp - type of document, default is 'lieferschein'
-
$chargenmhdnachprojekt: Only for use by xentral (system setting)
-
$forceseriennummerngeliefertsetzen: Only for use by xentral (system setting)
-
$nurrestmenge: Only for use by xentral (system setting)
-
$lager_platz_vpe: Only for use by xentral (system setting)
-
$lpiid: Only for use by xentral (system setting)
Now the actual withdrawals from the warehouse are made, using the LagerAuslagernRegal function.
Query:
function LagerAuslagernRegal($artikel,$regal,$menge,$projekt,$grund,$importer="", $doctype = "", $doctypeid = 0, $lager_platz_vpe = 0, $lpiid = 0)
Description of parameters:
-
$artikel: Item ID
-
$regal: Warehouse location ID, from which it is retrieved
-
$menge: Quantity that was removed
-
$projekt: project for stock movement
-
$grund: remark for stock movement table
-
$importer: current value from process is passed - leave current empty, default is empty string
-
$doctype: optional specification for which document is to be removed from the warehouse, default value is empty string
-
$doctypeid: optional specification for which document (ID of the document) is to be removed from the warehouse, default value is 0
-
$lager_platz_vpe: Optional: Specification of which VPE is to be retrieved, default value is 0. This specification is currently only used for Amazon
-
$lpiid: Optional: Current value from process is passed here - currently 0 is passed. Default value is 0
Timed triggers refer to individual process starters that can be integrated to perform data analysis, data manipulation or other database-based actions. This can be done by writing directly to the database as well as calling external functions (such as API calls).
All timed triggers (process starters) are located in the directory ../xentral/cronjobs.
The main process (starter2.php) is called every minute by the heartbeat cronjob (system cronjob). The starter2.php checks all entries of the database table processstarter and executes the processes which are in turn according to their time or period setting (see Handbook process starer).
-
The PHP file for the process starter code in the folder ../xentral/cronjobs is to be created
-
For the file name only ASCII characters are to be used, spaces are to be avoided
-
The following navigation has to be done in the xentral interface: Administration → Settings → Process starter. A new entry is to be created, thereby the time / period setting for the process is to be specified meaningfully
-
The name of the PHP file without file extension must be entered in the "Parameter" field. Using this parameter, the starter2.php will search for the PHP file to be executed in the cronjob folder and execute it
During development, the process starter usually has to be started manually several times for test purposes.
The following procedure has proven successful:
-
No work in the live system, otherwise the further points have possibly negative effects
-
It must be ensured that no heartbeat cronjob is registered for the development system
-
All process starters are to be switched to inactive, except for the one that is being processed
-
You need to specify the following settings for the process starter you are working on:
-
Type: periodic
-
Period: 0
-
-
The starter2.php in the cronjob folder are to be started over a command line, e.g. over the command php starter2.php
The following example creates a time-controlled export of the product data in BMEcat format, a standardized exchange format for catalog data in catalog management (see also BMEcat on Wikipedia). In this case, specifically changed articles are exported in the update format and only articles are selected that have been changed since the last execution of the CRON job. In this example, only changes to the master data of the articles are taken into account, but not, for example, changed sales prices.
<?php
use Xentral\Components\Database\Exception\QueryFailureException;
use Xentral\Components\Filesystem\Filesystem;
use Xentral\Components\Filesystem\FilesystemFactory;
use Xentral\Components\Util\StringUtil;
//predefined variables:
/** @var Application $app
* access to services and functions(e.g. Database, erpAPI, PHPMail...)
*/
/** @var array $task
* information about all current executed cronjobs
*/
/** @var int $task_index
* index of current cronjob in $task array
*/
//information about the current running cronjob
$jobInfo = $task[$task_index];
//get the time of the last execution of this cronjob
$lastExec = $jobInfo['letzteausfuerhung'];
//it makes sense to keep some settings flexible (e.g. for test environment)
$outputDir = '';
$exportObject = new BmecatExportCronjob($app, $lastExec, $outputDir);
$exportObject->execute();
final class BmecatExportCronjob
{
/** @var erpooSystem $app */
private $app;
/** @var array $data */
private $data;
/** @var string $lastExecution */
private $lastExecution;
/** @var SimpleXMLElement $xml */
private $xml;
/** @var string $outputDirectory */
private $outputDirectory;
/** @var bool $debugMode */
private $debugMode = true; //set to false for production usage
/**
* @param erpooSystem $app
* @param string $lastExecution
* @param string $outputDirectory
*/
public function __construct($app, $lastExecution, $outputDirectory = '')
{
$this->app = $app;
$this->lastExecution = $lastExecution;
if ($outputDirectory !== '') {
$this->outputDirectory = $outputDirectory;
} else {
//get the userdata directory of Xentral as default value for output directory
$this->outputDirectory = $app->Conf->WFuserdata;
}
}
/**
* Collects data for the BMECAT export and writes the result to a file
*
* @throws Exception
*
* @return void
*/
public function execute()
{
$this->app->erp->LogFile('BMECAT_export started', __FILE__);
//collect data for the export
$this->data['header'] = $this->getHeader();
$articles = $this->getArticles();
//if there are no changed articles we leave a hint in the logfile and exit cleanly
if (empty($articles)) {
$this->app->erp->LogFile('BMECAT_export: No changed articles since last execution.');
return;
}
$this->data['t_update_products']['_attr_prev_version'] = '0';
$this->data['t_update_products']['article'] = $articles;
//render the XML string according to BMECAT specs
$this->xml = $this->toXml();
//It is good practice to use filenames with timestamps for output to avoid colliding results
$date = new DateTime('now');
$filename = sprintf('bmecat_export_%s.xml', $date->format('Y-m-d_H_i_s'));
//write the XML content to a file
$this->writeXmlFile($this->xml, $filename);
$this->app->erp->LogFile('BMECAT_export completed');
}
/**
* Write the xml string to a file in the userdata directory
*
* uses Xentral FileSystem class to write the file.
*
* @param string $xml
* @param string $filename
*/
private function writeXmlFile($xml, $filename)
{
$path = sprintf('%s/%s', $this->outputDirectory, $filename);
$this->debug('BMECAT_export write xml file', $path);
//we do not want to overwrite old export files
if (file_exists($path)) {
throw new RuntimeException(sprintf('BMECAT_export Cannot overwrite file %s', $path));
}
/** @var FilesystemFactory $factory */
$factory = $this->app->Container->get('FilesystemFactory');
/** @var Filesystem $fileSystem */
$fileSystem = $factory->createLocal('/');
$fileSystem->write($path, $xml);
}
/**
* transforms the data to xml string
*
* @return string
*/
private function toXml()
{
$xml = new SimpleXMLElement('<BMECAT/>');
$xml->addAttribute('version', '1.2');
$this->appendXmlRecursive($xml, $this->data);
return $xml->asXML();
}
/**
* walks through the data recursively and buidls the data structure for bmecat
*
* @param SimpleXMLElement $startnode
* @param array $data
* @param string $parentName
*/
private function appendXmlRecursive(SimpleXMLElement $startnode, $data, $parentName = '')
{
foreach ($data as $name => $value) {
if ($parentName !== '') {
$name = $parentName;
}
if (is_array($value)) {
if (count(array_filter(array_keys($value), 'is_string')) === 0) {
$this->appendXmlRecursive($startnode, $value, $name);
} else {
$child = $startnode->addChild(mb_strtoupper($name), '');
$this->appendXmlRecursive($child, $value);
}
} else {
if (StringUtil::startsWith($name, '_attr_')) {
$startnode->addAttribute(substr($name, 6), $value);
} else {
$startnode->addChild(mb_strtoupper($name), $value);
}
}
}
}
/**
* collects necessary data for the bmecat header
*
* @throws Exception
*
* @return array
*/
private function getHeader()
{
$header = [];
$header['generator_info'] = 'Xentral custom cronjob';
$date = new DateTime('now');
$header['catalog'] = [
'language' => 'deu',
'catalog_id' => 'standard_endkunden_katalog',
'catalog_version' => '001.001',
'datetime' => [
'_attr_type' => 'generation_date',
'date' => $date->format('Y-m-d'),
'time' => $date->format('H:i:s'),
],
];
$header['supplier'] = ['suppliername' => $this->app->erp- >Firmendaten('footer_0_1')];
return $header;
}
/**
* collects data about changed articles
*
* @return array
*/
private function getArticles()
{
$data = [];
//this query gets all articles that were edited since the cronjob's last execution
$sql = sprintf(
"SELECT a.id, a.typ, a.nummer, a.ean, a.name_de, a.katalogbezeichnung_de, a.katalogtext_de,
a.hersteller, a.herstellernummer
FROM artikel AS a
WHERE a.useredittimestamp > '%s' AND a.katalog = 1",
$this->lastExecution
);
$articles = $this->app->DB->SelectArr($sql);
$dbError = $this->app->DB->error();
if (!empty($dbError)) {
//this is a critical error, the cronjob can not continue
//so log the error message and quit the cronjob with an error by throwing an exception
$this->app->erp->LogFile('BMECAT_export article query failed', $this->app->DB->real_escape_string($sql));
$this->app->erp->LogFile('BMECAT_export DB error', $this->app->DB->real_escape_string($dbError));
throw new QueryFailureException('database query failed');
}
if (empty($articles)) {
return [];
}
$this->debug(sprintf('BMECAT_export %s changed articles found', count($articles)));
foreach ($articles as $article) {
$item = [];
$item['_attr_mode'] = 'update';
$item['supplier_aid'] = $article['nummer'];
$item['article_details']['description_short'] = $article['katalogbezeichnung_de'];
$item['article_details']['description_long'] = $article['katalogtext_de'];
if ($article['ean'] !== '') {
$item['article_details']['ean'] = $article['ean'];
}
if ($article['herstellernummer'] !== '') {
$item['article_details']['manufacturer_aid'] = $article['herstellernummer'];
}
if ($article['hersteller'] !== '') {
$item['article_details']['manufacturer_name'] = $article['hersteller'];
}
$prices = $this->getPrices((int)$article['id']);
foreach ($prices as $price) {
$item['article_price_details']['article_price'][] = [
'price_amount' => $price['preis'],
'price_currency' => $price['waehrung'],
];
}
$data[] = $item;
}
return $data;
}
/**
* Gets currently active prices of one article
*
* @param int $articleId
*
* @return array
*/
private function getPrices($articleId)
{
$this->debug('get prices for article', $articleId);
//this query gets all prices of the specified article that are active on the current date
$sql = sprintf(
"SELECT p.preis, p.waehrung, p.ab_menge
FROM verkaufspreise AS p
WHERE p.art = 'kunde' AND p.adresse = 0 AND p.geloescht = 0
AND (p.gueltig_bis = '0000-00-00' OR curdate() <= p.gueltig_bis OR isnull(gueltig_bis))
AND (p.gueltig_ab = '0000-00-00' OR curdate() >= p.gueltig_ab OR isnull(gueltig_ab))
AND p.artikel = %s
ORDER BY p.ab_menge",
$articleId
);
$prices = $this->app->DB->SelectArr($sql);
$dbError = $this->app->DB->error();
if (!empty($dbError)) {
//this is a noncritical error, the cronjob can still continue, just log the error message
$this->app->erp->LogFile('BMECAT_export price query failed', $this->app->DB->real_escape_string($sql));
$this->app->erp->LogFile('BMECAT_export DB error', $this->app->DB->real_escape_string($dbError));
}
if (empty($prices)) {
return [];
}
return $prices;
}
/**
* Prints log message to logfile table
* (only if debug mode is enabled)
*
* @param string $message
* @param mixed|null $dump
*/
private function debug($message, $dump = null)
{
if ($this->debugMode === true) {
$this->app->erp->LogFile($message, $dump);
}
}
}
The following example shows how to create a file attachment to a document. This code example registers on the trigger when a document is changed and creates a file, which can then extend the document as an attachment. In xentral there are e.g. additional payment slips for invoices or dangerous goods declarations, which are anchored in the system according to this pattern. However, any type of file attachment is conceivable. As long as the file is stored as an attachment type, it can be sent automatically with the invoice or printed additionally when printing the invoice by a suitable configuration in the project settings.
-
The file must be saved in the www/pages folder
-
The file name must consist of lowercase letters
-
The class name must start with an uppercase letter, the rest are lowercase letters
-
If the file is located at the destination, it must be called once for the hook to be registered - so in this case: index.php?module=example
-
On the page itself, xentral has added a checkbox in the example, with which you can switch the stored logic on and off. At the first call the checkbox has to be set once. This will be saved automatically - therefore no save button is necessary
<?php
// TODO Wichtig! Nur der erste Buchstabe gross, kein CamelCase;
// Der Name der Datei selbst muss klein geschrieben sein, also hier beispiel.php
class Beispiel
{
/** @var erpooSystem $app */
public $app;
/**
* @param erpooSystem $app
* @param bool $intern
*/
public function __construct($app, $intern = false)
{
$this->app = $app;
if($intern){
return;
}
$this->app->ActionHandlerInit($this);
$this->app->ActionHandler('list','Liste');
$this->app->DefaultActionHandler('list');
$this->app->ActionHandlerListen($app);
}
public function Liste()
{
$this->Install();
$this->app->YUI->AutoSaveKonfiguration('belegeanhangerzeugen', 'belegeanhangerzeugen');
$belegeanhangerzeugen = $this->app->erp->GetKonfiguration('belegeanhangerzeugen');
$checked = '';
if(!empty($belegeanhangerzeugen)){
$checked = 'checked=""';
}
$this->app->Tpl->Set('TAB1','Automatisch eine Anhangsdatei für Belege erzeugen:
<input type="checkbox" name="belegeanhangerzeugen" id="belegeanhangerzeugen" '.$checked.'>' );
$this->app->Tpl->Parse('PAGE','tabview.tpl');
}
public function Install()
{
$modulName = 'beispiel'; // TODO frei vergebbar, sollte nach dem ersten Mal aber nicht mehr verändert werden; z.b. Klassenname mit Kleinbuchstaben
$this->app->erp->RegisterHook('ANABREGSNeuberechnenEnde', $modulName, 'createDocument');
}
public function createFile($tmpPath,$documentId, $documentType)
{
$belegId = (int)$documentId;
$beleg = $this->app->DB->SelectRow(
"SELECT *
FROM {$documentType} AS r
WHERE r.id = '{$belegId}'"
);
if (empty($beleg)) {
return;
}
// TODO
/*
* Hier wird die eigentliche Datei im z.b. temp-Verzeichnis erzeugt
*
*/
}
public function createDocument($documentId, $documentType)
{
if(
$documentType == 'auftrag' ||
empty($documentId) ||
$this->app->erp->GetKonfiguration('belegeanhangerzeugen')!=='on'
){
return;
}
$tmpPath = ''; // TODO z.b. tempnam(sys_get_temp_dir(), 'beleg') . '.csv'
$this->createFile($tmpPath,$documentId, $documentType); // TODO
$fileTitle = ''; // TODO
$fileDesc = ''; // TODO
$fileName = ''; // TODO
$fileHash = ''; // TODO z.b. md5(serialize($belegDatenObjekt));
$bearbeiter = $this->app->User->GetName();
// Prüfen ob vorher schon mal eine Datei zu Beleg generiert wurde
$dateiCheck = $this->app->DB->SelectArr(sprintf(
"SELECT d.id AS datei_id, dv.bemerkung AS datei_hash
FROM datei AS d
INNER JOIN datei_stichwoerter AS ds ON d.id = ds.datei
INNER JOIN datei_version AS dv ON d.id = dv.datei
WHERE ds.subjekt = 'anhang' AND ds.objekt = '%s'
AND d.titel = '%s' AND ds.parameter = '%s'
AND d.geloescht = 0
ORDER BY dv.id DESC
LIMIT 1",
$documentType, $fileTitle, $documentId
));
$dateiId = (int)$dateiCheck[0]['datei_id'];
$dateiHash = $dateiCheck[0]['datei_hash'];
if($dateiId > 0){
// Beleg ist vorhanden > Prüfen ob sich der Inhalt geändert hat
if($dateiHash !== $fileHash){
// Hash in Bemerkungsfeld hat sich geändert > Neue Datei-Version hinterlegen
// Hash stellt sicher dass Inhalte identisch sind.
$this->app->erp->AddDateiVersion($dateiId, $bearbeiter, $fileName, $fileHash, $tmpPath);
}
}
else{
// Datei nicht vorhanden > Datei als Anhang zum Beleg anlegen
$dateiId = $this->app->erp->CreateDatei($fileName, $fileTitle, $fileDesc, '', $tmpPath, $bearbeiter);
$this->app->erp->AddDateiStichwort($dateiId, 'anhang', $documentType, $documentId);
}
}
}
Within the process some variables are set, which provide useful information and functions:
-
$app: Access to various services
-
$app->DB Class DB: accesses to the xentral database
-
$app->Conf class Config: access to database connection data and userdata directory (e.g. to store result files there)
-
$app->Container class ServiceContainer: access to further services, e.g. FileSystem)
-
$app->erp class erpAPI: Access to all functions of the erpAPI. Here you can find predefined database queries and help functions
-
$app->mail Class PHPMailer: Access to email functions, e.g. to send a notification email to the administrator after successful execution
-
-
Create a class for the program logic of the process starter and keep the procedural code as small as possible
-
The $app->erp->LogFile() function is used to generate log messages in the Logfile table
-
Set a variable to switch the output of detailed log messages on and off to save resources in live operation
-
If an exception is thrown that the process starter does not handle itself, the process starter is marked as faulty in the overview and the error message can be viewed in the logfile table. Example error in logfile: Process starter error when calling the module bmecatexportarticles: Cant write XML file
-
This mechanism can be exploited and own exceptions can be thrown, if e.g. important conditions for the process are not fulfilled or a critical error occurs
We also welcome ideas, suggestions and code samples in our community: