Version Notes
See changelog.txt
Download this release
Release Info
Developer | SIEL Acumulus |
Extension | Siel_Acumulus |
Version | 4.3.0 |
Comparing to | |
See all releases |
Version 4.3.0
- app/code/community/Siel/Acumulus/Block/Adminhtml/Form.php +59 -0
- app/code/community/Siel/Acumulus/Block/Adminhtml/Form/Form.php +43 -0
- app/code/community/Siel/Acumulus/Helper/CompiledMagentoAutoLoader.php +16 -0
- app/code/community/Siel/Acumulus/Helper/Data.php +60 -0
- app/code/community/Siel/Acumulus/Model/Entry.php +46 -0
- app/code/community/Siel/Acumulus/Model/Order/Observer.php +67 -0
- app/code/community/Siel/Acumulus/Model/Resource/Entry.php +15 -0
- app/code/community/Siel/Acumulus/Model/Resource/Entry/Collection.php +17 -0
- app/code/community/Siel/Acumulus/Model/Resource/Setup.php +47 -0
- app/code/community/Siel/Acumulus/changelog.txt +275 -0
- app/code/community/Siel/Acumulus/controllers/Adminhtml/AcumulusController.php +75 -0
- app/code/community/Siel/Acumulus/etc/adminhtml.xml +41 -0
- app/code/community/Siel/Acumulus/etc/config.xml +102 -0
- app/code/community/Siel/Acumulus/events.txt +230 -0
- app/code/community/Siel/Acumulus/leesmij.txt +37 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/Countries.php +365 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/Form.php +590 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/FormRenderer.php +813 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/Log.php +210 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/MailTranslations.php +153 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/Mailer.php +176 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/Number.php +81 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/Requirements.php +36 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/TranslationCollection.php +34 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/Translator.php +51 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/TranslatorInterface.php +40 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/Completor.php +786 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/CompletorInvoiceLines.php +299 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/CompletorStrategy/ApplySameVatRate.php +61 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/CompletorStrategy/Fail.php +21 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/CompletorStrategy/SplitKnownDiscountLine.php +128 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/CompletorStrategy/SplitLine.php +233 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/CompletorStrategy/SplitNonMatchingLine.php +119 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/CompletorStrategy/TryAllVatRatePermutations.php +112 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/CompletorStrategyBase.php +344 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/CompletorStrategyLines.php +177 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/ConfigInterface.php +102 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/Creator.php +908 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/Source.php +137 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/Translations.php +69 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/Helpers/FormRenderer.php +45 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/Helpers/Log.php +56 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/Helpers/Mailer.php +29 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/HikaShop/Invoice/Creator.php +321 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/HikaShop/Invoice/Source.php +51 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/HikaShop/Shop/ConfigForm.php +40 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/HikaShop/Shop/InvoiceManager.php +79 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/Shop/AcumulusEntryModel.php +107 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/Shop/BatchForm.php +39 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/Shop/ConfigForm.php +23 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/Shop/ConfigStore.php +94 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/Shop/InvoiceManager.php +120 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/VirtueMart/Invoice/Creator.php +448 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/VirtueMart/Invoice/Source.php +52 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/VirtueMart/Shop/ConfigForm.php +41 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/VirtueMart/Shop/InvoiceManager.php +84 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Helpers/FormMapper.php +222 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Helpers/Log.php +46 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Helpers/Mailer.php +27 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Invoice/Completor.php +55 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Invoice/Creator.php +527 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Invoice/Source.php +95 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Shop/AcumulusEntryModel.php +104 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Shop/BatchForm.php +49 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Shop/ConfigForm.php +45 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Shop/ConfigStore.php +74 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Shop/InvoiceManager.php +111 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Helpers/Log.php +22 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Helpers/Mailer.php +43 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Helpers/OcHelper.php +319 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Helpers/Registry.php +96 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Invoice/Creator.php +352 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Invoice/Source.php +41 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/OpenCart1/Helpers/FormRenderer.php +27 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/OpenCart1/Helpers/OcHelper.php +12 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/OpenCart1/Shop/InvoiceManager.php +39 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/OpenCart2/Helpers/FormRenderer.php +51 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/OpenCart2/Helpers/OcHelper.php +96 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/OpenCart2/Shop/InvoiceManager.php +50 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Shop/AcumulusEntryModel.php +163 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Shop/BatchForm.php +27 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Shop/ConfigForm.php +59 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Shop/ConfigStore.php +71 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Shop/InvoiceManager.php +86 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Helpers/FormMapper.php +199 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Helpers/Log.php +45 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Helpers/Mailer.php +73 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Helpers/mails/.htaccess +14 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Helpers/mails/en/message.html +0 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Helpers/mails/en/message.txt +0 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Helpers/mails/nl/message.html +0 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Helpers/mails/nl/message.txt +0 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Invoice/Creator.php +499 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Invoice/Source.php +95 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Shop/AcumulusEntryModel.php +107 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Shop/BatchForm.php +45 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Shop/ConfigForm.php +115 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Shop/ConfigStore.php +73 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Shop/InvoiceManager.php +115 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/AcumulusEntryModel.php +148 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/BatchForm.php +254 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/BatchFormTranslations.php +125 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/BatchTranslations.php +32 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/Config.php +808 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/ConfigForm.php +647 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/ConfigFormTranslations.php +286 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/ConfigInterface.php +26 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/ConfigStore.php +46 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/ConfigStoreInterface.php +45 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/ConfirmUninstallForm.php +70 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/ConfirmUninstallFormTranslations.php +20 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/InjectorInterface.php +84 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/InvoiceManager.php +429 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/ModuleTranslations.php +60 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Web/Communicator.php +487 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Web/CommunicatorLocal.php +48 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Web/ConfigInterface.php +117 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Web/Service.php +412 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Web/Translations.php +36 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Helpers/FormMapper.php +81 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Helpers/FormRenderer.php +118 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Helpers/Log.php +23 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Helpers/Mailer.php +32 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Invoice/Creator.php +486 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Invoice/Source.php +57 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Shop/AcumulusEntryModel.php +150 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Shop/ConfigForm.php +67 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Shop/ConfigStore.php +61 -0
- app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Shop/InvoiceManager.php +181 -0
- app/code/community/Siel/Acumulus/libraries/Siel/leesmij.txt +54 -0
- app/code/community/Siel/Acumulus/libraries/Siel/psr4.php +28 -0
- app/code/community/Siel/Acumulus/license.txt +674 -0
- app/code/community/Siel/Acumulus/licentie-nl.pdf +0 -0
- app/code/community/Siel/Acumulus/sql/acumulus_setup/install-4.0.0.php +8 -0
- app/code/community/Siel/Acumulus/sql/acumulus_setup/upgrade-3.4.4-4.0.0.php +45 -0
- app/etc/modules/Siel_Acumulus.xml +9 -0
- package.xml +32 -0
app/code/community/Siel/Acumulus/Block/Adminhtml/Form.php
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class Siel_Acumulus_Block_Adminhtml_Form extends Mage_Adminhtml_Block_Widget_Form_Container {
|
4 |
+
|
5 |
+
/** @var Siel_Acumulus_Helper_Data */
|
6 |
+
protected $helper;
|
7 |
+
|
8 |
+
/** @var string */
|
9 |
+
protected $formType;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Constructor.
|
13 |
+
*
|
14 |
+
* @param array $args
|
15 |
+
*/
|
16 |
+
public function __construct(array $args = array()) {
|
17 |
+
$this->helper = Mage::helper('acumulus');
|
18 |
+
$this->formType = $args['formType'];
|
19 |
+
|
20 |
+
parent::__construct();
|
21 |
+
|
22 |
+
$this->_blockGroup = 'acumulus';
|
23 |
+
$this->_controller = 'adminhtml_form';
|
24 |
+
$this->_removeButton('delete');
|
25 |
+
$this->_removeButton('back');
|
26 |
+
if ($this->formType === 'batch') {
|
27 |
+
$this->_updateButton('save', 'label', $this->t('button_send'));
|
28 |
+
}
|
29 |
+
}
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Helper method to translate strings.
|
33 |
+
*
|
34 |
+
* @param string $key
|
35 |
+
* The key to get a translation for.
|
36 |
+
*
|
37 |
+
* @return string
|
38 |
+
* The translation for the given key or the key itself if no translation
|
39 |
+
* could be found.
|
40 |
+
*/
|
41 |
+
protected function t($key) {
|
42 |
+
return $this->helper->t($key);
|
43 |
+
}
|
44 |
+
|
45 |
+
protected function _prepareLayout()
|
46 |
+
{
|
47 |
+
// Don't let the parent create the block.
|
48 |
+
$old = $this->_mode;
|
49 |
+
$this->_mode = '';
|
50 |
+
$this->setChild('form', $this->getLayout()->createBlock('acumulus/adminhtml_form_form', '', array('formType' => $this->formType)));
|
51 |
+
$result = parent::_prepareLayout();
|
52 |
+
$this->_mode = $old;
|
53 |
+
return $result;
|
54 |
+
}
|
55 |
+
|
56 |
+
public function getHeaderText() {
|
57 |
+
return $this->t("{$this->formType}_form_header");
|
58 |
+
}
|
59 |
+
}
|
app/code/community/Siel/Acumulus/Block/Adminhtml/Form/Form.php
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
use Siel\Acumulus\Magento\Helpers\FormMapper;
|
4 |
+
|
5 |
+
class Siel_Acumulus_Block_Adminhtml_Form_Form extends Mage_Adminhtml_Block_Widget_Form {
|
6 |
+
|
7 |
+
/** @var Siel_Acumulus_Helper_Data */
|
8 |
+
protected $helper = NULL;
|
9 |
+
|
10 |
+
/** @var string */
|
11 |
+
protected $formType;
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Siel_Acumulus_Block_Adminhtml_Form constructor.
|
15 |
+
*
|
16 |
+
* @param array $attributes
|
17 |
+
*/
|
18 |
+
public function __construct(array $attributes = array()) {
|
19 |
+
$this->helper = Mage::helper('acumulus');
|
20 |
+
$this->formType = $attributes['formType'];
|
21 |
+
parent::__construct($attributes);
|
22 |
+
}
|
23 |
+
|
24 |
+
protected function _prepareForm() {
|
25 |
+
$acumulusForm = $this->helper-> getAcumulusConfig()->getForm($this->formType);
|
26 |
+
$form = new Varien_Data_Form();
|
27 |
+
$mapper = new FormMapper();
|
28 |
+
$mapper->map($form, $acumulusForm->getFields());
|
29 |
+
|
30 |
+
$form->setValues($acumulusForm->getFormValues());
|
31 |
+
/** @noinspection PhpUndefinedMethodInspection */
|
32 |
+
$form->setAction($this->getUrl("*/*/{$this->formType}"));
|
33 |
+
/** @noinspection PhpUndefinedMethodInspection */
|
34 |
+
$form->setMethod('post');
|
35 |
+
/** @noinspection PhpUndefinedMethodInspection */
|
36 |
+
$form->setUseContainer(true);
|
37 |
+
$form->setId('edit_form');
|
38 |
+
$this->setForm($form);
|
39 |
+
|
40 |
+
return parent::_prepareForm();
|
41 |
+
}
|
42 |
+
|
43 |
+
}
|
app/code/community/Siel/Acumulus/Helper/CompiledMagentoAutoLoader.php
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @file This file registers an autoloader for the Siel namespace library when
|
4 |
+
* Magento has been compiled.
|
5 |
+
*/
|
6 |
+
namespace Siel;
|
7 |
+
|
8 |
+
// Prepend this autoloader: it will not throw, nor warn, while the shop specific
|
9 |
+
// autoloader will do so.
|
10 |
+
spl_autoload_register(function($class) {
|
11 |
+
if (strpos($class, __NAMESPACE__ . '\\') === 0) {
|
12 |
+
$fileName = __DIR__ . DIRECTORY_SEPARATOR . 'Siel_Acumulus_libraries_' . str_replace('\\', '_', $class) . '.php';
|
13 |
+
/** @noinspection PhpIncludeInspection */
|
14 |
+
include($fileName);
|
15 |
+
}
|
16 |
+
}, FALSE, TRUE);
|
app/code/community/Siel/Acumulus/Helper/Data.php
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
use Siel\Acumulus\Shop\Config;
|
3 |
+
|
4 |
+
class Siel_Acumulus_Helper_Data extends Mage_Core_Helper_Abstract {
|
5 |
+
|
6 |
+
/** @var \Siel\Acumulus\Shop\Config */
|
7 |
+
protected static $acumulusConfig = NULL;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Siel_Acumulus_Helper_Data constructor.
|
11 |
+
*/
|
12 |
+
public function __construct() {
|
13 |
+
$this->init();
|
14 |
+
}
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Helper method that initializes our environment:
|
18 |
+
* - autoloader for the library part.
|
19 |
+
* - translator
|
20 |
+
* - acumulusConfig
|
21 |
+
*/
|
22 |
+
protected function init() {
|
23 |
+
if (static::$acumulusConfig === NULL) {
|
24 |
+
// Our library structure is incompatible with autoload in Magento: we
|
25 |
+
// register our own auto loader.
|
26 |
+
$acumulusDir = dirname(dirname(__FILE__));
|
27 |
+
if (!@include_once($acumulusDir . '/libraries/Siel/psr4.php')) {
|
28 |
+
// Magento has been "compiled", use a more specific autoloader.
|
29 |
+
require_once(dirname(__FILE__) . '/CompiledMagentoAutoLoader.php');
|
30 |
+
}
|
31 |
+
static::$acumulusConfig = new Config('Magento', Mage::app()->getLocale()->getLocaleCode());
|
32 |
+
}
|
33 |
+
}
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Helper method to translate strings.
|
37 |
+
*
|
38 |
+
* @param string $key
|
39 |
+
* The key to get a translation for.
|
40 |
+
*
|
41 |
+
* @return string
|
42 |
+
* The translation for the given key or the key itself if no translation
|
43 |
+
* could be found.
|
44 |
+
*/
|
45 |
+
public function t($key) {
|
46 |
+
return static::$acumulusConfig->getTranslator()->get($key);
|
47 |
+
}
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Returns the configuration settings object central to this extension.
|
51 |
+
*
|
52 |
+
* @return \Siel\Acumulus\Shop\Config
|
53 |
+
* The Acumulus config.
|
54 |
+
*/
|
55 |
+
public function getAcumulusConfig() {
|
56 |
+
$this->init();
|
57 |
+
return static::$acumulusConfig;
|
58 |
+
}
|
59 |
+
|
60 |
+
}
|
app/code/community/Siel/Acumulus/Model/Entry.php
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php /** @noinspection PhpLanguageLevelInspection */
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class Siel_Acumulus_Model_Entry Acumulus is used by
|
5 |
+
* Siel\Acumulus\Shop\Magento\AcumulusEntryModel to access the entry table from
|
6 |
+
* the database.
|
7 |
+
*
|
8 |
+
* @method $this setEntryId(int $value)
|
9 |
+
* @method int getEntryId()
|
10 |
+
* @method $this setToken(string $value)
|
11 |
+
* @method string getToken()
|
12 |
+
* @method $this setSourceType(string $value)
|
13 |
+
* @method string getSourceType()
|
14 |
+
* @method $this setSourceId(int $value)
|
15 |
+
* @method int getSourceId()
|
16 |
+
* @method $this setCreated(int $value)
|
17 |
+
* @method int getCreated()
|
18 |
+
* @method $this setUpdated(int $value)
|
19 |
+
* @method int getUpdated()
|
20 |
+
*/
|
21 |
+
class Siel_Acumulus_Model_Entry extends Mage_Core_Model_Abstract {
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Magento "internal constructor" not receiving any parameters.
|
25 |
+
*/
|
26 |
+
protected function _construct() {
|
27 |
+
$this->_init('acumulus/entry');
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Overrides the save() method to clear the created column and set the updated
|
32 |
+
* column before being written to the database. The created timestamp is set
|
33 |
+
* by the database and should not be set by the application. As MySQl < 5.6.5
|
34 |
+
* only allows one timestamp with a default value, we do set the updated
|
35 |
+
* timestamp in code (http://stackoverflow.com/a/17498167/1475662).
|
36 |
+
*
|
37 |
+
* @return $this
|
38 |
+
*/
|
39 |
+
public function save() {
|
40 |
+
$this
|
41 |
+
->setUpdated(Mage::app()->getLocale()->storeTimeStamp())
|
42 |
+
->unsetData('created');
|
43 |
+
return parent::save();
|
44 |
+
}
|
45 |
+
|
46 |
+
}
|
app/code/community/Siel/Acumulus/Model/Order/Observer.php
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
use Siel\Acumulus\Invoice\Source;
|
4 |
+
use Siel\Acumulus\Web\ConfigInterface as WebConfigInterface;
|
5 |
+
|
6 |
+
class Siel_Acumulus_Model_Order_Observer extends Mage_Core_Model_Abstract {
|
7 |
+
|
8 |
+
/** @var Siel_Acumulus_Helper_Data */
|
9 |
+
protected $helper;
|
10 |
+
|
11 |
+
public function __construct() {
|
12 |
+
$this->helper = Mage::helper('acumulus');
|
13 |
+
parent::__construct();
|
14 |
+
}
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Event handler for the sales_order_save_after event.
|
18 |
+
*
|
19 |
+
* @param Varien_Event_Observer $observer
|
20 |
+
*
|
21 |
+
* @return bool
|
22 |
+
*/
|
23 |
+
public function orderSaveAfter(Varien_Event_Observer $observer) {
|
24 |
+
/** @var Varien_Event $event */
|
25 |
+
$event = $observer->getEvent();
|
26 |
+
/** @var Mage_Sales_Model_Order $order */
|
27 |
+
/** @noinspection PhpUndefinedMethodInspection */
|
28 |
+
$order = $event->getOrder();
|
29 |
+
$source = $this->helper->getAcumulusConfig()->getSource(Source::Order, $order);
|
30 |
+
return $this->helper->getAcumulusConfig()->getManager()->sourceStatusChange($source) !== WebConfigInterface::Status_Exception;
|
31 |
+
}
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Event handler for the sales_order_creditmemo_save_after event.
|
35 |
+
*
|
36 |
+
* @param Varien_Event_Observer $observer
|
37 |
+
*
|
38 |
+
* @return bool
|
39 |
+
*/
|
40 |
+
public function creditMemoSaveAfter(Varien_Event_Observer $observer) {
|
41 |
+
/** @var Varien_Event $event */
|
42 |
+
$event = $observer->getEvent();
|
43 |
+
/** @var Mage_Sales_Model_Order_Creditmemo $creditMemo */
|
44 |
+
/** @noinspection PhpUndefinedMethodInspection */
|
45 |
+
$creditMemo = $event->getCreditmemo();
|
46 |
+
$source = $this->helper->getAcumulusConfig()->getSource(Source::CreditNote, $creditMemo);
|
47 |
+
return $this->helper->getAcumulusConfig()->getManager()->sourceStatusChange($source) !== WebConfigInterface::Status_Exception;
|
48 |
+
}
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Event handler for the sales_order_invoice_save_after event.
|
52 |
+
*
|
53 |
+
* @param Varien_Event_Observer $observer
|
54 |
+
*
|
55 |
+
* @return bool
|
56 |
+
*/
|
57 |
+
public function invoiceSaveAfter(Varien_Event_Observer $observer) {
|
58 |
+
/** @var Varien_Event $event */
|
59 |
+
$event = $observer->getEvent();
|
60 |
+
/** @var Mage_Sales_Model_Order_Invoice $invoice */
|
61 |
+
/** @noinspection PhpUndefinedMethodInspection */
|
62 |
+
$invoice = $event->getInvoice();
|
63 |
+
$source = $this->helper->getAcumulusConfig()->getSource(Source::Order, $invoice->getOrderId());
|
64 |
+
return $this->helper->getAcumulusConfig()->getManager()->invoiceCreate($source) !== WebConfigInterface::Status_Exception;
|
65 |
+
}
|
66 |
+
|
67 |
+
}
|
app/code/community/Siel/Acumulus/Model/Resource/Entry.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Acumulus entry resource
|
5 |
+
*/
|
6 |
+
class Siel_Acumulus_Model_Resource_Entry extends Mage_Core_Model_Resource_Db_Abstract {
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Magento "internal constructor" not receiving any parameters.
|
10 |
+
*/
|
11 |
+
protected function _construct() {
|
12 |
+
$this->_init('acumulus/entry', 'entity_id');
|
13 |
+
}
|
14 |
+
|
15 |
+
}
|
app/code/community/Siel/Acumulus/Model/Resource/Entry/Collection.php
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Acumulus entry collection class is used by
|
5 |
+
* Siel\Acumulus\Shop\Magento\AcumulusEntryModel to retrieve record sets from
|
6 |
+
* the database.
|
7 |
+
*/
|
8 |
+
class Siel_Acumulus_Model_Resource_Entry_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract {
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Magento "internal constructor" not receiving any parameters.
|
12 |
+
*/
|
13 |
+
protected function _construct() {
|
14 |
+
$this->_init('acumulus/entry');
|
15 |
+
}
|
16 |
+
|
17 |
+
}
|
app/code/community/Siel/Acumulus/Model/Resource/Setup.php
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Acumulus resource setup
|
5 |
+
*/
|
6 |
+
class Siel_Acumulus_Model_Resource_Setup extends Mage_Core_Model_Resource_Setup {
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Defines the table definition.
|
10 |
+
*
|
11 |
+
* Called by the install script and the update script.
|
12 |
+
*
|
13 |
+
* @return Varien_Db_Ddl_Table
|
14 |
+
*/
|
15 |
+
public function getTableDefinition() {
|
16 |
+
$table = $this->getConnection()->newTable($this->getTable('acumulus/entry'))
|
17 |
+
->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
|
18 |
+
'unsigned' => true,
|
19 |
+
'nullable' => false,
|
20 |
+
'primary' => true,
|
21 |
+
'identity' => true,
|
22 |
+
), 'Technical key')
|
23 |
+
->addColumn('entry_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
|
24 |
+
'unsigned' => true,
|
25 |
+
'nullable' => false,
|
26 |
+
), 'Acumulus entry id')
|
27 |
+
->addColumn('token', Varien_Db_Ddl_Table::TYPE_CHAR, 32, array(
|
28 |
+
'nullable' => false,
|
29 |
+
), 'Acumulus invoice token')
|
30 |
+
->addColumn('source_type', Varien_Db_Ddl_Table::TYPE_VARCHAR, 20, array(
|
31 |
+
'nullable' => false,
|
32 |
+
), 'Invoice source type')
|
33 |
+
->addColumn('source_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
|
34 |
+
'unsigned' => true,
|
35 |
+
'nullable' => true,
|
36 |
+
), 'Magento invoice source id')
|
37 |
+
->addColumn('created', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array(
|
38 |
+
'default' => Varien_Db_Ddl_Table::TIMESTAMP_INIT,
|
39 |
+
), 'Timestamp created')
|
40 |
+
->addColumn('updated', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array(), 'Timestamp updated')
|
41 |
+
->addIndex('siel_acumulus_entry_id', 'entry_id', array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE))
|
42 |
+
->addIndex('siel_acumulus_source', array('source_type', 'source_id'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE))
|
43 |
+
->setComment('Acumulus entry table');
|
44 |
+
return $table;
|
45 |
+
}
|
46 |
+
|
47 |
+
}
|
app/code/community/Siel/Acumulus/changelog.txt
ADDED
@@ -0,0 +1,275 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Changelog Acumulus webkoppelingen
|
2 |
+
=================================
|
3 |
+
ALL = Alle webshops.
|
4 |
+
HS = HikaShop (Joomla)
|
5 |
+
MA = Magento
|
6 |
+
OC = OpenCart 1
|
7 |
+
OC2 = OpenCart 2
|
8 |
+
PS = PrestaShop
|
9 |
+
VM3 = VirtueMart3 (Joomla)
|
10 |
+
WC = WooCommerce (WordPress)
|
11 |
+
|
12 |
+
4.3.0 (2016-03-08)
|
13 |
+
------------------
|
14 |
+
- ALL: Op PHP5.3: Fatal error: Uncaught exception 'ReflectionException' with
|
15 |
+
message 'Class Siel\Acumulus\WooCommerce\Shop\AcumulusEntryModel does not
|
16 |
+
have a constructor, so you cannot pass any constructor arguments' in
|
17 |
+
/wp-content/plugins/acumulus/libraries/Siel/Acumulus/Shop/Config.php:211.
|
18 |
+
- OC1: Eerste versie op deze library: Europese BTW tarieven; Batch verzending.
|
19 |
+
Nog geen refunds.
|
20 |
+
- OC2: Typo die voorkomt dat coupon regels gesplitst kunnen worden over
|
21 |
+
verschillende BTW tarieven.
|
22 |
+
- OC2: Typo die voorkomt dat het acumulus.invoice.sent event uitgevoerd word.
|
23 |
+
- ALL: Waarden voor de instellingen "moment van versturen" en "Bestelstatus"
|
24 |
+
worden niet getoond op formulier (wel opgeslagen en gebruikt).
|
25 |
+
- WC: Class batchform verwijderd, base batchform is niet meer abstract.
|
26 |
+
- PS: PS specifieke code volgens standards van https://addons.prestashop.com.
|
27 |
+
- ALL: Code nu volgens PSR-2 coding standard opgemaakt.
|
28 |
+
|
29 |
+
4.2.1 (2016-02-24)
|
30 |
+
------------------
|
31 |
+
- PS: Form submit van instellingenformulier wordt niet herkend en verwerkt.
|
32 |
+
- WC: Variabele producten waarbij de varities niet als aparte SKUs aangemaakt
|
33 |
+
zijn kwamen zonder variantomschrijving terecht op de factuurregels.
|
34 |
+
- ALL: Nieuwe optie toegevoegd om facturen met verlegde BTW te kunnen
|
35 |
+
onderscheiden van facturen met alleen BTW vrije producten. Dit voorkomt een
|
36 |
+
hoop waarschuwingen bij de meeste winkels die toch geen BTW vrije producten
|
37 |
+
of diensten leveren.
|
38 |
+
|
39 |
+
4.2.0 (2016-02-16)
|
40 |
+
------------------
|
41 |
+
- ALL: De completor kijkt nu naar de bestellingstotalen en de totalen van de
|
42 |
+
afzonderlijke bestellingsregels en voegt een correctieregel toe als deze niet
|
43 |
+
gelijk zijn. Dit leverde fouten in verschillende plugins op, met name bij
|
44 |
+
kortingen in refunds:
|
45 |
+
* MA: BTW op korting op verzendkosten in een credit note worden niet
|
46 |
+
opgeslagen, bereken aan de hand van ontbrekende bedragen.
|
47 |
+
* PS: Kortingsbedrag (ex BTW) op een order dat teruggevorderd wordt op een
|
48 |
+
credit slip is niet bekend: bereken aan de hand van het matchen van
|
49 |
+
ontbrekende bedragen met kortingen op de oorspronkelijke order.
|
50 |
+
* VM3: Verbeterde behandeling van kortingen zowel via calc rules als coupons.
|
51 |
+
- ALL: Sla geen leeg wachtwoord op (staat toe dat, eenmaal ingevuld, het niet
|
52 |
+
meer naar en van de browser verstuurd hoeft te worden).
|
53 |
+
- ALL: type fout waardoor removeEmptyShipping() niet correct werkte.
|
54 |
+
- PS: Mogelijke PHP warning als een order via batchformulier verzonden wordt.
|
55 |
+
- MA: Foute verzendkosten werden verstuurd als een korting ook van toepassing is
|
56 |
+
op die verzendkosten.
|
57 |
+
- MA: Payment fee kan vooralsnog niet worden herkend: getPaymentchargeAmount()
|
58 |
+
is een niet bestaande method.
|
59 |
+
- ALL: Source:;getStatus() toegevoegd. allows further refactorings.
|
60 |
+
- ALL: refactorings, waaronder
|
61 |
+
* hernoemen van een aantal 'meta-...' tags.
|
62 |
+
* introductie en gebruik van ShopConfig::getForm().
|
63 |
+
* introductie en gebruik van Source:;getStatus().
|
64 |
+
- OC2: bug fixing.
|
65 |
+
|
66 |
+
4.2.0-beta9 (2016-01-25)
|
67 |
+
------------------------
|
68 |
+
- OC2: Eerste versie op deze library: Europese BTW tarieven; Batch verzending.
|
69 |
+
Nog geen refunds.
|
70 |
+
- ALL: Verbeterde verwerking van facturen die (mogelijk of zeker) gesplitst
|
71 |
+
moeten worden. Als er meerdere factuurtypes mogelijk zijn, wordt er 1
|
72 |
+
conceptfactuur verstuurd die in Acumulus gecorrigeerd en evt gesplitst moet
|
73 |
+
worden.
|
74 |
+
- HS: zelf aangemaakte statussen werden niet herkend bij een orderstatuswijziging.
|
75 |
+
|
76 |
+
4.2.0-beta8 (2016-01-18)
|
77 |
+
------------------------
|
78 |
+
- ALL: Optie toegevoegd om aan te geven of u in uw winkel digitale diensten
|
79 |
+
aanbiedt. Dit wordt gebruikt voor een betere selectie van en controle op de
|
80 |
+
mogelijke BTW types. GEEF DEZE OPTIE EEN WAARDE IN HET INSTELLINGENSCHERM!
|
81 |
+
- WC: Klikbare link naar de changelog in de WP readme.txt.
|
82 |
+
- WC: filters.txt aangepast zodat huidige parameters en hun type beschreven
|
83 |
+
worden.
|
84 |
+
- PS: do no longer update not changed fields id_shop and id_shop_group.
|
85 |
+
|
86 |
+
|
87 |
+
4.2.0-beta7 (2016-01-13)
|
88 |
+
------------------------
|
89 |
+
- WC: Gebruik WC logging ipv de wel erg basic (= afwezige) WP logging. Acumulus
|
90 |
+
heeft nu zijn eigen log onder WooCommerce - Systeem status - Logs.
|
91 |
+
- ALL: Verwijder dubbele email adresseringen en display names.
|
92 |
+
- ALL: Verbeterde verwerking van gelijke BTW percentages (b.v. BE en NL 21%).
|
93 |
+
Als er meerdere factuurtypes mogelijk zijn wordt er 1 conceptfactuur verstuurd
|
94 |
+
die in Acumulus gecorrigeerd en evt gesplitst kan worden.
|
95 |
+
- ALL: Meer logging.
|
96 |
+
- HS/VM3: log message (gebruikt voor debugging) verwijderd.
|
97 |
+
|
98 |
+
4.2.0-beta6 (2016-01-11)
|
99 |
+
------------------------
|
100 |
+
- HS/VM3: Logging werkte alleen als Joomla in debug modus is en ook dan nog niet
|
101 |
+
voor debug messages. Acumulus heeft nu zijn eigen log file.
|
102 |
+
- ALL: empty <line> in XML als item with key = 0 is verwijderd, bv. door de
|
103 |
+
"Verzend geen gratis verzending regels" optie.
|
104 |
+
|
105 |
+
4.2.0-beta5 (2016-01-07)
|
106 |
+
------------------------
|
107 |
+
- HS/VM3: Corrected bad fix introduced in 4.2.0-beta3 (PHP5.5 constructs).
|
108 |
+
|
109 |
+
4.2.0-beta4 (2016-01-06)
|
110 |
+
------------------------
|
111 |
+
- WC, PS, MA: Uncaught exception 'ReflectionException' with message 'Class
|
112 |
+
Siel\Acumulus\WooCommerce\Shop\ConfigStore does not have a constructor, so you
|
113 |
+
cannot pass any constructor arguments' (error introduced in 4.2.0-beta2).
|
114 |
+
- WC: Hoodlettergevoeligheid bij doorgeven van namespace. Geeft: Fatal error:
|
115 |
+
Cannot instantiate abstract class Siel\Acumulus\Shop\InvoiceManager in
|
116 |
+
/Siel/Acumulus/Shop/Config.php on line 203 (error introduced in 4.2.0-alpha1).
|
117 |
+
|
118 |
+
4.2.0-beta3 (2016-01-05)
|
119 |
+
------------------------
|
120 |
+
- VM3,HS: PHP5.5 constructs in Joomla\Shop\InvoiceManager.php.
|
121 |
+
- WC: Added to Wordpress.org
|
122 |
+
|
123 |
+
4.2.0-beta2 (2016-01-05)
|
124 |
+
------------------------
|
125 |
+
- VM3,HS: Joomla componenten mogen dezelfde naam hebben.
|
126 |
+
|
127 |
+
4.2.0-beta1 (2016-01-04)
|
128 |
+
------------------------
|
129 |
+
- HS: Orders verzenden werkt, refunds nog niet
|
130 |
+
|
131 |
+
4.2.0-alpha5 (2015-12-31)
|
132 |
+
-------------------------
|
133 |
+
- VM3,HS: Meer logging tijdens installeren.
|
134 |
+
- PS: Ondersteun versies voor 1.6.0.x door te controleren of getOrderDetailTaxes
|
135 |
+
wel aanwezig is.
|
136 |
+
- HS: customer gedeelte van facturen zo nu correct moeten werken.
|
137 |
+
|
138 |
+
4.2.0-alpha3 (2015-12-28)
|
139 |
+
-------------------------
|
140 |
+
- VM3: Define and trigger Joomla events onAcumulusInvoice[Created|Completed|Sent].
|
141 |
+
- WC: Added readme.txt conform the wordpress.org instructions.
|
142 |
+
- WC: Add our own manual line to the manual lines of the system instead of
|
143 |
+
replacing them.
|
144 |
+
- VM3: return type van AcumulusEntryModel::getByEntryId en getByInvoiceSourceId
|
145 |
+
waren fout.
|
146 |
+
- VM3: Sta selecteren statussen om te verzenden toe op ConfigForm.
|
147 |
+
|
148 |
+
4.2.0-alpha1
|
149 |
+
------------
|
150 |
+
- Groot aantal refactorings om ondersteuning voor HikaShop toe te voegen. Code
|
151 |
+
die geen gebruik maakt van VM3 functies maar alleen maar van Joomla functies
|
152 |
+
is apart gezet.
|
153 |
+
- VM3: Ondersteuning voor automatische versturing.
|
154 |
+
- VM3: return type van AcumulusEntryModel::getByEntryId en getByInvoiceSourceId
|
155 |
+
waren fout, maar worden (nog) niet gebruikt.
|
156 |
+
- PS,WC: handmatig ingevoerde regels bij refunds konden genegeerd worden.
|
157 |
+
- VM3: Error in install script: readme-nl.txt is hernoemd naar leesmij.txt.
|
158 |
+
- PS: De koppeling werkt ook met PS1.5. Minimale versie requirement aangepast.
|
159 |
+
- ALL: Prevent autoloader warnings when file does not exist.
|
160 |
+
- VM3: Parse error: syntax error, unexpected T_OBJECT_OPERATOR in
|
161 |
+
com_acumulusInstallerScript.php on line 68: PHP5.3 incompatibiliteit.
|
162 |
+
|
163 |
+
4.1.1 (2015-11-07)
|
164 |
+
------------------
|
165 |
+
- ALL: Batchverzending: onterechte/verkeerde melding "niet verzonden omdat
|
166 |
+
factuur al verzonden is" als factuurverzending wordt tegengehouden door een
|
167 |
+
event.
|
168 |
+
- WC: Versienummer van de plugin zelf was niet bijgewerkt, alleen die van de
|
169 |
+
library.
|
170 |
+
- WC: Updated the documentation of filters and actions in filters.txt
|
171 |
+
- ALL: Undefined index: countrycode in Siel/Acumulus/Invoice/Creator.php line 294
|
172 |
+
- ALL: API hernoemd naar Library (in versieinfo).
|
173 |
+
- ALL [#39961]: Er bestaan producten die niet onderhevig aan BTW zijn.
|
174 |
+
|
175 |
+
4.1.0 (2015-10-31)
|
176 |
+
------------------
|
177 |
+
- MA,PS,WC [#33383]: Het is nu mogelijk om meerdere order statussen te kiezen
|
178 |
+
waarbij de order naar Acumulus verstuurd moet worden.
|
179 |
+
- WC [#33222]: Om de betaalstatus van een order te bepalen, wordt niet meer
|
180 |
+
gekeken naar paid_date, alleen nog maar naar needs_payments().
|
181 |
+
- WC [https://forum.acumulus.nl/index.php?topic=4984.0]: In WC is het mogelijk
|
182 |
+
om producten te verwijderen.
|
183 |
+
|
184 |
+
|
185 |
+
4.0.3 (2015-10-11)
|
186 |
+
------------------
|
187 |
+
- WC [https://forum.acumulus.nl/index.php?topic=4963 ]: Regression: WC denkt
|
188 |
+
altijd dat factuur al verzonden is.
|
189 |
+
|
190 |
+
|
191 |
+
4.0.2 (2015-10-07)
|
192 |
+
------------------
|
193 |
+
- PS,WC: Support upgrading from 3.x.
|
194 |
+
- WC: Lazy load config (LogLevel, debug).
|
195 |
+
|
196 |
+
|
197 |
+
4.0.1 (2015-10-04)
|
198 |
+
------------------
|
199 |
+
- WC [https://forum.acumulus.nl/index.php?topic=4942.0]: PHP Fatal error: Class
|
200 |
+
'Requirements' not found in AcumulusSetup.php line 81.
|
201 |
+
|
202 |
+
|
203 |
+
4.0.0 (2015-10-02)
|
204 |
+
------------------
|
205 |
+
De library 4.x is volledig vernieuwd. Er is meer gemeenschappelijke code en er
|
206 |
+
is meer documentatie. Hierdoor kan de library ook losstaand gebruikt worden.
|
207 |
+
|
208 |
+
Alle kennis en ervaring die met de library 3.x is opgedaan is verwerkt in deze
|
209 |
+
versie. Uiteraard zijn alle problemen die in 3.x zijn gevonden en ook van
|
210 |
+
toepassing waren op 4.x ook hier opgelost. Vanwege de grote verschillen is de
|
211 |
+
3.x changelog verwijderd en beginnen we hier met een schone lijst.
|
212 |
+
|
213 |
+
- ALL: Europese BTW tarieven.
|
214 |
+
- MA,PS,WC: Ondersteuning voor credit memo's.
|
215 |
+
- ALL: Batch verzendformulier.
|
216 |
+
- VM: Ondersteuning voor VirtueMart 3. Doch nog geen ondersteuning voor credit
|
217 |
+
memos, automatische verzending en de betaalde EU VAT checker extensie.
|
218 |
+
- WC: Ondersteuning voor de EU VAT extensie.
|
219 |
+
- WC: Ondersteuning voor de plugins woocommerce-sequential-order-numbers(-pro)
|
220 |
+
en wc-sequential-order-numbers.
|
221 |
+
|
222 |
+
|
223 |
+
|
224 |
+
|
225 |
+
Ondersteunde versies van de verschillende pakketten
|
226 |
+
===================================================
|
227 |
+
|
228 |
+
HikaShop
|
229 |
+
--------
|
230 |
+
* 2.6.1 (Joomla 3.4.8)
|
231 |
+
* 2.6.0 (Joomla 3.4.8)
|
232 |
+
|
233 |
+
|
234 |
+
Magento
|
235 |
+
-------
|
236 |
+
Geteste Magento versie(s):
|
237 |
+
* 1.9.2.3
|
238 |
+
* 1.9.2.1
|
239 |
+
|
240 |
+
|
241 |
+
OpenCart 2.x
|
242 |
+
------------
|
243 |
+
* 2.1.0.1
|
244 |
+
|
245 |
+
|
246 |
+
OpenCart 1.x
|
247 |
+
------------
|
248 |
+
Nog niet beschikbaar.
|
249 |
+
|
250 |
+
|
251 |
+
PrestaShop
|
252 |
+
----------
|
253 |
+
Geteste PrestaShop versie(s):
|
254 |
+
* 1.6.1.3
|
255 |
+
* 1.6.1.2 bevat een fout die deze koppeling raakt
|
256 |
+
* 1.6.1.1
|
257 |
+
* 1.6.1.0 bevat fouten die deze koppeling raken.
|
258 |
+
* 1.5.x (werkt volgens gebruikers vanaf 4.2.0-alpha5)
|
259 |
+
|
260 |
+
|
261 |
+
VirtueMart
|
262 |
+
----------
|
263 |
+
Geteste VirtueMart versie(s):
|
264 |
+
* 3.0.10 (Joomla 3.4.4)
|
265 |
+
* 3.0.8 (Joomla 3.4.1)
|
266 |
+
|
267 |
+
|
268 |
+
WooCommerce
|
269 |
+
-----------
|
270 |
+
Geteste WooCommerce versie(s):
|
271 |
+
* 2.5.2 (WordPress: 4.4.2)
|
272 |
+
* 2.4.12 (WordPress: 4.4.2)
|
273 |
+
* 2.4.7 (WordPress: 4.3.1)
|
274 |
+
* 2.3.13 (WordPress: 4.2.3)
|
275 |
+
* < 2.3 gaat niet correct werken met "apply after tax" coupons.
|
app/code/community/Siel/Acumulus/controllers/Adminhtml/AcumulusController.php
ADDED
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class Siel_Acumulus_Adminhtml_AcumulusController extends Mage_Adminhtml_Controller_Action {
|
4 |
+
|
5 |
+
/** @var Siel_Acumulus_Helper_Data */
|
6 |
+
protected $helper;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Magento "internal constructor" not receiving any parameters.
|
10 |
+
*/
|
11 |
+
protected function _construct() {
|
12 |
+
parent::_construct();
|
13 |
+
$this->helper = Mage::helper('acumulus');
|
14 |
+
}
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Helper method to translate strings.
|
18 |
+
*
|
19 |
+
* @param string $key
|
20 |
+
* The key to get a translation for.
|
21 |
+
*
|
22 |
+
* @return string
|
23 |
+
* The translation for the given key or the key itself if no translation
|
24 |
+
* could be found.
|
25 |
+
*/
|
26 |
+
protected function t($key) {
|
27 |
+
return $this->helper->t($key);
|
28 |
+
}
|
29 |
+
|
30 |
+
protected function _isAllowed() {
|
31 |
+
/** @var Mage_Admin_Model_Session $session */
|
32 |
+
$session = Mage::getSingleton('admin/session');
|
33 |
+
return $session->isAllowed('acumulus/acumulus_'. $this->getRequest()->getRequestedActionName() . '_form');
|
34 |
+
}
|
35 |
+
|
36 |
+
protected function formAction($formType) {
|
37 |
+
$activeMenu = "acumulus/acumulus_{$formType}_form";
|
38 |
+
$block = "siel_acumulus_block_adminhtml_form";
|
39 |
+
$titleKey = "{$formType}_form_title";
|
40 |
+
|
41 |
+
/** @var Mage_Adminhtml_Model_Session $session */
|
42 |
+
$session = Mage::getSingleton('adminhtml/session');
|
43 |
+
try {
|
44 |
+
// Create the form first: this will load the translations.
|
45 |
+
$form = $this->helper->getAcumulusConfig()->getForm($formType);
|
46 |
+
|
47 |
+
$this->_title($this->__('System'))->_title($this->t($titleKey));
|
48 |
+
|
49 |
+
$form->process();
|
50 |
+
foreach($form->getSuccessMessages() as $message) {
|
51 |
+
$session->addSuccess($message);
|
52 |
+
}
|
53 |
+
foreach($form->getErrorMessages() as $message) {
|
54 |
+
$session->addError($message);
|
55 |
+
}
|
56 |
+
} catch (Exception $e) {
|
57 |
+
$session->addException($e, $e->getMessage());
|
58 |
+
}
|
59 |
+
|
60 |
+
$this->loadLayout();
|
61 |
+
$this->_setActiveMenu($activeMenu);
|
62 |
+
$this->_addContent($this->getLayout()->createBlock($block, '', array('formType' => $formType)));
|
63 |
+
$this->renderLayout();
|
64 |
+
}
|
65 |
+
|
66 |
+
public function configAction() {
|
67 |
+
$this->formAction('config');
|
68 |
+
}
|
69 |
+
|
70 |
+
public function batchAction() {
|
71 |
+
$this->formAction('batch');
|
72 |
+
}
|
73 |
+
|
74 |
+
}
|
75 |
+
|
app/code/community/Siel/Acumulus/etc/adminhtml.xml
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0"?>
|
2 |
+
<config>
|
3 |
+
<menu>
|
4 |
+
<acumulus translate="title" module="acumulus">
|
5 |
+
<title>Acumulus</title>
|
6 |
+
<sort_order>85</sort_order>
|
7 |
+
<children>
|
8 |
+
<acumulus_config_form translate="title" module="acumulus">
|
9 |
+
<title>Acumulus instellingen</title>
|
10 |
+
<action>adminhtml/acumulus/config</action>
|
11 |
+
<sort_order>5</sort_order>
|
12 |
+
</acumulus_config_form>
|
13 |
+
<acumulus_batch_form translate="title" module="acumulus">
|
14 |
+
<title>Verzend facturen naar Acumulus</title>
|
15 |
+
<action>adminhtml/acumulus/batch</action>
|
16 |
+
<sort_order>10</sort_order>
|
17 |
+
</acumulus_batch_form>
|
18 |
+
</children>
|
19 |
+
</acumulus>
|
20 |
+
</menu>
|
21 |
+
|
22 |
+
<acl>
|
23 |
+
<resources>
|
24 |
+
<admin>
|
25 |
+
<children>
|
26 |
+
<acumulus>
|
27 |
+
<children>
|
28 |
+
<acumulus_config_form>
|
29 |
+
<title>Acumulus instellingen</title>
|
30 |
+
</acumulus_config_form>
|
31 |
+
<acumulus_batch_form>
|
32 |
+
<title>Verzend facturen naar Acumulus</title>
|
33 |
+
</acumulus_batch_form>
|
34 |
+
</children>
|
35 |
+
</acumulus>
|
36 |
+
</children>
|
37 |
+
</admin>
|
38 |
+
</resources>
|
39 |
+
</acl>
|
40 |
+
|
41 |
+
</config>
|
app/code/community/Siel/Acumulus/etc/config.xml
ADDED
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0"?>
|
2 |
+
<config>
|
3 |
+
<modules>
|
4 |
+
<Siel_Acumulus>
|
5 |
+
<version>4.3.0</version>
|
6 |
+
</Siel_Acumulus>
|
7 |
+
</modules>
|
8 |
+
|
9 |
+
<stores>
|
10 |
+
<admin>
|
11 |
+
<dev>
|
12 |
+
<debug>
|
13 |
+
<template_hints>0</template_hints>
|
14 |
+
<template_hints_blocks>0</template_hints_blocks>
|
15 |
+
</debug>
|
16 |
+
</dev>
|
17 |
+
</admin>
|
18 |
+
</stores>
|
19 |
+
|
20 |
+
<global>
|
21 |
+
<models>
|
22 |
+
<acumulus>
|
23 |
+
<class>Siel_Acumulus_Model</class>
|
24 |
+
<resourceModel>acumulus_resource</resourceModel>
|
25 |
+
</acumulus>
|
26 |
+
<acumulus_resource>
|
27 |
+
<class>Siel_Acumulus_Model_Resource</class>
|
28 |
+
<entities>
|
29 |
+
<entry>
|
30 |
+
<table>acumulus_entries</table>
|
31 |
+
</entry>
|
32 |
+
</entities>
|
33 |
+
</acumulus_resource>
|
34 |
+
</models>
|
35 |
+
|
36 |
+
<resources>
|
37 |
+
<acumulus_setup>
|
38 |
+
<setup>
|
39 |
+
<module>Siel_Acumulus</module>
|
40 |
+
<class>Siel_Acumulus_Model_Resource_Setup</class>
|
41 |
+
</setup>
|
42 |
+
</acumulus_setup>
|
43 |
+
</resources>
|
44 |
+
|
45 |
+
<blocks>
|
46 |
+
<acumulus>
|
47 |
+
<class>Siel_Acumulus_Block</class>
|
48 |
+
</acumulus>
|
49 |
+
</blocks>
|
50 |
+
|
51 |
+
<helpers>
|
52 |
+
<acumulus>
|
53 |
+
<class>Siel_Acumulus_Helper</class>
|
54 |
+
</acumulus>
|
55 |
+
</helpers>
|
56 |
+
|
57 |
+
<events>
|
58 |
+
<sales_order_save_after>
|
59 |
+
<observers>
|
60 |
+
<Siel_Acumulus_order_observer>
|
61 |
+
<type>singleton</type>
|
62 |
+
<class>acumulus/order_observer</class>
|
63 |
+
<method>orderSaveAfter</method>
|
64 |
+
</Siel_Acumulus_order_observer>
|
65 |
+
</observers>
|
66 |
+
</sales_order_save_after>
|
67 |
+
|
68 |
+
<sales_order_invoice_save_after>
|
69 |
+
<observers>
|
70 |
+
<Siel_Acumulus_order_observer>
|
71 |
+
<type>singleton</type>
|
72 |
+
<class>acumulus/order_observer</class>
|
73 |
+
<method>invoiceSaveAfter</method>
|
74 |
+
</Siel_Acumulus_order_observer>
|
75 |
+
</observers>
|
76 |
+
</sales_order_invoice_save_after>
|
77 |
+
|
78 |
+
<sales_order_creditmemo_save_after>
|
79 |
+
<observers>
|
80 |
+
<Siel_Acumulus_order_observer>
|
81 |
+
<type>singleton</type>
|
82 |
+
<class>acumulus/order_observer</class>
|
83 |
+
<method>creditMemoSaveAfter</method>
|
84 |
+
</Siel_Acumulus_order_observer>
|
85 |
+
</observers>
|
86 |
+
</sales_order_creditmemo_save_after>
|
87 |
+
</events>
|
88 |
+
</global>
|
89 |
+
|
90 |
+
<admin>
|
91 |
+
<routers>
|
92 |
+
<adminhtml>
|
93 |
+
<args>
|
94 |
+
<modules>
|
95 |
+
<acumulus before="Mage_Adminhtml">Siel_Acumulus_Adminhtml</acumulus>
|
96 |
+
</modules>
|
97 |
+
</args>
|
98 |
+
</adminhtml>
|
99 |
+
</routers>
|
100 |
+
</admin>
|
101 |
+
|
102 |
+
</config>
|
app/code/community/Siel/Acumulus/events.txt
ADDED
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Acumulus module events
|
2 |
+
=======================
|
3 |
+
|
4 |
+
Warning
|
5 |
+
-------
|
6 |
+
The events are experimental. Depending on feedback from actual usage, details
|
7 |
+
may change in future versions!
|
8 |
+
|
9 |
+
|
10 |
+
Introduction
|
11 |
+
------------
|
12 |
+
This file documents the events that the Acumulus module defines. Currently, 3
|
13 |
+
events are defined:
|
14 |
+
- acumulus_invoice_created
|
15 |
+
- acumulus_invoice_completed
|
16 |
+
- acumulus_invoice_sent
|
17 |
+
|
18 |
+
The event acumulus_invoice_created is triggered when the invoice has been
|
19 |
+
created with raw data from the webshop order or credit memo, but has yet to be
|
20 |
+
completed and refined. Use this event when you want to do some completion
|
21 |
+
yourself. This should be rare and most of the times the
|
22 |
+
acumulus_invoice_completed event should be preferred.
|
23 |
+
|
24 |
+
The event acumulus_invoice_completed is triggered when the invoice has been
|
25 |
+
completed. Some defaults have been added; VAT rates have been corrected for
|
26 |
+
rounding errors; and the 'vat type' has been determined.
|
27 |
+
|
28 |
+
Typical usages for the latter event allow to implement business logic specific
|
29 |
+
to your business, think of:
|
30 |
+
- Template, account number, or cost center selection based on order specifics.
|
31 |
+
- Adding descriptive info to the invoice or invoice lines based on custom order
|
32 |
+
meta data.
|
33 |
+
- Correcting payment info based on payment modules not supported by this module.
|
34 |
+
|
35 |
+
The event acumulus_invoice_sent is triggered when the invoice has been sent to
|
36 |
+
Acumulus. This allows to perform some actions depending on the result. Note that
|
37 |
+
this event is called independent of the result, so on success as well as
|
38 |
+
failure.
|
39 |
+
|
40 |
+
Typical usages for this event allow to use the invoice number assigned by
|
41 |
+
Acumulus in your webshop or to link your order to Acumulus info, although
|
42 |
+
this is also already done by this module itself.
|
43 |
+
|
44 |
+
|
45 |
+
acumulus_invoice_created
|
46 |
+
------------------------
|
47 |
+
|
48 |
+
Usage
|
49 |
+
-----
|
50 |
+
When the "acumulus_invoice_created" event is called, it is passed 2 arguments:
|
51 |
+
- invoice:
|
52 |
+
[Note: To allow an observer to change the invoice it is packed in a
|
53 |
+
transportObject, see the example below to see how to get and change the
|
54 |
+
invoice.]
|
55 |
+
A keyed array containing the invoice that will be sent to Acumulus. The array
|
56 |
+
structure is similar to the XML structure as defined on
|
57 |
+
https://apidoc.sielsystems.nl/content/invoice-add, though at this stage, only
|
58 |
+
the customer key will be present at the highest level. The line tag is
|
59 |
+
represented as a numeric array of lines, even if if there is only 1 line.
|
60 |
+
|
61 |
+
Some keys will be missing, if they are still missing after this event has
|
62 |
+
been applied, they will be filled in (not overwritten) by the Completor before
|
63 |
+
the actual sending takes place:
|
64 |
+
- ['customer']['locationcode']
|
65 |
+
- ['customer']['overwriteifexists']
|
66 |
+
- ['customer']['type']
|
67 |
+
- ['customer']['invoice']['concept']
|
68 |
+
- ['customer']['invoice']['accountnumber']
|
69 |
+
- ['customer']['invoice']['costcenter']
|
70 |
+
- ['customer']['invoice']['template]
|
71 |
+
- ['customer']['invoice']['vattype]
|
72 |
+
|
73 |
+
The following keys may be filled with a null value, if the 'unitprice' of that
|
74 |
+
'line' = 0. These null values will be replaced by the module by the most
|
75 |
+
occurring vat rate before the actual sending takes place:
|
76 |
+
- ['customer']['invoice']['line']['taxrate']
|
77 |
+
- source: The Acumulus \Siel\Acumulus\Invoice\Source object that is the base of
|
78 |
+
the invoice to be sent.
|
79 |
+
|
80 |
+
|
81 |
+
acumulus_invoice_completed
|
82 |
+
--------------------------
|
83 |
+
|
84 |
+
Usage
|
85 |
+
-----
|
86 |
+
When the "acumulus_invoice_completed" event is called, it is passed 2 arguments:
|
87 |
+
- invoice:
|
88 |
+
[Note: To allow an observer to change the invoice it is packed in a
|
89 |
+
transportObject, see the example below to see how to get and change the
|
90 |
+
invoice.]
|
91 |
+
A keyed array containing the invoice that will be sent to Acumulus. The array
|
92 |
+
structure is similar to the XML structure as defined on
|
93 |
+
https://apidoc.sielsystems.nl/content/invoice-add, though at this stage, only
|
94 |
+
the customer key will be present at the highest level. The line tag is
|
95 |
+
represented as a numeric array of lines, even if if there is only 1 line.
|
96 |
+
- source: The Acumulus \Siel\Acumulus\Invoice\Source object that is the base of
|
97 |
+
the invoice to be sent.
|
98 |
+
|
99 |
+
|
100 |
+
Example
|
101 |
+
-------
|
102 |
+
config.xml:
|
103 |
+
<?xml version="1.0"?>
|
104 |
+
<config>
|
105 |
+
<modules>
|
106 |
+
<My_Module>
|
107 |
+
<version>0.1-beta1</version>
|
108 |
+
</My_Module>
|
109 |
+
</modules>
|
110 |
+
...
|
111 |
+
<global>
|
112 |
+
...
|
113 |
+
<events>
|
114 |
+
<acumulus_invoice_completed>
|
115 |
+
<observers>
|
116 |
+
<My_Module_acumulus_observer>
|
117 |
+
<type>singleton</type>
|
118 |
+
<class>module/acumulus_observer</class>
|
119 |
+
<method>acumulusInvoiceCompleted</method>
|
120 |
+
</My_Module_acumulus_observer>
|
121 |
+
</observers>
|
122 |
+
</acumulus_invoice_completed>
|
123 |
+
</events>
|
124 |
+
...
|
125 |
+
</global>
|
126 |
+
...
|
127 |
+
</config>
|
128 |
+
|
129 |
+
app\code\local\My\Module\Model\Acumulus\Observer.php:
|
130 |
+
use Siel\Acumulus\Invoice\Source;
|
131 |
+
|
132 |
+
class My_Module_Model_Acumulus_Observer extends Mage_Core_Model_Abstract {
|
133 |
+
|
134 |
+
public function acumulusInvoiceCompleted(Varien_Event_Observer $observer) {
|
135 |
+
$invoice = $observer->getTransportObject()->getInvoice();
|
136 |
+
/** @var \Siel\Acumulus\Invoice\Source $source */
|
137 |
+
$source = $observer->getSource();
|
138 |
+
if ($source->getType() === Source::Order) {
|
139 |
+
// Process order.
|
140 |
+
$order = $source->getSource();
|
141 |
+
$invoice['test'] = 'order test';
|
142 |
+
}
|
143 |
+
else { // $source->getType() === Source::CreditNote
|
144 |
+
// Process credit memo.
|
145 |
+
$creditmemo = $source->getSource();
|
146 |
+
$invoice['test'] = 'creditmemo test';
|
147 |
+
}
|
148 |
+
|
149 |
+
// Make sure that the changes on the invoice are returned.
|
150 |
+
$observer->getTransportObject()->setInvoice($invoice);
|
151 |
+
}
|
152 |
+
}
|
153 |
+
|
154 |
+
|
155 |
+
|
156 |
+
acumulus_invoice_sent
|
157 |
+
---------------------
|
158 |
+
|
159 |
+
Usage
|
160 |
+
-----
|
161 |
+
When the "acumulus_invoice_sent" event is called, it is passed 2 arguments:
|
162 |
+
- invoice:
|
163 |
+
A keyed array containing the invoice that will be sent to Acumulus. The array
|
164 |
+
structure is similar to the XML structure as defined on
|
165 |
+
https://apidoc.sielsystems.nl/content/invoice-add, though at this stage, only
|
166 |
+
the customer key will be present at the highest level. The line tag is
|
167 |
+
represented as a numeric array of lines, even if if there is only 1 line.
|
168 |
+
- source: The Acumulus \Siel\Acumulus\Invoice\Source object that is the base of
|
169 |
+
the invoice to be sent.
|
170 |
+
- result: An array that contains the information as sent back by Acumulus, see
|
171 |
+
https://apidoc.sielsystems.nl/content/invoice-add.
|
172 |
+
|
173 |
+
|
174 |
+
Example
|
175 |
+
-------
|
176 |
+
config.xml:
|
177 |
+
<?xml version="1.0"?>
|
178 |
+
<config>
|
179 |
+
<modules>
|
180 |
+
<My_Module>
|
181 |
+
<version>0.1-beta1</version>
|
182 |
+
</My_Module>
|
183 |
+
</modules>
|
184 |
+
...
|
185 |
+
<global>
|
186 |
+
...
|
187 |
+
<events>
|
188 |
+
<acumulus_invoice_sent>
|
189 |
+
<observers>
|
190 |
+
<My_Module_acumulus_observer>
|
191 |
+
<type>singleton</type>
|
192 |
+
<class>module/acumulus_observer</class>
|
193 |
+
<method>acumulusInvoiceSent</method>
|
194 |
+
</My_Module_acumulus_observer>
|
195 |
+
</observers>
|
196 |
+
</acumulus_invoice_sent>
|
197 |
+
</events>
|
198 |
+
...
|
199 |
+
</global>
|
200 |
+
...
|
201 |
+
</config>
|
202 |
+
|
203 |
+
app\code\local\My\Module\Model\Acumulus\Observer.php:
|
204 |
+
use Siel\Acumulus\Invoice\Source;
|
205 |
+
|
206 |
+
class My_Module_Model_Acumulus_Observer extends Mage_Core_Model_Abstract {
|
207 |
+
|
208 |
+
public function acumulusInvoiceSent(Varien_Event_Observer $observer) {
|
209 |
+
/** @var array $invoice */
|
210 |
+
$invoice = $observer->getInvoice();
|
211 |
+
/** @var \Siel\Acumulus\Invoice\Source $source */
|
212 |
+
$source = $observer->getSource();
|
213 |
+
/** @var array $result */
|
214 |
+
$result = $observer->getResult();
|
215 |
+
if (!empty($result['invoice']['invoicenumber']) {
|
216 |
+
// Success.
|
217 |
+
$acumulusInvoiceNumber = $result['invoice']['invoicenumber'];
|
218 |
+
$acumulusEntryId = $result['invoice']['entryid'];
|
219 |
+
$acumulusToken = $result['invoice']['token'];
|
220 |
+
}
|
221 |
+
else {
|
222 |
+
// Failure.
|
223 |
+
}
|
224 |
+
}
|
225 |
+
}
|
226 |
+
|
227 |
+
External Resources
|
228 |
+
------------------
|
229 |
+
- https://apidoc.sielsystems.nl/content/invoice-add.
|
230 |
+
- https://apidoc.sielsystems.nl/content/warning-error-and-status-response-section-most-api-calls
|
app/code/community/Siel/Acumulus/leesmij.txt
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Deze Acumulus webshopkoppeling is ontwikkeld door Buro Rader
|
2 |
+
(http://www.burorader.com/)
|
3 |
+
|
4 |
+
De koppeling is uitvoerig getest tegen een standaard installatie
|
5 |
+
van een recente, stabiele versie van de webshop software.
|
6 |
+
|
7 |
+
Indien u over een installatie beschikt waarin aangepaste of
|
8 |
+
conflicterende plugins opgenomen zijn dan zou de koppeling
|
9 |
+
niet naar verwachting kunnen werken.
|
10 |
+
|
11 |
+
Mocht u in deze situatie, of in een standaardomgeving problemen
|
12 |
+
met de installatie of werking ondervinden dan kunt u een
|
13 |
+
hulpvraag stellen op het Acumulus forum of voor verdere support
|
14 |
+
contact opnemen met Buro Rader (support@burorader.com).
|
15 |
+
|
16 |
+
Indien u van de ondersteuning van Buro Rader gebruik wilt maken
|
17 |
+
dan verdient het de aanbeveling om onderstaande gegevens paraat
|
18 |
+
te hebben:
|
19 |
+
- De huidige versie van uw webwinkel.
|
20 |
+
- Eventuele gemaakte aanpassingen.
|
21 |
+
- De versie van de webshopkoppeling die u probeert te installeren.
|
22 |
+
- Contactgegevens van u en/of uw webbouwer.
|
23 |
+
|
24 |
+
En pas als daar uitdrukkelijk om gevraagd wordt:
|
25 |
+
- Uw FTP-gegevens voor beheer van de webshop.
|
26 |
+
- Uw controlpanel gegevens voor beheer van de webshop.
|
27 |
+
- Een test-account waarmee testorders gemaakt kunnen worden.
|
28 |
+
|
29 |
+
Deze software wordt u ter beschikking gesteld onder de GNU GENERAL
|
30 |
+
PUBLIC LICENSE versie 3. Voor meer info zie de licentiebestanden
|
31 |
+
die meegeleverd worden in de "hoofdfolder" van de koppeling. Dat
|
32 |
+
is voor:
|
33 |
+
- Magento: app/code/community/Siel/Acumulus
|
34 |
+
- OpenCart: system/library/Siel/Acumulus/OpenCart
|
35 |
+
- PrestaShop: modules/acumulus
|
36 |
+
- Joomla: administrator/components/com_acumulus
|
37 |
+
- WooCommerce: wp-content/plugins/acumulus
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/Countries.php
ADDED
@@ -0,0 +1,365 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Helpers;
|
3 |
+
|
4 |
+
/**
|
5 |
+
* Class Countries defines country related features.
|
6 |
+
*/
|
7 |
+
class Countries
|
8 |
+
{
|
9 |
+
/**
|
10 |
+
* Returns whether the country is the Netherlands.
|
11 |
+
*
|
12 |
+
* For now only the alpha-2 codes are allowed. Other notations might be added
|
13 |
+
* as soon we support a web shop with a different way of storing countries.
|
14 |
+
*
|
15 |
+
* @param string $countryCode
|
16 |
+
* Case insensitive ISO 3166-1 alpha-2 country code.
|
17 |
+
*
|
18 |
+
* @return bool
|
19 |
+
*/
|
20 |
+
public function isNl($countryCode)
|
21 |
+
{
|
22 |
+
return strtoupper($countryCode) === 'NL';
|
23 |
+
}
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Returns whether the country is a EU country outside the Netherlands.
|
27 |
+
*
|
28 |
+
* For now only the alpha-2 codes are allowed. Other notations might be added
|
29 |
+
* as soon we support a web shop with a different way of storing countries.
|
30 |
+
*
|
31 |
+
* @param string $countryCode
|
32 |
+
* Case insensitive ISO 3166-1 alpha-2 country code.
|
33 |
+
*
|
34 |
+
* @return bool
|
35 |
+
*/
|
36 |
+
public function isEu($countryCode)
|
37 |
+
{
|
38 |
+
// Sources:
|
39 |
+
// - http://publications.europa.eu/code/pdf/370000en.htm
|
40 |
+
// - http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
|
41 |
+
// EFTA countries are not part of this list because regarding invoicing they
|
42 |
+
// are considered to be outside of the EU.
|
43 |
+
$euCountryCodes = array(
|
44 |
+
'BE',
|
45 |
+
'BG',
|
46 |
+
'CZ',
|
47 |
+
'DK',
|
48 |
+
'DE',
|
49 |
+
'EE',
|
50 |
+
'IE',
|
51 |
+
'ES',
|
52 |
+
'FR',
|
53 |
+
'GB', // Great Britain/United Kingdom according to ISO.
|
54 |
+
'GR', // Greece according to the ISO
|
55 |
+
'HR',
|
56 |
+
'IT',
|
57 |
+
'CY',
|
58 |
+
'LV',
|
59 |
+
'LT',
|
60 |
+
'LU',
|
61 |
+
'HU',
|
62 |
+
'MT',
|
63 |
+
//'NL', // In EU, but here we want "in EU but outside the Netherlands".
|
64 |
+
'AT',
|
65 |
+
'PL',
|
66 |
+
'PT',
|
67 |
+
'RO',
|
68 |
+
'SI',
|
69 |
+
'SK',
|
70 |
+
'FI',
|
71 |
+
'SE',
|
72 |
+
);
|
73 |
+
return in_array(strtoupper($countryCode), $euCountryCodes);
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Converts EU country codes to their ISO equivalent:
|
78 |
+
*
|
79 |
+
* The EU has 2 country codes that differ form ISO:
|
80 |
+
* - UK instead of GB
|
81 |
+
* - EL instead of GR.
|
82 |
+
*
|
83 |
+
* @param string $countryCode
|
84 |
+
* An EU orISO country code.
|
85 |
+
*
|
86 |
+
* @return string
|
87 |
+
* The ISO country code.
|
88 |
+
*/
|
89 |
+
public function convertEuCountryCode($countryCode)
|
90 |
+
{
|
91 |
+
if ($countryCode === 'EL') {
|
92 |
+
$countryCode = 'GR';
|
93 |
+
}
|
94 |
+
if ($countryCode === 'UK') {
|
95 |
+
$countryCode = 'GB';
|
96 |
+
}
|
97 |
+
return $countryCode;
|
98 |
+
}
|
99 |
+
|
100 |
+
/**
|
101 |
+
* Returns the dutch name for the given country code.
|
102 |
+
*
|
103 |
+
* @param $countryCode
|
104 |
+
* ISO country code (2 characters).
|
105 |
+
*
|
106 |
+
* @return string
|
107 |
+
* The (dutch) name of the country or the empty string if the code could not
|
108 |
+
* be looked up.
|
109 |
+
*/
|
110 |
+
public function getCountryName($countryCode)
|
111 |
+
{
|
112 |
+
$countryNames = array(
|
113 |
+
'AF' => 'Afghanistan',
|
114 |
+
'AX' => 'Åland',
|
115 |
+
'AL' => 'Albanië',
|
116 |
+
'DZ' => 'Algerije',
|
117 |
+
'VI' => 'Amerikaanse Maagdeneilanden',
|
118 |
+
'AS' => 'Amerikaans-Samoa',
|
119 |
+
'AD' => 'Andorra',
|
120 |
+
'AO' => 'Angola',
|
121 |
+
'AI' => 'Anguilla',
|
122 |
+
'AQ' => 'Antarctica',
|
123 |
+
'AG' => 'Antigua en Barbuda',
|
124 |
+
'AR' => 'Argentinië',
|
125 |
+
'AM' => 'Armenië',
|
126 |
+
'AW' => 'Aruba',
|
127 |
+
'AU' => 'Australië',
|
128 |
+
'AZ' => 'Azerbeidzjan',
|
129 |
+
'BS' => 'Bahama\'s',
|
130 |
+
'BH' => 'Bahrein',
|
131 |
+
'BD' => 'Bangladesh',
|
132 |
+
'BB' => 'Barbados',
|
133 |
+
'BE' => 'België',
|
134 |
+
'BZ' => 'Belize',
|
135 |
+
'BJ' => 'Benin',
|
136 |
+
'BM' => 'Bermuda',
|
137 |
+
'BT' => 'Bhutan',
|
138 |
+
'BO' => 'Bolivia',
|
139 |
+
'BQ' => 'Bonaire, Sint Eustatius en Saba',
|
140 |
+
'BA' => 'Bosnië en Herzegovina',
|
141 |
+
'BW' => 'Botswana',
|
142 |
+
'BV' => 'Bouveteiland',
|
143 |
+
'BR' => 'Brazilië',
|
144 |
+
'VG' => 'Britse Maagdeneilanden',
|
145 |
+
'IO' => 'Brits Indische Oceaanterritorium',
|
146 |
+
'BN' => 'Brunei',
|
147 |
+
'BG' => 'Bulgarije',
|
148 |
+
'BF' => 'Burkina Faso',
|
149 |
+
'BI' => 'Burundi',
|
150 |
+
'KH' => 'Cambodja',
|
151 |
+
'CA' => 'Canada',
|
152 |
+
'CF' => 'Centraal-Afrikaanse Republiek',
|
153 |
+
'CL' => 'Chili',
|
154 |
+
'CN' => 'China',
|
155 |
+
'CX' => 'Christmaseiland',
|
156 |
+
'CC' => 'Cocoseilanden',
|
157 |
+
'CO' => 'Colombia',
|
158 |
+
'KM' => 'Comoren',
|
159 |
+
'CG' => 'Congo-Brazzaville',
|
160 |
+
'CD' => 'Congo-Kinshasa',
|
161 |
+
'CK' => 'Cookeilanden',
|
162 |
+
'CR' => 'Costa Rica',
|
163 |
+
'CU' => 'Cuba',
|
164 |
+
'CW' => 'Curaçao',
|
165 |
+
'CY' => 'Cyprus',
|
166 |
+
'DK' => 'Denemarken',
|
167 |
+
'DJ' => 'Djibouti',
|
168 |
+
'DM' => 'Dominica',
|
169 |
+
'DO' => 'Dominicaanse Republiek',
|
170 |
+
'DE' => 'Duitsland',
|
171 |
+
'EC' => 'Ecuador',
|
172 |
+
'EG' => 'Egypte',
|
173 |
+
'SV' => 'El Salvador',
|
174 |
+
'GQ' => 'Equatoriaal-Guinea',
|
175 |
+
'ER' => 'Eritrea',
|
176 |
+
'EE' => 'Estland',
|
177 |
+
'ET' => 'Ethiopië',
|
178 |
+
'FO' => 'Faeröer',
|
179 |
+
'FK' => 'Falklandeilanden',
|
180 |
+
'FJ' => 'Fiji',
|
181 |
+
'PH' => 'Filipijnen',
|
182 |
+
'FI' => 'Finland',
|
183 |
+
'FR' => 'Frankrijk',
|
184 |
+
'TF' => 'Franse Zuidelijke en Antarctische Gebieden',
|
185 |
+
'GF' => 'Frans-Guyana',
|
186 |
+
'PF' => 'Frans-Polynesië',
|
187 |
+
'GA' => 'Gabon',
|
188 |
+
'GM' => 'Gambia',
|
189 |
+
'GE' => 'Georgië',
|
190 |
+
'GH' => 'Ghana',
|
191 |
+
'GI' => 'Gibraltar',
|
192 |
+
'GD' => 'Grenada',
|
193 |
+
'GR' => 'Griekenland',
|
194 |
+
'GL' => 'Groenland',
|
195 |
+
'GP' => 'Guadeloupe',
|
196 |
+
'GU' => 'Guam',
|
197 |
+
'GT' => 'Guatemala',
|
198 |
+
'GG' => 'Guernsey',
|
199 |
+
'GN' => 'Guinee',
|
200 |
+
'GW' => 'Guinee-Bissau',
|
201 |
+
'GY' => 'Guyana',
|
202 |
+
'HT' => 'Haïti',
|
203 |
+
'HM' => 'Heard en McDonaldeilanden',
|
204 |
+
'HN' => 'Honduras',
|
205 |
+
'HU' => 'Hongarije',
|
206 |
+
'HK' => 'Hongkong',
|
207 |
+
'IE' => 'Ierland',
|
208 |
+
'IS' => 'IJsland',
|
209 |
+
'IN' => 'India',
|
210 |
+
'ID' => 'Indonesië',
|
211 |
+
'IQ' => 'Irak',
|
212 |
+
'IR' => 'Iran',
|
213 |
+
'IL' => 'Israël',
|
214 |
+
'IT' => 'Italië',
|
215 |
+
'CI' => 'Ivoorkust',
|
216 |
+
'JM' => 'Jamaica',
|
217 |
+
'JP' => 'Japan',
|
218 |
+
'YE' => 'Jemen',
|
219 |
+
'JE' => 'Jersey',
|
220 |
+
'JO' => 'Jordanië',
|
221 |
+
'KY' => 'Kaaimaneilanden',
|
222 |
+
'CV' => 'Kaapverdië',
|
223 |
+
'CM' => 'Kameroen',
|
224 |
+
'KZ' => 'Kazachstan',
|
225 |
+
'KE' => 'Kenia',
|
226 |
+
'KG' => 'Kirgizië',
|
227 |
+
'KI' => 'Kiribati',
|
228 |
+
'UM' => 'Kleine Pacifische eilanden van de Verenigde Staten',
|
229 |
+
'KW' => 'Koeweit',
|
230 |
+
'HR' => 'Kroatië',
|
231 |
+
'LA' => 'Laos',
|
232 |
+
'LS' => 'Lesotho',
|
233 |
+
'LV' => 'Letland',
|
234 |
+
'LB' => 'Libanon',
|
235 |
+
'LR' => 'Liberia',
|
236 |
+
'LY' => 'Libië',
|
237 |
+
'LI' => 'Liechtenstein',
|
238 |
+
'LT' => 'Litouwen',
|
239 |
+
'LU' => 'Luxemburg',
|
240 |
+
'MO' => 'Macau',
|
241 |
+
'MK' => 'Macedonië',
|
242 |
+
'MG' => 'Madagaskar',
|
243 |
+
'MW' => 'Malawi',
|
244 |
+
'MV' => 'Maldiven',
|
245 |
+
'MY' => 'Maleisië',
|
246 |
+
'ML' => 'Mali',
|
247 |
+
'MT' => 'Malta',
|
248 |
+
'IM' => 'Man',
|
249 |
+
'MA' => 'Marokko',
|
250 |
+
'MH' => 'Marshalleilanden',
|
251 |
+
'MQ' => 'Martinique',
|
252 |
+
'MR' => 'Mauritanië',
|
253 |
+
'MU' => 'Mauritius',
|
254 |
+
'YT' => 'Mayotte',
|
255 |
+
'MX' => 'Mexico',
|
256 |
+
'FM' => 'Micronesia',
|
257 |
+
'MD' => 'Moldavië',
|
258 |
+
'MC' => 'Monaco',
|
259 |
+
'MN' => 'Mongolië',
|
260 |
+
'ME' => 'Montenegro',
|
261 |
+
'MS' => 'Montserrat',
|
262 |
+
'MZ' => 'Mozambique',
|
263 |
+
'MM' => 'Myanmar',
|
264 |
+
'NA' => 'Namibië',
|
265 |
+
'NR' => 'Nauru',
|
266 |
+
'NL' => 'Nederland',
|
267 |
+
'NP' => 'Nepal',
|
268 |
+
'NI' => 'Nicaragua',
|
269 |
+
'NC' => 'Nieuw-Caledonië',
|
270 |
+
'NZ' => 'Nieuw-Zeeland',
|
271 |
+
'NE' => 'Niger',
|
272 |
+
'NG' => 'Nigeria',
|
273 |
+
'NU' => 'Niue',
|
274 |
+
'MP' => 'Noordelijke Marianen',
|
275 |
+
'KP' => 'Noord-Korea',
|
276 |
+
"NO" => 'Noorwegen',
|
277 |
+
'NF' => 'Norfolk',
|
278 |
+
'UG' => 'Oeganda',
|
279 |
+
'UA' => 'Oekraïne',
|
280 |
+
'UZ' => 'Oezbekistan',
|
281 |
+
'OM' => 'Oman',
|
282 |
+
'AT' => 'Oostenrijk',
|
283 |
+
'TL' => 'Oost-Timor',
|
284 |
+
'PK' => 'Pakistan',
|
285 |
+
'PW' => 'Palau',
|
286 |
+
'PS' => 'Palestina',
|
287 |
+
'PA' => 'Panama',
|
288 |
+
'PG' => 'Papoea-Nieuw-Guinea',
|
289 |
+
'PY' => 'Paraguay',
|
290 |
+
'PE' => 'Peru',
|
291 |
+
'PN' => 'Pitcairneilanden',
|
292 |
+
'PL' => 'Polen',
|
293 |
+
'PT' => 'Portugal',
|
294 |
+
'PR' => 'Puerto Rico',
|
295 |
+
'QA' => 'Qatar',
|
296 |
+
'RE' => 'Réunion',
|
297 |
+
'RO' => 'Roemenië',
|
298 |
+
'RU' => 'Rusland',
|
299 |
+
'RW' => 'Rwanda',
|
300 |
+
'BL' => 'Saint-Barthélemy',
|
301 |
+
'KN' => 'Saint Kitts en Nevis',
|
302 |
+
'LC' => 'Saint Lucia',
|
303 |
+
'PM' => 'Saint-Pierre en Miquelon',
|
304 |
+
'VC' => 'Saint Vincent en de Grenadines',
|
305 |
+
'SB' => 'Salomonseilanden',
|
306 |
+
'WS' => 'Samoa',
|
307 |
+
'SM' => 'San Marino',
|
308 |
+
'SA' => 'Saoedi-Arabië',
|
309 |
+
'ST' => 'Sao Tomé en Principe',
|
310 |
+
'SN' => 'Senegal',
|
311 |
+
'RS' => 'Servië',
|
312 |
+
'SC' => 'Seychellen',
|
313 |
+
'SL' => 'Sierra Leone',
|
314 |
+
'SG' => 'Singapore',
|
315 |
+
'SH' => 'Sint-Helena, Ascension en Tristan da Cunha',
|
316 |
+
'MF' => 'Sint-Maarten',
|
317 |
+
'SX' => 'Sint Maarten',
|
318 |
+
'SI' => 'Slovenië',
|
319 |
+
'SK' => 'Slowakije',
|
320 |
+
'SD' => 'Soedan',
|
321 |
+
'SO' => 'Somalië',
|
322 |
+
'ES' => 'Spanje',
|
323 |
+
'SJ' => 'Spitsbergen en Jan Mayen',
|
324 |
+
'LK' => 'Sri Lanka',
|
325 |
+
'SR' => 'Suriname',
|
326 |
+
'SZ' => 'Swaziland',
|
327 |
+
'SY' => 'Syrië',
|
328 |
+
'TJ' => 'Tadzjikistan',
|
329 |
+
'TW' => 'Taiwan',
|
330 |
+
'TZ' => 'Tanzania',
|
331 |
+
'TH' => 'Thailand',
|
332 |
+
'TG' => 'Togo',
|
333 |
+
'TK' => 'Tokelau',
|
334 |
+
'TO' => 'Tonga',
|
335 |
+
'TT' => 'Trinidad en Tobago',
|
336 |
+
'TD' => 'Tsjaad',
|
337 |
+
'CZ' => 'Tsjechië',
|
338 |
+
'TN' => 'Tunesië',
|
339 |
+
'TR' => 'Turkije',
|
340 |
+
'TM' => 'Turkmenistan',
|
341 |
+
'TC' => 'Turks- en Caicoseilanden',
|
342 |
+
'TV' => 'Tuvalu',
|
343 |
+
'UY' => 'Uruguay',
|
344 |
+
'VU' => 'Vanuatu',
|
345 |
+
'VA' => 'Vaticaanstad',
|
346 |
+
'VE' => 'Venezuela',
|
347 |
+
'AE' => 'Verenigde Arabische Emiraten',
|
348 |
+
'US' => 'Verenigde Staten',
|
349 |
+
'GB' => 'Verenigd Koninkrijk',
|
350 |
+
'VN' => 'Vietnam',
|
351 |
+
'WF' => 'Wallis en Futuna',
|
352 |
+
'EH' => 'Westelijke Sahara',
|
353 |
+
'BY' => 'Wit-Rusland',
|
354 |
+
'ZM' => 'Zambia',
|
355 |
+
'ZW' => 'Zimbabwe',
|
356 |
+
'ZA' => 'Zuid-Afrika',
|
357 |
+
'GS' => 'Zuid-Georgia en de Zuidelijke Sandwicheilanden',
|
358 |
+
'KR' => 'Zuid-Korea',
|
359 |
+
'SS' => 'Zuid-Soedan',
|
360 |
+
'SE' => 'Zweden',
|
361 |
+
'CH' => 'Zwitserland',
|
362 |
+
);
|
363 |
+
return isset($countryNames[$countryCode]) ? $countryNames[$countryCode] : '';
|
364 |
+
}
|
365 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/Form.php
ADDED
@@ -0,0 +1,590 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Helpers;
|
3 |
+
|
4 |
+
/**
|
5 |
+
* Provides basic form handling.
|
6 |
+
*
|
7 |
+
* Most webshop and CMS software provide their own sort of form API. To be able
|
8 |
+
* to generalize or abstract our form handling, this class defines our own
|
9 |
+
* minimal form API. This allows us to define our form handing in a cross
|
10 |
+
* webshop compatible way. The webshop/CMs specific part should then define a
|
11 |
+
* wrapper/mapper to the webshop specific way of form handling.
|
12 |
+
*
|
13 |
+
* This base Form class defines a way to:
|
14 |
+
* - Define the form elements.
|
15 |
+
* - Render the form, including their values.
|
16 |
+
* - Process a form submission:
|
17 |
+
* - Recognise form submission.
|
18 |
+
* - Perform form (submission) validation.
|
19 |
+
* - Execute a task on valid form submission.
|
20 |
+
* - Show success and/or error messages.
|
21 |
+
*
|
22 |
+
* Usage:
|
23 |
+
* This code is typically performed by a controller that processes the request:
|
24 |
+
* <code>
|
25 |
+
* $form = new ChildForm();
|
26 |
+
* $form->process();
|
27 |
+
* // process any messages ... may also be in the view
|
28 |
+
* </code>
|
29 |
+
*
|
30 |
+
* This code is typically performed by a view and should be used when the CMS or
|
31 |
+
* webshop does not really provide a form object:
|
32 |
+
* <code>
|
33 |
+
* // Displays the form.
|
34 |
+
* // Create the form and add the values to the elements.
|
35 |
+
* $form = new ChildForm();
|
36 |
+
* $formFields = $form->addValues();
|
37 |
+
* // Render the html for the form.
|
38 |
+
* $formRenderer = new FormRenderer();
|
39 |
+
* $formRenderer->render($form)
|
40 |
+
* </code>
|
41 |
+
*
|
42 |
+
* This code is to be used when the CMS or webshop does provide its own form
|
43 |
+
* handling and processing.
|
44 |
+
* <code>
|
45 |
+
* // Create shop specific Form object
|
46 |
+
* $shopForm = new ShopForm()
|
47 |
+
* // Map the elements and settings of the Acumulus Form to the shop form.
|
48 |
+
* $formMapper = new FormMapper();
|
49 |
+
* $formMapper->map($shopForm, $form);
|
50 |
+
* // Continue with the shop form by setting the form values ...
|
51 |
+
* $shopForm->setValues($form->GetValues)
|
52 |
+
* // and rendering it.
|
53 |
+
* $shopForm->render()
|
54 |
+
*
|
55 |
+
* </code>
|
56 |
+
*/
|
57 |
+
abstract class Form
|
58 |
+
{
|
59 |
+
/** @var \Siel\Acumulus\Helpers\TranslatorInterface */
|
60 |
+
protected $translator;
|
61 |
+
|
62 |
+
/** @var array[] */
|
63 |
+
protected $fields;
|
64 |
+
|
65 |
+
/** @var bool */
|
66 |
+
protected $formValuesSet;
|
67 |
+
|
68 |
+
/** @var array The values to be placed on the configuration form. */
|
69 |
+
protected $formValues;
|
70 |
+
|
71 |
+
/** @var string[] The values as filled in on form submission. */
|
72 |
+
protected $submittedValues;
|
73 |
+
|
74 |
+
/** @var string[] Any success messages. */
|
75 |
+
protected $successMessages;
|
76 |
+
|
77 |
+
/** @var string[] Any error messages. */
|
78 |
+
protected $errorMessages;
|
79 |
+
|
80 |
+
/**
|
81 |
+
* @param \Siel\Acumulus\Helpers\TranslatorInterface $translator
|
82 |
+
*/
|
83 |
+
public function __construct(TranslatorInterface $translator)
|
84 |
+
{
|
85 |
+
$this->successMessages = array();
|
86 |
+
$this->errorMessages = array();
|
87 |
+
$this->formValuesSet = false;
|
88 |
+
$this->submittedValues = array();
|
89 |
+
|
90 |
+
$this->translator = $translator;
|
91 |
+
$this->fields = array();
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Helper method to translate strings.
|
96 |
+
*
|
97 |
+
* @param string $key
|
98 |
+
* The key to get a translation for.
|
99 |
+
*
|
100 |
+
* @return string
|
101 |
+
* The translation for the given key or the key itself if no translation
|
102 |
+
* could be found.
|
103 |
+
*/
|
104 |
+
protected function t($key)
|
105 |
+
{
|
106 |
+
return $this->translator->get($key);
|
107 |
+
}
|
108 |
+
|
109 |
+
/**
|
110 |
+
* To be used by web shop specific form handling to display a success message.
|
111 |
+
*
|
112 |
+
* @return string[]
|
113 |
+
*/
|
114 |
+
public function getSuccessMessages()
|
115 |
+
{
|
116 |
+
return $this->successMessages;
|
117 |
+
}
|
118 |
+
|
119 |
+
/**
|
120 |
+
* To be used by web shop specific form handling to display validation and
|
121 |
+
* connection error messages.
|
122 |
+
*
|
123 |
+
* An empty result indicates successful validation.
|
124 |
+
*
|
125 |
+
* @return string[]
|
126 |
+
* An array of translated messages. In case of validation errors they are
|
127 |
+
* keyed by the name of the invalid form field.
|
128 |
+
*/
|
129 |
+
public function getErrorMessages()
|
130 |
+
{
|
131 |
+
return $this->errorMessages;
|
132 |
+
}
|
133 |
+
|
134 |
+
/**
|
135 |
+
* Indicates whether the current form handling is a form submission.
|
136 |
+
*
|
137 |
+
* @return bool
|
138 |
+
*/
|
139 |
+
public function isSubmitted()
|
140 |
+
{
|
141 |
+
return $_SERVER['REQUEST_METHOD'] === 'POST';
|
142 |
+
}
|
143 |
+
|
144 |
+
/**
|
145 |
+
* Returns whether the submitted form values are valid.
|
146 |
+
*
|
147 |
+
* @return bool
|
148 |
+
*/
|
149 |
+
public function isValid()
|
150 |
+
{
|
151 |
+
return empty($this->errorMessages);
|
152 |
+
}
|
153 |
+
|
154 |
+
/**
|
155 |
+
* Sets the form values to use.
|
156 |
+
*
|
157 |
+
* This is typically the union of the default values, any submitted values,
|
158 |
+
* and explicitly set field values.
|
159 |
+
*/
|
160 |
+
protected function setFormValues()
|
161 |
+
{
|
162 |
+
if (!$this->formValuesSet) {
|
163 |
+
$this->formValues = array_fill_keys($this->getKeys(), '');
|
164 |
+
$this->formValues = array_merge($this->formValues, $this->getDefaultFormValues());
|
165 |
+
if (!empty($this->submittedValues)) {
|
166 |
+
$this->formValues = array_merge($this->formValues, $this->submittedValues);
|
167 |
+
}
|
168 |
+
$this->formValues = array_merge($this->formValues, $this->getFieldValues($this->getFields()));
|
169 |
+
|
170 |
+
$this->formValuesSet = true;
|
171 |
+
}
|
172 |
+
}
|
173 |
+
|
174 |
+
/**
|
175 |
+
* Returns the values for all the fields on the form definition.
|
176 |
+
*
|
177 |
+
* This method will not have a use on every web shop, but, e.g. Magento and
|
178 |
+
* PrestaShop have a separate "bind" method to bind a set of values to a form
|
179 |
+
* at once.
|
180 |
+
*
|
181 |
+
* @return array
|
182 |
+
* An array of values keyed by the form field names.
|
183 |
+
*/
|
184 |
+
public function getFormValues()
|
185 |
+
{
|
186 |
+
$this->setFormValues();
|
187 |
+
return $this->formValues;
|
188 |
+
}
|
189 |
+
|
190 |
+
/**
|
191 |
+
* Returns the value for a specific form field.
|
192 |
+
*
|
193 |
+
* @param string $name
|
194 |
+
* The name of the form field.
|
195 |
+
*
|
196 |
+
* @return string
|
197 |
+
* The value for this form field or the empty string if not set.
|
198 |
+
*/
|
199 |
+
protected function getFormValue($name)
|
200 |
+
{
|
201 |
+
$this->setFormValues();
|
202 |
+
return isset($this->formValues[$name]) ? $this->formValues[$name] : '';
|
203 |
+
}
|
204 |
+
|
205 |
+
/**
|
206 |
+
* Sets the value for a specific form field.
|
207 |
+
*
|
208 |
+
* @param string $name
|
209 |
+
* The name of the form field.
|
210 |
+
* @param mixed $value
|
211 |
+
* The value for the form field.
|
212 |
+
*/
|
213 |
+
protected function setFormValue($name, $value)
|
214 |
+
{
|
215 |
+
$this->formValues[$name] = $value;
|
216 |
+
}
|
217 |
+
|
218 |
+
/**
|
219 |
+
* Adds the form values to the field definitions.
|
220 |
+
*
|
221 |
+
* This method will not have a use on every web shop, but, e.g. VirtueMart and
|
222 |
+
* OpenCart have a form helper/renderer to render individual fields including
|
223 |
+
* their value attribute instead of binding values to a form and rendering the
|
224 |
+
* form.
|
225 |
+
*/
|
226 |
+
public function addValues()
|
227 |
+
{
|
228 |
+
$this->fields = $this->addValuesToFields($this->getFields());
|
229 |
+
}
|
230 |
+
|
231 |
+
/**
|
232 |
+
* Adds the form values to the field definitions.
|
233 |
+
*
|
234 |
+
* This internal version of addValues() passes the fields as a parameter to
|
235 |
+
* allow to recursively process field sets.
|
236 |
+
*
|
237 |
+
* @param array[] $fields
|
238 |
+
*
|
239 |
+
* @return array[]
|
240 |
+
*/
|
241 |
+
protected function addValuesToFields(array $fields)
|
242 |
+
{
|
243 |
+
foreach ($fields as $name => &$field) {
|
244 |
+
if ($field['type'] === 'fieldset') {
|
245 |
+
$field['fields'] = $this->addValuesToFields($field['fields']);
|
246 |
+
} else if ($field['type'] === 'checkbox') {
|
247 |
+
// Value is a list of checked options.
|
248 |
+
$value = array();
|
249 |
+
foreach ($field['options'] as $optionName => $optionLabel) {
|
250 |
+
if ($this->getFormValue($optionName)) {
|
251 |
+
$value[] = $optionName;
|
252 |
+
}
|
253 |
+
}
|
254 |
+
$field['value'] = $value;
|
255 |
+
} else {
|
256 |
+
// Explicitly set values (in the 'value' key) take precedence over
|
257 |
+
// submitted values, which in turn take precedence over default values
|
258 |
+
// (gathered via getDefaultFormValues()).
|
259 |
+
if (!isset($field['value'])) {
|
260 |
+
$field['value'] = $this->getFormValue($name);
|
261 |
+
}
|
262 |
+
}
|
263 |
+
}
|
264 |
+
return $fields;
|
265 |
+
}
|
266 |
+
|
267 |
+
/**
|
268 |
+
* Returns a set of default values for the form fields.
|
269 |
+
*
|
270 |
+
* This default implementation returns an empty array, i.e. all form fields
|
271 |
+
* are empty, not selected, or unchecked.
|
272 |
+
*
|
273 |
+
* @return array
|
274 |
+
* An array of default values keyed by the form field names.
|
275 |
+
*/
|
276 |
+
protected function getDefaultFormValues()
|
277 |
+
{
|
278 |
+
return array();
|
279 |
+
}
|
280 |
+
|
281 |
+
/**
|
282 |
+
* Returns the set of values directly assigned to the field definitions.
|
283 |
+
*
|
284 |
+
* These take precedence over default values
|
285 |
+
*
|
286 |
+
* @param array[] $fields
|
287 |
+
*
|
288 |
+
* @return array An array of values keyed by the form field names.
|
289 |
+
* An array of values keyed by the form field names.
|
290 |
+
*/
|
291 |
+
protected function getFieldValues($fields)
|
292 |
+
{
|
293 |
+
$result = array();
|
294 |
+
foreach ($fields as $id => $field) {
|
295 |
+
if (isset($field['value'])) {
|
296 |
+
$result[$id] = $field['value'];
|
297 |
+
}
|
298 |
+
if ($field['type'] === 'fieldset') {
|
299 |
+
$result = array_merge($result, $this->getFieldValues($field['fields']));
|
300 |
+
}
|
301 |
+
}
|
302 |
+
return $result;
|
303 |
+
}
|
304 |
+
|
305 |
+
/**
|
306 |
+
* Extracts the submitted values.
|
307 |
+
*
|
308 |
+
* Override to restrict the $_POST values to expected values and to do any
|
309 |
+
* sanitation.
|
310 |
+
*/
|
311 |
+
protected function setSubmittedValues()
|
312 |
+
{
|
313 |
+
$this->submittedValues = $this->getPostedValues();
|
314 |
+
}
|
315 |
+
|
316 |
+
/**
|
317 |
+
* Processes the form.
|
318 |
+
*
|
319 |
+
* @param bool $executeIfValid
|
320 |
+
* Whether this method should execute the intended action after successful
|
321 |
+
* validation. Some web shops (WooCommerce) sometimes do their own form
|
322 |
+
* handling (setting pages) and we should only do the validation and setting
|
323 |
+
* admin notices as necessary.
|
324 |
+
*
|
325 |
+
* @return bool
|
326 |
+
* True if there was no form submission or a successful submission.
|
327 |
+
*/
|
328 |
+
public function process($executeIfValid = true)
|
329 |
+
{
|
330 |
+
$this->formValues = array();
|
331 |
+
$this->submittedValues = array();
|
332 |
+
|
333 |
+
// Process the form if it was submitted.
|
334 |
+
if ($this->isSubmitted() && $this->systemValidate()) {
|
335 |
+
$this->setSubmittedValues();
|
336 |
+
$this->validate();
|
337 |
+
if ($executeIfValid && $this->isValid()) {
|
338 |
+
if ($this->execute()) {
|
339 |
+
$message = $this->t('message_form_success');
|
340 |
+
if ($message !== 'message_form_success') {
|
341 |
+
$this->successMessages[] = $message;
|
342 |
+
}
|
343 |
+
} else {
|
344 |
+
$message = $this->t('message_form_error');
|
345 |
+
if ($message !== 'message_form_error') {
|
346 |
+
$this->errorMessages[] = $message;
|
347 |
+
}
|
348 |
+
}
|
349 |
+
}
|
350 |
+
}
|
351 |
+
|
352 |
+
return $this->isValid();
|
353 |
+
}
|
354 |
+
|
355 |
+
/**
|
356 |
+
* Checks system specific form properties.
|
357 |
+
*
|
358 |
+
* This typically includes protection against CSRF attacks and such.
|
359 |
+
* Override this per form with the proper checks from the system.
|
360 |
+
*/
|
361 |
+
protected function systemValidate()
|
362 |
+
{
|
363 |
+
return true;
|
364 |
+
}
|
365 |
+
|
366 |
+
/**
|
367 |
+
* Performs config form validation.
|
368 |
+
*
|
369 |
+
* Any errors are stored as a user readable message in the $errorMessages
|
370 |
+
* property and will be keyed by the field name.
|
371 |
+
*
|
372 |
+
* This default implementation does no validation at all. Override to add form
|
373 |
+
* specific validation.
|
374 |
+
*/
|
375 |
+
protected function validate()
|
376 |
+
{
|
377 |
+
}
|
378 |
+
|
379 |
+
/**
|
380 |
+
* Executes the form action on valid form submission.
|
381 |
+
*
|
382 |
+
* Override to implement the actual form handling, like saving values.
|
383 |
+
*
|
384 |
+
* @return bool
|
385 |
+
* Success.
|
386 |
+
*/
|
387 |
+
abstract protected function execute();
|
388 |
+
|
389 |
+
/**
|
390 |
+
* Returns a definition of the form fields.
|
391 |
+
*
|
392 |
+
* This should NOT include any:
|
393 |
+
* - Submit or cancel buttons. These are often added by the web shop software
|
394 |
+
* in their specific way.
|
395 |
+
* - Tokens, form-id's or other (hidden) fields used by the web shop software
|
396 |
+
* to protect against certain attacks or to facilitate internal form
|
397 |
+
* processing.
|
398 |
+
*
|
399 |
+
* This is a recursive, keyed array defining each form field. The key defines
|
400 |
+
* the name of the form field, to be used for the name, and possibly id,
|
401 |
+
* attribute. The values are a keyed array, that can have the following keys:
|
402 |
+
* - type: (required, string) fieldset, text, email, password, date, textarea,
|
403 |
+
* select, radio, checkbox, markup.
|
404 |
+
* - label: (string) human readable label.
|
405 |
+
* - description: (string) human readable help text.
|
406 |
+
* - value: (string) the value for the form field.
|
407 |
+
* - attributes: (array) keyed array with other - possibly html5 - attributes
|
408 |
+
* to be rendered. Possible keys include:
|
409 |
+
* - size
|
410 |
+
* - class
|
411 |
+
* - required: (bool) whether the field is required.
|
412 |
+
* - fields: (array) If the type = 'fieldset', this value defines the fields
|
413 |
+
* (and possibly sub fieldsets) of the fieldset.
|
414 |
+
* - options: (array) If the type = checkbox, select or radio, this value
|
415 |
+
* contains the options as a keyed array, the keys being the value to
|
416 |
+
* submit if that choice is selected and the value being the label to show.
|
417 |
+
*
|
418 |
+
* Do NOT override this method, instead override getFieldDefinitions().
|
419 |
+
*
|
420 |
+
* @return array[]
|
421 |
+
* The definition of the form.
|
422 |
+
*/
|
423 |
+
public function getFields()
|
424 |
+
{
|
425 |
+
if (empty($this->fields)) {
|
426 |
+
$this->fields = $this->getFieldDefinitions();
|
427 |
+
}
|
428 |
+
return $this->fields;
|
429 |
+
}
|
430 |
+
|
431 |
+
/**
|
432 |
+
* Internal version of getFields();
|
433 |
+
*
|
434 |
+
* Internal method, do not call directly.
|
435 |
+
* DO override this method not getFields().
|
436 |
+
*
|
437 |
+
* @return array[]
|
438 |
+
* The definition of the form.
|
439 |
+
*/
|
440 |
+
abstract protected function getFieldDefinitions();
|
441 |
+
|
442 |
+
/**
|
443 |
+
* Returns a list of field ids/keys appearing in the form.
|
444 |
+
*
|
445 |
+
* @return array
|
446 |
+
* Array of key names appearing in the form, keyed by these names.
|
447 |
+
*/
|
448 |
+
protected function getKeys()
|
449 |
+
{
|
450 |
+
return $this->getFieldKeys($this->getFields());
|
451 |
+
}
|
452 |
+
|
453 |
+
/**
|
454 |
+
* Returns the keys of the fields in the given array.
|
455 |
+
*
|
456 |
+
* Internal method, do not call directly.
|
457 |
+
*
|
458 |
+
* @param array[] $fields
|
459 |
+
*
|
460 |
+
* @return array
|
461 |
+
* Array of key names, keyed by these names.
|
462 |
+
*/
|
463 |
+
protected function getFieldKeys(array $fields)
|
464 |
+
{
|
465 |
+
$result = array();
|
466 |
+
foreach ($fields as $key => $field) {
|
467 |
+
$result[$key] = $key;
|
468 |
+
if ($field['type'] === 'fieldset') {
|
469 |
+
$result += $this->getFieldKeys($field['fields']);
|
470 |
+
}
|
471 |
+
}
|
472 |
+
return $result;
|
473 |
+
}
|
474 |
+
|
475 |
+
/**
|
476 |
+
* Helper method to copy a value from one array to another array.
|
477 |
+
*
|
478 |
+
* @param array $target
|
479 |
+
* @param string $key
|
480 |
+
* @param array $source
|
481 |
+
* @param string $sourceKey
|
482 |
+
*
|
483 |
+
* @return bool
|
484 |
+
* True if the value isset and thus has been copied, false otherwise,
|
485 |
+
*/
|
486 |
+
protected function addIfIsset(array &$target, $key, array $source, $sourceKey = '')
|
487 |
+
{
|
488 |
+
if (empty($sourceKey)) {
|
489 |
+
$sourceKey = $key;
|
490 |
+
}
|
491 |
+
if (isset($source[$sourceKey])) {
|
492 |
+
$target[$key] = $source[$sourceKey];
|
493 |
+
return true;
|
494 |
+
}
|
495 |
+
return false;
|
496 |
+
}
|
497 |
+
|
498 |
+
/**
|
499 |
+
* Indicates whether the given key defines a checkbox field.
|
500 |
+
*
|
501 |
+
* This base implementation returns false. Override this method if your form
|
502 |
+
* has checkbox fields.
|
503 |
+
*
|
504 |
+
* @param string $key
|
505 |
+
* The name of the field.
|
506 |
+
*
|
507 |
+
* @return bool
|
508 |
+
* Whether the given key defines a checkbox field.
|
509 |
+
*/
|
510 |
+
protected function isCheckboxKey($key)
|
511 |
+
{
|
512 |
+
return array_key_exists($key, $this->getCheckboxKeys());
|
513 |
+
}
|
514 |
+
|
515 |
+
/**
|
516 |
+
* Returns a list of the checkbox names for this form.
|
517 |
+
*
|
518 |
+
* This base implementation returns an empty array. Override this method if
|
519 |
+
* your form has checkbox fields.
|
520 |
+
*
|
521 |
+
* @return array
|
522 |
+
* An array with as keys the checkbox names of this form and as values the
|
523 |
+
* checkbox collection name the checkbox belongs to.
|
524 |
+
*/
|
525 |
+
protected function getCheckboxKeys()
|
526 |
+
{
|
527 |
+
return array();
|
528 |
+
}
|
529 |
+
|
530 |
+
/**
|
531 |
+
* Returns a flat array of the posted values.
|
532 |
+
*
|
533 |
+
* As especially checkbox handling differs per web shop, often resulting in an
|
534 |
+
* array of checkbox values, this method returns a flattened version of the
|
535 |
+
* posted values.
|
536 |
+
*
|
537 |
+
* @return array
|
538 |
+
*/
|
539 |
+
protected function getPostedValues()
|
540 |
+
{
|
541 |
+
$result = $_POST;
|
542 |
+
|
543 |
+
// Handle checkboxes.
|
544 |
+
foreach ($this->getCheckboxKeys() as $checkboxName => $collectionName) {
|
545 |
+
// Check if checkboxes are handled as an array of checkboxes.
|
546 |
+
if (isset($result[$collectionName]) && is_array($result[$collectionName])) {
|
547 |
+
// Extract the checked values.
|
548 |
+
$checkedValues = array_combine(array_values($result[$collectionName]), array_fill(0, count($result[$collectionName]), 1));
|
549 |
+
// Replace the array value with the checked values, unset first as the
|
550 |
+
// keys for the collection and (1 of the) checkboxes may be the same.
|
551 |
+
unset($result[$collectionName]);
|
552 |
+
$result += $checkedValues;
|
553 |
+
} // Check if checkbox keys are prepended with their collection name.
|
554 |
+
else if (isset($result["{$collectionName}_{$checkboxName}"])) {
|
555 |
+
$result[$checkboxName] = $result["{$collectionName}_{$checkboxName}"];
|
556 |
+
unset($result["{$collectionName}_{$checkboxName}"]);
|
557 |
+
}
|
558 |
+
}
|
559 |
+
|
560 |
+
return $result;
|
561 |
+
}
|
562 |
+
|
563 |
+
/**
|
564 |
+
* Returns the expected date format using a format as accepted by
|
565 |
+
* DateTime::createFromFormat().
|
566 |
+
*
|
567 |
+
* This default implementation assumes that the shop uses the same format
|
568 |
+
* options as PHP.
|
569 |
+
*
|
570 |
+
* @return string
|
571 |
+
*/
|
572 |
+
public function getDateFormat()
|
573 |
+
{
|
574 |
+
return $this->getShopDateFormat();
|
575 |
+
}
|
576 |
+
|
577 |
+
/**
|
578 |
+
* Returns the expected date format using a format as accepted by the shop's
|
579 |
+
* date handling functions.
|
580 |
+
*
|
581 |
+
* This default implementation assumes that the shop does not provide its own
|
582 |
+
* date handling formats and settings.
|
583 |
+
*
|
584 |
+
* @return string
|
585 |
+
*/
|
586 |
+
public function getShopDateFormat()
|
587 |
+
{
|
588 |
+
return 'Y-m-d';
|
589 |
+
}
|
590 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/FormRenderer.php
ADDED
@@ -0,0 +1,813 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Helpers;
|
3 |
+
|
4 |
+
/**
|
5 |
+
* Provides form element rendering functionality. This basic implementation
|
6 |
+
* renders the elements as wrapped html input elements. To comply with shop
|
7 |
+
* specific styling, it is supposed to be overridden per shop that uses this
|
8 |
+
* way of rendering. for now those are: HikaShop/VirtueMart (Joomla), OpenCart,
|
9 |
+
* and WooCommerce (WordPress).
|
10 |
+
*/
|
11 |
+
class FormRenderer
|
12 |
+
{
|
13 |
+
/** @var bool */
|
14 |
+
protected $html5 = true;
|
15 |
+
|
16 |
+
/** @var string */
|
17 |
+
protected $elementWrapperTag = 'div';
|
18 |
+
|
19 |
+
/** @var string */
|
20 |
+
protected $elementWrapperClass = 'form-element';
|
21 |
+
|
22 |
+
/** @var string */
|
23 |
+
protected $fieldsetWrapperTag = 'fieldset';
|
24 |
+
|
25 |
+
/** @var string */
|
26 |
+
protected $fieldsetWrapperClass = '';
|
27 |
+
|
28 |
+
/** @var string */
|
29 |
+
protected $legendWrapperTag = 'legend';
|
30 |
+
|
31 |
+
/** @var string */
|
32 |
+
protected $legendWrapperClass = '';
|
33 |
+
|
34 |
+
/** @var string */
|
35 |
+
protected $labelWrapperTag = '';
|
36 |
+
|
37 |
+
/** @var string */
|
38 |
+
protected $labelWrapperClass = '';
|
39 |
+
|
40 |
+
/** @var string */
|
41 |
+
protected $markupWrapperTag = 'div';
|
42 |
+
|
43 |
+
/** @var string */
|
44 |
+
protected $markupWrapperClass = 'message';
|
45 |
+
|
46 |
+
/** @var string */
|
47 |
+
protected $inputWrapperTag = '';
|
48 |
+
|
49 |
+
/** @var string */
|
50 |
+
protected $inputWrapperClass = '';
|
51 |
+
|
52 |
+
/** @var string */
|
53 |
+
protected $radioWrapperTag = 'div';
|
54 |
+
|
55 |
+
/** @var string */
|
56 |
+
protected $radioWrapperClass = 'radio';
|
57 |
+
|
58 |
+
/** @var string */
|
59 |
+
protected $radio1WrapperTag = '';
|
60 |
+
|
61 |
+
/** @var string */
|
62 |
+
protected $radio1WrapperClass = '';
|
63 |
+
|
64 |
+
/** @var string */
|
65 |
+
protected $checkboxWrapperTag = 'div';
|
66 |
+
|
67 |
+
/** @var string */
|
68 |
+
protected $checkboxWrapperClass = 'checkbox';
|
69 |
+
|
70 |
+
/** @var string */
|
71 |
+
protected $checkbox1WrapperTag = '';
|
72 |
+
|
73 |
+
/** @var string */
|
74 |
+
protected $checkbox1WrapperClass = '';
|
75 |
+
|
76 |
+
/** @var string */
|
77 |
+
protected $multiLabelTag = 'span';
|
78 |
+
|
79 |
+
/** @var string */
|
80 |
+
protected $multiLabelClass = 'label';
|
81 |
+
|
82 |
+
/** @var string */
|
83 |
+
protected $descriptionClass = 'description';
|
84 |
+
|
85 |
+
/** @var string */
|
86 |
+
protected $requiredMarkup = '<span class="required">*</span>';
|
87 |
+
|
88 |
+
/** @var Form */
|
89 |
+
protected $form;
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Renders the form.
|
93 |
+
*
|
94 |
+
* @param \Siel\Acumulus\Helpers\Form $form
|
95 |
+
*
|
96 |
+
* @return string
|
97 |
+
*/
|
98 |
+
public function render(Form $form)
|
99 |
+
{
|
100 |
+
$this->form = $form;
|
101 |
+
$this->form->addValues();
|
102 |
+
return $this->fields($this->form->getFields());
|
103 |
+
}
|
104 |
+
|
105 |
+
/**
|
106 |
+
* Renders a set of field definitions.
|
107 |
+
*
|
108 |
+
* @param array[] $fields
|
109 |
+
*
|
110 |
+
* @return string
|
111 |
+
*/
|
112 |
+
protected function fields(array $fields)
|
113 |
+
{
|
114 |
+
$output = '';
|
115 |
+
foreach ($fields as $id => $field) {
|
116 |
+
if (!isset($field['id'])) {
|
117 |
+
$field['id'] = $id;
|
118 |
+
}
|
119 |
+
if (!isset($field['name'])) {
|
120 |
+
$field['name'] = $id;
|
121 |
+
}
|
122 |
+
$output .= $this->field($field);
|
123 |
+
}
|
124 |
+
return $output;
|
125 |
+
}
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Renders 1 field definition (which may be a fieldset with multiple fields).
|
129 |
+
*
|
130 |
+
* @param array $field
|
131 |
+
*
|
132 |
+
* @return string
|
133 |
+
*/
|
134 |
+
protected function field(array $field)
|
135 |
+
{
|
136 |
+
$output = '';
|
137 |
+
if (!isset($field['attributes'])) {
|
138 |
+
$field['attributes'] = array();
|
139 |
+
}
|
140 |
+
switch ($field['type']) {
|
141 |
+
case 'fieldset':
|
142 |
+
$output .= $this->renderFieldset($field);
|
143 |
+
break;
|
144 |
+
case 'markup':
|
145 |
+
$output .= $this->renderMarkup($field);
|
146 |
+
break;
|
147 |
+
default:
|
148 |
+
$output .= $this->renderField($field);
|
149 |
+
break;
|
150 |
+
}
|
151 |
+
return $output;
|
152 |
+
}
|
153 |
+
|
154 |
+
/**
|
155 |
+
* Renders a fieldset.
|
156 |
+
*
|
157 |
+
* @param array $field
|
158 |
+
*
|
159 |
+
* @return string
|
160 |
+
* The rendered fieldset.
|
161 |
+
*/
|
162 |
+
protected function renderFieldset(array $field)
|
163 |
+
{
|
164 |
+
$output = '';
|
165 |
+
$output .= $this->fieldsetBegin($field);
|
166 |
+
$output .= $this->fields($field['fields']);
|
167 |
+
$output .= $this->fieldsetEnd($field);
|
168 |
+
return $output;
|
169 |
+
}
|
170 |
+
|
171 |
+
/**
|
172 |
+
* Outputs the beginning of a fieldset.
|
173 |
+
*
|
174 |
+
* @param array $field
|
175 |
+
*
|
176 |
+
* @return string
|
177 |
+
*/
|
178 |
+
protected function fieldsetBegin(array $field)
|
179 |
+
{
|
180 |
+
$output = '';
|
181 |
+
$output .= $this->getWrapper('fieldset', $field['attributes']);
|
182 |
+
$output .= $this->getWrapper('legend', $field['attributes']);
|
183 |
+
$output .= $field['legend'];
|
184 |
+
$output .= $this->getWrapperEnd('legend');
|
185 |
+
if (!empty($field['description'])) {
|
186 |
+
$output .= $this->renderDescription($field['description']);
|
187 |
+
}
|
188 |
+
return $output;
|
189 |
+
}
|
190 |
+
|
191 |
+
/**
|
192 |
+
* Outputs the end of a fieldset.
|
193 |
+
*
|
194 |
+
* @param array $field
|
195 |
+
*
|
196 |
+
* @return string
|
197 |
+
*/
|
198 |
+
protected function fieldsetEnd(
|
199 |
+
/** @noinspection PhpUnusedParameterInspection */
|
200 |
+
array $field
|
201 |
+
) {
|
202 |
+
$output = '';
|
203 |
+
$output .= $this->getWrapperEnd('fieldset');
|
204 |
+
return $output;
|
205 |
+
}
|
206 |
+
|
207 |
+
/**
|
208 |
+
* Renders a form field including its label and description.
|
209 |
+
*
|
210 |
+
* @param array $field
|
211 |
+
*
|
212 |
+
* @return string
|
213 |
+
* Html for this form field.
|
214 |
+
*/
|
215 |
+
protected function renderField(array $field)
|
216 |
+
{
|
217 |
+
$type = $field['type'];
|
218 |
+
$id = $field['id'];
|
219 |
+
$name = $field['name'];
|
220 |
+
$label = isset($field['label']) ? $field['label'] : '';
|
221 |
+
$value = isset($field['value']) ? $field['value'] : '';
|
222 |
+
$attributes = isset($field['attributes']) ? $field['attributes'] : array();
|
223 |
+
$description = isset($field['description']) ? $field['description'] : '';
|
224 |
+
$options = isset($field['options']) ? $field['options'] : array();
|
225 |
+
|
226 |
+
$output = '';
|
227 |
+
|
228 |
+
$labelAttributes = array();
|
229 |
+
if (!empty($attributes['required'])) {
|
230 |
+
$labelAttributes['required'] = $attributes['required'];
|
231 |
+
}
|
232 |
+
|
233 |
+
if ($type !== 'hidden') {
|
234 |
+
$output .= $this->getWrapper('element');
|
235 |
+
$output .= $this->renderLabel($label, $type !== 'radio' && $type !== 'checkbox' ? $id : null, $labelAttributes);
|
236 |
+
}
|
237 |
+
$output .= $this->renderElement($type, $id, $name, $value, $attributes, $options);
|
238 |
+
if ($type !== 'hidden') {
|
239 |
+
$output .= $this->renderDescription($description);
|
240 |
+
$output .= $this->getWrapperEnd('element');
|
241 |
+
}
|
242 |
+
return $output;
|
243 |
+
}
|
244 |
+
|
245 |
+
/**
|
246 |
+
* Renders a form field itself, ie without label and description.
|
247 |
+
*
|
248 |
+
* @param string $type
|
249 |
+
* @param string $id
|
250 |
+
* @param string $name
|
251 |
+
* @param string|int $value
|
252 |
+
* @param array $attributes
|
253 |
+
* @param array $options
|
254 |
+
*
|
255 |
+
* @return string
|
256 |
+
*/
|
257 |
+
protected function renderElement($type, $id, $name, $value, array $attributes = array(), array $options = array())
|
258 |
+
{
|
259 |
+
switch ($type) {
|
260 |
+
case 'textarea':
|
261 |
+
return $this->textarea($id, $name, $value, $attributes);
|
262 |
+
case 'select':
|
263 |
+
case 'radio':
|
264 |
+
case 'checkbox':
|
265 |
+
return $this->$type($id, $name, $value, $options, $attributes);
|
266 |
+
default:
|
267 |
+
return $this->input($type, $id, $name, $value, $attributes);
|
268 |
+
}
|
269 |
+
}
|
270 |
+
|
271 |
+
/**
|
272 |
+
* Renders a descriptive help text.
|
273 |
+
*
|
274 |
+
* @param string $text
|
275 |
+
* @param string $tag
|
276 |
+
*
|
277 |
+
* @return string
|
278 |
+
* The rendered description.
|
279 |
+
*/
|
280 |
+
protected function renderDescription($text, $tag = 'div')
|
281 |
+
{
|
282 |
+
$output = '';
|
283 |
+
|
284 |
+
// Help text.
|
285 |
+
if (!empty($text)) {
|
286 |
+
// Allow for links in the help text, so no filtering anymore.
|
287 |
+
//$output .= "<$tag class=\"{$this->descriptionClass}\">" . htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8') . "</$tag>";
|
288 |
+
$output .= "<$tag class=\"{$this->descriptionClass}\">$text</$tag>";
|
289 |
+
}
|
290 |
+
|
291 |
+
return $output;
|
292 |
+
}
|
293 |
+
|
294 |
+
/**
|
295 |
+
* Renders a label.
|
296 |
+
*
|
297 |
+
* @param string $text
|
298 |
+
* The label text.
|
299 |
+
* @param string|null $id
|
300 |
+
* The value of the for attribute. If null, not a label tag but a span with
|
301 |
+
* a class="label" will be rendered.
|
302 |
+
* @param array $attributes
|
303 |
+
* Any additional attributes to render for the label. The array is a keyed
|
304 |
+
* array, the keys being the attribute names, the values being the
|
305 |
+
* value of that attribute. If that value is an array it is rendered as a
|
306 |
+
* joined string of the values separated by a space (e.g. multiple classes).
|
307 |
+
* @param bool $wrapLabel
|
308 |
+
* Whether to wrap this label within the defined label wrapper tag.
|
309 |
+
*
|
310 |
+
* @return string The rendered label.
|
311 |
+
* The rendered label.
|
312 |
+
*/
|
313 |
+
protected function renderLabel($text, $id = null, array $attributes = array(), $wrapLabel = true)
|
314 |
+
{
|
315 |
+
$output = '';
|
316 |
+
|
317 |
+
$attributes = $this->addLabelAttributes($attributes, $id);
|
318 |
+
|
319 |
+
// Tag around main labels.
|
320 |
+
if ($wrapLabel) {
|
321 |
+
$output .= $this->getWrapper('label', $attributes);
|
322 |
+
}
|
323 |
+
|
324 |
+
// Label.
|
325 |
+
$required = !empty($attributes['required']) ? $this->requiredMarkup : '';
|
326 |
+
$tag = empty($id) ? $this->multiLabelTag : 'label';
|
327 |
+
$output .= '<' . $tag . $this->renderAttributes($attributes) . '>' . htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8') . $required . '</' . $tag . '>';
|
328 |
+
|
329 |
+
// Tag around labels.
|
330 |
+
if ($wrapLabel) {
|
331 |
+
$output .= $this->getWrapperEnd('label');
|
332 |
+
}
|
333 |
+
return $output;
|
334 |
+
}
|
335 |
+
|
336 |
+
/**
|
337 |
+
* Renders an input field.
|
338 |
+
*
|
339 |
+
* @param string $type
|
340 |
+
* The input type, required.
|
341 |
+
* @param string $id
|
342 |
+
* The id attribute of the text field, required.
|
343 |
+
* @param string $name
|
344 |
+
* The name attribute of the text field, required.
|
345 |
+
* @param string $value
|
346 |
+
* The initial value of the text field.
|
347 |
+
* @param array $attributes
|
348 |
+
* Any additional attributes to render for the text field, think of size or
|
349 |
+
* maxlength. The array is a keyed array, the keys being the attribute
|
350 |
+
* names, the values being the value of that attribute. If that value is an
|
351 |
+
* array it is rendered as a joined string of the values separated by a
|
352 |
+
* space (e.g. multiple classes).
|
353 |
+
*
|
354 |
+
* @return string
|
355 |
+
* The rendered text field.
|
356 |
+
*/
|
357 |
+
protected function input($type, $id, $name, $value = '', array $attributes = array())
|
358 |
+
{
|
359 |
+
$output = '';
|
360 |
+
|
361 |
+
// Tag around input element.
|
362 |
+
$output .= $this->getWrapper('input');
|
363 |
+
|
364 |
+
$attributes = $this->addAttribute($attributes, 'type', $type);
|
365 |
+
$attributes = $this->addAttribute($attributes, 'id', $id);
|
366 |
+
$attributes = $this->addAttribute($attributes, 'name', $name);
|
367 |
+
$attributes = $this->addAttribute($attributes, 'value', $value);
|
368 |
+
$output .= '<input' . $this->renderAttributes($attributes) . '/>';
|
369 |
+
|
370 |
+
// Tag around input element.
|
371 |
+
$output .= $this->getWrapperEnd('input');
|
372 |
+
|
373 |
+
return $output;
|
374 |
+
}
|
375 |
+
|
376 |
+
/**
|
377 |
+
* Renders a textarea field.
|
378 |
+
*
|
379 |
+
* @param string $id
|
380 |
+
* The id attribute of the text field, required.
|
381 |
+
* @param string $name
|
382 |
+
* The name attribute of the text field, required.
|
383 |
+
* @param string $value
|
384 |
+
* The initial value of the text field.
|
385 |
+
* @param array $attributes
|
386 |
+
* Any additional attributes to render for the text field, think of size or
|
387 |
+
* maxlength. The array is a keyed array, the keys being the attribute
|
388 |
+
* names, the values being the value of that attribute. If that value is an
|
389 |
+
* array it is rendered as a joined string of the values separated by a
|
390 |
+
* space (e.g. multiple classes).
|
391 |
+
*
|
392 |
+
* @return string The rendered textarea field.
|
393 |
+
* The rendered textarea field.
|
394 |
+
*/
|
395 |
+
protected function textarea($id, $name, $value = '', array $attributes = array())
|
396 |
+
{
|
397 |
+
$output = '';
|
398 |
+
|
399 |
+
// Tag around input element.
|
400 |
+
$output .= $this->getWrapper('input');
|
401 |
+
|
402 |
+
$attributes = $this->addAttribute($attributes, 'id', $id);
|
403 |
+
$attributes = $this->addAttribute($attributes, 'name', $name);
|
404 |
+
$output .= '<textarea' . $this->renderAttributes($attributes) . '>';
|
405 |
+
$output .= htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8');
|
406 |
+
$output .= '</textarea>';
|
407 |
+
|
408 |
+
// Tag around input element.
|
409 |
+
$output .= $this->getWrapperEnd('input');
|
410 |
+
|
411 |
+
return $output;
|
412 |
+
}
|
413 |
+
|
414 |
+
/**
|
415 |
+
* Renders a text field (input tag with type="text").
|
416 |
+
*
|
417 |
+
* @param string $id
|
418 |
+
* The id attribute of the text field, required.
|
419 |
+
* @param string $name
|
420 |
+
* The name attribute of the text field, required.
|
421 |
+
* @param string $value
|
422 |
+
* The initial value of the text field.
|
423 |
+
* @param array $attributes
|
424 |
+
* Any additional attributes to render for the text field, think of size or
|
425 |
+
* maxlength. The array is a keyed array, the keys being the attribute
|
426 |
+
* names, the values being the value of that attribute. If that value is an
|
427 |
+
* array it is rendered as a joined string of the values separated by a
|
428 |
+
* space (e.g. multiple classes).
|
429 |
+
*
|
430 |
+
* @return string
|
431 |
+
* The rendered text field.
|
432 |
+
*
|
433 |
+
* @deprecated
|
434 |
+
*/
|
435 |
+
protected function text($id, $name, $value = '', array $attributes = array())
|
436 |
+
{
|
437 |
+
return $this->input('text', $id, $name, $value, $attributes);
|
438 |
+
}
|
439 |
+
|
440 |
+
/**
|
441 |
+
* Renders a password field (input tag with type="password").
|
442 |
+
*
|
443 |
+
* @param string $id
|
444 |
+
* The id attribute of the password field, required.
|
445 |
+
* @param string $name
|
446 |
+
* The name attribute of the password field, required.
|
447 |
+
* @param string $value
|
448 |
+
* The initial value of the field.
|
449 |
+
* @param array $attributes
|
450 |
+
* Any additional attributes to render for this field. The array is a keyed
|
451 |
+
* array, the keys being the attribute names, the values being the value of
|
452 |
+
* that attribute. If that value is an array it is rendered as a joined
|
453 |
+
* string of the values separated by a space (e.g. multiple classes).
|
454 |
+
*
|
455 |
+
* @return string
|
456 |
+
* The rendered field.
|
457 |
+
*
|
458 |
+
* @deprecated
|
459 |
+
*/
|
460 |
+
protected function password($id, $name, $value = '', array $attributes = array())
|
461 |
+
{
|
462 |
+
return $this->input('password', $id, $name, $value, $attributes);
|
463 |
+
}
|
464 |
+
|
465 |
+
/**
|
466 |
+
* Renders a select element.
|
467 |
+
*
|
468 |
+
* @param string $id
|
469 |
+
* The id attribute of the select, required.
|
470 |
+
* @param string $name
|
471 |
+
* The name attribute of the select, required.
|
472 |
+
* @param mixed|null $selected
|
473 |
+
* The selected value, null if no value has to be set to selected.
|
474 |
+
* @param array $options
|
475 |
+
* The list of options as a keyed array, the keys being the value attribute
|
476 |
+
* of the option tag, the values being the text within the option tag.
|
477 |
+
* @param array $attributes
|
478 |
+
* Any additional attributes to render for the select tag, think of disabled.
|
479 |
+
* The array is a keyed array, the keys being the attribute names, the
|
480 |
+
* values being the value of that attribute. If that value is an array it is
|
481 |
+
* rendered as a joined string of the values separated by a space (e.g.
|
482 |
+
* multiple classes).
|
483 |
+
*
|
484 |
+
* @return string The rendered select element.
|
485 |
+
* The rendered select element.
|
486 |
+
*/
|
487 |
+
protected function select($id, $name, $selected, array $options, array $attributes = array())
|
488 |
+
{
|
489 |
+
$output = '';
|
490 |
+
|
491 |
+
// Tag around select element: same as for an input element.
|
492 |
+
$output .= $this->getWrapper('input');
|
493 |
+
|
494 |
+
// Select tag.
|
495 |
+
$attributes = array_merge(array('id' => $id, 'name' => $name), $attributes);
|
496 |
+
$output .= '<select' . $this->renderAttributes($attributes) . '>';
|
497 |
+
|
498 |
+
// Options.
|
499 |
+
foreach ($options as $value => $text) {
|
500 |
+
$optionAttributes = array('value' => $value);
|
501 |
+
if ($this->compareValues($selected, $value)) {
|
502 |
+
$optionAttributes['selected'] = true;
|
503 |
+
}
|
504 |
+
$output .= '<option' . $this->renderAttributes($optionAttributes) . '>' . htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8') . '</option>';
|
505 |
+
}
|
506 |
+
|
507 |
+
// End tag.
|
508 |
+
$output .= '</select>';
|
509 |
+
// Tag around select element.
|
510 |
+
$output .= $this->getWrapperEnd('input');
|
511 |
+
|
512 |
+
return $output;
|
513 |
+
}
|
514 |
+
|
515 |
+
/**
|
516 |
+
* Renders a list of radio buttons (input tag with type="radio").
|
517 |
+
*
|
518 |
+
* @param string $id
|
519 |
+
* The id attribute for all the radio buttons, required.
|
520 |
+
* @param string $name
|
521 |
+
* The name attribute for all the radio buttons, required.
|
522 |
+
* @param mixed|null $selected
|
523 |
+
* The selected value, null if no value has to be set to selected.
|
524 |
+
* @param array $options
|
525 |
+
* The list of radio buttons as a keyed array, the keys being the value
|
526 |
+
* attribute of the radio button, the values being the label of the radio
|
527 |
+
* button.
|
528 |
+
* @param array $attributes
|
529 |
+
* Any additional attributes to render on the div tag. The array is a keyed
|
530 |
+
* array, the keys being the attribute names, the values being the value of
|
531 |
+
* that attribute. If that value is an array it is rendered as a joined
|
532 |
+
* string of the values separated by a space (e.g. multiple classes).
|
533 |
+
*
|
534 |
+
* @return string The rendered radio buttons.
|
535 |
+
* The rendered radio buttons.
|
536 |
+
*/
|
537 |
+
protected function radio($id, $name, $selected, array $options, array $attributes = array())
|
538 |
+
{
|
539 |
+
$output = '';
|
540 |
+
|
541 |
+
// Handling of required attribute: may appear on on all radio buttons with
|
542 |
+
// the same name.
|
543 |
+
$required = !empty($attributes['required']);
|
544 |
+
unset($attributes['required']);
|
545 |
+
|
546 |
+
// Tag(s) around radio buttons.
|
547 |
+
$output .= $this->getWrapper('input', $attributes);
|
548 |
+
$output .= $this->getWrapper('radio', $attributes);
|
549 |
+
|
550 |
+
// Radio buttons.
|
551 |
+
foreach ($options as $value => $text) {
|
552 |
+
$radioAttributes = $this->getRadioAttributes($id, $name, $value);
|
553 |
+
$radioAttributes = $this->addAttribute($radioAttributes, 'required', $required);
|
554 |
+
if ($this->compareValues($selected, $value)) {
|
555 |
+
$radioAttributes['checked'] = true;
|
556 |
+
}
|
557 |
+
$output .= $this->getWrapper('radio1');
|
558 |
+
$output .= '<input' . $this->renderAttributes($radioAttributes) . '>';
|
559 |
+
$output .= $this->renderLabel($text, $radioAttributes['id'], array(), false);
|
560 |
+
$output .= $this->getWrapperEnd('radio1');
|
561 |
+
}
|
562 |
+
|
563 |
+
// End tag.
|
564 |
+
$output .= $this->getWrapperEnd('radio');
|
565 |
+
$output .= $this->getWrapperEnd('input');
|
566 |
+
|
567 |
+
return $output;
|
568 |
+
}
|
569 |
+
|
570 |
+
/**
|
571 |
+
* Renders a list of checkboxes (input tag with type="checkbox") enclosed in a
|
572 |
+
* div.
|
573 |
+
*
|
574 |
+
* @param string $id
|
575 |
+
* The id prefix attribute for the checkboxes, required.
|
576 |
+
* @param string $name
|
577 |
+
* The name attribute for all the checkboxes, required. When rendering
|
578 |
+
* multiple checkboxes, use a name that ends with [] for easy PHP processing.
|
579 |
+
* @param array $selected
|
580 |
+
* The selected values.
|
581 |
+
* @param array $options
|
582 |
+
* The list of checkboxes as a keyed array, the keys being the value
|
583 |
+
* attribute of the checkbox, the values being the label of the checkbox.
|
584 |
+
* @param array $attributes
|
585 |
+
* Any additional attributes to render on the div tag. The array is a keyed
|
586 |
+
* array, the keys being the attribute names, the values being the value of
|
587 |
+
* that attribute. If that value is an array it is rendered as a joined
|
588 |
+
* string of the values separated by a space (e.g. multiple classes).
|
589 |
+
*
|
590 |
+
* @return string The rendered checkboxes.
|
591 |
+
* The rendered checkboxes.
|
592 |
+
*/
|
593 |
+
protected function checkbox($id, $name, array $selected, array $options, array $attributes = array())
|
594 |
+
{
|
595 |
+
$output = '';
|
596 |
+
|
597 |
+
// Div tag.
|
598 |
+
unset($attributes['required']);
|
599 |
+
$output .= $this->getWrapper('input', $attributes);
|
600 |
+
$output .= $this->getWrapper('checkbox', $attributes);
|
601 |
+
|
602 |
+
// Checkboxes.
|
603 |
+
foreach ($options as $value => $text) {
|
604 |
+
$checkboxAttributes = $this->getCheckboxAttributes($id, $name, $value);
|
605 |
+
if (in_array($value, $selected)) {
|
606 |
+
$checkboxAttributes['checked'] = true;
|
607 |
+
}
|
608 |
+
$output .= $this->getWrapper('checkbox1');
|
609 |
+
$output .= '<input' . $this->renderAttributes($checkboxAttributes) . '>';
|
610 |
+
$output .= $this->renderLabel($text, $checkboxAttributes['id'], array(), false);
|
611 |
+
$output .= $this->getWrapperEnd('checkbox1');
|
612 |
+
}
|
613 |
+
|
614 |
+
// End tag.
|
615 |
+
$output .= $this->getWrapperEnd('checkbox');
|
616 |
+
$output .= $this->getWrapperEnd('input');
|
617 |
+
|
618 |
+
return $output;
|
619 |
+
}
|
620 |
+
|
621 |
+
/**
|
622 |
+
* Renders a markup (free format output) element.
|
623 |
+
*
|
624 |
+
* @param array $field
|
625 |
+
*
|
626 |
+
* @return string
|
627 |
+
* The rendered markup.
|
628 |
+
*/
|
629 |
+
protected function renderMarkup(array $field)
|
630 |
+
{
|
631 |
+
$output = '';
|
632 |
+
$output .= $this->getWrapper('markup');
|
633 |
+
$output .= $field['value'];
|
634 |
+
$output .= $this->getWrapperEnd('markup');
|
635 |
+
return $output;
|
636 |
+
}
|
637 |
+
|
638 |
+
/**
|
639 |
+
* @param string $type
|
640 |
+
* @param array $attributes
|
641 |
+
*
|
642 |
+
* @return string
|
643 |
+
*/
|
644 |
+
protected function getWrapper($type, array $attributes = array())
|
645 |
+
{
|
646 |
+
$tag = "{$type}WrapperTag";
|
647 |
+
$class = "{$type}WrapperClass";
|
648 |
+
$output = '';
|
649 |
+
if (!empty($this->$tag)) {
|
650 |
+
if (!empty($this->$class)) {
|
651 |
+
$attributes = $this->addAttribute($attributes, 'class', $this->$class);
|
652 |
+
}
|
653 |
+
$output .= "<{$this->$tag}";
|
654 |
+
$output .= $this->renderAttributes($attributes);
|
655 |
+
$output .= '>';
|
656 |
+
}
|
657 |
+
return $output;
|
658 |
+
}
|
659 |
+
|
660 |
+
/**
|
661 |
+
* @param string $type
|
662 |
+
*
|
663 |
+
* @return string
|
664 |
+
*/
|
665 |
+
protected function getWrapperEnd($type)
|
666 |
+
{
|
667 |
+
$tag = "{$type}WrapperTag";
|
668 |
+
$output = '';
|
669 |
+
if (!empty($this->$tag)) {
|
670 |
+
$output .= "</{$this->$tag}>";
|
671 |
+
}
|
672 |
+
return $output;
|
673 |
+
}
|
674 |
+
|
675 |
+
/**
|
676 |
+
* Renders a list of attributes.
|
677 |
+
*
|
678 |
+
* @param array $attributes
|
679 |
+
*
|
680 |
+
* @return string
|
681 |
+
* html string with the rendered attributes and 1 space in front of it.
|
682 |
+
*/
|
683 |
+
protected function renderAttributes(array $attributes)
|
684 |
+
{
|
685 |
+
$attributeString = '';
|
686 |
+
foreach ($attributes as $key => $value) {
|
687 |
+
if (is_array($value)) {
|
688 |
+
$value = implode(' ', $value);
|
689 |
+
}
|
690 |
+
// Skip attributes that are not to be set (required, disabled, ...).
|
691 |
+
if ($value !== false) {
|
692 |
+
$attributeString .= ' ' . htmlspecialchars($key, ENT_NOQUOTES, 'UTF-8');
|
693 |
+
// HTML5: do not add a value to boolean attributes.
|
694 |
+
// HTML4: add the name of the key as value for the attribute.
|
695 |
+
if (!$this->html5 && $value === true) {
|
696 |
+
$value = $key;
|
697 |
+
}
|
698 |
+
if ($value !== true) {
|
699 |
+
$attributeString .= '="' . htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8') . '"';
|
700 |
+
}
|
701 |
+
}
|
702 |
+
}
|
703 |
+
return $attributeString;
|
704 |
+
}
|
705 |
+
|
706 |
+
/**
|
707 |
+
* @param array $attributes
|
708 |
+
* The array of attributes to add the value to.
|
709 |
+
* @param string $attribute
|
710 |
+
* The name of the attribute to add.
|
711 |
+
* @param string $value
|
712 |
+
* The value of the attribute to add.
|
713 |
+
* @param bool|null $multiple
|
714 |
+
* Allow multiple values for the given attribute. By default this is
|
715 |
+
* only allowed for the class attribute.
|
716 |
+
*
|
717 |
+
* @return array
|
718 |
+
* The set of attributes with the value added.
|
719 |
+
*/
|
720 |
+
protected function addAttribute(array $attributes, $attribute, $value, $multiple = null)
|
721 |
+
{
|
722 |
+
// Do add false and 0, but not an empty string or null.
|
723 |
+
if ($value !== null && $value !== '') {
|
724 |
+
if ($multiple === null) {
|
725 |
+
$multiple = $attribute === 'class';
|
726 |
+
}
|
727 |
+
|
728 |
+
if ($multiple) {
|
729 |
+
// Multiple values allowed: set or add, not overwriting.
|
730 |
+
if (isset($attributes[$attribute])) {
|
731 |
+
// Assure it is an array, not a scalar
|
732 |
+
$attributes[$attribute] = (array) $attributes[$attribute];
|
733 |
+
} else {
|
734 |
+
// Set as an empty array
|
735 |
+
$attributes[$attribute] = array();
|
736 |
+
}
|
737 |
+
// Now we know for sure that it is an array, add it.
|
738 |
+
$attributes[$attribute][] = $value;
|
739 |
+
} else {
|
740 |
+
// Single value: just set, possibly overwriting.
|
741 |
+
$attributes[$attribute] = $value;
|
742 |
+
}
|
743 |
+
}
|
744 |
+
return $attributes;
|
745 |
+
}
|
746 |
+
|
747 |
+
/**
|
748 |
+
* @param array $attributes
|
749 |
+
* @param string $id
|
750 |
+
*
|
751 |
+
* @return array
|
752 |
+
*/
|
753 |
+
protected function addLabelAttributes(array $attributes, $id)
|
754 |
+
{
|
755 |
+
$attributes = $this->addAttribute($attributes, 'for', $id);
|
756 |
+
if (empty($id)) {
|
757 |
+
$attributes = $this->addAttribute($attributes, 'class', $this->multiLabelClass);
|
758 |
+
}
|
759 |
+
return $attributes;
|
760 |
+
}
|
761 |
+
|
762 |
+
/**
|
763 |
+
* @param string $id
|
764 |
+
* @param string $name
|
765 |
+
* @param string $value
|
766 |
+
*
|
767 |
+
* @return array
|
768 |
+
*/
|
769 |
+
protected function getCheckboxAttributes(
|
770 |
+
/** @noinspection PhpUnusedParameterInspection */
|
771 |
+
$id, $name, $value
|
772 |
+
) {
|
773 |
+
$checkboxAttributes = array(
|
774 |
+
'type' => 'checkbox',
|
775 |
+
'id' => "{$name}_{$value}",
|
776 |
+
'name' => $value,
|
777 |
+
'value' => 1,
|
778 |
+
);
|
779 |
+
return $checkboxAttributes;
|
780 |
+
}
|
781 |
+
|
782 |
+
/**
|
783 |
+
* @param string $id
|
784 |
+
* @param string $name
|
785 |
+
* @param string $value
|
786 |
+
*
|
787 |
+
* @return array
|
788 |
+
*/
|
789 |
+
protected function getRadioAttributes($id, $name, $value)
|
790 |
+
{
|
791 |
+
$radioAttributes = array(
|
792 |
+
'type' => 'radio',
|
793 |
+
'id' => "{$id}_{$value}",
|
794 |
+
'name' => $name,
|
795 |
+
'value' => $value,
|
796 |
+
);
|
797 |
+
return $radioAttributes;
|
798 |
+
}
|
799 |
+
|
800 |
+
/**
|
801 |
+
* Compares an option and a value to see if this option should be "selected".
|
802 |
+
*
|
803 |
+
* @param string|int|array $value
|
804 |
+
* @param string|int $optionValue
|
805 |
+
*
|
806 |
+
* @return bool
|
807 |
+
* If this option equals the value.
|
808 |
+
*/
|
809 |
+
protected function compareValues($value, $optionValue)
|
810 |
+
{
|
811 |
+
return is_array($value) ? in_array((string) $optionValue, $value) : (string) $optionValue === (string) $value;
|
812 |
+
}
|
813 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/Log.php
ADDED
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Helpers;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Web\ConfigInterface;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Defines a logger class. This class is supposed to be overridden by shop
|
8 |
+
* specific classes to integrate with the shop specific way of logging.
|
9 |
+
*/
|
10 |
+
class Log
|
11 |
+
{
|
12 |
+
const None = 0;
|
13 |
+
const Error = 1;
|
14 |
+
const Warning = 2;
|
15 |
+
const Notice = 3;
|
16 |
+
const Debug = 4;
|
17 |
+
const NotYetSet = 5;
|
18 |
+
|
19 |
+
/** @var Log */
|
20 |
+
static protected $instance = null;
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Returns an instance of the log class (or web shop specific child class).
|
24 |
+
*
|
25 |
+
* @return Log
|
26 |
+
*/
|
27 |
+
public static function getInstance()
|
28 |
+
{
|
29 |
+
return static::$instance;
|
30 |
+
}
|
31 |
+
|
32 |
+
/** @var int */
|
33 |
+
protected $logLevel;
|
34 |
+
|
35 |
+
/** @var ConfigInterface */
|
36 |
+
protected $config;
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Log constructor.
|
40 |
+
*
|
41 |
+
* @param ConfigInterface $config
|
42 |
+
*/
|
43 |
+
public function __construct($config)
|
44 |
+
{
|
45 |
+
// Start with logging everything. Soon after the creation of this log object
|
46 |
+
// the log level should be set based on the configuration.
|
47 |
+
$this->logLevel = static::NotYetSet;
|
48 |
+
$this->config = $config;
|
49 |
+
static::$instance = $this;
|
50 |
+
}
|
51 |
+
|
52 |
+
/**
|
53 |
+
* Gets the actual log level.
|
54 |
+
*
|
55 |
+
* @return int
|
56 |
+
*/
|
57 |
+
public function getLogLevel()
|
58 |
+
{
|
59 |
+
// To support lazy load of the config, the log level is not yet set until
|
60 |
+
// actually needed.
|
61 |
+
if ($this->logLevel === static::NotYetSet && $this->config !== null) {
|
62 |
+
$this->logLevel = $this->config->getLogLevel();
|
63 |
+
}
|
64 |
+
return $this->logLevel;
|
65 |
+
}
|
66 |
+
|
67 |
+
/**
|
68 |
+
* Sets the log level, eg. based on configuration.
|
69 |
+
*
|
70 |
+
* @param int $logLevel
|
71 |
+
*/
|
72 |
+
public function setLogLevel($logLevel)
|
73 |
+
{
|
74 |
+
$this->logLevel = $logLevel;
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Returns a textual representation of the severity.
|
79 |
+
*
|
80 |
+
* @param int $severity
|
81 |
+
* One of the constants of this class.
|
82 |
+
*
|
83 |
+
* @return string
|
84 |
+
* A textual representation of the severity.
|
85 |
+
*/
|
86 |
+
protected function getSeverityString($severity)
|
87 |
+
{
|
88 |
+
switch ($severity) {
|
89 |
+
case Log::Error:
|
90 |
+
return 'Error';
|
91 |
+
case Log::Warning:
|
92 |
+
return 'Warning';
|
93 |
+
case Log::Notice:
|
94 |
+
return 'Notice';
|
95 |
+
case Log::Debug:
|
96 |
+
default:
|
97 |
+
return 'Debug';
|
98 |
+
}
|
99 |
+
}
|
100 |
+
|
101 |
+
/**
|
102 |
+
* Formats and logs the message if the severity equals or is worse than the
|
103 |
+
* current log level.
|
104 |
+
*
|
105 |
+
* Formatting involves:
|
106 |
+
* - calling vsprintf() if $args is not empty
|
107 |
+
* - adding "Acumulus {version} {severity}: " in front of the message.
|
108 |
+
*
|
109 |
+
* @param int $severity
|
110 |
+
* @param string $message
|
111 |
+
* @param array $args
|
112 |
+
*
|
113 |
+
* @return string
|
114 |
+
* The full formatted message whether it got logged or not.
|
115 |
+
*/
|
116 |
+
public function log($severity, $message, array $args = array())
|
117 |
+
{
|
118 |
+
if ($this->getLogLevel() >= $severity) {
|
119 |
+
if (count($args) > 0) {
|
120 |
+
$message = vsprintf($message, $args);
|
121 |
+
}
|
122 |
+
$message = sprintf('Acumulus: %s', $message);
|
123 |
+
$this->write($message, $severity);
|
124 |
+
}
|
125 |
+
return $message;
|
126 |
+
}
|
127 |
+
|
128 |
+
/**
|
129 |
+
* Logs a debug message
|
130 |
+
*
|
131 |
+
* @param string $message,...
|
132 |
+
* The message to log, optionally followed by arguments. If there are
|
133 |
+
* arguments the $message is passed through vsprintf().
|
134 |
+
*
|
135 |
+
* @return string
|
136 |
+
* The full formatted message whether it got logged or not.
|
137 |
+
*/
|
138 |
+
public function debug($message)
|
139 |
+
{
|
140 |
+
$args = func_get_args();
|
141 |
+
array_shift($args);
|
142 |
+
return $this->log(Log::Debug, $message, $args);
|
143 |
+
}
|
144 |
+
|
145 |
+
/**
|
146 |
+
* Logs a notice.
|
147 |
+
*
|
148 |
+
* @param string $message,...
|
149 |
+
* The message to log, optionally followed by arguments. If there are
|
150 |
+
* arguments the $message is passed through vsprintf().
|
151 |
+
*
|
152 |
+
* @return string
|
153 |
+
* The full formatted message whether it got logged or not.
|
154 |
+
*/
|
155 |
+
public function notice($message)
|
156 |
+
{
|
157 |
+
$args = func_get_args();
|
158 |
+
array_shift($args);
|
159 |
+
return $this->log(Log::Notice, $message, $args);
|
160 |
+
}
|
161 |
+
|
162 |
+
/**
|
163 |
+
* Logs a warning.
|
164 |
+
*
|
165 |
+
* @param string $message,...
|
166 |
+
* The message to log, optionally followed by arguments. If there are
|
167 |
+
* arguments the $message is passed through vsprintf().
|
168 |
+
*
|
169 |
+
* @return string
|
170 |
+
* The full formatted message whether it got logged or not.
|
171 |
+
*/
|
172 |
+
public function warning($message)
|
173 |
+
{
|
174 |
+
$args = func_get_args();
|
175 |
+
array_shift($args);
|
176 |
+
return $this->log(Log::Warning, $message, $args);
|
177 |
+
}
|
178 |
+
|
179 |
+
/**
|
180 |
+
* Logs an error message.
|
181 |
+
*
|
182 |
+
* @param string $message,...
|
183 |
+
* The message to log, optionally followed by arguments. If there are
|
184 |
+
* arguments the $message is passed through vsprintf().
|
185 |
+
*
|
186 |
+
* @return string
|
187 |
+
* The full formatted message whether it got logged or not.
|
188 |
+
*/
|
189 |
+
public function error($message)
|
190 |
+
{
|
191 |
+
$args = func_get_args();
|
192 |
+
array_shift($args);
|
193 |
+
return $this->log(Log::Error, $message, $args);
|
194 |
+
}
|
195 |
+
|
196 |
+
/**
|
197 |
+
* Writes the message to the actual log sink.
|
198 |
+
*
|
199 |
+
* This base implementation sends the message to error_log(). Override if the
|
200 |
+
* web shop offers its own log mechanism.
|
201 |
+
*
|
202 |
+
* @param string $message
|
203 |
+
* @param int $severity
|
204 |
+
*/
|
205 |
+
protected function write($message, $severity)
|
206 |
+
{
|
207 |
+
$message = sprintf('%s - %s', $this->getSeverityString($severity), $message);
|
208 |
+
error_log($message);
|
209 |
+
}
|
210 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/MailTranslations.php
ADDED
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Helpers;
|
3 |
+
|
4 |
+
/**
|
5 |
+
* Contains translations for mails.
|
6 |
+
*/
|
7 |
+
class MailTranslations extends TranslationCollection
|
8 |
+
{
|
9 |
+
protected $nl = array(
|
10 |
+
// Mails.
|
11 |
+
'mail_sender_name' => 'Uw webwinkel',
|
12 |
+
'message_no_invoice' => 'niet aangemaakt in Acumulus',
|
13 |
+
'mail_subject_errors' => 'Fouten bij het verzenden van een factuur naar Acumulus',
|
14 |
+
'mail_subject_warnings' => 'Waarschuwingen bij het verzenden van een factuur naar Acumulus',
|
15 |
+
'mail_subject_debug' => 'Factuur verzonden naar Acumulus',
|
16 |
+
'mail_text_errors' => <<<LONGSTRING
|
17 |
+
Bij het verzenden van een factuur naar Acumulus zijn er foutmeldingen terug
|
18 |
+
gestuurd. Het versturen is niet gelukt. U dient de factuur aan te passen en
|
19 |
+
nogmaals te versturen of deze handmatig aan te maken in Acumulus.
|
20 |
+
LONGSTRING
|
21 |
+
,
|
22 |
+
'mail_html_errors' => <<<LONGSTRING
|
23 |
+
<p>Bij het verzenden van een factuur naar Acumulus zijn er foutmeldingen terug
|
24 |
+
gestuurd. Het versturen is niet gelukt. U dient de factuur aan te passen en
|
25 |
+
nogmaals te versturen of deze handmatig aan te maken in Acumulus.</p>
|
26 |
+
LONGSTRING
|
27 |
+
,
|
28 |
+
'mail_text_warnings' => <<<LONGSTRING
|
29 |
+
Bij het verzenden van een factuur naar Acumulus zijn er waarschuwingen terug
|
30 |
+
gestuurd. De factuur is aangemaakt in Acumulus, maar u dient deze te
|
31 |
+
controleren op correctheid.
|
32 |
+
LONGSTRING
|
33 |
+
,
|
34 |
+
'mail_html_warnings' => <<<LONGSTRING
|
35 |
+
<p>Bij het verzenden van een factuur naar Acumulus zijn er waarschuwingen terug
|
36 |
+
gestuurd. De factuur is aangemaakt in Acumulus, maar u dient deze te
|
37 |
+
controleren op correctheid.</p>
|
38 |
+
LONGSTRING
|
39 |
+
,
|
40 |
+
'mail_text_debug' => <<<LONGSTRING
|
41 |
+
Onderstaande factuur is succesvol naar Acumulus verstuurd. Normaal gesproken
|
42 |
+
krijgt u daar geen bercht van, maar omdat u dit zo heeft ingesteld krijgt u
|
43 |
+
hier nu toch bericht van.
|
44 |
+
LONGSTRING
|
45 |
+
,
|
46 |
+
'mail_html_debug' => <<<LONGSTRING
|
47 |
+
<p>Onderstaande factuur is succesvol naar Acumulus verstuurd. Normaal gesproken
|
48 |
+
krijgt u daar geen bercht van, maar omdat u dit zo heeft ingesteld krijgt u
|
49 |
+
hier nu toch bericht van.</p>
|
50 |
+
LONGSTRING
|
51 |
+
,
|
52 |
+
'mail_text' => <<<LONGSTRING
|
53 |
+
{status_specific_text}
|
54 |
+
|
55 |
+
(Webshop){invoice_source_type}: {invoice_source_reference}
|
56 |
+
(Acumulus) factuur: {acumulus_invoice_id}
|
57 |
+
Verzendstatus: {status} {status_message}.
|
58 |
+
|
59 |
+
Berichten:
|
60 |
+
{messages_text}
|
61 |
+
|
62 |
+
Meer informatie over eventueel vermeldde foutcodes kunt u vinden op
|
63 |
+
https://apidoc.sielsystems.nl/node/16.
|
64 |
+
LONGSTRING
|
65 |
+
,
|
66 |
+
'mail_html' => <<<LONGSTRING
|
67 |
+
{status_specific_html}
|
68 |
+
<table>
|
69 |
+
<tr><td>(Webshop){invoice_source_type}:</td><td>{invoice_source_reference}</td></tr>
|
70 |
+
<tr><td>(Acumulus) factuur:</td><td>{acumulus_invoice_id}</td></tr>
|
71 |
+
<tr><td>Verzendstatus:</td><td>{status} {status_message}.</td></tr>
|
72 |
+
</table>
|
73 |
+
<p>Berichten:<br>
|
74 |
+
{messages_html}</p>
|
75 |
+
<p>Meer informatie over eventueel vermeldde foutcodes kunt u vinden op
|
76 |
+
<a href="https://apidoc.sielsystems.nl/node/16">Acumulus - API documentation: exit and warning codes</a>.</p>
|
77 |
+
LONGSTRING
|
78 |
+
,
|
79 |
+
);
|
80 |
+
|
81 |
+
protected $en = array(
|
82 |
+
'mail_sender_name' => 'Your web store',
|
83 |
+
'message_no_invoice' => 'not created in Acumulus',
|
84 |
+
'mail_subject_errors' => 'Errors on sending an invoice to Acumulus',
|
85 |
+
'mail_subject_warnings' => 'Warnings on sending an invoice to Acumulus',
|
86 |
+
'mail_subject_debug' => 'Invoice sent to Acumulus',
|
87 |
+
'mail_text_errors' => <<<LONGSTRING
|
88 |
+
On sending an invoice to Acumulus, some errors occurred. The invoice has NOT
|
89 |
+
been created. You will have to manually create the invoice in Acumulus or
|
90 |
+
adapt it in your web shop and resend it to Acumulus.
|
91 |
+
LONGSTRING
|
92 |
+
,
|
93 |
+
'mail_html_errors' => <<<LONGSTRING
|
94 |
+
<p>On sending an invoice to Acumulus, some errors occurred. The invoice has NOT
|
95 |
+
been created. You will have to manually create the invoice in Acumulus or
|
96 |
+
adapt it in your web shop and resend it to Acumulus.
|
97 |
+
</p>
|
98 |
+
LONGSTRING
|
99 |
+
,
|
100 |
+
'mail_text_warnings' => <<<LONGSTRING
|
101 |
+
On sending an invoice to Acumulus, some warnings occurred, but the invoice
|
102 |
+
has been created. However, we advice you to check the invoice in Acumulus
|
103 |
+
for correctness.
|
104 |
+
LONGSTRING
|
105 |
+
,
|
106 |
+
'mail_html_warnings' => <<<LONGSTRING
|
107 |
+
<p>On sending an invoice to Acumulus, some warnings occurred, but the invoice
|
108 |
+
has been created. However, we advice you to check the invoice in Acumulus for
|
109 |
+
correctness.</p>
|
110 |
+
LONGSTRING
|
111 |
+
,
|
112 |
+
'mail_text_debug' => <<<LONGSTRING
|
113 |
+
The invoice below has successfully been sent to Acumulus. Normally you won't
|
114 |
+
receive a message saying so, but because you configured so, you do receive
|
115 |
+
this message now.
|
116 |
+
LONGSTRING
|
117 |
+
,
|
118 |
+
'mail_html_debug' => <<<LONGSTRING
|
119 |
+
<p>The invoice below has successfully been sent to Acumulus. Normally you won't
|
120 |
+
receive a message saying so, but because you configured so, you do receive
|
121 |
+
this message now.</p>
|
122 |
+
LONGSTRING
|
123 |
+
,
|
124 |
+
'mail_text' => <<<LONGSTRING
|
125 |
+
{status_specific_text}
|
126 |
+
|
127 |
+
(Webshop){invoice_source_type}: {invoice_source_reference}
|
128 |
+
(Acumulus) invoice: {acumulus_invoice_id}
|
129 |
+
Send status: {status} {status_message}.
|
130 |
+
|
131 |
+
Messages:
|
132 |
+
{messages_text}
|
133 |
+
|
134 |
+
At https://apidoc.sielsystems.nl/node/16 you can find more information
|
135 |
+
regarding any error codes mentioned above.
|
136 |
+
LONGSTRING
|
137 |
+
,
|
138 |
+
'mail_html' => <<<LONGSTRING
|
139 |
+
{status_specific_html}
|
140 |
+
<table>
|
141 |
+
<tr><td>(Webshop){invoice_source_type}:</td><td>{invoice_source_reference}</td></tr>
|
142 |
+
<tr><td>(Acumulus) invoice:</td><td>{acumulus_invoice_id}</td></tr>
|
143 |
+
<tr><td>Send status:</td><td>{status} {status_message}.</td></tr>
|
144 |
+
</table>
|
145 |
+
<p>Messages:<br>
|
146 |
+
{messages_html}</p>
|
147 |
+
<p>At <a href="https://apidoc.sielsystems.nl/node/16">Acumulus - API documentation: exit and warning codes</a>
|
148 |
+
you can find more information regarding any error codes mentioned above.</p>
|
149 |
+
LONGSTRING
|
150 |
+
,
|
151 |
+
|
152 |
+
);
|
153 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/Mailer.php
ADDED
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Helpers;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Web\ConfigInterface;
|
5 |
+
use Siel\Acumulus\Web\Service;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class Mailer allows to send mails. This class should be overridden per shop
|
9 |
+
* to use the shop provided mailing features.
|
10 |
+
*/
|
11 |
+
abstract class Mailer
|
12 |
+
{
|
13 |
+
/** @var \Siel\Acumulus\Helpers\TranslatorInterface */
|
14 |
+
protected $translator;
|
15 |
+
|
16 |
+
/** @var \Siel\Acumulus\Web\Service */
|
17 |
+
protected $service;
|
18 |
+
|
19 |
+
/**
|
20 |
+
* @param \Siel\Acumulus\Web\ConfigInterface $config
|
21 |
+
* @param TranslatorInterface $translator
|
22 |
+
* @param \Siel\Acumulus\Web\Service $service
|
23 |
+
*/
|
24 |
+
public function __construct(ConfigInterface $config, TranslatorInterface $translator, Service $service)
|
25 |
+
{
|
26 |
+
$this->config = $config;
|
27 |
+
$this->service = $service;
|
28 |
+
|
29 |
+
$this->translator = $translator;
|
30 |
+
$translations = new MailTranslations();
|
31 |
+
$this->translator->add($translations);
|
32 |
+
}
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Helper method to translate strings.
|
36 |
+
*
|
37 |
+
* @param string $key
|
38 |
+
* The key to get a translation for.
|
39 |
+
*
|
40 |
+
* @return string
|
41 |
+
* The translation for the given key or the key itself if no translation
|
42 |
+
* could be found.
|
43 |
+
*/
|
44 |
+
protected function t($key)
|
45 |
+
{
|
46 |
+
return $this->translator->get($key);
|
47 |
+
}
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Sends an email with the results of a sent invoice.
|
51 |
+
*
|
52 |
+
* The mail is sent to the shop administrator (emailonerror setting).
|
53 |
+
*
|
54 |
+
* @param array $result
|
55 |
+
* @param string[] $messages
|
56 |
+
* @param string $invoiceSourceType
|
57 |
+
* @param string $invoiceSourceReference
|
58 |
+
*
|
59 |
+
* @return bool
|
60 |
+
* Success.
|
61 |
+
*/
|
62 |
+
abstract public function sendInvoiceAddMailResult(array $result, array $messages, $invoiceSourceType, $invoiceSourceReference);
|
63 |
+
|
64 |
+
/**
|
65 |
+
* Returns the mail from name.
|
66 |
+
*
|
67 |
+
* @return string
|
68 |
+
*/
|
69 |
+
protected function getFromName()
|
70 |
+
{
|
71 |
+
return $this->t('mail_sender_name');
|
72 |
+
}
|
73 |
+
|
74 |
+
/**
|
75 |
+
* Returns the mail to address.
|
76 |
+
*
|
77 |
+
* @return string
|
78 |
+
*/
|
79 |
+
protected function getToAddress()
|
80 |
+
{
|
81 |
+
$credentials = $this->config->getCredentials();
|
82 |
+
if (isset($credentials['emailonerror'])) {
|
83 |
+
return $credentials['emailonerror'];
|
84 |
+
}
|
85 |
+
if (method_exists($this->config, 'getHostName')) {
|
86 |
+
return 'webshop@' . $this->config->getHostName();
|
87 |
+
}
|
88 |
+
return 'you@example.com';
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Returns the subject for the mail.
|
93 |
+
*
|
94 |
+
* The subject depends on the result status.
|
95 |
+
*
|
96 |
+
* @param array $result
|
97 |
+
*
|
98 |
+
* @return string
|
99 |
+
*/
|
100 |
+
protected function getSubject(array $result)
|
101 |
+
{
|
102 |
+
switch ($result['status']) {
|
103 |
+
case ConfigInterface::Status_Exception:
|
104 |
+
case ConfigInterface::Status_Errors:
|
105 |
+
return $this->t('mail_subject_errors');
|
106 |
+
case ConfigInterface::Status_Warnings:
|
107 |
+
return $this->t('mail_subject_warnings');
|
108 |
+
case ConfigInterface::Status_Success:
|
109 |
+
default:
|
110 |
+
return $this->t('mail_subject_debug');
|
111 |
+
}
|
112 |
+
}
|
113 |
+
|
114 |
+
/**
|
115 |
+
* Returns the subject for the mail.
|
116 |
+
*
|
117 |
+
* The subject depends on the result status.
|
118 |
+
*
|
119 |
+
* @param array $result
|
120 |
+
*
|
121 |
+
* @return string
|
122 |
+
*/
|
123 |
+
protected function getStatusSpecificBody(array $result)
|
124 |
+
{
|
125 |
+
$texts = array();
|
126 |
+
switch ($result['status']) {
|
127 |
+
case ConfigInterface::Status_Exception:
|
128 |
+
case ConfigInterface::Status_Errors:
|
129 |
+
$texts['text'] = $this->t('mail_text_errors');
|
130 |
+
$texts['html'] = $this->t('mail_html_errors');
|
131 |
+
break;
|
132 |
+
case ConfigInterface::Status_Warnings:
|
133 |
+
$texts['text'] = $this->t('mail_text_warnings');
|
134 |
+
$texts['html'] = $this->t('mail_html_warnings');
|
135 |
+
break;
|
136 |
+
case ConfigInterface::Status_Success:
|
137 |
+
default:
|
138 |
+
$texts['text'] = $this->t('mail_text_debug');
|
139 |
+
$texts['html'] = $this->t('mail_html_debug');
|
140 |
+
break;
|
141 |
+
}
|
142 |
+
return $texts;
|
143 |
+
}
|
144 |
+
|
145 |
+
/**
|
146 |
+
* Returns the mail body as text and as html.
|
147 |
+
*
|
148 |
+
* @param array $result
|
149 |
+
* @param string[] $messages
|
150 |
+
* @param string $invoiceSourceType
|
151 |
+
* @param string $invoiceSourceReference
|
152 |
+
*
|
153 |
+
* @return array
|
154 |
+
* An array with keys text and html.
|
155 |
+
*/
|
156 |
+
protected function getBody(array $result, array $messages, $invoiceSourceType, $invoiceSourceReference)
|
157 |
+
{
|
158 |
+
$bodyTexts = $this->getStatusSpecificBody($result);
|
159 |
+
$replacements = array(
|
160 |
+
'{invoice_source_type}' => $this->t($invoiceSourceType),
|
161 |
+
'{invoice_source_reference}' => $invoiceSourceReference,
|
162 |
+
'{acumulus_invoice_id}' => isset($result['invoice']['invoicenumber']) ? $result['invoice']['invoicenumber'] : $this->t('message_no_invoice'),
|
163 |
+
'{status}' => $result['status'],
|
164 |
+
'{status_message}' => $this->service->getStatusText($result['status']),
|
165 |
+
'{status_specific_text}' => $bodyTexts['text'],
|
166 |
+
'{status_specific_html}' => $bodyTexts['html'],
|
167 |
+
'{messages_text}' => $this->service->messagesToText($messages),
|
168 |
+
'{messages_html}' => $this->service->messagesToHtml($messages),
|
169 |
+
);
|
170 |
+
$text = $this->t('mail_text');
|
171 |
+
$text = strtr($text, $replacements);
|
172 |
+
$html = $this->t('mail_html');
|
173 |
+
$html = strtr($html, $replacements);
|
174 |
+
return array('text' => $text, 'html' => $html);
|
175 |
+
}
|
176 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/Number.php
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Helpers;
|
3 |
+
|
4 |
+
/**
|
5 |
+
* Class Number contains features to work with numbers, especially amounts.
|
6 |
+
*/
|
7 |
+
class Number
|
8 |
+
{
|
9 |
+
/**
|
10 |
+
* Returns the range within which the result of the division should fall given
|
11 |
+
* the precision range for the 2 numbers to divide.
|
12 |
+
*
|
13 |
+
* @param float $numerator
|
14 |
+
* @param float $denominator
|
15 |
+
* @param float $precisionNumerator
|
16 |
+
* @param float $precisionDenominator
|
17 |
+
*
|
18 |
+
* @return array
|
19 |
+
* Array of floats with keys min, max and calculated.
|
20 |
+
*/
|
21 |
+
static public function getDivisionRange($numerator, $denominator, $precisionNumerator = 0.01, $precisionDenominator = 0.01)
|
22 |
+
{
|
23 |
+
// The actual value can be half the precision lower or higher.
|
24 |
+
$numeratorHalfRange = $precisionNumerator / 2.0;
|
25 |
+
$denominatorHalfRange = $precisionDenominator / 2.0;
|
26 |
+
|
27 |
+
// The min values should be closer to 0 then the value.
|
28 |
+
// The max values should be further from 0 then the value.
|
29 |
+
if ($numerator < 0.0) {
|
30 |
+
$numeratorHalfRange = -$numeratorHalfRange;
|
31 |
+
}
|
32 |
+
$minNumerator = $numerator - $numeratorHalfRange;
|
33 |
+
$maxNumerator = $numerator + $numeratorHalfRange;
|
34 |
+
|
35 |
+
if ($denominator < 0.0) {
|
36 |
+
$denominatorHalfRange = -$denominatorHalfRange;
|
37 |
+
}
|
38 |
+
$minDenominator = $denominator - $denominatorHalfRange;
|
39 |
+
$maxDenominator = $denominator + $denominatorHalfRange;
|
40 |
+
|
41 |
+
// We get the min value of the division by dividing the minimum numerator by
|
42 |
+
// the maximum denominator and vice versa.
|
43 |
+
$min = $minNumerator / $maxDenominator;
|
44 |
+
$max = $maxNumerator / $minDenominator;
|
45 |
+
$calculated = $numerator / $denominator;
|
46 |
+
|
47 |
+
return array('min' => $min, 'calculated' => $calculated, 'max' => $max);
|
48 |
+
}
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Helper method to do a float comparison.
|
52 |
+
*
|
53 |
+
* @param float $f1
|
54 |
+
* @param float $f2
|
55 |
+
* @param float $maxDiff
|
56 |
+
*
|
57 |
+
* @return bool
|
58 |
+
* True if the the floats are "equal", i.e. do not differ more than the
|
59 |
+
* specified maximum difference.
|
60 |
+
*/
|
61 |
+
static public function floatsAreEqual($f1, $f2, $maxDiff = 0.005)
|
62 |
+
{
|
63 |
+
return abs((float) $f2 - (float) $f1) < $maxDiff;
|
64 |
+
}
|
65 |
+
|
66 |
+
/**
|
67 |
+
* indicates if a float is to be considered zero.
|
68 |
+
*
|
69 |
+
* This is a wrapper around floatsAreEqual() for the often used case where
|
70 |
+
* an amount is checked for being 0.0.
|
71 |
+
*
|
72 |
+
* @param $f1
|
73 |
+
* @param float $maxDiff
|
74 |
+
*
|
75 |
+
* @return bool
|
76 |
+
*/
|
77 |
+
static public function isZero($f1, $maxDiff = 0.001)
|
78 |
+
{
|
79 |
+
return static::floatsAreEqual($f1, 0.0, $maxDiff);
|
80 |
+
}
|
81 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/Requirements.php
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Helpers;
|
3 |
+
|
4 |
+
/**
|
5 |
+
* Defines and checks the requirements for this library. Used on install.
|
6 |
+
*/
|
7 |
+
class Requirements
|
8 |
+
{
|
9 |
+
/**
|
10 |
+
* Checks if the requirements for the environment are met.
|
11 |
+
*
|
12 |
+
* @return string[]
|
13 |
+
* An array with messages regarding missing requirements, empty if all
|
14 |
+
* requirements are met. The keys are the translations keys, the values are
|
15 |
+
* the English translations.
|
16 |
+
*/
|
17 |
+
static public function check()
|
18 |
+
{
|
19 |
+
$result = array();
|
20 |
+
|
21 |
+
// PHP 5.3 is a requirement as well because we use namespaces. But as the
|
22 |
+
// parser will already have failed fatally before we get here, it makes no
|
23 |
+
// sense to check here.
|
24 |
+
if (!extension_loaded('curl')) {
|
25 |
+
$result['message_error_req_curl'] = 'The CURL PHP extension needs to be activated on your server for this module to work.';
|
26 |
+
}
|
27 |
+
if (!extension_loaded('simplexml')) {
|
28 |
+
$result['message_error_req_xml'] = 'The SimpleXML extension needs to be activated on your server for the Acumulus module to be able to work with the XML format.';
|
29 |
+
}
|
30 |
+
if (!extension_loaded('dom')) {
|
31 |
+
$result['message_error_req_dom'] = 'The DOM PHP extension needs to be activated on your server for the Acumulus mpdule to work.';
|
32 |
+
}
|
33 |
+
|
34 |
+
return $result;
|
35 |
+
}
|
36 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/TranslationCollection.php
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Helpers;
|
3 |
+
|
4 |
+
/**
|
5 |
+
* Represents a collection of translated texts.
|
6 |
+
*
|
7 |
+
* Most web shops offer their own language handling, but to prevent redoing all
|
8 |
+
* the translations in the web shop specific way, a simple general way is
|
9 |
+
* defined.
|
10 |
+
*/
|
11 |
+
class TranslationCollection
|
12 |
+
{
|
13 |
+
/**
|
14 |
+
* Returns a set of translations for the given language, completed with Dutch
|
15 |
+
* translations if no translation for the given language for some key was
|
16 |
+
* defined.
|
17 |
+
*
|
18 |
+
* @param string $language
|
19 |
+
*
|
20 |
+
* @return array
|
21 |
+
* A keyed array with translations.
|
22 |
+
*/
|
23 |
+
public function get($language)
|
24 |
+
{
|
25 |
+
$result = array();
|
26 |
+
if (isset($this->nl)) {
|
27 |
+
$result = array_merge($result, $this->nl);
|
28 |
+
}
|
29 |
+
if (isset($this->{$language})) {
|
30 |
+
$result = array_merge($result, $this->{$language});
|
31 |
+
}
|
32 |
+
return $result;
|
33 |
+
}
|
34 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/Translator.php
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Helpers;
|
3 |
+
|
4 |
+
/**
|
5 |
+
* Translator provides a simple way of translating texts.
|
6 |
+
*/
|
7 |
+
class Translator implements TranslatorInterface
|
8 |
+
{
|
9 |
+
/** @var string */
|
10 |
+
protected $language;
|
11 |
+
|
12 |
+
/** @var array */
|
13 |
+
protected $translations;
|
14 |
+
|
15 |
+
/**
|
16 |
+
* @param string $language
|
17 |
+
* The 2 character language code.
|
18 |
+
*/
|
19 |
+
public function __construct($language)
|
20 |
+
{
|
21 |
+
$this->language = $language;
|
22 |
+
$this->translations = array();
|
23 |
+
}
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Instructs this translator to use the translations.
|
27 |
+
*
|
28 |
+
* @param \Siel\Acumulus\Helpers\TranslationCollection $translationCollection
|
29 |
+
* The translations to use
|
30 |
+
*/
|
31 |
+
public function add(TranslationCollection $translationCollection)
|
32 |
+
{
|
33 |
+
$this->translations = array_merge($this->translations, $translationCollection->get($this->getLanguage()));
|
34 |
+
}
|
35 |
+
|
36 |
+
/**
|
37 |
+
* @inheritdoc
|
38 |
+
*/
|
39 |
+
public function getLanguage()
|
40 |
+
{
|
41 |
+
return $this->language;
|
42 |
+
}
|
43 |
+
|
44 |
+
/**
|
45 |
+
* @inheritdoc
|
46 |
+
*/
|
47 |
+
public function get($key)
|
48 |
+
{
|
49 |
+
return (isset($this->translations[$key]) ? $this->translations[$key] : $key);
|
50 |
+
}
|
51 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Helpers/TranslatorInterface.php
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Helpers;
|
3 |
+
|
4 |
+
/**
|
5 |
+
* TranslatorInterface defines an interface to retrieve language dependent texts.
|
6 |
+
* Most web shops offer their own language handling, but to prevent redoing all
|
7 |
+
* the translations in the web shop specific way, a simple generic way is
|
8 |
+
* defined here and in the base translator class.
|
9 |
+
*/
|
10 |
+
interface TranslatorInterface
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* Returns the current (2 character) language (code).
|
14 |
+
*
|
15 |
+
* @return string
|
16 |
+
*/
|
17 |
+
public function getLanguage();
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Returns the string in the current language for the given key.
|
21 |
+
*
|
22 |
+
* @param string $key
|
23 |
+
* The key to look up.
|
24 |
+
*
|
25 |
+
* @return string
|
26 |
+
* Return in order of being available:
|
27 |
+
* - The string in the current language for the given key.
|
28 |
+
* - The string in dutch for the given key.
|
29 |
+
* - The key itself.
|
30 |
+
*/
|
31 |
+
public function get($key);
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Instructs this translator to use this collection of translations.
|
35 |
+
*
|
36 |
+
* @param \Siel\Acumulus\Helpers\TranslationCollection $translationCollection
|
37 |
+
* The translations to use.
|
38 |
+
*/
|
39 |
+
public function add(TranslationCollection $translationCollection);
|
40 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/Completor.php
ADDED
@@ -0,0 +1,786 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Invoice;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\Countries;
|
5 |
+
use Siel\Acumulus\Helpers\Number;
|
6 |
+
use Siel\Acumulus\Helpers\TranslatorInterface;
|
7 |
+
use Siel\Acumulus\Web\Service;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* The invoice completor class provides functionality to correct and complete
|
11 |
+
* invoices before sending them to Acumulus.
|
12 |
+
*
|
13 |
+
* This class:
|
14 |
+
* - Changes the customer into a fictitious client if set so in the config.
|
15 |
+
* - Validates the email address: the webservice does not allow an empty email
|
16 |
+
* address (but does allow a non provided email address).
|
17 |
+
* - Adds line totals. These are compared with what the shop advertises as order
|
18 |
+
* totals to see if an amount might be missing.
|
19 |
+
* - Adds the vat type based on inspection of the completed invoice.
|
20 |
+
* - Removes an empty shipping line if set to do so in the config.
|
21 |
+
*
|
22 |
+
* - Calls the invoice line completor.
|
23 |
+
* - Calls the strategy line completor.
|
24 |
+
*
|
25 |
+
* @package Siel\Acumulus
|
26 |
+
*/
|
27 |
+
class Completor
|
28 |
+
{
|
29 |
+
const VatRateSource_Calculated_Corrected = 'calculated-corrected';
|
30 |
+
const VatRateSource_Completor_Completed = 'completor-completed';
|
31 |
+
const VatRateSource_Strategy_Completed = 'strategy-completed';
|
32 |
+
|
33 |
+
static $CorrectVatRateSources = array(
|
34 |
+
Creator::VatRateSource_Exact,
|
35 |
+
Creator::VatRateSource_Exact0,
|
36 |
+
self::VatRateSource_Calculated_Corrected,
|
37 |
+
self::VatRateSource_Completor_Completed,
|
38 |
+
);
|
39 |
+
|
40 |
+
/** @var \Siel\Acumulus\Invoice\ConfigInterface */
|
41 |
+
protected $config;
|
42 |
+
|
43 |
+
/** @var \Siel\Acumulus\Helpers\TranslatorInterface */
|
44 |
+
protected $translator;
|
45 |
+
|
46 |
+
/** @var \Siel\Acumulus\Web\Service */
|
47 |
+
protected $service;
|
48 |
+
|
49 |
+
/** @var \Siel\Acumulus\Helpers\Countries */
|
50 |
+
protected $countries;
|
51 |
+
|
52 |
+
/** @var array */
|
53 |
+
protected $messages;
|
54 |
+
|
55 |
+
/** @var array */
|
56 |
+
protected $invoice;
|
57 |
+
|
58 |
+
/** @var Source */
|
59 |
+
protected $source;
|
60 |
+
|
61 |
+
/**
|
62 |
+
* @var int[]
|
63 |
+
* The list of possible vat types, initially filled with possible vat types
|
64 |
+
* based on client country, invoiceHasLineWithVat(), is_company(), and the
|
65 |
+
* digital services setting. But then reduced by VAT rates we find on the
|
66 |
+
* order lines.
|
67 |
+
*/
|
68 |
+
protected $possibleVatTypes;
|
69 |
+
|
70 |
+
/** @var array[] */
|
71 |
+
protected $possibleVatRates;
|
72 |
+
|
73 |
+
/** @var \Siel\Acumulus\Invoice\CompletorInvoiceLines */
|
74 |
+
protected $invoiceLineCompletor = null;
|
75 |
+
|
76 |
+
/** @var \Siel\Acumulus\Invoice\CompletorStrategyLines */
|
77 |
+
protected $strategyLineCompletor = null;
|
78 |
+
|
79 |
+
/** @var array */
|
80 |
+
protected $incompleteValues;
|
81 |
+
|
82 |
+
/**
|
83 |
+
* Constructor.
|
84 |
+
*
|
85 |
+
* @param \Siel\Acumulus\Invoice\ConfigInterface $config
|
86 |
+
* @param \Siel\Acumulus\Helpers\TranslatorInterface $translator
|
87 |
+
* @param \Siel\Acumulus\Web\Service $service
|
88 |
+
*/
|
89 |
+
public function __construct(ConfigInterface $config, TranslatorInterface $translator, Service $service)
|
90 |
+
{
|
91 |
+
$this->config = $config;
|
92 |
+
|
93 |
+
$this->translator = $translator;
|
94 |
+
$invoiceHelperTranslations = new Translations();
|
95 |
+
$this->translator->add($invoiceHelperTranslations);
|
96 |
+
|
97 |
+
$this->service = $service;
|
98 |
+
$this->countries = new Countries();
|
99 |
+
|
100 |
+
$this->invoiceLineCompletor = new CompletorInvoiceLines();
|
101 |
+
$this->strategyLineCompletor = new CompletorStrategyLines($translator);
|
102 |
+
}
|
103 |
+
|
104 |
+
/**
|
105 |
+
* Helper method to translate strings.
|
106 |
+
*
|
107 |
+
* @param string $key
|
108 |
+
* The key to get a translation for.
|
109 |
+
*
|
110 |
+
* @return string
|
111 |
+
* The translation for the given key or the key itself if no translation
|
112 |
+
* could be found.
|
113 |
+
*/
|
114 |
+
protected function t($key)
|
115 |
+
{
|
116 |
+
return $this->translator->get($key);
|
117 |
+
}
|
118 |
+
|
119 |
+
/**
|
120 |
+
* Completes the invoice with default settings that do not depend on shop
|
121 |
+
* specific data.
|
122 |
+
*
|
123 |
+
* @param array $invoice
|
124 |
+
* The invoice to complete.
|
125 |
+
* @param Source $source
|
126 |
+
* The source object for which this invoice was created.
|
127 |
+
* @param array $messages
|
128 |
+
* A response structure where errors and warnings can be added. Any local
|
129 |
+
* messages will be added to arrays under the keys 'errors' and 'warnings'.
|
130 |
+
*
|
131 |
+
* @return array
|
132 |
+
* The completed invoice.
|
133 |
+
*/
|
134 |
+
public function complete(array $invoice, Source $source, array &$messages)
|
135 |
+
{
|
136 |
+
$this->invoice = $invoice;
|
137 |
+
$this->source = $source;
|
138 |
+
$this->messages = &$messages;
|
139 |
+
|
140 |
+
$this->initPossibleVatTypes();
|
141 |
+
$this->initPossibleVatRates();
|
142 |
+
|
143 |
+
// Completes the invoice with default settings that do not depend on shop
|
144 |
+
// specific data.
|
145 |
+
$this->fictitiousClient();
|
146 |
+
$this->validateEmail();
|
147 |
+
|
148 |
+
// Complete lines as far as they can be completed om their own.
|
149 |
+
$this->invoice = $this->invoiceLineCompletor->complete($this->invoice, $this->possibleVatTypes, $this->possibleVatRates);
|
150 |
+
|
151 |
+
// Check if we are missing an amount and, if so, add a line for it.
|
152 |
+
$this->completeLineTotals();
|
153 |
+
$areTotalsEqual = $this->areTotalsEqual();
|
154 |
+
if (!$areTotalsEqual) {
|
155 |
+
$this->addMissingAmountLine($areTotalsEqual);
|
156 |
+
}
|
157 |
+
|
158 |
+
// Complete strategy lines: those lines that have to be completed based on
|
159 |
+
// the whole invoice.
|
160 |
+
$this->invoice = $this->strategyLineCompletor->complete($this->invoice, $this->source, $this->possibleVatTypes, $this->possibleVatRates);
|
161 |
+
|
162 |
+
// Fill in the VAT type, adding a warning if possible vat types are possible.
|
163 |
+
$this->completeVatType();
|
164 |
+
|
165 |
+
// Completes the invoice with settings or behaviour that might depend on the
|
166 |
+
// fact that the invoice lines have been completed.
|
167 |
+
$this->removeEmptyShipping();
|
168 |
+
|
169 |
+
return $this->invoice;
|
170 |
+
}
|
171 |
+
|
172 |
+
/**
|
173 |
+
* Initializes the list of possible vat types for this invoice.
|
174 |
+
*
|
175 |
+
* The list of possible vat types depends on:
|
176 |
+
* - whether there are lines with vat or if all lines appear vat free.
|
177 |
+
* - the country of the client.
|
178 |
+
* - optionally, the date of the invoice.
|
179 |
+
*/
|
180 |
+
protected function initPossibleVatTypes()
|
181 |
+
{
|
182 |
+
$possibleVatTypes = array();
|
183 |
+
$shopSettings = $this->config->getShopSettings();
|
184 |
+
$digitalServices = $shopSettings['digitalServices'];
|
185 |
+
$vatFreeProducts = $shopSettings['vatFreeProducts'];
|
186 |
+
|
187 |
+
if (!empty($this->invoice['customer']['invoice']['vattype'])) {
|
188 |
+
// If shop specific code or an event handler has already set the vat type,
|
189 |
+
// we obey so.
|
190 |
+
$possibleVatTypes[] = $this->invoice['customer']['invoice']['vattype'];
|
191 |
+
} else {
|
192 |
+
if (!$this->invoiceHasLineWithVat()) {
|
193 |
+
// No VAT at all: National/EU reversed vat, only vat free products, or
|
194 |
+
// no vat (rest of world).
|
195 |
+
if ($this->isNl()) {
|
196 |
+
// Can it be VAT free products (e.g. education)? Digital services are
|
197 |
+
// never VAT free, nor are there any if set so.
|
198 |
+
if ($digitalServices !== ConfigInterface::DigitalServices_Only && $vatFreeProducts !== ConfigInterface::VatFreeProducts_No) {
|
199 |
+
$possibleVatTypes[] = ConfigInterface::VatType_National;
|
200 |
+
}
|
201 |
+
// National reversed VAT: not really supported, but it is possible.
|
202 |
+
if ($this->isCompany()) {
|
203 |
+
$possibleVatTypes[] = ConfigInterface::VatType_NationalReversed;
|
204 |
+
}
|
205 |
+
} else if ($this->isEu()) {
|
206 |
+
// U reversed VAT.
|
207 |
+
if ($this->isCompany()) {
|
208 |
+
$possibleVatTypes[] = ConfigInterface::VatType_EuReversed;
|
209 |
+
}
|
210 |
+
// Can it be VAT free products (e.g. education)? Digital services are
|
211 |
+
// never VAT free, nor are there any if set so.
|
212 |
+
if ($digitalServices !== ConfigInterface::DigitalServices_Only && $vatFreeProducts !== ConfigInterface::VatFreeProducts_No) {
|
213 |
+
$possibleVatTypes[] = ConfigInterface::VatType_National;
|
214 |
+
}
|
215 |
+
} else if ($this->isOutsideEu()) {
|
216 |
+
$possibleVatTypes[] = ConfigInterface::VatType_RestOfWorld;
|
217 |
+
}
|
218 |
+
|
219 |
+
if (empty($possibleVatTypes)) {
|
220 |
+
// Warning + fall back.
|
221 |
+
$this->messages['warnings'][] = array(
|
222 |
+
'code' => '',
|
223 |
+
'codetag' => '',
|
224 |
+
'message' => $this->t('message_warning_no_vat'),
|
225 |
+
);
|
226 |
+
$possibleVatTypes[] = ConfigInterface::VatType_National;
|
227 |
+
$possibleVatTypes[] = ConfigInterface::VatType_EuReversed;
|
228 |
+
$possibleVatTypes[] = ConfigInterface::VatType_NationalReversed;
|
229 |
+
$this->invoice['customer']['invoice']['concept'] = ConfigInterface::Concept_Yes;
|
230 |
+
}
|
231 |
+
} else {
|
232 |
+
// NL or EU Foreign vat.
|
233 |
+
if ($digitalServices === ConfigInterface::DigitalServices_No) {
|
234 |
+
// No electronic services are sold: can only be dutch VAT.
|
235 |
+
$possibleVatTypes[] = ConfigInterface::VatType_National;
|
236 |
+
} else {
|
237 |
+
if ($this->isEu() && $this->getInvoiceDate() >= '2015-01-01') {
|
238 |
+
if ($digitalServices !== ConfigInterface::DigitalServices_Only) {
|
239 |
+
// Also normal goods are sold, so dutch VAT still possible.
|
240 |
+
$possibleVatTypes[] = ConfigInterface::VatType_National;
|
241 |
+
}
|
242 |
+
// As of 2015, electronic services should be taxed with the rates of
|
243 |
+
// the clients' country. And they might be sold in the shop.
|
244 |
+
$possibleVatTypes[] = ConfigInterface::VatType_ForeignVat;
|
245 |
+
} else {
|
246 |
+
// Not EU or before 2015-01-01: special regulations for electronic
|
247 |
+
// services were not yet active: dutch VAT only.
|
248 |
+
$possibleVatTypes[] = ConfigInterface::VatType_National;
|
249 |
+
}
|
250 |
+
}
|
251 |
+
}
|
252 |
+
}
|
253 |
+
$this->possibleVatTypes = $possibleVatTypes;
|
254 |
+
}
|
255 |
+
|
256 |
+
/**
|
257 |
+
* Initializes the list of possible vat rates.
|
258 |
+
*
|
259 |
+
* The possible vat rats depend on:
|
260 |
+
* - the possible vat types.
|
261 |
+
* - optionally, the date of the invoice.
|
262 |
+
* - optionally, the country of the client.
|
263 |
+
*
|
264 |
+
* @return array
|
265 |
+
* Array with possible vat rates. a vat rate being an array with keys
|
266 |
+
* vatrate and vattype. This to be able to retrieve to which vat type a vat
|
267 |
+
* rate belongs and to allow for the same vat rate to be valid for multiple
|
268 |
+
* vat types.
|
269 |
+
*/
|
270 |
+
protected function initPossibleVatRates()
|
271 |
+
{
|
272 |
+
$possibleVatRates = array();
|
273 |
+
foreach ($this->possibleVatTypes as $vatType) {
|
274 |
+
switch ($vatType) {
|
275 |
+
case ConfigInterface::VatType_National:
|
276 |
+
case ConfigInterface::VatType_MarginScheme:
|
277 |
+
default:
|
278 |
+
$vatTypeVatRates = $this->getVatRates('nl');
|
279 |
+
break;
|
280 |
+
case ConfigInterface::VatType_NationalReversed:
|
281 |
+
case ConfigInterface::VatType_EuReversed:
|
282 |
+
$vatTypeVatRates = array(0);
|
283 |
+
break;
|
284 |
+
case ConfigInterface::VatType_RestOfWorld:
|
285 |
+
$vatTypeVatRates = array(-1);
|
286 |
+
break;
|
287 |
+
case ConfigInterface::VatType_ForeignVat:
|
288 |
+
$vatTypeVatRates = $this->getVatRates($this->invoice['customer']['countrycode']);
|
289 |
+
break;
|
290 |
+
}
|
291 |
+
$vatTypeVatRates = array_map(function ($vatRate) use ($vatType) {
|
292 |
+
return array('vatrate' => $vatRate, 'vattype' => $vatType);
|
293 |
+
}, $vatTypeVatRates);
|
294 |
+
$possibleVatRates = array_merge($possibleVatRates, $vatTypeVatRates);
|
295 |
+
}
|
296 |
+
$this->possibleVatRates = $possibleVatRates;
|
297 |
+
}
|
298 |
+
|
299 |
+
/**
|
300 |
+
* Anonymize customer if set so. We don't do this for business clients, only
|
301 |
+
* consumers.
|
302 |
+
*/
|
303 |
+
protected function fictitiousClient()
|
304 |
+
{
|
305 |
+
$customerSettings = $this->config->getCustomerSettings();
|
306 |
+
if (!$customerSettings['sendCustomer'] && empty($this->invoice['customer']['companyname1']) && empty($this->invoice['customer']['vatnumber'])) {
|
307 |
+
unset($this->invoice['customer']['type']);
|
308 |
+
unset($this->invoice['customer']['companyname1']);
|
309 |
+
unset($this->invoice['customer']['companyname2']);
|
310 |
+
unset($this->invoice['customer']['fullname']);
|
311 |
+
unset($this->invoice['customer']['salutation']);
|
312 |
+
unset($this->invoice['customer']['address1']);
|
313 |
+
unset($this->invoice['customer']['address2']);
|
314 |
+
unset($this->invoice['customer']['postalcode']);
|
315 |
+
unset($this->invoice['customer']['city']);
|
316 |
+
unset($this->invoice['customer']['countrycode']);
|
317 |
+
unset($this->invoice['customer']['vatnumber']);
|
318 |
+
unset($this->invoice['customer']['telephone']);
|
319 |
+
unset($this->invoice['customer']['fax']);
|
320 |
+
unset($this->invoice['customer']['bankaccountnumber']);
|
321 |
+
unset($this->invoice['customer']['mark']);
|
322 |
+
$this->invoice['customer']['email'] = $customerSettings['genericCustomerEmail'];
|
323 |
+
$this->invoice['customer']['overwriteifexists'] = 0;
|
324 |
+
}
|
325 |
+
}
|
326 |
+
|
327 |
+
/**
|
328 |
+
* Validates the email address of the invoice.
|
329 |
+
*
|
330 |
+
* - The email address may not be empty but may be left out though.
|
331 |
+
* - Multiple, comma separated, email addresses are not allowed.
|
332 |
+
* - Display names (My Name <my.name@example.com>) are not allowed.
|
333 |
+
*/
|
334 |
+
protected function validateEmail()
|
335 |
+
{
|
336 |
+
// Check email address.
|
337 |
+
if (empty($this->invoice['customer']['email'])) {
|
338 |
+
unset($this->invoice['customer']['email']);
|
339 |
+
} else {
|
340 |
+
$email = $this->invoice['customer']['email'];
|
341 |
+
$at = strpos($email, '@');
|
342 |
+
// Comma (,) used as separator?
|
343 |
+
$comma = strpos($email, ',', $at);
|
344 |
+
if ($at < $comma) {
|
345 |
+
$email = trim(substr($email, 0, $comma));
|
346 |
+
}
|
347 |
+
// Semicolon (;) used as separator?
|
348 |
+
$semicolon = strpos($email, ';', $at);
|
349 |
+
if ($at < $semicolon) {
|
350 |
+
$email = trim(substr($email, 0, $semicolon));
|
351 |
+
}
|
352 |
+
|
353 |
+
// Display name used in single remaining address?
|
354 |
+
if (preg_match('/^(.+?)<([^>]+)>$/', $email, $matches)) {
|
355 |
+
$email = trim($matches[2]);
|
356 |
+
}
|
357 |
+
$this->invoice['customer']['email'] = $email;
|
358 |
+
}
|
359 |
+
}
|
360 |
+
|
361 |
+
/**
|
362 |
+
* Calculates the total amount and vat amount for the invoice lines and adds
|
363 |
+
* these to the fields meta-lines-amount and meta-lines-vatamount.
|
364 |
+
*/
|
365 |
+
protected function completeLineTotals()
|
366 |
+
{
|
367 |
+
$linesAmount = 0.0;
|
368 |
+
$linesAmountInc = 0.0;
|
369 |
+
$linesVatAmount = 0.0;
|
370 |
+
$this->incompleteValues = array();
|
371 |
+
|
372 |
+
$invoiceLines = $this->invoice['customer']['invoice']['line'];
|
373 |
+
foreach ($invoiceLines as $line) {
|
374 |
+
if (isset($line['meta-line-price'])) {
|
375 |
+
$linesAmount += $line['meta-line-price'];
|
376 |
+
} else if (isset($line['unitprice'])) {
|
377 |
+
$linesAmount += $line['quantity'] * $line['unitprice'];
|
378 |
+
} else {
|
379 |
+
$this->incompleteValues['meta-lines-amount'] = 'meta-lines-amount';
|
380 |
+
}
|
381 |
+
|
382 |
+
if (isset($line['meta-line-priceinc'])) {
|
383 |
+
$linesAmountInc += $line['meta-line-priceinc'];
|
384 |
+
} else if (isset($line['unitpriceinc'])) {
|
385 |
+
$linesAmountInc += $line['quantity'] * $line['unitpriceinc'];
|
386 |
+
} else {
|
387 |
+
$this->incompleteValues['meta-lines-amountinc'] = 'meta-lines-amountinc';
|
388 |
+
}
|
389 |
+
|
390 |
+
if (isset($line['meta-line-vatamount'])) {
|
391 |
+
$linesVatAmount += $line['meta-line-vatamount'];
|
392 |
+
} else if (isset($line['vatamount'])) {
|
393 |
+
$linesVatAmount += $line['quantity'] * $line['vatamount'];
|
394 |
+
} else {
|
395 |
+
$this->incompleteValues['meta-lines-vatamount'] = 'meta-lines-vatamount';
|
396 |
+
}
|
397 |
+
}
|
398 |
+
|
399 |
+
$this->invoice['customer']['invoice']['meta-lines-amount'] = $linesAmount;
|
400 |
+
$this->invoice['customer']['invoice']['meta-lines-amountinc'] = $linesAmountInc;
|
401 |
+
$this->invoice['customer']['invoice']['meta-lines-vatamount'] = $linesVatAmount;
|
402 |
+
if (!empty($this->incompleteValues)) {
|
403 |
+
sort($this->incompleteValues);
|
404 |
+
$this->invoice['customer']['invoice']['meta-lines-incomplete'] = implode(',', $this->incompleteValues);
|
405 |
+
}
|
406 |
+
}
|
407 |
+
|
408 |
+
/**
|
409 |
+
* Compares the invoice totals metadata with the line totals metadata.
|
410 |
+
*
|
411 |
+
* If any of the 3 values are equal we do consider the totals to be equal
|
412 |
+
* (except for a 0 VAT amount (for reversed VAT invoices)). This because in
|
413 |
+
* many cases 1 or 2 of the 3 values are either incomplete or incorrect.
|
414 |
+
*
|
415 |
+
* @todo: if 1 is correct but other not, that would be an indication of an error: warn?
|
416 |
+
* @return bool|null
|
417 |
+
* True if the totals are equal, false if not equal, null if undecided (all
|
418 |
+
* 3 values are incomplete).
|
419 |
+
*/
|
420 |
+
protected function areTotalsEqual()
|
421 |
+
{
|
422 |
+
$invoice = $this->invoice['customer']['invoice'];
|
423 |
+
if (!in_array('meta-lines-amount', $this->incompleteValues) && Number::floatsAreEqual($invoice['meta-invoice-amount'], $invoice['meta-lines-amount'], 0.05)) {
|
424 |
+
return true;
|
425 |
+
}
|
426 |
+
if (!in_array('meta-lines-amountinc', $this->incompleteValues) && Number::floatsAreEqual($invoice['meta-invoice-amountinc'], $invoice['meta-lines-amountinc'], 0.05)) {
|
427 |
+
return true;
|
428 |
+
}
|
429 |
+
if (!in_array('meta-lines-vatamount', $this->incompleteValues)
|
430 |
+
&& Number::floatsAreEqual($invoice['meta-invoice-vatamount'], $invoice['meta-lines-vatamount'], 0.05)
|
431 |
+
&& !Number::isZero($invoice['meta-invoice-vatamount'])
|
432 |
+
) {
|
433 |
+
return true;
|
434 |
+
}
|
435 |
+
return count($this->incompleteValues) === 3 ? null : false;
|
436 |
+
}
|
437 |
+
|
438 |
+
/**
|
439 |
+
* Adds an invoice line if the total amount (meta-invoice-amount) is not
|
440 |
+
* matching the total amount of the lines.
|
441 |
+
*
|
442 |
+
* This can happen if:
|
443 |
+
* - we missed a fee that is stored in custom fields
|
444 |
+
* - a manual adjustment
|
445 |
+
* - an error in the logic or data as provided by the webshop.
|
446 |
+
*
|
447 |
+
* However, we can only add this line if we have at least 2 complete values,
|
448 |
+
* that is, there are no strategy lines,
|
449 |
+
*
|
450 |
+
* @param bool|null $areTotalsEqualResult
|
451 |
+
* Result of areTotalsEqual() (false or null)
|
452 |
+
*/
|
453 |
+
protected function addMissingAmountLine($areTotalsEqualResult)
|
454 |
+
{
|
455 |
+
$invoice = &$this->invoice['customer']['invoice'];
|
456 |
+
if (!in_array('meta-lines-amount', $this->incompleteValues)) {
|
457 |
+
$missingAmount = $invoice['meta-invoice-amount'] - $invoice['meta-lines-amount'];
|
458 |
+
}
|
459 |
+
if (!in_array('meta-lines-amountinc', $this->incompleteValues)) {
|
460 |
+
$missingAmountInc = $invoice['meta-invoice-amountinc'] - $invoice['meta-lines-amountinc'];
|
461 |
+
}
|
462 |
+
if (!in_array('meta-lines-vatamount', $this->incompleteValues)) {
|
463 |
+
$missingVatAmount = $invoice['meta-invoice-vatamount'] - $invoice['meta-lines-vatamount'];
|
464 |
+
}
|
465 |
+
|
466 |
+
if (count($this->incompleteValues) <= 1) {
|
467 |
+
if (!isset($missingAmount)) {
|
468 |
+
/** @noinspection PhpUndefinedVariableInspection */
|
469 |
+
$missingAmount = $missingAmountInc - $missingVatAmount;
|
470 |
+
}
|
471 |
+
if (!isset($missingVatAmount)) {
|
472 |
+
/** @noinspection PhpUndefinedVariableInspection */
|
473 |
+
$missingVatAmount = $missingAmountInc - $missingAmount;
|
474 |
+
}
|
475 |
+
|
476 |
+
$settings = $this->config->getInvoiceSettings();
|
477 |
+
if ($settings['addMissingAmountLine']) {
|
478 |
+
if ($this->source->getType() === Source::CreditNote) {
|
479 |
+
$product = $this->t('refund_adjustment');
|
480 |
+
} else if ($missingAmount < 0.0) {
|
481 |
+
$product = $this->t('discount_adjustment');
|
482 |
+
} else {
|
483 |
+
$product = $this->t('fee_adjustment');
|
484 |
+
}
|
485 |
+
$countLines = count($invoice['line']);
|
486 |
+
$line = array(
|
487 |
+
'product' => $product,
|
488 |
+
'quantity' => 1,
|
489 |
+
'unitprice' => $missingAmount,
|
490 |
+
'vatamount' => $missingVatAmount,
|
491 |
+
) + Creator::getVatRangeTags($missingVatAmount, $missingAmount, $countLines * 0.02, $countLines * 0.02)
|
492 |
+
+ array(
|
493 |
+
'meta-line-type' => Creator::LineType_Corrector,
|
494 |
+
);
|
495 |
+
// Correct and add this line.
|
496 |
+
if ($line['meta-vatrate-source'] === Creator::VatRateSource_Calculated) {
|
497 |
+
$line = $this->invoiceLineCompletor->correctVatRateByRange($line);
|
498 |
+
}
|
499 |
+
$invoice['line'][] = $line;
|
500 |
+
} else {
|
501 |
+
// Add some diagnostic info to the message sent.
|
502 |
+
// @todo: this could/should be turned into a warning (after some testing).
|
503 |
+
/** @noinspection PhpUndefinedVariableInspection */
|
504 |
+
$invoice['meta-missing-amount'] = "Ex: $missingAmount, Inc: $missingAmountInc, VAT: $missingVatAmount";
|
505 |
+
}
|
506 |
+
} else {
|
507 |
+
if ($areTotalsEqualResult === false) {
|
508 |
+
// Due to lack of information, we cannot add a missing line, even though
|
509 |
+
// we know we are missing something ($areTotalsEqualResult is false, not
|
510 |
+
// null). Add some diagnostic info to the message sent.
|
511 |
+
// @todo: this could/should be turned into a warning (after some testing).
|
512 |
+
$invoice['meta-missing-amount'] = array();
|
513 |
+
if (isset($missingAmount)) {
|
514 |
+
$invoice['meta-missing-amount'][] = "Ex: $missingAmount";
|
515 |
+
}
|
516 |
+
if (isset($missingAmountInc)) {
|
517 |
+
$invoice['meta-missing-amount'][] = "Inc: $missingAmountInc";
|
518 |
+
}
|
519 |
+
if (isset($missingVatAmount)) {
|
520 |
+
$invoice['meta-missing-amount'][] = "VAT: $missingVatAmount";
|
521 |
+
}
|
522 |
+
$invoice['meta-missing-amount'] = implode(', ', $invoice['meta-missing-amount']);
|
523 |
+
}
|
524 |
+
}
|
525 |
+
}
|
526 |
+
|
527 |
+
/**
|
528 |
+
* Fills the vattype field of the invoice.
|
529 |
+
*
|
530 |
+
* This method (and class) is aware of:
|
531 |
+
* - The setting digitalServices.
|
532 |
+
* - The country of the client.
|
533 |
+
* - Whether the client is a company.
|
534 |
+
* - The actual VAT rates on the day of the order.
|
535 |
+
*
|
536 |
+
* So to start with, any list of (possible) vat types is based on the above.
|
537 |
+
* Furthermore this method is aware of:
|
538 |
+
* - The fact that orders do not have to be split over different vat types,
|
539 |
+
* but that invoices should be split if both national and foreign VAT rates
|
540 |
+
* appear on the order.
|
541 |
+
* - The fact that the vat type may be indeterminable if EU countries have VAT
|
542 |
+
* rates in common with NL and the settings indicate that this shop sells
|
543 |
+
* products in both vat type categories.
|
544 |
+
*
|
545 |
+
* If multiple vat types are possible, the invoice is sent as concept, so that
|
546 |
+
* it may be edited in Acumulus.
|
547 |
+
*/
|
548 |
+
protected function completeVatType()
|
549 |
+
{
|
550 |
+
// If shop specific code or an event handler has already set the vat type,
|
551 |
+
// we don't change it.
|
552 |
+
if (empty($this->invoice['customer']['invoice']['vattype'])) {
|
553 |
+
|
554 |
+
$possibleVatTypes = $this->getPossibleVatTypesByCorrectVatRates();
|
555 |
+
if (empty($possibleVatTypes)) {
|
556 |
+
$this->messages['warnings'][] = array(
|
557 |
+
'code' => '',
|
558 |
+
'codetag' => '',
|
559 |
+
'message' => $this->t('message_warning_no_vattype'),
|
560 |
+
);
|
561 |
+
$this->invoice['customer']['invoice']['vattype'] = reset($this->possibleVatTypes);
|
562 |
+
$this->invoice['customer']['invoice']['meta-vattypes-possible'] = implode(',', $this->possibleVatTypes);
|
563 |
+
$this->invoice['customer']['invoice']['concept'] = ConfigInterface::Concept_Yes;
|
564 |
+
} else if (count($possibleVatTypes) === 1) {
|
565 |
+
// Pick the first and only vat type.
|
566 |
+
$this->invoice['customer']['invoice']['vattype'] = reset($possibleVatTypes);
|
567 |
+
} else {
|
568 |
+
// Get the intersection of possible vat types per line.
|
569 |
+
$vatTypesOnAllLines = $this->getVatTypesAppearingOnAllLines();
|
570 |
+
if (empty($vatTypesOnAllLines)) {
|
571 |
+
// We must split.
|
572 |
+
$message = 'message_warning_multiple_vattype_must_split';
|
573 |
+
// Pick the first and hopefully correct vat type, but ...
|
574 |
+
$this->invoice['customer']['invoice']['vattype'] = reset($possibleVatTypes);
|
575 |
+
} else {
|
576 |
+
// We may have to split.
|
577 |
+
$message = 'message_warning_multiple_vattype_may_split';
|
578 |
+
// Pick the first vat type that appears on all lines, but ...
|
579 |
+
$this->invoice['customer']['invoice']['vattype'] = reset($vatTypesOnAllLines);
|
580 |
+
}
|
581 |
+
|
582 |
+
// But add a message and meta info.
|
583 |
+
$this->messages['warnings'][] = array(
|
584 |
+
'code' => '',
|
585 |
+
'codetag' => '',
|
586 |
+
'message' => $this->t($message),
|
587 |
+
);
|
588 |
+
$this->invoice['customer']['invoice']['concept'] = ConfigInterface::Concept_Yes;
|
589 |
+
$this->invoice['customer']['invoice']['meta-vattypes-possible'] = implode(',', $possibleVatTypes);
|
590 |
+
}
|
591 |
+
}
|
592 |
+
}
|
593 |
+
|
594 |
+
/**
|
595 |
+
* Returns a list of possible vat types based on possible vat types for all
|
596 |
+
* lines with a "correct" vat rate.
|
597 |
+
*
|
598 |
+
* If that results in 1 vat type, that will be the vat type for the invoice,
|
599 |
+
* otherwise a warning will be issued.
|
600 |
+
*
|
601 |
+
* This method may return multiple vat types because:
|
602 |
+
* - If vat types share equal vat rates we cannot make a choice (e.g. NL and
|
603 |
+
* BE high VAT rates are equal).
|
604 |
+
* - If the invoice ought to be split into multiple invoices because multiple
|
605 |
+
* vat regimes apply (digital services and normal goods) (e.g. both the FR
|
606 |
+
* 20% high rate and the NL 21% high rate appear on the invoice).
|
607 |
+
*
|
608 |
+
* @return int[]
|
609 |
+
* List of possible vat type for this invoice (keyed by the vat types).
|
610 |
+
*/
|
611 |
+
protected function getPossibleVatTypesByCorrectVatRates()
|
612 |
+
{
|
613 |
+
// We only want to process correct vat rates.
|
614 |
+
// Define vat types that do know a zero rate.
|
615 |
+
$zeroRateVatTypes = array(
|
616 |
+
ConfigInterface::VatType_National,
|
617 |
+
ConfigInterface::VatType_NationalReversed,
|
618 |
+
ConfigInterface::VatType_EuReversed,
|
619 |
+
ConfigInterface::VatType_RestOfWorld,
|
620 |
+
);
|
621 |
+
|
622 |
+
// We keep track of vat types found per appearing vat rate.
|
623 |
+
// The intersection of these sets should result in the new, hopefully
|
624 |
+
// smaller list, of possible vat types.
|
625 |
+
$invoiceVatTypes = array();
|
626 |
+
foreach ($this->invoice['customer']['invoice']['line'] as &$line) {
|
627 |
+
if (in_array($line['meta-vatrate-source'], static::$CorrectVatRateSources)) {
|
628 |
+
// We ignore "0" vat rates (0 and -1).
|
629 |
+
if ($line['vatrate'] > 0) {
|
630 |
+
$lineVatTypes = array();
|
631 |
+
foreach ($this->possibleVatRates as $vatRateInfo) {
|
632 |
+
if ($vatRateInfo['vatrate'] == $line['vatrate']) {
|
633 |
+
// Ensure that the values remain unique by keying them.
|
634 |
+
$invoiceVatTypes[$vatRateInfo['vattype']] = $vatRateInfo['vattype'];
|
635 |
+
$lineVatTypes[$vatRateInfo['vattype']] = $vatRateInfo['vattype'];
|
636 |
+
}
|
637 |
+
}
|
638 |
+
} else {
|
639 |
+
// Reduce the possible vat types to those that do know a zero rate.
|
640 |
+
$lineVatTypes = array_intersect($this->possibleVatTypes, $zeroRateVatTypes);
|
641 |
+
foreach ($lineVatTypes AS $lineVatType) {
|
642 |
+
$invoiceVatTypes[$lineVatType] = $lineVatType;
|
643 |
+
}
|
644 |
+
}
|
645 |
+
$line['meta-vattypes-possible'] = implode(',', $lineVatTypes);
|
646 |
+
}
|
647 |
+
}
|
648 |
+
|
649 |
+
return $invoiceVatTypes;
|
650 |
+
}
|
651 |
+
|
652 |
+
|
653 |
+
/**
|
654 |
+
* Returns a list of vat types that are possible for all lines of the invoice.
|
655 |
+
*
|
656 |
+
* @return int[]
|
657 |
+
*/
|
658 |
+
protected function getVatTypesAppearingOnAllLines()
|
659 |
+
{
|
660 |
+
$result = null;
|
661 |
+
foreach ($this->invoice['customer']['invoice']['line'] as $line) {
|
662 |
+
$lineVatTypes = explode(',', $line['meta-vattypes-possible']);
|
663 |
+
if ($result === null) {
|
664 |
+
// 1st line.
|
665 |
+
$result = $lineVatTypes;
|
666 |
+
} else {
|
667 |
+
$result = array_intersect($result, $lineVatTypes);
|
668 |
+
}
|
669 |
+
}
|
670 |
+
return $result;
|
671 |
+
}
|
672 |
+
|
673 |
+
/**
|
674 |
+
* Removes an empty shipping line (if so configured).
|
675 |
+
*/
|
676 |
+
protected function removeEmptyShipping()
|
677 |
+
{
|
678 |
+
$invoiceSettings = $this->config->getInvoiceSettings();
|
679 |
+
if ($invoiceSettings['removeEmptyShipping']) {
|
680 |
+
$this->invoice['customer']['invoice']['line'] = array_filter($this->invoice['customer']['invoice']['line'],
|
681 |
+
function ($line) {
|
682 |
+
return $line['meta-line-type'] !== Creator::LineType_Shipping || !Number::isZero($line['unitprice']);
|
683 |
+
});
|
684 |
+
}
|
685 |
+
}
|
686 |
+
|
687 |
+
/**
|
688 |
+
* Returns whether the invoice has at least 1 line with a non-0 vat rate.
|
689 |
+
*
|
690 |
+
* 0 (VAT free/reversed VAT) and -1 (no VAT) are valid 0-vat rates.
|
691 |
+
* As vatrate may be null, the vatamount value is also checked.
|
692 |
+
*
|
693 |
+
* @return bool
|
694 |
+
*/
|
695 |
+
protected function invoiceHasLineWithVat()
|
696 |
+
{
|
697 |
+
$isLineWithVat = false;
|
698 |
+
foreach ($this->invoice['customer']['invoice']['line'] as $line) {
|
699 |
+
if (!empty($line['vatrate'])) {
|
700 |
+
if (!Number::isZero($line['vatrate']) && !Number::floatsAreEqual($line['vatrate'], -1.0)) {
|
701 |
+
$isLineWithVat = true;
|
702 |
+
break;
|
703 |
+
}
|
704 |
+
} else if (!empty($line['vatamount']) && !Number::isZero($line['vatamount'])) {
|
705 |
+
$isLineWithVat = true;
|
706 |
+
break;
|
707 |
+
}
|
708 |
+
}
|
709 |
+
return $isLineWithVat;
|
710 |
+
}
|
711 |
+
|
712 |
+
/**
|
713 |
+
* Helper method to get vat info for the current invoice from the Acumulus API.
|
714 |
+
*
|
715 |
+
* The vat rates as they were at the invoice date are retrieved.
|
716 |
+
*
|
717 |
+
* @param string $countryCode
|
718 |
+
* The country to fetch the vat rates for.
|
719 |
+
*
|
720 |
+
* @return float[]
|
721 |
+
* Actual type will be string[].
|
722 |
+
*
|
723 |
+
* @see \Siel\Acumulus\Web\Service::getVatInfo().
|
724 |
+
*/
|
725 |
+
protected function getVatRates($countryCode)
|
726 |
+
{
|
727 |
+
$date = $this->getInvoiceDate();
|
728 |
+
$vatInfo = $this->service->getVatInfo($countryCode, $date);
|
729 |
+
// PHP5.5: array_column($vatInfo['vatinfo'], 'vatrate');
|
730 |
+
$result = array_unique(array_map(function ($vatInfo1) {
|
731 |
+
return $vatInfo1['vatrate'];
|
732 |
+
}, $vatInfo['vatinfo']));
|
733 |
+
return $result;
|
734 |
+
}
|
735 |
+
|
736 |
+
/**
|
737 |
+
* Returns the invoice date in the iso yyyy-mm-dd format.
|
738 |
+
*
|
739 |
+
* @return string
|
740 |
+
*/
|
741 |
+
protected function getInvoiceDate()
|
742 |
+
{
|
743 |
+
$date = !empty($this->invoice['customer']['invoice']['issuedate']) ? $this->invoice['customer']['invoice']['issuedate'] : date('Y-m-d');
|
744 |
+
return $date;
|
745 |
+
}
|
746 |
+
|
747 |
+
/**
|
748 |
+
* Wrapper around Countries::isNl().
|
749 |
+
*
|
750 |
+
* @return bool
|
751 |
+
*/
|
752 |
+
protected function isNl()
|
753 |
+
{
|
754 |
+
return $this->countries->isNl($this->invoice['customer']['countrycode']);
|
755 |
+
}
|
756 |
+
|
757 |
+
/**
|
758 |
+
* Wrapper around Countries::isEu().
|
759 |
+
*
|
760 |
+
* @return bool
|
761 |
+
*/
|
762 |
+
protected function isEu()
|
763 |
+
{
|
764 |
+
return $this->countries->isEu($this->invoice['customer']['countrycode']);
|
765 |
+
}
|
766 |
+
|
767 |
+
/**
|
768 |
+
* Returns whether the client is located outside the EU.
|
769 |
+
*
|
770 |
+
* @return bool
|
771 |
+
*/
|
772 |
+
protected function isOutsideEu()
|
773 |
+
{
|
774 |
+
return !$this->isNl() && !$this->isEu();
|
775 |
+
}
|
776 |
+
|
777 |
+
/**
|
778 |
+
* Returns whether the client is a company with a vat number.
|
779 |
+
*
|
780 |
+
* @return bool
|
781 |
+
*/
|
782 |
+
protected function isCompany()
|
783 |
+
{
|
784 |
+
return !empty($this->invoice['customer']['vatnumber']);
|
785 |
+
}
|
786 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/CompletorInvoiceLines.php
ADDED
@@ -0,0 +1,299 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Invoice;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\Number;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* The invoice lines completor class provides functionality to correct and
|
8 |
+
* complete invoice lines before sending them to Acumulus.
|
9 |
+
*
|
10 |
+
* This class:
|
11 |
+
* - Adds required but missing fields on the invoice lines.
|
12 |
+
* - Validates (and correct rounding errors of) vat rates using the VAT rate
|
13 |
+
* lookup webservice call.
|
14 |
+
* - Adds vat rates to 0 price lines (with a 0 price and thus 0 vat, not all
|
15 |
+
* web shops can fill in a vat rate).
|
16 |
+
*
|
17 |
+
* @package Siel\Acumulus
|
18 |
+
*/
|
19 |
+
class CompletorInvoiceLines
|
20 |
+
{
|
21 |
+
/** @var array[] */
|
22 |
+
protected $invoice;
|
23 |
+
|
24 |
+
/** @var array[] */
|
25 |
+
protected $invoiceLines;
|
26 |
+
|
27 |
+
/**
|
28 |
+
* @var int[]
|
29 |
+
* The list of possible vat types, initially filled with possible vat types
|
30 |
+
* based on client country, invoiceHasLineWithVat(), is_company(), and the
|
31 |
+
* digital services setting. But then reduced by VAT rates we find on the
|
32 |
+
* order lines.
|
33 |
+
*/
|
34 |
+
protected $possibleVatTypes;
|
35 |
+
|
36 |
+
/** @var array[] */
|
37 |
+
protected $possibleVatRates;
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Constructor.
|
41 |
+
*/
|
42 |
+
public function __construct()
|
43 |
+
{
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* Completes the invoice with default settings that do not depend on shop
|
48 |
+
* specific data.
|
49 |
+
*
|
50 |
+
* @param array $invoice
|
51 |
+
* The invoice to complete.
|
52 |
+
* @param int[] $possibleVatTypes
|
53 |
+
* @param array[] $possibleVatRates
|
54 |
+
* A response structure where errors and warnings can be added. Any local
|
55 |
+
* messages will be added to arrays under the keys 'errors' and 'warnings'.
|
56 |
+
*
|
57 |
+
* @return array The completed invoice.
|
58 |
+
* The completed invoice.
|
59 |
+
*/
|
60 |
+
public function complete(array $invoice, array $possibleVatTypes, array $possibleVatRates)
|
61 |
+
{
|
62 |
+
$this->invoice = $invoice;
|
63 |
+
$this->invoiceLines = &$this->invoice['customer']['invoice']['line'];
|
64 |
+
$this->possibleVatTypes = $possibleVatTypes;
|
65 |
+
$this->possibleVatRates = $possibleVatRates;
|
66 |
+
|
67 |
+
$this->completeInvoiceLines();
|
68 |
+
|
69 |
+
return $this->invoice;
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Completes the invoice lines.
|
74 |
+
*/
|
75 |
+
protected function completeInvoiceLines()
|
76 |
+
{
|
77 |
+
$this->completeLineRequiredData();
|
78 |
+
$this->correctCalculatedVatRates();
|
79 |
+
$this->addVatRateTo0PriceLines();
|
80 |
+
$this->completeLineMetaData();
|
81 |
+
}
|
82 |
+
|
83 |
+
/**
|
84 |
+
* Completes the fields that are required by the rest of this completor phase.
|
85 |
+
*
|
86 |
+
* The creator filled in the fields that are directly available from the
|
87 |
+
* shops' data store. This method completes (if not filled in):
|
88 |
+
* - unitprice.
|
89 |
+
*/
|
90 |
+
protected function completeLineRequiredData()
|
91 |
+
{
|
92 |
+
$invoiceLines = &$this->invoice['customer']['invoice']['line'];
|
93 |
+
foreach ($invoiceLines as &$line) {
|
94 |
+
$calculatedFields = isset($line['meta-calculated-fields'])
|
95 |
+
? (is_array($line['meta-calculated-fields']) ? $line['meta-calculated-fields'] : explode(',', $line['meta-calculated-fields']))
|
96 |
+
: array();
|
97 |
+
|
98 |
+
if (!isset($line['unitprice'])) {
|
99 |
+
if (isset($line['unitpriceinc'])) {
|
100 |
+
if (isset($line['vatamount'])) {
|
101 |
+
$line['unitprice'] = $line['unitpriceinc'] - $line['vatamount'];
|
102 |
+
$calculatedFields[] = 'unitprice';
|
103 |
+
} else if (isset($line['vatrate']) && in_array($line['meta-vatrate-source'], Completor::$CorrectVatRateSources)) {
|
104 |
+
$line['unitprice'] = $line['unitpriceinc'] / (100.0 + $line['vatrate']) * 100.0;
|
105 |
+
$calculatedFields[] = 'unitprice';
|
106 |
+
}
|
107 |
+
}
|
108 |
+
}
|
109 |
+
|
110 |
+
if (!empty($calculatedFields)) {
|
111 |
+
$line['meta-calculated-fields'] = implode(',', $calculatedFields);
|
112 |
+
}
|
113 |
+
}
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
117 |
+
* Try to correct 'calculated' vat rates for rounding errors by matching them
|
118 |
+
* with possible vatRates
|
119 |
+
*/
|
120 |
+
protected function correctCalculatedVatRates()
|
121 |
+
{
|
122 |
+
foreach ($this->invoiceLines as &$line) {
|
123 |
+
if (!empty($line['meta-vatrate-source']) && $line['meta-vatrate-source'] === Creator::VatRateSource_Calculated) {
|
124 |
+
$line = $this->correctVatRateByRange($line);
|
125 |
+
}
|
126 |
+
}
|
127 |
+
}
|
128 |
+
|
129 |
+
/**
|
130 |
+
* Checks and corrects a 'calculated' vat rate to an allowed vat rate.
|
131 |
+
*
|
132 |
+
* The meta-vatrate-source must be Creator::VatRateSource_Calculated.
|
133 |
+
*
|
134 |
+
* The check is done on comparing allowed vat rates with the meta-vatrate-min
|
135 |
+
* and meta-vatrate-max values. If only 1 match is found that will be used.
|
136 |
+
*
|
137 |
+
* If multiple matches are found with all equal rates - e.g. Dutch and Belgium
|
138 |
+
* 21% - the vat rate will be corrected, but the VAT Type will remain
|
139 |
+
* undecided.
|
140 |
+
*
|
141 |
+
* This method is public to allow a 2nd call to just this method for a single
|
142 |
+
* line added after a 1st round of correcting. Do not use unless
|
143 |
+
* $this->possibleVAtRates has been initialized
|
144 |
+
*
|
145 |
+
* @param array $line
|
146 |
+
* A line with a calculated vat rate.
|
147 |
+
*
|
148 |
+
* @return array
|
149 |
+
* The line with a corrected vat rate.
|
150 |
+
*/
|
151 |
+
public function correctVatRateByRange(array $line)
|
152 |
+
{
|
153 |
+
$matchedVatRates = array();
|
154 |
+
foreach ($this->possibleVatRates as $vatRate) {
|
155 |
+
if ($vatRate['vatrate'] >= $line['meta-vatrate-min'] && $vatRate['vatrate'] <= $line['meta-vatrate-max']) {
|
156 |
+
$matchedVatRates[] = $vatRate;
|
157 |
+
}
|
158 |
+
}
|
159 |
+
|
160 |
+
$vatRate = $this->getUniqueVatRate($matchedVatRates);
|
161 |
+
if ($vatRate === null || $vatRate === false) {
|
162 |
+
// We remove the calculated vatrate
|
163 |
+
// @todo: pick closest or pick just 1?
|
164 |
+
unset($line['vatrate']);
|
165 |
+
$line['meta-vatrate-matches'] = $vatRate === null
|
166 |
+
? 'none'
|
167 |
+
: array_reduce($matchedVatRates, function ($carry, $item) {
|
168 |
+
return $carry . ($carry === '' ? '' : ',') . $item['vatrate'] . '(' . $item['vattype'] . ')';
|
169 |
+
}, '');
|
170 |
+
if (!empty($line['meta-strategy-split'])) {
|
171 |
+
// Give the strategy phase a chance to correct this line.
|
172 |
+
$line['meta-vatrate-source'] = Creator::VatRateSource_Strategy;
|
173 |
+
}
|
174 |
+
} else {
|
175 |
+
$line['vatrate'] = $vatRate;
|
176 |
+
$line['meta-vatrate-source'] = Completor::VatRateSource_Calculated_Corrected;
|
177 |
+
}
|
178 |
+
return $line;
|
179 |
+
}
|
180 |
+
|
181 |
+
/**
|
182 |
+
* Determines if all (matched) vat rates are equal.
|
183 |
+
*
|
184 |
+
* @param array $matchedVatRates
|
185 |
+
*
|
186 |
+
* @return float|FALSE|NULL
|
187 |
+
* If all vat rates are equal that vat rate, null if $matchedVatRates is
|
188 |
+
* empty, false otherwise (multiple but different vat rates).
|
189 |
+
*/
|
190 |
+
protected function getUniqueVatRate(array $matchedVatRates)
|
191 |
+
{
|
192 |
+
$result = array_reduce($matchedVatRates, function ($carry, $matchedVatRate) {
|
193 |
+
if ($carry === null) {
|
194 |
+
// 1st item: return its vat rate.
|
195 |
+
return $matchedVatRate['vatrate'];
|
196 |
+
} else if ($carry == $matchedVatRate['vatrate']) {
|
197 |
+
// Note that in PHP: '21' == '21.0000' returns true. So using == works.
|
198 |
+
// Vat rate equals all previous vat rates: return that vat rate.
|
199 |
+
return $carry;
|
200 |
+
} else {
|
201 |
+
// Vat rate does not match previous vat rates or carry is already false,
|
202 |
+
// return false.
|
203 |
+
return false;
|
204 |
+
}
|
205 |
+
}, null);
|
206 |
+
return $result;
|
207 |
+
}
|
208 |
+
|
209 |
+
/**
|
210 |
+
* Completes lines with free items (price = 0) by giving them the maximum tax
|
211 |
+
* rate that appears in the other lines.
|
212 |
+
*/
|
213 |
+
protected function addVatRateTo0PriceLines()
|
214 |
+
{
|
215 |
+
// Get appearing vat rates and their frequency.
|
216 |
+
$vatRates = $this->getAppearingVatRates();
|
217 |
+
|
218 |
+
// Get the highest vat rate.
|
219 |
+
$maxVatRate = -1.0;
|
220 |
+
foreach ($vatRates as $vatRate => $frequency) {
|
221 |
+
if ((float) $vatRate > $maxVatRate) {
|
222 |
+
$maxVatRate = (float) $vatRate;
|
223 |
+
}
|
224 |
+
}
|
225 |
+
|
226 |
+
foreach ($this->invoiceLines as &$line) {
|
227 |
+
if ($line['meta-vatrate-source'] === Creator::VatRateSource_Completor && $line['vatrate'] === null && Number::isZero($line['unitprice'])) {
|
228 |
+
$line['vatrate'] = $maxVatRate;
|
229 |
+
$line['meta-vatrate-source'] = Completor::VatRateSource_Completor_Completed;
|
230 |
+
}
|
231 |
+
}
|
232 |
+
}
|
233 |
+
|
234 |
+
/**
|
235 |
+
* Returns a list of vat rates that actually appear in the invoice.
|
236 |
+
*
|
237 |
+
* @return array
|
238 |
+
* An array with the vat rates as key and the number of times they appear in
|
239 |
+
* the invoice lines as value.
|
240 |
+
*/
|
241 |
+
protected function getAppearingVatRates()
|
242 |
+
{
|
243 |
+
$vatRates = array();
|
244 |
+
foreach ($this->invoiceLines as $line) {
|
245 |
+
if (isset($line['vatrate'])) {
|
246 |
+
if (isset($vatRates[$line['vatrate']])) {
|
247 |
+
$vatRates[$line['vatrate']]++;
|
248 |
+
} else {
|
249 |
+
$vatRates[$line['vatrate']] = 1;
|
250 |
+
}
|
251 |
+
}
|
252 |
+
}
|
253 |
+
return $vatRates;
|
254 |
+
}
|
255 |
+
|
256 |
+
/**
|
257 |
+
* Completes each (non-strategy) line with missing (meta) info.
|
258 |
+
*
|
259 |
+
* All non strategy lines have unitprice and vatrate filled in and should by
|
260 |
+
* now have correct(ed) VAT rates. In some shops these non strategy lines may
|
261 |
+
* have a meta-line-discount-vatamount or meta-line-discount-amountinc field,
|
262 |
+
* that can be used with the SplitKnownDiscountLine strategy. Complete (if
|
263 |
+
* missing):
|
264 |
+
* - unitpriceinc
|
265 |
+
* - vatamount
|
266 |
+
* - meta-line-discount-amountinc (if meta-line-discount-vatamount is
|
267 |
+
* available).
|
268 |
+
*/
|
269 |
+
protected function completeLineMetaData()
|
270 |
+
{
|
271 |
+
$invoiceLines = &$this->invoice['customer']['invoice']['line'];
|
272 |
+
foreach ($invoiceLines as &$line) {
|
273 |
+
$calculatedFields = isset($line['meta-calculated-fields'])
|
274 |
+
? (is_array($line['meta-calculated-fields']) ? $line['meta-calculated-fields'] : explode(',', $line['meta-calculated-fields']))
|
275 |
+
: array();
|
276 |
+
|
277 |
+
if (in_array($line['meta-vatrate-source'], Completor::$CorrectVatRateSources)) {
|
278 |
+
if (!isset($line['unitpriceinc'])) {
|
279 |
+
$line['unitpriceinc'] = $line['unitprice'] / 100.0 * (100.0 + $line['vatrate']);
|
280 |
+
$calculatedFields[] = 'unitpriceinc';
|
281 |
+
}
|
282 |
+
|
283 |
+
if (!isset($line['vatamount'])) {
|
284 |
+
$line['vatamount'] = $line['vatrate'] / 100.0 * $line['unitprice'];
|
285 |
+
$calculatedFields[] = 'vatamount';
|
286 |
+
}
|
287 |
+
|
288 |
+
if (isset($line['meta-line-discount-vatamount']) && !isset($line['meta-line-discount-amountinc'])) {
|
289 |
+
$line['meta-line-discount-amountinc'] = $line['meta-line-discount-vatamount'] / $line['vatrate'] * (100 + $line['vatrate']);
|
290 |
+
$calculatedFields[] = 'meta-line-discount-amountinc';
|
291 |
+
}
|
292 |
+
|
293 |
+
if (!empty($calculatedFields)) {
|
294 |
+
$line['meta-calculated-fields'] = implode(',', $calculatedFields);
|
295 |
+
}
|
296 |
+
}
|
297 |
+
}
|
298 |
+
}
|
299 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/CompletorStrategy/ApplySameVatRate.php
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Invoice\CompletorStrategy;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\Number;
|
5 |
+
use Siel\Acumulus\Invoice\CompletorStrategyBase;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class ApplySameVatRate implements a vat completor strategy by applying the
|
9 |
+
* same vat rate to each line to complete.
|
10 |
+
*
|
11 |
+
* It also tries a vat rate of 0%. If that works, the system might be
|
12 |
+
* misconfigured or we have prepaid vouchers, but as we have to follow the
|
13 |
+
* system anyway, we will return it as is.
|
14 |
+
*
|
15 |
+
* Current known usages:
|
16 |
+
* - ???
|
17 |
+
*/
|
18 |
+
class ApplySameVatRate extends CompletorStrategyBase
|
19 |
+
{
|
20 |
+
/** @var int This strategy should be tried (as one of the) first. */
|
21 |
+
static public $tryOrder = 0;
|
22 |
+
|
23 |
+
/**
|
24 |
+
* {@inheritdoc}
|
25 |
+
*/
|
26 |
+
protected function execute()
|
27 |
+
{
|
28 |
+
// Try all vat rates.
|
29 |
+
foreach ($this->possibleVatRates as $vatRate) {
|
30 |
+
$vatRate = $vatRate['vatrate'];
|
31 |
+
if ($this->tryVatRate($vatRate)) {
|
32 |
+
return true;
|
33 |
+
}
|
34 |
+
}
|
35 |
+
|
36 |
+
// Try with a 0 tax rate as prepaid vouchers have 0 vat rate this might be a
|
37 |
+
// valid situation if the only lines to complete are voucher lines.
|
38 |
+
return $this->tryVatRate(0.0);
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Tries 1 of the possible vat rates.
|
43 |
+
*
|
44 |
+
* @param float $vatRate
|
45 |
+
*
|
46 |
+
* @return bool
|
47 |
+
*/
|
48 |
+
protected function tryVatRate($vatRate)
|
49 |
+
{
|
50 |
+
$this->description = "ApplySameVatRate($vatRate)";
|
51 |
+
$this->completedLines = array();
|
52 |
+
$vatAmount = 0.0;
|
53 |
+
foreach ($this->lines2Complete as $line2Complete) {
|
54 |
+
$vatAmount += $this->completeLine($line2Complete, $vatRate);
|
55 |
+
}
|
56 |
+
|
57 |
+
// If the vat totals are equal, the strategy worked.
|
58 |
+
// We allow for a reasonable margin, as rounding errors may add up.
|
59 |
+
return Number::floatsAreEqual($vatAmount, $this->vat2Divide, 0.04);
|
60 |
+
}
|
61 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/CompletorStrategy/Fail.php
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Invoice\CompletorStrategy;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Invoice\CompletorStrategyBase;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* FailStrategy implements a complete strategy that always fails.
|
8 |
+
*/
|
9 |
+
class Fail extends CompletorStrategyBase {
|
10 |
+
/** @var int This strategy should be executed last. */
|
11 |
+
static public $tryOrder = PHP_INT_MAX;
|
12 |
+
|
13 |
+
/**
|
14 |
+
* {@inheritdoc}
|
15 |
+
*/
|
16 |
+
public function execute() {
|
17 |
+
$this->description = "Fail";
|
18 |
+
$this->completedLines = $this->lines2Complete;
|
19 |
+
return true;
|
20 |
+
}
|
21 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/CompletorStrategy/SplitKnownDiscountLine.php
ADDED
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Invoice\CompletorStrategy;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\Number;
|
5 |
+
use Siel\Acumulus\Invoice\Completor;
|
6 |
+
use Siel\Acumulus\Invoice\CompletorStrategyBase;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Class SplitKnownDiscountLine implements a vat completor strategy by using the
|
10 |
+
* 'meta-line-discount-amountinc' tags to split a discount line over several
|
11 |
+
* lines with different vat rates as it may be considered as the total discount
|
12 |
+
* over multiple products that may have different vat rates.
|
13 |
+
*
|
14 |
+
* Preconditions:
|
15 |
+
* - lines2Complete contains 1 line that may be split.
|
16 |
+
* - There should be other lines that have a 'meta-line-discount-amountinc' tag
|
17 |
+
* and an exact vat rate, and these amounts must add up to the amount of the
|
18 |
+
* line that is to be split.
|
19 |
+
* - This strategy should be executed early as it is as sure win and can even
|
20 |
+
* be used as a partial solution.
|
21 |
+
*
|
22 |
+
* Strategy:
|
23 |
+
* The amounts in the lines that have a 'meta-line-discount-amountinc' tag are
|
24 |
+
* summed by their vat rates and these "discount amounts per vat rate" are used
|
25 |
+
* to create the lines that replace the single discount line.
|
26 |
+
*
|
27 |
+
* Current usages:
|
28 |
+
* - Magento
|
29 |
+
* - @todo PrestaShop (if no discount on shipping and other fees this might work
|
30 |
+
* but will only be needed when we actually have multiple vat rates, otherwise
|
31 |
+
* it is a simple corrector.
|
32 |
+
*/
|
33 |
+
class SplitKnownDiscountLine extends CompletorStrategyBase
|
34 |
+
{
|
35 |
+
/**
|
36 |
+
* @var int
|
37 |
+
* This strategy should be tried last before the fail strategy as there
|
38 |
+
* are chances of returning a wrong true result.
|
39 |
+
*/
|
40 |
+
static public $tryOrder = 1;
|
41 |
+
|
42 |
+
/** @var array[] */
|
43 |
+
protected $splitLines;
|
44 |
+
|
45 |
+
/** @var array */
|
46 |
+
protected $splitLine;
|
47 |
+
|
48 |
+
/** @var float */
|
49 |
+
protected $knownDiscountAmountInc;
|
50 |
+
|
51 |
+
/** @var float */
|
52 |
+
protected $knownDiscountVatAmount;
|
53 |
+
|
54 |
+
/** @var float[] */
|
55 |
+
protected $discountsPerVatRate;
|
56 |
+
|
57 |
+
/**
|
58 |
+
* {@inheritdoc}
|
59 |
+
*/
|
60 |
+
protected function init()
|
61 |
+
{
|
62 |
+
$splitLines = array();
|
63 |
+
foreach ($this->lines2Complete as $line2Complete) {
|
64 |
+
if (isset($line2Complete['meta-strategy-split']) && $line2Complete['meta-strategy-split']) {
|
65 |
+
$splitLines[] = $line2Complete;
|
66 |
+
}
|
67 |
+
}
|
68 |
+
if (count($splitLines) === 1) {
|
69 |
+
$this->splitLine = reset($splitLines);
|
70 |
+
}
|
71 |
+
|
72 |
+
$this->discountsPerVatRate = array();
|
73 |
+
$this->knownDiscountAmountInc = 0.0;
|
74 |
+
$this->knownDiscountVatAmount = 0.0;
|
75 |
+
foreach ($this->invoice['customer']['invoice']['line'] as $line) {
|
76 |
+
if (isset($line['meta-line-discount-amountinc']) && in_array($line['meta-vatrate-source'], Completor::$CorrectVatRateSources)) {
|
77 |
+
$this->knownDiscountAmountInc += $line['meta-line-discount-amountinc'];
|
78 |
+
$this->knownDiscountVatAmount += $line['meta-line-discount-amountinc'] / (100.0 + $line['vatrate']) * $line['vatrate'];
|
79 |
+
$vatRate = sprintf('%.3f', $line['vatrate']);
|
80 |
+
if (isset($this->discountsPerVatRate[$vatRate])) {
|
81 |
+
$this->discountsPerVatRate[$vatRate] += $line['meta-line-discount-amountinc'];
|
82 |
+
} else {
|
83 |
+
$this->discountsPerVatRate[$vatRate] = $line['meta-line-discount-amountinc'];
|
84 |
+
}
|
85 |
+
}
|
86 |
+
}
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* {@inheritdoc}
|
91 |
+
*/
|
92 |
+
protected function checkPreconditions()
|
93 |
+
{
|
94 |
+
$result = false;
|
95 |
+
if (isset($this->splitLine)) {
|
96 |
+
if ((isset($this->splitLine['unitprice']) && Number::floatsAreEqual($this->splitLine['unitprice'], $this->knownDiscountAmountInc - $this->knownDiscountVatAmount))
|
97 |
+
|| (isset($this->splitLine['unitpriceinc']) && Number::floatsAreEqual($this->splitLine['unitpriceinc'], $this->knownDiscountAmountInc))
|
98 |
+
) {
|
99 |
+
$result = true;
|
100 |
+
}
|
101 |
+
}
|
102 |
+
return $result;
|
103 |
+
}
|
104 |
+
|
105 |
+
/**
|
106 |
+
* {@inheritdoc}
|
107 |
+
*/
|
108 |
+
public function execute()
|
109 |
+
{
|
110 |
+
return $this->splitDiscountLine();
|
111 |
+
}
|
112 |
+
|
113 |
+
/**
|
114 |
+
* @return bool
|
115 |
+
*/
|
116 |
+
protected function splitDiscountLine()
|
117 |
+
{
|
118 |
+
$this->description = "SplitKnownDiscountLine({$this->knownDiscountAmountInc}, {$this->knownDiscountVatAmount})";
|
119 |
+
$this->completedLines = array();
|
120 |
+
foreach ($this->discountsPerVatRate as $vatRate => $discountAmountInc) {
|
121 |
+
$line = $this->splitLine;
|
122 |
+
$line['product'] = "{$line['product']} ($vatRate%)";
|
123 |
+
$line['unitpriceinc'] = $discountAmountInc;
|
124 |
+
$this->completeLine($line, $vatRate);
|
125 |
+
}
|
126 |
+
return true;
|
127 |
+
}
|
128 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/CompletorStrategy/SplitLine.php
ADDED
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Invoice\CompletorStrategy;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Invoice\CompletorStrategyBase;
|
5 |
+
use Siel\Acumulus\Invoice\Creator;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class SplitLine implements a vat completor strategy by recognizing that a
|
9 |
+
* (discount) line can have any vat rate between the minimum and maximum vat
|
10 |
+
* rate. This because the discount can be divided over multiple products that
|
11 |
+
* have different vat rates.
|
12 |
+
*
|
13 |
+
* Preconditions:
|
14 |
+
* - lines2Complete contains at least 1 line that may be split.
|
15 |
+
* - Exactly 2 vat rates that appear in the invoice, otherwise we can't compute
|
16 |
+
* 1 division.
|
17 |
+
* - This strategy should be executed after the tryAllVatRatePermutations, so we
|
18 |
+
* may assume that we *have to* split to arrive at a solution.
|
19 |
+
*
|
20 |
+
* Strategy:
|
21 |
+
* Other non completed lines, typically shipping and other fees, are all given
|
22 |
+
* the same vat rate and subsequently the then remaining vat to divide is split
|
23 |
+
* ove the slit lines.
|
24 |
+
*
|
25 |
+
* If there are multiple split lines, we cannot arrive at a correct division
|
26 |
+
* for all theses line separately, so we combine them into 1 discount line and
|
27 |
+
* split that line in 2.
|
28 |
+
*
|
29 |
+
* As this strategy has a lot of freedom it will probably succeed with the first
|
30 |
+
* try. Therefore we should start with the "most correct" vat rate for the fee
|
31 |
+
* lines, being the key component (hoofdbestanddeel). But as no known shop
|
32 |
+
* implements this, we start with the maximum rate (this is used by most shops)
|
33 |
+
* followed by the minimum rate but only if it is the key component.
|
34 |
+
*
|
35 |
+
* Possible improvements:
|
36 |
+
* - Instead of restricting us to vat rates that already appear on the invoice,
|
37 |
+
* we could also look at the possible vat rates that are part of the vat type
|
38 |
+
* for the invoice (given that the vat type has 2 vat rates).
|
39 |
+
* Example: all low vat products, but high vat shipping (because that is set
|
40 |
+
* as such, even if it would not be necessary in this case) and a discount
|
41 |
+
* over the whole order amount, thus products + shipping, and both the
|
42 |
+
* shipping and discount line are strategy lines, where the discount line is
|
43 |
+
* the split line.
|
44 |
+
* - Instead of restricting us to 1 vat type, we could check for all still
|
45 |
+
* possible vat types that have only 2 vat rates.
|
46 |
+
* - Instead of restricting us to appearing or possible vat rates we could try
|
47 |
+
* to assume that 1 line is a prepaid voucher and therefore vat free, and
|
48 |
+
* split just the other line(s).
|
49 |
+
*
|
50 |
+
* Current known usages:
|
51 |
+
* - ???
|
52 |
+
*/
|
53 |
+
class SplitLine extends CompletorStrategyBase
|
54 |
+
{
|
55 |
+
/**
|
56 |
+
* @var int
|
57 |
+
* This strategy should be tried last before the fail strategy as there
|
58 |
+
* are chances of returning a wrong true result.
|
59 |
+
*/
|
60 |
+
static public $tryOrder = 10;
|
61 |
+
|
62 |
+
/** @var array[] */
|
63 |
+
protected $splitLines;
|
64 |
+
|
65 |
+
/** @var float */
|
66 |
+
protected $splitLinesAmount;
|
67 |
+
|
68 |
+
/** @var array[] */
|
69 |
+
protected $otherLines;
|
70 |
+
|
71 |
+
/** @var float */
|
72 |
+
protected $otherLinesAmount;
|
73 |
+
|
74 |
+
/** @var float */
|
75 |
+
protected $nonStrategyAmount;
|
76 |
+
|
77 |
+
/** @var array */
|
78 |
+
protected $minVatRate;
|
79 |
+
|
80 |
+
/** @var array */
|
81 |
+
protected $maxVatRate;
|
82 |
+
|
83 |
+
/** @var array */
|
84 |
+
protected $keyComponent;
|
85 |
+
|
86 |
+
/**
|
87 |
+
* {@inheritdoc}
|
88 |
+
*/
|
89 |
+
protected function init()
|
90 |
+
{
|
91 |
+
$this->splitLines = array();
|
92 |
+
$this->otherLines = array();
|
93 |
+
$this->otherLinesAmount = 0.0;
|
94 |
+
foreach ($this->lines2Complete as $line2Complete) {
|
95 |
+
if (!empty($line2Complete['meta-strategy-split'])) {
|
96 |
+
$this->splitLines[] = $line2Complete;
|
97 |
+
} else {
|
98 |
+
$this->otherLines[] = $line2Complete;
|
99 |
+
$this->otherLinesAmount += $line2Complete['unitprice'] * $line2Complete['quantity'];
|
100 |
+
}
|
101 |
+
}
|
102 |
+
|
103 |
+
$this->nonStrategyAmount = 0.0;
|
104 |
+
foreach ($this->invoice['customer']['invoice']['line'] as $line2Complete) {
|
105 |
+
if ($line2Complete['meta-vatrate-source'] !== Creator::VatRateSource_Strategy) {
|
106 |
+
$this->nonStrategyAmount += $line2Complete['unitprice'] * $line2Complete['quantity'];
|
107 |
+
}
|
108 |
+
}
|
109 |
+
$this->splitLinesAmount = $this->invoiceAmount - $this->nonStrategyAmount - $this->otherLinesAmount;
|
110 |
+
|
111 |
+
$this->minVatRate = $this->getVatBreakDownMinRate();
|
112 |
+
$this->maxVatRate = $this->getVatBreakDownMaxRate();
|
113 |
+
$this->keyComponent = $this->getVatBreakDownMaxAmount();
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
117 |
+
* {@inheritdoc}
|
118 |
+
*/
|
119 |
+
protected function checkPreconditions()
|
120 |
+
{
|
121 |
+
return count($this->vatBreakdown) === 2 && count($this->splitLines) >= 1;
|
122 |
+
}
|
123 |
+
|
124 |
+
/**
|
125 |
+
* {@inheritdoc}
|
126 |
+
*/
|
127 |
+
public function execute()
|
128 |
+
{
|
129 |
+
if ($this->tryVatRate($this->maxVatRate['vatrate'])) {
|
130 |
+
return true;
|
131 |
+
}
|
132 |
+
if ($this->maxVatRate !== $this->keyComponent) {
|
133 |
+
if ($this->tryVatRate($this->keyComponent['vatrate'])) {
|
134 |
+
return true;
|
135 |
+
}
|
136 |
+
}
|
137 |
+
|
138 |
+
// Try a rate of 0% for all other lines.
|
139 |
+
//return $this->tryVatRate(0, $minRate, $maxRate);
|
140 |
+
return false;
|
141 |
+
}
|
142 |
+
|
143 |
+
/**
|
144 |
+
*
|
145 |
+
*
|
146 |
+
* @param float $vatRateForOtherLines
|
147 |
+
*
|
148 |
+
* @return bool
|
149 |
+
*
|
150 |
+
*/
|
151 |
+
protected function tryVatRate($vatRateForOtherLines)
|
152 |
+
{
|
153 |
+
$this->description = "SplitLine($vatRateForOtherLines, {$this->minVatRate['vatrate']}, {$this->maxVatRate['vatrate']})";
|
154 |
+
$this->completedLines = array();
|
155 |
+
$otherVatAmount = 0.0;
|
156 |
+
foreach ($this->otherLines as $otherLine2Complete) {
|
157 |
+
$otherVatAmount += $this->completeLine($otherLine2Complete, $vatRateForOtherLines);
|
158 |
+
}
|
159 |
+
return $this->divideAmountOver2VatRates($this->vat2Divide - $otherVatAmount,
|
160 |
+
(float) $this->minVatRate['vatrate'] / 100.0,
|
161 |
+
(float) $this->maxVatRate['vatrate'] / 100.0);
|
162 |
+
}
|
163 |
+
|
164 |
+
/**
|
165 |
+
* Tries to split $this->splitLinesAmount over 2 lines with $lowVatRate and
|
166 |
+
* $highVatRate such that the vat amount for those 2 lines equals the
|
167 |
+
* Given an amount and a vat over that amount, split that amount over 2 given
|
168 |
+
* vat rates such that the total vat amount remains equal.
|
169 |
+
*
|
170 |
+
* Example €15,- with €2.40 vat and vat rates of 21% and 6% results in €10,-
|
171 |
+
* at 21% vat and €5,- at 6% vat.
|
172 |
+
*
|
173 |
+
* The math:
|
174 |
+
* 1) highAmount + LowAmount = Amount
|
175 |
+
* 2) highRate * highAmount + lowRate * lowAmount = VatAmount
|
176 |
+
*
|
177 |
+
* This results in:
|
178 |
+
* 1) highAmount = (vatAmount - Amount * lowRate) / (highRate - lowRate)
|
179 |
+
* 2) lowAmount = Amount - highAmount
|
180 |
+
*
|
181 |
+
* This may be considered successful if the sign of all 3 amounts is the same
|
182 |
+
* and both low and high amount are not 0 (this is a split strategy, not
|
183 |
+
* splitting but using 1 vat rate is tried by another strategy).
|
184 |
+
*
|
185 |
+
* @param float $splitVatAmount
|
186 |
+
* @param float $lowVatRate
|
187 |
+
* number between 0 and $highRate.
|
188 |
+
* @param float $highVatRate
|
189 |
+
* number between $lowRate and 1.
|
190 |
+
*
|
191 |
+
* @return bool
|
192 |
+
* Success.
|
193 |
+
*/
|
194 |
+
protected function divideAmountOver2VatRates($splitVatAmount, $lowVatRate, $highVatRate)
|
195 |
+
{
|
196 |
+
// Divide the amount over the 2 vat rates, such that the sum of the divided
|
197 |
+
// amounts and the sum of the vat amounts equals the total amount and vat.
|
198 |
+
list($lowAmount, $highAmount) = $this->splitAmountOver2VatRates($this->splitLinesAmount, $splitVatAmount, $lowVatRate, $highVatRate);
|
199 |
+
|
200 |
+
// Dividing was possible if both amounts have the same sign.
|
201 |
+
if (($highAmount < -0.005 && $lowAmount < -0.005 && $this->splitLinesAmount < -0.005)
|
202 |
+
|| ($highAmount > 0.005 && $lowAmount > 0.005 && $this->splitLinesAmount > 0.005)
|
203 |
+
) {
|
204 |
+
// We split all lines by the same percentage.
|
205 |
+
$highPercentage = $highAmount / $this->splitLinesAmount;
|
206 |
+
$lowPercentage = $lowAmount / $this->splitLinesAmount;
|
207 |
+
foreach ($this->splitLines as $line) {
|
208 |
+
$splitLine = $line;
|
209 |
+
$splitLine['product'] .= ' ' . $highVatRate . '% ' . $this->t('vat');
|
210 |
+
if (isset($splitLine['unitprice'])) {
|
211 |
+
$splitLine['unitprice'] = $highPercentage * $splitLine['unitprice'];
|
212 |
+
}
|
213 |
+
if (isset($splitLine['unitpriceinc'])) {
|
214 |
+
$splitLine['unitpriceinc'] = $highPercentage * $splitLine['unitpriceinc'];
|
215 |
+
}
|
216 |
+
$this->completeLine($splitLine, $highVatRate);
|
217 |
+
|
218 |
+
$splitLine = $line;
|
219 |
+
$splitLine['product'] .= ' ' . $lowVatRate . '% ' . $this->t('vat');
|
220 |
+
if (isset($splitLine['unitprice'])) {
|
221 |
+
$splitLine['unitprice'] = $lowPercentage * $splitLine['unitprice'];
|
222 |
+
}
|
223 |
+
if (isset($splitLine['unitpriceinc'])) {
|
224 |
+
$splitLine['unitpriceinc'] = $lowPercentage * $splitLine['unitpriceinc'];
|
225 |
+
}
|
226 |
+
$this->completeLine($splitLine, $lowVatRate);
|
227 |
+
}
|
228 |
+
return true;
|
229 |
+
}
|
230 |
+
|
231 |
+
return false;
|
232 |
+
}
|
233 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/CompletorStrategy/SplitNonMatchingLine.php
ADDED
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Invoice\CompletorStrategy;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Invoice\CompletorStrategyBase;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Class SplitNonMatchingLine implements a vat completor strategy by recognizing
|
8 |
+
* that a discount line (or ultra-correct shipping line) that does not match an
|
9 |
+
* allowed vat rate may have to be split over the vat rates that appear in the
|
10 |
+
* invoice. This because the discount can have been divided over multiple
|
11 |
+
* products that have different vat rates.
|
12 |
+
*
|
13 |
+
* Preconditions:
|
14 |
+
* - Exactly 2 VAT rates must be used in other lines.
|
15 |
+
* - lines2Complete may contain multiple lines that may be split.
|
16 |
+
*
|
17 |
+
* Note:
|
18 |
+
* - This strategy should be executed early as it is an almost sure win and is
|
19 |
+
* used as a partial solution for only some of the strategy lines.
|
20 |
+
*
|
21 |
+
* Strategy:
|
22 |
+
* Only lines that satisfy the following conditions are corrected:
|
23 |
+
* - To prevent "correcting" errors that lead to this non matching VAT rate,
|
24 |
+
* only lines that are marked with the value meta-strategy-split are
|
25 |
+
* corrected.
|
26 |
+
* - Each line2complete must have at least 2 of the values vatamount, unitprice,
|
27 |
+
* or unitpriceinc, such that it can be divided on its own.
|
28 |
+
* - They have gone through the correction pass, but no single matching VAT rate
|
29 |
+
* was found, so the value meta-vatrate-matches is set.
|
30 |
+
* These lines are split in such a way over the 2 allowed vat rates, that the
|
31 |
+
* values in the completed lines add up to the values in the line to be split.
|
32 |
+
*
|
33 |
+
* Current known usages:
|
34 |
+
* - PrestaShop (discount lines)
|
35 |
+
*/
|
36 |
+
class SplitNonMatchingLine extends CompletorStrategyBase
|
37 |
+
{
|
38 |
+
/**
|
39 |
+
* @var int
|
40 |
+
* This strategy should be tried last before the fail strategy as there
|
41 |
+
* are chances of returning a wrong true result.
|
42 |
+
*/
|
43 |
+
static public $tryOrder = 1;
|
44 |
+
|
45 |
+
/** @var array */
|
46 |
+
protected $minVatRate;
|
47 |
+
|
48 |
+
/** @var array */
|
49 |
+
protected $maxVatRate;
|
50 |
+
|
51 |
+
/**
|
52 |
+
* {@inheritdoc}
|
53 |
+
*/
|
54 |
+
protected function init()
|
55 |
+
{
|
56 |
+
$this->linesCompleted = array();
|
57 |
+
}
|
58 |
+
|
59 |
+
/**
|
60 |
+
* {@inheritdoc}
|
61 |
+
*/
|
62 |
+
protected function checkPreconditions()
|
63 |
+
{
|
64 |
+
$result = count($this->vatBreakdown) === 2;
|
65 |
+
return $result;
|
66 |
+
}
|
67 |
+
|
68 |
+
/**
|
69 |
+
* {@inheritdoc}
|
70 |
+
*/
|
71 |
+
public function execute()
|
72 |
+
{
|
73 |
+
$this->minVatRate = $this->getVatBreakDownMinRate();
|
74 |
+
$this->maxVatRate = $this->getVatBreakDownMaxRate();
|
75 |
+
$this->description = sprintf('"SplitNonMatchingLine(%s, %s)', $this->minVatRate['vatrate'], $this->maxVatRate['vatrate']);
|
76 |
+
$result = false;
|
77 |
+
foreach ($this->lines2Complete as $key => $line2Complete) {
|
78 |
+
if (!empty($line2Complete['meta-strategy-split']) && !empty($line2Complete['meta-vatrate-matches'])) {
|
79 |
+
if ($this->splitNonMatchingLine($line2Complete)) {
|
80 |
+
$result = true;
|
81 |
+
$this->linesCompleted[] = $key;
|
82 |
+
}
|
83 |
+
}
|
84 |
+
}
|
85 |
+
return $result;
|
86 |
+
}
|
87 |
+
|
88 |
+
/**
|
89 |
+
* @param array $line
|
90 |
+
*
|
91 |
+
* @return bool
|
92 |
+
*/
|
93 |
+
protected function splitNonMatchingLine(array $line)
|
94 |
+
{
|
95 |
+
list($lowAmount, $highAmount) = $this->splitAmountOver2VatRates($line['meta-line-price'],
|
96 |
+
$line['meta-line-priceinc'] - $line['meta-line-price'],
|
97 |
+
$this->minVatRate['vatrate'] / 100.0,
|
98 |
+
$this->maxVatRate['vatrate'] / 100.0);
|
99 |
+
|
100 |
+
// Dividing was possible if both amounts have the same sign.
|
101 |
+
if (($highAmount < -0.005 && $lowAmount < -0.005 && $line['meta-line-price'] < -0.005)
|
102 |
+
|| ($highAmount > 0.005 && $lowAmount > 0.005 && $line['meta-line-price'] > 0.005)
|
103 |
+
) {
|
104 |
+
$splitLine = $line;
|
105 |
+
$splitLine['product'] .= sprintf(' (%s%% %s)', $this->maxVatRate['vatrate'], $this->t('vat'));
|
106 |
+
$splitLine['unitprice'] = $highAmount;
|
107 |
+
unset($splitLine['unitpriceinc']);
|
108 |
+
$this->completeLine($splitLine, $this->maxVatRate['vatrate']);
|
109 |
+
|
110 |
+
$splitLine = $line;
|
111 |
+
$splitLine['product'] .= sprintf(' (%s%% %s)', $this->minVatRate['vatrate'], $this->t('vat'));
|
112 |
+
$splitLine['unitprice'] = $lowAmount;
|
113 |
+
unset($splitLine['unitpriceinc']);
|
114 |
+
$this->completeLine($splitLine, $this->minVatRate['vatrate']);
|
115 |
+
return true;
|
116 |
+
}
|
117 |
+
return false;
|
118 |
+
}
|
119 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/CompletorStrategy/TryAllVatRatePermutations.php
ADDED
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Invoice\CompletorStrategy;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\Number;
|
5 |
+
use Siel\Acumulus\Invoice\CompletorStrategyBase;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class TryAllTaxRatePermutations implements a vat completor strategy by trying
|
9 |
+
* all possible permutations of the vat rates for all possible vat types.
|
10 |
+
*
|
11 |
+
* Current known usages:
|
12 |
+
* - ???
|
13 |
+
*/
|
14 |
+
class TryAllVatRatePermutations extends CompletorStrategyBase
|
15 |
+
{
|
16 |
+
/**
|
17 |
+
* @var int
|
18 |
+
* This strategy should be tried before the split strategy as that one will
|
19 |
+
* easily succeed.
|
20 |
+
*/
|
21 |
+
static public $tryOrder = 25;
|
22 |
+
|
23 |
+
/** @var float[] */
|
24 |
+
protected $vatRates;
|
25 |
+
|
26 |
+
/** @var int */
|
27 |
+
protected $countLines;
|
28 |
+
|
29 |
+
/**
|
30 |
+
* {@inheritdoc}
|
31 |
+
*/
|
32 |
+
public function execute()
|
33 |
+
{
|
34 |
+
$this->countLines = count($this->lines2Complete);
|
35 |
+
|
36 |
+
// Try without and with a 0 tax rate (prepaid vouchers have 0 vat rate, so
|
37 |
+
// discount lines may have 0 vat rate).
|
38 |
+
foreach (array(false, true) as $include0) {
|
39 |
+
foreach ($this->possibleVatTypes as $vatType) {
|
40 |
+
$this->setVatRates($vatType, $include0);
|
41 |
+
if ($this->tryAllPermutations(array())) {
|
42 |
+
return true;
|
43 |
+
}
|
44 |
+
}
|
45 |
+
}
|
46 |
+
|
47 |
+
return false;
|
48 |
+
}
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Initializes the array of vat rates to use for this permutation.
|
52 |
+
*
|
53 |
+
* @param float $vatType
|
54 |
+
* @param bool $include0
|
55 |
+
*/
|
56 |
+
protected function setVatRates($vatType, $include0)
|
57 |
+
{
|
58 |
+
$this->vatRates = array();
|
59 |
+
foreach ($this->possibleVatRates as $vatRate) {
|
60 |
+
if ($vatRate['vattype'] === $vatType) {
|
61 |
+
$this->vatRates[] = $vatRate['vatrate'];
|
62 |
+
}
|
63 |
+
}
|
64 |
+
if ($include0) {
|
65 |
+
$this->vatRates[] = 0.0;
|
66 |
+
}
|
67 |
+
}
|
68 |
+
|
69 |
+
/**
|
70 |
+
* @param array $permutation
|
71 |
+
*
|
72 |
+
* @return bool
|
73 |
+
*/
|
74 |
+
protected function tryAllPermutations(array $permutation)
|
75 |
+
{
|
76 |
+
if (count($permutation) === $this->countLines) {
|
77 |
+
// Try this (complete) permutation.
|
78 |
+
return $this->try1Permutation($permutation);
|
79 |
+
} else {
|
80 |
+
// Complete this permutation recursively before we can try it.
|
81 |
+
$permutationIndex = count($permutation);
|
82 |
+
// Try all tax rates for the current line.
|
83 |
+
foreach ($this->vatRates as $taxRate => $amount) {
|
84 |
+
$permutation[$permutationIndex] = $taxRate;
|
85 |
+
if ($this->tryAllPermutations($permutation)) {
|
86 |
+
return true;
|
87 |
+
}
|
88 |
+
}
|
89 |
+
}
|
90 |
+
return false;
|
91 |
+
}
|
92 |
+
|
93 |
+
/**
|
94 |
+
* @param array $permutation
|
95 |
+
*
|
96 |
+
* @return bool
|
97 |
+
*/
|
98 |
+
protected function try1Permutation(array $permutation)
|
99 |
+
{
|
100 |
+
$this->description = 'TryAllVatRatePermutations(' . implode(', ', $permutation) . ')';
|
101 |
+
$this->completedLines = array();
|
102 |
+
$vatAmount = 0.0;
|
103 |
+
$i = 0;
|
104 |
+
foreach ($this->lines2Complete as $line2Complete) {
|
105 |
+
$vatAmount += $this->completeLine($line2Complete, $permutation[$i]);
|
106 |
+
$i++;
|
107 |
+
}
|
108 |
+
|
109 |
+
// The strategy worked if the vat totals equals the vat to divide.
|
110 |
+
return Number::floatsAreEqual($vatAmount, $this->vat2Divide);
|
111 |
+
}
|
112 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/CompletorStrategyBase.php
ADDED
@@ -0,0 +1,344 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Invoice;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\TranslatorInterface;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* CompletorStrategyBase is the base class for all strategies that might
|
8 |
+
* be able to complete invoice lines by applying a strategy to divide a
|
9 |
+
* remaining vat amount over lines without a vat rate yet.
|
10 |
+
*/
|
11 |
+
abstract class CompletorStrategyBase
|
12 |
+
{
|
13 |
+
/** @var \Siel\Acumulus\Helpers\TranslatorInterface */
|
14 |
+
protected $translator;
|
15 |
+
|
16 |
+
/** @var int Indication of the order of execution of the strategy. */
|
17 |
+
static $tryOrder = 50;
|
18 |
+
|
19 |
+
/** @var array[] */
|
20 |
+
protected $invoice;
|
21 |
+
|
22 |
+
/** @var array[] */
|
23 |
+
protected $possibleVatTypes;
|
24 |
+
|
25 |
+
/** @var array[] */
|
26 |
+
protected $possibleVatRates;
|
27 |
+
|
28 |
+
/** @var int[] */
|
29 |
+
protected $linesCompleted;
|
30 |
+
|
31 |
+
/** @var array[] */
|
32 |
+
protected $lines2Complete;
|
33 |
+
|
34 |
+
/** @var array[] */
|
35 |
+
protected $completedLines;
|
36 |
+
|
37 |
+
/** @var float */
|
38 |
+
protected $vat2Divide;
|
39 |
+
|
40 |
+
/** @var float */
|
41 |
+
protected $vatAmount;
|
42 |
+
|
43 |
+
/** @var float */
|
44 |
+
protected $invoiceAmount;
|
45 |
+
|
46 |
+
/** @var string */
|
47 |
+
protected $description = 'Not yet set';
|
48 |
+
|
49 |
+
/** @var array[] */
|
50 |
+
protected $vatBreakdown;
|
51 |
+
|
52 |
+
/** @var \Siel\Acumulus\Invoice\Source */
|
53 |
+
protected $source;
|
54 |
+
|
55 |
+
/**
|
56 |
+
* @param \Siel\Acumulus\Helpers\TranslatorInterface $translator
|
57 |
+
* @param array $invoice
|
58 |
+
* @param array $possibleVatTypes
|
59 |
+
* @param array $possibleVatRates
|
60 |
+
* @param \Siel\Acumulus\Invoice\Source $source
|
61 |
+
*/
|
62 |
+
public function __construct(TranslatorInterface $translator, array $invoice, array $possibleVatTypes, array $possibleVatRates, Source $source)
|
63 |
+
{
|
64 |
+
$this->translator = $translator;
|
65 |
+
$this->invoice = $invoice;
|
66 |
+
$this->possibleVatTypes = $possibleVatTypes;
|
67 |
+
$this->possibleVatRates = $possibleVatRates;
|
68 |
+
$this->source = $source;
|
69 |
+
$this->initAmounts();
|
70 |
+
$this->initLines2Complete();
|
71 |
+
$this->initVatBreakdown();
|
72 |
+
}
|
73 |
+
|
74 |
+
/**
|
75 |
+
* Helper method to translate strings.
|
76 |
+
*
|
77 |
+
* @param string $key
|
78 |
+
* The key to get a translation for.
|
79 |
+
*
|
80 |
+
* @return string
|
81 |
+
* The translation for the given key or the key itself if no translation
|
82 |
+
* could be found.
|
83 |
+
*/
|
84 |
+
protected function t($key)
|
85 |
+
{
|
86 |
+
return $this->translator->get($key);
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* Returns the (parameterised) description of the latest tried strategy.
|
91 |
+
*
|
92 |
+
* @return string
|
93 |
+
*/
|
94 |
+
public function getDescription()
|
95 |
+
{
|
96 |
+
return $this->description;
|
97 |
+
}
|
98 |
+
|
99 |
+
/**
|
100 |
+
* Returns the keys of the lines that are completed (and thus should be
|
101 |
+
* replaced by the completed lines).
|
102 |
+
*
|
103 |
+
* Should only be called after success.
|
104 |
+
*
|
105 |
+
* @return int[]
|
106 |
+
*/
|
107 |
+
public function getLinesCompleted()
|
108 |
+
{
|
109 |
+
return $this->linesCompleted;
|
110 |
+
}
|
111 |
+
|
112 |
+
/**
|
113 |
+
* Returns the completed lines (that should replace the lines completed).
|
114 |
+
*
|
115 |
+
* Should only be called after success.
|
116 |
+
*
|
117 |
+
* @return array[]
|
118 |
+
*/
|
119 |
+
public function getCompletedLines()
|
120 |
+
{
|
121 |
+
return $this->completedLines;
|
122 |
+
}
|
123 |
+
|
124 |
+
/**
|
125 |
+
* Initializes the amount properties.
|
126 |
+
*
|
127 |
+
* to be able to calculate the amounts, at least 2 of the 3 meta amounts
|
128 |
+
* meta-invoice-vatamount, meta-invoice-amountinc, or meta-invoice-amount must
|
129 |
+
* be known.
|
130 |
+
*/
|
131 |
+
protected function initAmounts()
|
132 |
+
{
|
133 |
+
$invoicePart = &$this->invoice['customer']['invoice'];
|
134 |
+
$this->vatAmount = isset($invoicePart['meta-invoice-vatamount']) ? $invoicePart['meta-invoice-vatamount'] : $invoicePart['meta-invoice-amountinc'] - $invoicePart['meta-invoice-amount'];
|
135 |
+
$this->invoiceAmount = isset($invoicePart['meta-invoice-amount']) ? $invoicePart['meta-invoice-amount'] : $invoicePart['meta-invoice-amountinc'] - $invoicePart['meta-invoice-vatamount'];
|
136 |
+
|
137 |
+
// The vat amount to divide over the non completed lines is the total vat
|
138 |
+
// amount of the invoice minus all known vat amounts per line.
|
139 |
+
$this->vat2Divide = (float) $this->vatAmount;
|
140 |
+
foreach ($invoicePart['line'] as $line) {
|
141 |
+
if ($line['meta-vatrate-source'] !== Creator::VatRateSource_Strategy) {
|
142 |
+
// Deduct the vat amount from this line: if set, deduct it directly,
|
143 |
+
// otherwise calculate the vat amount using the vat rate and unit price.
|
144 |
+
if (isset($line['vatamount'])) {
|
145 |
+
$this->vat2Divide -= $line['vatamount'] * $line['quantity'];
|
146 |
+
} else {
|
147 |
+
$this->vat2Divide -= ($line['vatrate'] / 100.0) * $line['unitprice'] * $line['quantity'];
|
148 |
+
}
|
149 |
+
}
|
150 |
+
}
|
151 |
+
}
|
152 |
+
|
153 |
+
/**
|
154 |
+
* Initializes $this->lines2Complete with all strategy lines.
|
155 |
+
*/
|
156 |
+
protected function initLines2Complete()
|
157 |
+
{
|
158 |
+
$this->linesCompleted = array();
|
159 |
+
$this->lines2Complete = array();
|
160 |
+
foreach ($this->invoice['customer']['invoice']['line'] as $key => $line) {
|
161 |
+
if ($line['meta-vatrate-source'] === Creator::VatRateSource_Strategy) {
|
162 |
+
$this->linesCompleted[] = $key;
|
163 |
+
$this->lines2Complete[] = $line;
|
164 |
+
}
|
165 |
+
}
|
166 |
+
}
|
167 |
+
|
168 |
+
/**
|
169 |
+
* Initializes $this->vatBreakdown with a breakdown of vat rates and amounts
|
170 |
+
* occurring on the invoice (and not being strategy lines).
|
171 |
+
*/
|
172 |
+
protected function initVatBreakdown()
|
173 |
+
{
|
174 |
+
$this->vatBreakdown = array();
|
175 |
+
foreach ($this->invoice['customer']['invoice']['line'] as $line) {
|
176 |
+
if ($line['meta-vatrate-source'] !== Creator::VatRateSource_Strategy && isset($line['vatrate'])) {
|
177 |
+
$amount = $line['unitprice'] * $line['quantity'];
|
178 |
+
$vatAmount = $line['vatrate'] / 100.0 * $amount;
|
179 |
+
$vatRate = sprintf('%.3f', $line['vatrate']);
|
180 |
+
// Add amount to existing vatrate line or create a new line.
|
181 |
+
if (isset($this->vatBreakdown[$vatRate])) {
|
182 |
+
$breakdown = &$this->vatBreakdown[$vatRate];
|
183 |
+
$breakdown['vatamount'] += $vatAmount;
|
184 |
+
$breakdown['amount'] += $amount;
|
185 |
+
$breakdown['count']++;
|
186 |
+
} else {
|
187 |
+
$this->vatBreakdown[$vatRate] = array(
|
188 |
+
'vatrate' => $vatRate,
|
189 |
+
'vatamount' => $vatAmount,
|
190 |
+
'amount' => $amount,
|
191 |
+
'count' => 1,
|
192 |
+
);
|
193 |
+
}
|
194 |
+
}
|
195 |
+
}
|
196 |
+
}
|
197 |
+
|
198 |
+
/**
|
199 |
+
* Returns the minimum vat rate on the invoice.
|
200 |
+
*
|
201 |
+
* @return array
|
202 |
+
* A vat rate overview (array with vatrate, vatamount, amount, count).
|
203 |
+
*/
|
204 |
+
protected function getVatBreakDownMinRate()
|
205 |
+
{
|
206 |
+
$result = array('vatrate' => PHP_INT_MAX);
|
207 |
+
foreach ($this->vatBreakdown as $breakDown) {
|
208 |
+
if ($breakDown['vatrate'] < $result['vatrate']) {
|
209 |
+
$result = $breakDown;
|
210 |
+
}
|
211 |
+
}
|
212 |
+
return $result;
|
213 |
+
}
|
214 |
+
|
215 |
+
/**
|
216 |
+
* Returns the maximum vat rate on the invoice.
|
217 |
+
*
|
218 |
+
* @return array
|
219 |
+
* A vat rate overview (array with vatrate, vatamount, amount, count).
|
220 |
+
*/
|
221 |
+
protected function getVatBreakDownMaxRate()
|
222 |
+
{
|
223 |
+
$result = array('vatrate' => -PHP_INT_MAX);
|
224 |
+
foreach ($this->vatBreakdown as $breakDown) {
|
225 |
+
if ($breakDown['vatrate'] > $result['vatrate']) {
|
226 |
+
$result = $breakDown;
|
227 |
+
}
|
228 |
+
}
|
229 |
+
return $result;
|
230 |
+
}
|
231 |
+
|
232 |
+
/**
|
233 |
+
* Returns the key component vat rate on the invoice (NL: hoofdbestanddeel).
|
234 |
+
*
|
235 |
+
* @return array
|
236 |
+
* A vat rate overview (array with vatrate, vatamount, amount, count).
|
237 |
+
*/
|
238 |
+
protected function getVatBreakDownMaxAmount()
|
239 |
+
{
|
240 |
+
$result = array('amount' => -PHP_INT_MAX);
|
241 |
+
foreach ($this->vatBreakdown as $breakDown) {
|
242 |
+
if ($breakDown['amount'] > $result['amount']) {
|
243 |
+
$result = $breakDown;
|
244 |
+
}
|
245 |
+
}
|
246 |
+
return $result;
|
247 |
+
}
|
248 |
+
|
249 |
+
/**
|
250 |
+
* Applies the strategy to see if it results in a valid solution.
|
251 |
+
*
|
252 |
+
* @return bool
|
253 |
+
* Success.
|
254 |
+
*/
|
255 |
+
public function apply()
|
256 |
+
{
|
257 |
+
$this->completedLines = array();
|
258 |
+
$this->init();
|
259 |
+
return $this->checkPreconditions() && $this->execute();
|
260 |
+
}
|
261 |
+
|
262 |
+
/**
|
263 |
+
* Strategy dependent initialization.
|
264 |
+
*/
|
265 |
+
protected function init()
|
266 |
+
{
|
267 |
+
}
|
268 |
+
|
269 |
+
/**
|
270 |
+
* Some strategies can only be tried when some conditions are met, e.g. if
|
271 |
+
* there's only 1 line to complete.
|
272 |
+
*
|
273 |
+
* These checks are extracted from execute() and should be placed in this
|
274 |
+
* method instead.
|
275 |
+
*
|
276 |
+
* @return bool
|
277 |
+
* True if this strategy might be used, false otherwise.
|
278 |
+
*/
|
279 |
+
protected function checkPreconditions()
|
280 |
+
{
|
281 |
+
return true;
|
282 |
+
}
|
283 |
+
|
284 |
+
/**
|
285 |
+
* Tries to apply the strategy.
|
286 |
+
*
|
287 |
+
* A strategy might be parameterised, most likely by 1 or more vat rates, and
|
288 |
+
* thus involve multiple tries. The implementations of execute() should run
|
289 |
+
* all possible permutations and return true as soon as 1 permutation is
|
290 |
+
* successful.
|
291 |
+
*
|
292 |
+
* If the result is true, $this->$completedLines must contain the completed
|
293 |
+
* lines that will replace the lines to complete.
|
294 |
+
*
|
295 |
+
* @return bool
|
296 |
+
* true the strategy has been applied successfully, false otherwise.
|
297 |
+
*/
|
298 |
+
abstract protected function execute();
|
299 |
+
|
300 |
+
/**
|
301 |
+
* Completes a line by filling in the given vat rate and calculating other
|
302 |
+
* possibly missing fields (vatamount, unitprice).
|
303 |
+
*
|
304 |
+
* @param $line2Complete
|
305 |
+
* @param $vatRate
|
306 |
+
*
|
307 |
+
* @return float
|
308 |
+
* The vat amount for the completed line.
|
309 |
+
*/
|
310 |
+
protected function completeLine(&$line2Complete, $vatRate)
|
311 |
+
{
|
312 |
+
if (!isset($line2Complete['quantity'])) {
|
313 |
+
$line2Complete['quantity'] = 1;
|
314 |
+
}
|
315 |
+
$line2Complete['vatrate'] = $vatRate;
|
316 |
+
if (isset($line2Complete['unitprice'])) {
|
317 |
+
$line2Complete['vatamount'] = ($line2Complete['vatrate'] / 100.0) * $line2Complete['unitprice'];
|
318 |
+
} else { // isset($line2Complete['unitpriceinc'])
|
319 |
+
$line2Complete['vatamount'] = ($line2Complete['vatrate'] / (100.0 + $line2Complete['vatrate'])) * $line2Complete['unitpriceinc'];
|
320 |
+
$line2Complete['unitprice'] = $line2Complete['unitpriceinc'] - $line2Complete['vatamount'];
|
321 |
+
}
|
322 |
+
$this->completedLines[] = $line2Complete;
|
323 |
+
return $line2Complete['vatamount'] * $line2Complete['quantity'];
|
324 |
+
}
|
325 |
+
|
326 |
+
/**
|
327 |
+
* Splits $amount 9ex VAT) in 2 amounts, such that if the first amount is
|
328 |
+
* taxed with the $lowVatRate and the 2nd amount is taxed with the
|
329 |
+
* $highVatRate, the sum of the 2 vat amounts add up to the given vat amount.
|
330 |
+
*
|
331 |
+
* @param float $amount
|
332 |
+
* @param float $vatAmount
|
333 |
+
* @param float $lowVatRate
|
334 |
+
* @param float $highVatRate
|
335 |
+
*
|
336 |
+
* @return float[]
|
337 |
+
*/
|
338 |
+
protected function splitAmountOver2VatRates($amount, $vatAmount, $lowVatRate, $highVatRate)
|
339 |
+
{
|
340 |
+
$highAmount = ($vatAmount - $amount * $lowVatRate) / ($highVatRate - $lowVatRate);
|
341 |
+
$lowAmount = $amount - $highAmount;
|
342 |
+
return array($lowAmount, $highAmount);
|
343 |
+
}
|
344 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/CompletorStrategyLines.php
ADDED
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Invoice;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\TranslatorInterface;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* The strategy lines completor class provides functionality to correct and
|
8 |
+
* complete invoice lines before sending them to Acumulus.
|
9 |
+
*
|
10 |
+
* This class:
|
11 |
+
* - Adds vat rates to lines that need a strategy to compute their vat rates.
|
12 |
+
*
|
13 |
+
* @package Siel\Acumulus
|
14 |
+
*/
|
15 |
+
class CompletorStrategyLines
|
16 |
+
{
|
17 |
+
/** @var \Siel\Acumulus\Helpers\TranslatorInterface */
|
18 |
+
protected $translator;
|
19 |
+
|
20 |
+
/** @var array[] */
|
21 |
+
protected $invoice;
|
22 |
+
|
23 |
+
/** @var array[] */
|
24 |
+
protected $invoiceLines;
|
25 |
+
|
26 |
+
/** @var Source */
|
27 |
+
protected $source;
|
28 |
+
|
29 |
+
/**
|
30 |
+
* @var int[]
|
31 |
+
* The list of possible vat types, initially filled with possible vat types
|
32 |
+
* based on client country, invoiceHasLineWithVat(), is_company(), and the
|
33 |
+
* digital services setting. But then reduced by VAT rates we find on the
|
34 |
+
* order lines.
|
35 |
+
*/
|
36 |
+
protected $possibleVatTypes;
|
37 |
+
|
38 |
+
/** @var array[] */
|
39 |
+
protected $possibleVatRates;
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Constructor.
|
43 |
+
*
|
44 |
+
* @param \Siel\Acumulus\Helpers\TranslatorInterface $translator
|
45 |
+
*/
|
46 |
+
public function __construct(TranslatorInterface $translator)
|
47 |
+
{
|
48 |
+
$this->translator = $translator;
|
49 |
+
}
|
50 |
+
|
51 |
+
/**
|
52 |
+
* Completes the invoice with default settings that do not depend on shop
|
53 |
+
* specific data.
|
54 |
+
*
|
55 |
+
* @param array $invoice
|
56 |
+
* The invoice to complete.
|
57 |
+
* @param Source $source
|
58 |
+
* The source object for which this invoice was created.
|
59 |
+
* @param int[] $possibleVatTypes
|
60 |
+
* @param array[] $possibleVatRates
|
61 |
+
*
|
62 |
+
* @return array
|
63 |
+
* The completed invoice.
|
64 |
+
*/
|
65 |
+
public function complete(array $invoice, Source $source, array $possibleVatTypes, array $possibleVatRates)
|
66 |
+
{
|
67 |
+
$this->invoice = $invoice;
|
68 |
+
$this->invoiceLines = &$this->invoice['customer']['invoice']['line'];
|
69 |
+
$this->source = $source;
|
70 |
+
$this->possibleVatTypes = $possibleVatTypes;
|
71 |
+
$this->possibleVatRates = $possibleVatRates;
|
72 |
+
|
73 |
+
$this->completeStrategyLines();
|
74 |
+
return $this->invoice;
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Complete all lines that need a vat divide strategy to compute correct
|
79 |
+
* values.
|
80 |
+
*/
|
81 |
+
protected function completeStrategyLines()
|
82 |
+
{
|
83 |
+
if ($this->invoiceHasStrategyLine()) {
|
84 |
+
$input = array_reduce($this->possibleVatRates, function ($carry, $item) {
|
85 |
+
return $carry . (empty($carry) ? '' : ',') . '[' . $item['vatrate'] . '%,' . $item['vattype'] . ']';
|
86 |
+
},
|
87 |
+
'');
|
88 |
+
$this->invoice['customer']['invoice']['meta-completor-strategy-input'] = "strategy input($input)";
|
89 |
+
|
90 |
+
$strategies = $this->getStrategyClasses();
|
91 |
+
foreach ($strategies as $strategyClass) {
|
92 |
+
/** @var CompletorStrategyBase $strategy */
|
93 |
+
$strategy = new $strategyClass($this->translator, $this->invoice, $this->possibleVatTypes, $this->possibleVatRates, $this->source);
|
94 |
+
if ($strategy->apply()) {
|
95 |
+
$this->replaceLinesCompleted($strategy->getLinesCompleted(), $strategy->getCompletedLines());
|
96 |
+
if (empty($this->invoice['customer']['invoice']['meta-completor-strategy-used'])) {
|
97 |
+
$this->invoice['customer']['invoice']['meta-completor-strategy-used'] = $strategy->getDescription();
|
98 |
+
} else {
|
99 |
+
$this->invoice['customer']['invoice']['meta-completor-strategy-used'] .= '; ' . $strategy->getDescription();
|
100 |
+
}
|
101 |
+
// Allow for partial solutions: a strategy may correct only some of
|
102 |
+
// the strategy lines and leave the rest up to other strategies.
|
103 |
+
if (!$this->invoiceHasStrategyLine()) {
|
104 |
+
break;
|
105 |
+
}
|
106 |
+
}
|
107 |
+
}
|
108 |
+
}
|
109 |
+
}
|
110 |
+
|
111 |
+
/**
|
112 |
+
* Returns whether the invoice has lines that are to be completed using a tax
|
113 |
+
* divide strategy.
|
114 |
+
*
|
115 |
+
* @return bool
|
116 |
+
*/
|
117 |
+
protected function invoiceHasStrategyLine()
|
118 |
+
{
|
119 |
+
$result = false;
|
120 |
+
foreach ($this->invoiceLines as $line) {
|
121 |
+
if ($line['meta-vatrate-source'] === Creator::VatRateSource_Strategy
|
122 |
+
|| (!empty($line['meta-strategy-split']) && isset($line['meta-vatrate-matches']))
|
123 |
+
) {
|
124 |
+
$result = true;
|
125 |
+
break;
|
126 |
+
}
|
127 |
+
}
|
128 |
+
return $result;
|
129 |
+
}
|
130 |
+
|
131 |
+
/**
|
132 |
+
* Returns a list of strategy class names.
|
133 |
+
*
|
134 |
+
* @return string[]
|
135 |
+
*/
|
136 |
+
protected function getStrategyClasses()
|
137 |
+
{
|
138 |
+
$result = array();
|
139 |
+
|
140 |
+
// For now hardcoded, but this can be turned into a discovery.
|
141 |
+
$namespace = '\Siel\Acumulus\Invoice\CompletorStrategy';
|
142 |
+
$result[] = "$namespace\\SplitNonMatchingLine";
|
143 |
+
$result[] = "$namespace\\SplitKnownDiscountLine";
|
144 |
+
$result[] = "$namespace\\ApplySameVatRate";
|
145 |
+
$result[] = "$namespace\\SplitLine";
|
146 |
+
$result[] = "$namespace\\TryAllVatRatePermutations";
|
147 |
+
$result[] = "$namespace\\Fail";
|
148 |
+
|
149 |
+
return $result;
|
150 |
+
}
|
151 |
+
|
152 |
+
/**
|
153 |
+
* Replaces all completed strategy lines with the given completed lines.
|
154 |
+
*
|
155 |
+
* @param int[] $linesCompleted
|
156 |
+
* @param array[] $completedLines
|
157 |
+
* An array of completed invoice lines to replace the strategy lines with.
|
158 |
+
*/
|
159 |
+
protected function replaceLinesCompleted(array $linesCompleted, array $completedLines)
|
160 |
+
{
|
161 |
+
// Remove old strategy lines that are now completed.
|
162 |
+
$lines = array();
|
163 |
+
foreach ($this->invoice['customer']['invoice']['line'] as $key => $line) {
|
164 |
+
if (!in_array($key, $linesCompleted)) {
|
165 |
+
$lines[] = $line;
|
166 |
+
}
|
167 |
+
}
|
168 |
+
|
169 |
+
// And merge in the new completed ones.
|
170 |
+
foreach ($completedLines as &$completedLine) {
|
171 |
+
if ($completedLine['meta-vatrate-source'] === Creator::VatRateSource_Strategy) {
|
172 |
+
$completedLine['meta-vatrate-source'] = Completor::VatRateSource_Strategy_Completed;
|
173 |
+
}
|
174 |
+
}
|
175 |
+
$this->invoice['customer']['invoice']['line'] = array_merge($lines, $completedLines);
|
176 |
+
}
|
177 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/ConfigInterface.php
ADDED
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Invoice;
|
3 |
+
|
4 |
+
/**
|
5 |
+
* InvoiceConfigInterface defines an interface to store and retrieve invoice
|
6 |
+
* specific configuration values.
|
7 |
+
*
|
8 |
+
* Configuration is stored in the host environment (normally a web shop), this
|
9 |
+
* interface abstracts from how a specific web shop does so.
|
10 |
+
*/
|
11 |
+
interface ConfigInterface
|
12 |
+
{
|
13 |
+
// Invoice API related constants.
|
14 |
+
const PaymentStatus_Due = 1;
|
15 |
+
const PaymentStatus_Paid = 2;
|
16 |
+
|
17 |
+
const Concept_No = 0;
|
18 |
+
const Concept_Yes = 1;
|
19 |
+
|
20 |
+
const InvoiceNrSource_ShopInvoice = 1;
|
21 |
+
const InvoiceNrSource_ShopOrder = 2;
|
22 |
+
const InvoiceNrSource_Acumulus = 3;
|
23 |
+
|
24 |
+
const InvoiceDate_InvoiceCreate = 1;
|
25 |
+
const InvoiceDate_OrderCreate = 2;
|
26 |
+
const InvoiceDate_Transfer = 3;
|
27 |
+
|
28 |
+
const OverwriteIfExists_No = 0;
|
29 |
+
const OverwriteIfExists_Yes = 1;
|
30 |
+
|
31 |
+
const ConfirmReading_No = 0;
|
32 |
+
const ConfirmReading_Yes = 1;
|
33 |
+
|
34 |
+
const DigitalServices_Unknown = 0;
|
35 |
+
const DigitalServices_Both = 1;
|
36 |
+
const DigitalServices_No = 2;
|
37 |
+
const DigitalServices_Only = 3;
|
38 |
+
|
39 |
+
const VatFreeProducts_Unknown = 0;
|
40 |
+
const VatFreeProducts_Both = 1;
|
41 |
+
const VatFreeProducts_No = 2;
|
42 |
+
const VatFreeProducts_Only = 3;
|
43 |
+
|
44 |
+
const VatType_National = 1;
|
45 |
+
const VatType_NationalReversed = 2;
|
46 |
+
const VatType_EuReversed = 3;
|
47 |
+
const VatType_RestOfWorld = 4;
|
48 |
+
const VatType_MarginScheme = 5;
|
49 |
+
const VatType_ForeignVat = 6;
|
50 |
+
|
51 |
+
/**
|
52 |
+
* Returns the set of settings related to the customer part of an invoice.
|
53 |
+
*
|
54 |
+
* @return array
|
55 |
+
* A keyed array with the keys:
|
56 |
+
* - defaultCustomerType
|
57 |
+
* - sendCustomer
|
58 |
+
* - genericCustomerEmail
|
59 |
+
* - overwriteIfExists
|
60 |
+
*/
|
61 |
+
public function getCustomerSettings();
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Returns the set of settings related to the invoice part of an invoice.
|
65 |
+
*
|
66 |
+
* @return array
|
67 |
+
* A keyed array with the keys:
|
68 |
+
* - digitalServices
|
69 |
+
* - defaultAccountNumber
|
70 |
+
* - defaultCostCenter
|
71 |
+
* - defaultInvoiceTemplate
|
72 |
+
* - defaultInvoicePaidTemplate
|
73 |
+
* - removeEmptyShipping
|
74 |
+
* - useMargin
|
75 |
+
*/
|
76 |
+
public function getInvoiceSettings();
|
77 |
+
|
78 |
+
|
79 |
+
/**
|
80 |
+
* Returns the set of settings related to the shop characteristics that
|
81 |
+
* influence the invoice creation and completion
|
82 |
+
*
|
83 |
+
* @return array
|
84 |
+
* A keyed array with the keys:
|
85 |
+
* - invoiceNrSource
|
86 |
+
* - dateToUse
|
87 |
+
*/
|
88 |
+
public function getShopSettings();
|
89 |
+
|
90 |
+
/**
|
91 |
+
* Returns the set of settings related to sending an email.
|
92 |
+
*
|
93 |
+
* @return array
|
94 |
+
* A keyed array with the keys:
|
95 |
+
* - emailAsPdf
|
96 |
+
* - emailBcc
|
97 |
+
* - emailFrom
|
98 |
+
* - subject
|
99 |
+
* - confirmReading
|
100 |
+
*/
|
101 |
+
public function getEmailAsPdfSettings();
|
102 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/Creator.php
ADDED
@@ -0,0 +1,908 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Invoice;
|
3 |
+
|
4 |
+
use Exception;
|
5 |
+
use Siel\Acumulus\Helpers\Countries;
|
6 |
+
use Siel\Acumulus\Helpers\Number;
|
7 |
+
use Siel\Acumulus\Helpers\TranslatorInterface;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Allows to create arrays in the Acumulus invoice structure from a web shop
|
11 |
+
* order or credit note. This array can than be sent to Acumulus using the
|
12 |
+
* Invoice:Add Acumulus API call.
|
13 |
+
*
|
14 |
+
* See https://apidoc.sielsystems.nl/content/invoice-add for the structure. In
|
15 |
+
* addition to the scheme as defined over there, additional keys or values are
|
16 |
+
* accepted:
|
17 |
+
* - all keys starting with meta- are used for debugging purposes.
|
18 |
+
* - other keys are used to complete and correct the invoice in the completor
|
19 |
+
* stage.
|
20 |
+
* - (PHP) null values indicates the absence of a value that should be there but
|
21 |
+
* could not be (easily) retrieved from the invoice source. These should be
|
22 |
+
* replaced with actual values in the completor stage as well.
|
23 |
+
*
|
24 |
+
* This base class:
|
25 |
+
* - Implements the basic break down into smaller actions that web shops should
|
26 |
+
* subsequently implement.
|
27 |
+
* - Provides helper methods for some recurring functionality.
|
28 |
+
*/
|
29 |
+
abstract class Creator
|
30 |
+
{
|
31 |
+
const VatRateSource_Exact = 'exact';
|
32 |
+
const VatRateSource_Exact0 = 'exact-0';
|
33 |
+
const VatRateSource_Calculated = 'calculated';
|
34 |
+
const VatRateSource_Completor = 'completor';
|
35 |
+
const VatRateSource_Strategy = 'strategy';
|
36 |
+
|
37 |
+
const LineType_Shipping = 'shipping';
|
38 |
+
const LineType_PaymentFee = 'payment';
|
39 |
+
const LineType_GiftWrapping = 'gift';
|
40 |
+
const LineType_Manual = 'manual';
|
41 |
+
const LineType_Order = 'product';
|
42 |
+
const LineType_Discount = 'discount';
|
43 |
+
const LineType_Voucher = 'voucher';
|
44 |
+
const LineType_Other = 'other';
|
45 |
+
const LineType_Corrector = 'missing-amount-corrector';
|
46 |
+
|
47 |
+
/** @var \Siel\Acumulus\Shop\Config */
|
48 |
+
protected $config;
|
49 |
+
|
50 |
+
/** @var \Siel\Acumulus\Helpers\TranslatorInterface */
|
51 |
+
protected $translator;
|
52 |
+
|
53 |
+
/** @var \Siel\Acumulus\Helpers\Countries */
|
54 |
+
protected $countries;
|
55 |
+
|
56 |
+
/** @var array Resulting Acumulus invoice */
|
57 |
+
protected $invoice = array();
|
58 |
+
|
59 |
+
/** @var Source */
|
60 |
+
protected $invoiceSource;
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Constructor.
|
64 |
+
*
|
65 |
+
* @param \Siel\Acumulus\Invoice\ConfigInterface $config
|
66 |
+
* @param \Siel\Acumulus\Helpers\TranslatorInterface $translator
|
67 |
+
*/
|
68 |
+
public function __construct(ConfigInterface $config, TranslatorInterface $translator)
|
69 |
+
{
|
70 |
+
$this->config = $config;
|
71 |
+
|
72 |
+
$this->translator = $translator;
|
73 |
+
$invoiceHelperTranslations = new Translations();
|
74 |
+
$this->translator->add($invoiceHelperTranslations);
|
75 |
+
|
76 |
+
$this->countries = new Countries();
|
77 |
+
}
|
78 |
+
|
79 |
+
/**
|
80 |
+
* Helper method to translate strings.
|
81 |
+
*
|
82 |
+
* @param string $key
|
83 |
+
* The key to get a translation for.
|
84 |
+
*
|
85 |
+
* @return string
|
86 |
+
* The translation for the given key or the key itself if no translation
|
87 |
+
* could be found.
|
88 |
+
*/
|
89 |
+
protected function t($key)
|
90 |
+
{
|
91 |
+
return $this->translator->get($key);
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Sets the source to create the invoice for.
|
96 |
+
*
|
97 |
+
* @param Source $invoiceSource
|
98 |
+
*/
|
99 |
+
protected function setInvoiceSource($invoiceSource)
|
100 |
+
{
|
101 |
+
$this->invoiceSource = $invoiceSource;
|
102 |
+
if (!in_array($invoiceSource->getType(), array(Source::Order, Source::CreditNote))) {
|
103 |
+
$this->config->getLog()->error('Creator::setSource(): unknown source type %s', $this->invoiceSource->getType());
|
104 |
+
};
|
105 |
+
}
|
106 |
+
|
107 |
+
/**
|
108 |
+
* Creates an Acumulus invoice from an order or credit note.
|
109 |
+
*
|
110 |
+
* @param Source $source
|
111 |
+
* The web shop order.
|
112 |
+
*
|
113 |
+
* @return array
|
114 |
+
* The acumulus invoice for this order.
|
115 |
+
*/
|
116 |
+
public function create($source)
|
117 |
+
{
|
118 |
+
$this->setInvoiceSource($source);
|
119 |
+
$this->invoice = array();
|
120 |
+
$this->invoice['customer'] = $this->getCustomer();
|
121 |
+
$this->addCustomerDefaults();
|
122 |
+
$this->invoice['customer']['invoice'] = $this->getInvoice();
|
123 |
+
$this->addInvoiceDefaults();
|
124 |
+
$this->addInvoiceTotals();
|
125 |
+
$this->invoice['customer']['invoice']['line'] = $this->getInvoiceLines();
|
126 |
+
$this->addEmailAsPdf();
|
127 |
+
return $this->invoice;
|
128 |
+
}
|
129 |
+
|
130 |
+
/**
|
131 |
+
* Returns the 'customer' part of the invoice add structure.
|
132 |
+
*
|
133 |
+
* The following keys are allowed/expected by the API:
|
134 |
+
* - type: not needed, will be filled by the Completor
|
135 |
+
* - contactid: not expected: Acumulus id for this customer, in the absence of
|
136 |
+
* this value, the API uses
|
137 |
+
* - contactyourid: shop customer id
|
138 |
+
* - companyname1
|
139 |
+
* - companyname2
|
140 |
+
* - fullname
|
141 |
+
* - salutation
|
142 |
+
* - address1
|
143 |
+
* - address2
|
144 |
+
* - postalcode
|
145 |
+
* - city
|
146 |
+
* - countrycode
|
147 |
+
* - vatnumber
|
148 |
+
* - telephone
|
149 |
+
* - fax
|
150 |
+
* - email: used to identify clients.
|
151 |
+
* - overwriteifexists: not needed, will be filled by the Completor
|
152 |
+
* - bankaccountnumber
|
153 |
+
* - mark
|
154 |
+
*
|
155 |
+
* @return array
|
156 |
+
* A keyed array with the customer data.
|
157 |
+
*/
|
158 |
+
abstract protected function getCustomer();
|
159 |
+
|
160 |
+
/**
|
161 |
+
* Completes the customer part with default settings that do not depend on
|
162 |
+
* shop specific data.
|
163 |
+
*/
|
164 |
+
protected function addCustomerDefaults()
|
165 |
+
{
|
166 |
+
$customerSettings = $this->config->getCustomerSettings();
|
167 |
+
$this->addDefault($this->invoice['customer'], 'overwriteifexists', $customerSettings['overwriteIfExists'] ? ConfigInterface::OverwriteIfExists_Yes : ConfigInterface::OverwriteIfExists_No);
|
168 |
+
$this->addDefault($this->invoice['customer'], 'type', $customerSettings['defaultCustomerType']);
|
169 |
+
if (!empty($customerSettings['salutation'])) {
|
170 |
+
$this->invoice['customer']['salutation'] = $this->getSalutation($customerSettings['salutation']);
|
171 |
+
}
|
172 |
+
$this->addDefault($this->invoice['customer'], 'countrycode', 'nl');
|
173 |
+
$this->convertEuCountryCode();
|
174 |
+
$this->addDefault($this->invoice['customer'], 'country', $this->countries->getCountryName($this->invoice['customer']['countrycode']));
|
175 |
+
}
|
176 |
+
|
177 |
+
/**
|
178 |
+
* Returns the salutation based on the (customer) setting for the salutation.
|
179 |
+
*
|
180 |
+
* This base implementation will do token expansion by searching for tokens
|
181 |
+
* and calling the getProperty() method for each token found. So, normally it
|
182 |
+
* won't be necessary to override this method, as overriding getProperty will
|
183 |
+
* be more likely.
|
184 |
+
*
|
185 |
+
* @param string $salutation
|
186 |
+
*
|
187 |
+
* @return string
|
188 |
+
* The salutation for the customer of this order.
|
189 |
+
*/
|
190 |
+
protected function getSalutation($salutation)
|
191 |
+
{
|
192 |
+
$salutation = preg_replace_callback('/\[#([^]]+)]/', array($this, 'salutationMatch'), $salutation);
|
193 |
+
return $salutation;
|
194 |
+
}
|
195 |
+
|
196 |
+
/**
|
197 |
+
* Callback for the preg_replace_callback call in Creator::getSalutation().
|
198 |
+
*
|
199 |
+
* This base implementation call the getProperty() method with the part of the
|
200 |
+
* match between the #s, so, normally it won't be necessary to override this
|
201 |
+
* method, as overriding getProperty will be more likely.
|
202 |
+
*
|
203 |
+
* @param array $matches
|
204 |
+
*
|
205 |
+
* @return string
|
206 |
+
* The salutation for the customer of this order.
|
207 |
+
*/
|
208 |
+
protected function salutationMatch($matches)
|
209 |
+
{
|
210 |
+
return $this->searchProperty($matches[1]);
|
211 |
+
}
|
212 |
+
|
213 |
+
/**
|
214 |
+
* Searches for a property in the various shop specific arrays and/or objects.
|
215 |
+
*
|
216 |
+
* This base implementation only looks up in the shop source object or array,
|
217 |
+
* but that may be insufficient if the customer data is in a separate object
|
218 |
+
* or array.
|
219 |
+
*
|
220 |
+
* @param string $property
|
221 |
+
*
|
222 |
+
* @return string
|
223 |
+
*
|
224 |
+
*/
|
225 |
+
protected function searchProperty($property)
|
226 |
+
{
|
227 |
+
return $this->getProperty($property, $this->invoiceSource->getSource());
|
228 |
+
}
|
229 |
+
|
230 |
+
/**
|
231 |
+
* Looks up a property in the web shop specific order object/array.
|
232 |
+
*
|
233 |
+
* This default implementation looks for the property in the following ways:
|
234 |
+
* If the passed parameter is an array:
|
235 |
+
* - looking up the property as key.
|
236 |
+
* If the passed parameter is an object:
|
237 |
+
* - Looking up the property by name (as existing property or via __get).
|
238 |
+
* - Calling the get{Property} getter.
|
239 |
+
* - Calling the {property}() method (as existing method or via __call).
|
240 |
+
*
|
241 |
+
* Override if the property name or getter method is constructed differently.
|
242 |
+
*
|
243 |
+
* @param string $property
|
244 |
+
* @param object|array $object
|
245 |
+
*
|
246 |
+
* @return string
|
247 |
+
* The value for the property of the given name or the empty string if not
|
248 |
+
* available.
|
249 |
+
*/
|
250 |
+
protected function getProperty($property, $object)
|
251 |
+
{
|
252 |
+
$value = '';
|
253 |
+
if (is_array($object)) {
|
254 |
+
if (isset($object[$property])) {
|
255 |
+
$value = $object[$property];
|
256 |
+
}
|
257 |
+
} else {
|
258 |
+
if (isset($object->$property)) {
|
259 |
+
$value = $object->$property;
|
260 |
+
} else if (method_exists($object, $property)) {
|
261 |
+
$value = $object->$property();
|
262 |
+
} else {
|
263 |
+
$method = 'get' . ucfirst($property);
|
264 |
+
if (method_exists($object, $method)) {
|
265 |
+
$value = $object->$method();
|
266 |
+
} else if (method_exists($object, '__get')) {
|
267 |
+
@$value = $object->$property;
|
268 |
+
} else if (method_exists($object, '__call')) {
|
269 |
+
try {
|
270 |
+
$value = @$object->$property();
|
271 |
+
} catch (Exception $e) {
|
272 |
+
}
|
273 |
+
if (empty($value)) {
|
274 |
+
try {
|
275 |
+
$value = $object->$method();
|
276 |
+
} catch (Exception $e) {
|
277 |
+
}
|
278 |
+
}
|
279 |
+
}
|
280 |
+
}
|
281 |
+
}
|
282 |
+
return $value;
|
283 |
+
}
|
284 |
+
|
285 |
+
/**
|
286 |
+
* Wrapper around Countries::convertEuCountryCod().
|
287 |
+
*
|
288 |
+
* Converts EU country codes to their ISO equivalent:
|
289 |
+
* - Change UK to GB
|
290 |
+
* - Change EL to GR.
|
291 |
+
*
|
292 |
+
* This could/should be server side, but for now it is done client side.
|
293 |
+
*/
|
294 |
+
protected function convertEuCountryCode()
|
295 |
+
{
|
296 |
+
$this->invoice['customer']['countrycode'] = $this->countries->convertEuCountryCode($this->invoice['customer']['countrycode']);
|
297 |
+
}
|
298 |
+
|
299 |
+
/**
|
300 |
+
* Returns the 'invoice' part of the invoice add structure.
|
301 |
+
*
|
302 |
+
* The following keys are allowed/expected by the API:
|
303 |
+
* - concept: not needed, will be filled by the Completor
|
304 |
+
* - number
|
305 |
+
* - vattype: not needed, will be filled by the Completor
|
306 |
+
* - issuedate
|
307 |
+
* - costcenter: not needed, will be filled by the Completor
|
308 |
+
* - accountnumber: not needed, will be filled by the Completor
|
309 |
+
* - paymentstatus
|
310 |
+
* - paymentdate
|
311 |
+
* - description
|
312 |
+
* - template: not needed, will be filled by the Completor
|
313 |
+
*
|
314 |
+
* Additional keys (not recognised by the API but used by the Completor or
|
315 |
+
* for support and debugging purposes):
|
316 |
+
* - meta-invoice-amount: the total invoice amount excluding VAT.
|
317 |
+
* - meta-invoice-amountinc: the total invoice amount including VAT.
|
318 |
+
* - meta-invoice-vatamount: the total vat amount for the invoice.
|
319 |
+
* - meta-lines-amount: the total invoice amount excluding VAT.
|
320 |
+
* - meta-lines-vatamount: the total vat amount for the invoice.
|
321 |
+
*
|
322 |
+
* Extending classes should normally not have to override this method, but
|
323 |
+
* should instead implement getInvoiceNumber(), getInvoiceDate(),
|
324 |
+
* getPaymentState(), getPaymentDate(), and, optionally, getDescription().
|
325 |
+
*
|
326 |
+
* @return array
|
327 |
+
* A keyed array with the invoice data (without the invoice lines).
|
328 |
+
*/
|
329 |
+
protected function getInvoice()
|
330 |
+
{
|
331 |
+
$result = array();
|
332 |
+
|
333 |
+
$shopSettings = $this->config->getShopSettings();
|
334 |
+
|
335 |
+
$sourceToUse = $shopSettings['invoiceNrSource'];
|
336 |
+
if ($sourceToUse != ConfigInterface::InvoiceNrSource_Acumulus) {
|
337 |
+
$result['number'] = $this->getInvoiceNumber($sourceToUse);
|
338 |
+
}
|
339 |
+
|
340 |
+
$dateToUse = $shopSettings['dateToUse'];
|
341 |
+
if ($dateToUse != ConfigInterface::InvoiceDate_Transfer) {
|
342 |
+
$result['issuedate'] = $this->getInvoiceDate($dateToUse);
|
343 |
+
}
|
344 |
+
|
345 |
+
// Payment status and date.
|
346 |
+
$result['paymentstatus'] = $this->getPaymentState();
|
347 |
+
if ($result['paymentstatus'] === ConfigInterface::PaymentStatus_Paid) {
|
348 |
+
$this->addIfNotEmpty($result, 'paymentdate', $this->getPaymentDate());
|
349 |
+
}
|
350 |
+
|
351 |
+
$result['description'] = $this->getDescription();
|
352 |
+
|
353 |
+
return $result;
|
354 |
+
}
|
355 |
+
|
356 |
+
/**
|
357 |
+
* Completes the invoice part with default settings that do not depend on
|
358 |
+
* shop specific data.
|
359 |
+
*/
|
360 |
+
protected function addInvoiceDefaults()
|
361 |
+
{
|
362 |
+
$invoiceSettings = $this->config->getInvoiceSettings();
|
363 |
+
$invoice = &$this->invoice['customer']['invoice'];
|
364 |
+
$this->addDefault($invoice, 'concept', ConfigInterface::Concept_No);
|
365 |
+
$this->addDefault($invoice, 'accountnumber', $invoiceSettings['defaultAccountNumber']);
|
366 |
+
$this->addDefault($invoice, 'costcenter', $invoiceSettings['defaultCostCenter']);
|
367 |
+
if (isset($invoice['paymentstatus'])
|
368 |
+
&& $invoice['paymentstatus'] == ConfigInterface::PaymentStatus_Paid
|
369 |
+
// 0 = empty = use same invoice template as for non paid invoices
|
370 |
+
&& $invoiceSettings['defaultInvoicePaidTemplate'] != 0
|
371 |
+
) {
|
372 |
+
$this->addDefault($invoice, 'template', $invoiceSettings['defaultInvoicePaidTemplate']);
|
373 |
+
} else {
|
374 |
+
$this->addDefault($invoice, 'template', $invoiceSettings['defaultInvoiceTemplate']);
|
375 |
+
}
|
376 |
+
}
|
377 |
+
|
378 |
+
/**
|
379 |
+
* Returns the number to use as invoice number.
|
380 |
+
*
|
381 |
+
* @param int $invoiceNumberSource
|
382 |
+
* \Siel\Acumulus\Invoice\ConfigInterface\InvoiceNrSource_ShopInvoice or
|
383 |
+
* \Siel\Acumulus\Invoice\ConfigInterface\InvoiceNrSource_ShopOrder.
|
384 |
+
*
|
385 |
+
* @return string
|
386 |
+
* The number to use as "invoice number" on the invoice, may contain a
|
387 |
+
* prefix.
|
388 |
+
*/
|
389 |
+
abstract protected function getInvoiceNumber($invoiceNumberSource);
|
390 |
+
|
391 |
+
/**
|
392 |
+
* Returns the date to use as invoice date.
|
393 |
+
*
|
394 |
+
* @param int $dateToUse
|
395 |
+
* \Siel\Acumulus\Invoice\ConfigInterface\InvoiceDate_InvoiceCreate or
|
396 |
+
* \Siel\Acumulus\Invoice\ConfigInterface\InvoiceDate_OrderCreate
|
397 |
+
*
|
398 |
+
* @return string
|
399 |
+
* Date to send to Acumulus as the invoice date: yyyy-mm-dd.
|
400 |
+
*/
|
401 |
+
protected function getInvoiceDate($dateToUse)
|
402 |
+
{
|
403 |
+
return $this->callSourceTypeSpecificMethod(__FUNCTION__, func_get_args());
|
404 |
+
}
|
405 |
+
|
406 |
+
/**
|
407 |
+
* Returns whether the order has been paid or not.
|
408 |
+
*
|
409 |
+
* @return int
|
410 |
+
* \Siel\Acumulus\Invoice\ConfigInterface::PaymentStatus_Paid or
|
411 |
+
* \Siel\Acumulus\Invoice\ConfigInterface::PaymentStatus_Due
|
412 |
+
*/
|
413 |
+
protected function getPaymentState()
|
414 |
+
{
|
415 |
+
return $this->callSourceTypeSpecificMethod(__FUNCTION__, func_get_args());
|
416 |
+
}
|
417 |
+
|
418 |
+
/**
|
419 |
+
* Returns the payment date.
|
420 |
+
*
|
421 |
+
* The payment date is defined as the date on which the status changed from a
|
422 |
+
* non-paid state to a paid state. If there are multiple state changes, the
|
423 |
+
* last one is taken.
|
424 |
+
*
|
425 |
+
* @return string|null
|
426 |
+
* The payment date (yyyy-mm-dd) or null if the order has not been paid yet.
|
427 |
+
*/
|
428 |
+
protected function getPaymentDate()
|
429 |
+
{
|
430 |
+
return $this->callSourceTypeSpecificMethod(__FUNCTION__, func_get_args());
|
431 |
+
}
|
432 |
+
|
433 |
+
/**
|
434 |
+
* Returns the description for this invoice.
|
435 |
+
*
|
436 |
+
* This default implementation returns something like "Order 123".
|
437 |
+
*
|
438 |
+
* @return string
|
439 |
+
* Description of this invoice
|
440 |
+
*/
|
441 |
+
protected function getDescription()
|
442 |
+
{
|
443 |
+
return ucfirst($this->t($this->invoiceSource->getType())) . ' ' . $this->invoiceSource->getReference();
|
444 |
+
}
|
445 |
+
|
446 |
+
/**
|
447 |
+
* Adds metadata about invoice totals to the invoice.
|
448 |
+
*/
|
449 |
+
protected function addInvoiceTotals()
|
450 |
+
{
|
451 |
+
$this->invoice['customer']['invoice'] += $this->getInvoiceTotals();
|
452 |
+
$this->completeInvoiceTotals();
|
453 |
+
}
|
454 |
+
|
455 |
+
/**
|
456 |
+
* Returns an array with the totals fields.
|
457 |
+
*
|
458 |
+
* All total fields are optional but may be used or even expected by the
|
459 |
+
* Completor or are used for support and debugging purposes.
|
460 |
+
*
|
461 |
+
* This default implementation returns an empty array. Override to provide the
|
462 |
+
* values.
|
463 |
+
*
|
464 |
+
* @return array
|
465 |
+
* An array with the following possible keys:
|
466 |
+
* - meta-invoice-amount: the total invoice amount excluding VAT.
|
467 |
+
* - meta-invoice-amountinc: the total invoice amount including VAT.
|
468 |
+
* - meta-invoice-vatamount: the total vat amount for the invoice.
|
469 |
+
* - meta-invoice-vat: a vat breakdown per vat rate.
|
470 |
+
*/
|
471 |
+
abstract protected function getInvoiceTotals();
|
472 |
+
|
473 |
+
/**
|
474 |
+
* Completes the set of invoice totals as set by getInvoiceTotals.
|
475 |
+
*
|
476 |
+
* Most shops only provide 2 out of these 3 in their data, so we calculate the
|
477 |
+
* 3rd.
|
478 |
+
*/
|
479 |
+
protected function completeInvoiceTotals()
|
480 |
+
{
|
481 |
+
$invoice = &$this->invoice['customer']['invoice'];
|
482 |
+
if (!isset($invoice['meta-invoice-amount'])) {
|
483 |
+
$invoice['meta-invoice-amount'] = $invoice['meta-invoice-amountinc'] - $invoice['meta-invoice-vatamount'];
|
484 |
+
$invoice['meta-invoice-calculated'] = 'meta-invoice-amount';
|
485 |
+
}
|
486 |
+
if (!isset($invoice['meta-invoice-amountinc'])) {
|
487 |
+
$invoice['meta-invoice-amountinc'] = $invoice['meta-invoice-amount'] + $invoice['meta-invoice-vatamount'];
|
488 |
+
$invoice['meta-invoice-calculated'] = 'meta-invoice-amountinc';
|
489 |
+
}
|
490 |
+
if (!isset($invoice['meta-invoice-vatamount'])) {
|
491 |
+
$invoice['meta-invoice-vatamount'] = $invoice['meta-invoice-amountinc'] - $invoice['meta-invoice-amount'];
|
492 |
+
$invoice['meta-invoice-calculated'] = 'meta-invoice-vatamount';
|
493 |
+
}
|
494 |
+
}
|
495 |
+
|
496 |
+
/**
|
497 |
+
* Returns the 'invoice''line' part of the invoice add structure.
|
498 |
+
*
|
499 |
+
* Each invoice line is a keyed array.
|
500 |
+
* The following keys are allowed/expected by the API:
|
501 |
+
* -itemnumber
|
502 |
+
* -product
|
503 |
+
* -unitprice
|
504 |
+
* -vatrate
|
505 |
+
* -quantity
|
506 |
+
* -costprice: optional, only for margin products
|
507 |
+
*
|
508 |
+
* Additional keys (not recognised by the API but used by the Completor or
|
509 |
+
* for support and debugging purposes):
|
510 |
+
* - unitpriceinc: the price of the item per unit including VAT.
|
511 |
+
* - vatamount: the amount of vat per unit.
|
512 |
+
* - meta-vatrate-source: the source for the vatrate value. Can be one of:
|
513 |
+
* - exact: should be an existing VAT rate.
|
514 |
+
* - calculated: should be close to an existing VAT rate, but may contain a
|
515 |
+
* rounding error.
|
516 |
+
* - completor: to be filled in by the completor.
|
517 |
+
* - strategy: to be filled in by a tax divide strategy. This may lead to
|
518 |
+
* this line being split into multiple lines.
|
519 |
+
* - meta-vatrate-min: the minimum value for the vat rate, based on the
|
520 |
+
* precision of the numbers used to calculate the vat rate.
|
521 |
+
* - meta-vatrate-max: the maximum value for the vat rate, based on the
|
522 |
+
* precision of the numbers used to calculate the vat rate.
|
523 |
+
* - meta-strategy-split: boolean that indicates if this line may be split
|
524 |
+
* into multiple lines to divide vat.
|
525 |
+
* - meta-line-price: the total price for this line excluding VAT.
|
526 |
+
* - meta-line-priceinc: the total price for this line including VAT.
|
527 |
+
* - meta-line-vatamount: the amount of VAT for the whole line.
|
528 |
+
* - meta-line-type: the type of line (order, shipping, discount, etc.)
|
529 |
+
* (*) = these are not yet used.
|
530 |
+
*
|
531 |
+
* These keys can be used to complete missing values or to assist in
|
532 |
+
* correcting rounding errors in values that are present.
|
533 |
+
*
|
534 |
+
* The base CompletorInvoiceLines expects;
|
535 |
+
* - meta-vatrate-source to be filled
|
536 |
+
* - If meta-vatrate-source = exact: no other keys expected
|
537 |
+
* - If meta-vatrate-source = calculated: meta-vatrate-min and
|
538 |
+
* meta-vatrate-max are expected to be filled. These values should come
|
539 |
+
* from calling the helper method getDivisionRange() with the values used to
|
540 |
+
* calculate the vat rate and their precision.
|
541 |
+
* - If meta-vatrate-source = completor: vatrate should be null and unitprice
|
542 |
+
* should be 0. The completor will typically fill vatrate with the highest
|
543 |
+
* or most appearing vat rate, looking at the exact and calculated (after
|
544 |
+
* correcting them for rounding errors) vat rates.
|
545 |
+
* - If meta-vatrate-source = strategy: vat rate should be null and either
|
546 |
+
* unitprice or unitpriceinc should be filled wit a non-0 amount (typically
|
547 |
+
* a negative amount as this is mostly used for spreading discounts over tax
|
548 |
+
* rates). Moreover, on the invoice level meta-invoice-amount and
|
549 |
+
* meta-invoice-vatamount should be filled in. The completor will use a tax
|
550 |
+
* divide strategy to arrive at valid values for the missing fields.
|
551 |
+
*
|
552 |
+
* Extending classes should normally not have to override this method, but
|
553 |
+
* should instead implement getItemLines(), getManualLines(),
|
554 |
+
* getGiftWrappingLine(), getShippingLine(), getPaymentFeeLine(),
|
555 |
+
* and getDiscountLines().
|
556 |
+
*
|
557 |
+
* @return array[]
|
558 |
+
* A non keyed array with all invoice lines.
|
559 |
+
*/
|
560 |
+
protected function getInvoiceLines()
|
561 |
+
{
|
562 |
+
$itemLines = $this->getItemLines();
|
563 |
+
$itemLines = $this->addLineType($itemLines, static::LineType_Order);
|
564 |
+
|
565 |
+
$feeLines = $this->getFeeLines();
|
566 |
+
|
567 |
+
$discountLines = $this->getDiscountLines();
|
568 |
+
$discountLines = $this->addLineType($discountLines, static::LineType_Discount);
|
569 |
+
|
570 |
+
$manualLines = $this->getManualLines();
|
571 |
+
$manualLines = $this->addLineType($manualLines, static::LineType_Manual);
|
572 |
+
|
573 |
+
$result = array_merge($itemLines, $feeLines, $discountLines, $manualLines);
|
574 |
+
return $result;
|
575 |
+
}
|
576 |
+
|
577 |
+
/**
|
578 |
+
* Returns the item/product lines of the order.
|
579 |
+
*
|
580 |
+
* @return array[]
|
581 |
+
* Array of item line arrays.
|
582 |
+
*/
|
583 |
+
protected function getItemLines()
|
584 |
+
{
|
585 |
+
return $this->callSourceTypeSpecificMethod(__FUNCTION__, func_get_args());
|
586 |
+
}
|
587 |
+
|
588 |
+
/**
|
589 |
+
* Returns all the fee lines for the order.
|
590 |
+
*
|
591 |
+
* @return array[]
|
592 |
+
* An array of fee line arrays
|
593 |
+
*/
|
594 |
+
protected function getFeeLines()
|
595 |
+
{
|
596 |
+
$result = array();
|
597 |
+
|
598 |
+
$line = $this->getGiftWrappingLine();
|
599 |
+
if ($line) {
|
600 |
+
$line['meta-line-type'] = static::LineType_GiftWrapping;
|
601 |
+
$result[] = $line;
|
602 |
+
}
|
603 |
+
|
604 |
+
$line = $this->getShippingLine();
|
605 |
+
if ($line) {
|
606 |
+
$line['meta-line-type'] = static::LineType_Shipping;
|
607 |
+
$result[] = $line;
|
608 |
+
}
|
609 |
+
|
610 |
+
$line = $this->getPaymentFeeLine();
|
611 |
+
if ($line) {
|
612 |
+
$line['meta-line-type'] = static::LineType_PaymentFee;
|
613 |
+
$result[] = $line;
|
614 |
+
}
|
615 |
+
|
616 |
+
return $result;
|
617 |
+
}
|
618 |
+
|
619 |
+
/**
|
620 |
+
* Returns the gift wrapping costs line.
|
621 |
+
*
|
622 |
+
* This base implementation return an empty array: no gift wrapping.
|
623 |
+
*
|
624 |
+
* @return array
|
625 |
+
* A line array, empty if there is no gift wrapping fee line.
|
626 |
+
*/
|
627 |
+
protected function getGiftWrappingLine()
|
628 |
+
{
|
629 |
+
return array();
|
630 |
+
}
|
631 |
+
|
632 |
+
/**
|
633 |
+
* Returns the shipping costs line.
|
634 |
+
*
|
635 |
+
* To be able to produce a packing slip, a shipping line should normally be
|
636 |
+
* added, even for free shipping.
|
637 |
+
*
|
638 |
+
* @return array
|
639 |
+
* A line array, empty if there is no shipping fee line.
|
640 |
+
*/
|
641 |
+
protected function getShippingLine()
|
642 |
+
{
|
643 |
+
return $this->callSourceTypeSpecificMethod(__FUNCTION__, func_get_args());
|
644 |
+
}
|
645 |
+
|
646 |
+
/**
|
647 |
+
* Returns the payment fee line.
|
648 |
+
*
|
649 |
+
* This base implementation returns an empty array: no payment fee line.
|
650 |
+
*
|
651 |
+
* @return array
|
652 |
+
* A line array, empty if there is no payment fee line.
|
653 |
+
*/
|
654 |
+
protected function getPaymentFeeLine()
|
655 |
+
{
|
656 |
+
return array();
|
657 |
+
}
|
658 |
+
|
659 |
+
/**
|
660 |
+
* Returns any applied discounts and partial payments.
|
661 |
+
*
|
662 |
+
* @return array[]
|
663 |
+
* An array of discount line arrays.
|
664 |
+
*/
|
665 |
+
protected function getDiscountLines()
|
666 |
+
{
|
667 |
+
return $this->callSourceTypeSpecificMethod(__FUNCTION__, func_get_args());
|
668 |
+
}
|
669 |
+
|
670 |
+
/**
|
671 |
+
* Returns any manual lines.
|
672 |
+
*
|
673 |
+
* Manual lines may appear on credit notes to overrule amounts as calculated
|
674 |
+
* by the system. E.g. discounts applied on items should be taken into
|
675 |
+
* account when refunding (while the system did not or does not know if the
|
676 |
+
* discount also applied to that product), shipping costs may be returned
|
677 |
+
* except for the handling costs, etc.
|
678 |
+
*
|
679 |
+
* @return array[]
|
680 |
+
* An array of manual line arrays, may be empty.
|
681 |
+
*/
|
682 |
+
protected function getManualLines()
|
683 |
+
{
|
684 |
+
return array();
|
685 |
+
}
|
686 |
+
|
687 |
+
/**
|
688 |
+
* Adds an emailaspdf section if enabled.
|
689 |
+
*/
|
690 |
+
protected function addEmailAsPdf()
|
691 |
+
{
|
692 |
+
$emailAsPdfSettings = $this->config->getEmailAsPdfSettings();
|
693 |
+
if ($emailAsPdfSettings['emailAsPdf'] && !empty($this->invoice['customer']['email'])) {
|
694 |
+
$emailasPdf = array();
|
695 |
+
$emailasPdf['emailto'] = $this->invoice['customer']['email'];
|
696 |
+
$this->addDefault($emailasPdf, 'emailbcc', $emailAsPdfSettings['emailBcc']);
|
697 |
+
$this->addDefault($emailasPdf, 'emailfrom', $emailAsPdfSettings['emailFrom']);
|
698 |
+
$this->addDefault($emailasPdf, 'subject', strtr($emailAsPdfSettings['subject'], array(
|
699 |
+
'[#b]' => $this->invoiceSource->getReference(),
|
700 |
+
'[#f]' => isset($this->invoice['customer']['invoice']['number']) ? $this->invoice['customer']['invoice']['number'] : '',
|
701 |
+
)));
|
702 |
+
$invoice['customer']['invoice']['emailaspdf']['confirmreading'] = $emailAsPdfSettings['confirmReading'] ? ConfigInterface::ConfirmReading_Yes : ConfigInterface::ConfirmReading_No;
|
703 |
+
|
704 |
+
$this->invoice['customer']['invoice']['emailaspdf'] = $emailasPdf;
|
705 |
+
}
|
706 |
+
}
|
707 |
+
|
708 |
+
/**
|
709 |
+
* Returns whether the margin scheme may be used.
|
710 |
+
*
|
711 |
+
* @return bool
|
712 |
+
*/
|
713 |
+
protected function allowMarginScheme()
|
714 |
+
{
|
715 |
+
$invoiceSettings = $this->config->getInvoiceSettings();
|
716 |
+
return $invoiceSettings['useMargin'];
|
717 |
+
}
|
718 |
+
|
719 |
+
/**
|
720 |
+
* Helper method to add a value from an array or object only if it is set and
|
721 |
+
* not empty.
|
722 |
+
*
|
723 |
+
* @param array $targetArray
|
724 |
+
* @param string $targetKey
|
725 |
+
* @param array|object $source
|
726 |
+
* @param string $sourceKey
|
727 |
+
*
|
728 |
+
* @return bool
|
729 |
+
* Whether the array value or object property is set and not empty and thus
|
730 |
+
* has been added.
|
731 |
+
*/
|
732 |
+
protected function addIfSetAndNotEmpty(array &$targetArray, $targetKey, $source, $sourceKey)
|
733 |
+
{
|
734 |
+
if (is_array($source)) {
|
735 |
+
if (!empty($source[$sourceKey])) {
|
736 |
+
$targetArray[$targetKey] = $source[$sourceKey];
|
737 |
+
return true;
|
738 |
+
}
|
739 |
+
} else {
|
740 |
+
if (!empty($source->$sourceKey)) {
|
741 |
+
$targetArray[$targetKey] = $source->$sourceKey;
|
742 |
+
return true;
|
743 |
+
}
|
744 |
+
}
|
745 |
+
return false;
|
746 |
+
}
|
747 |
+
|
748 |
+
/**
|
749 |
+
* Helper method to add a non-empty value to an array.
|
750 |
+
*
|
751 |
+
* @param array $array
|
752 |
+
* @param string $key
|
753 |
+
* @param mixed $value
|
754 |
+
*
|
755 |
+
* @return bool
|
756 |
+
* True if the value was not empty and thus has been added, false otherwise,
|
757 |
+
*/
|
758 |
+
protected function addIfNotEmpty(array &$array, $key, $value)
|
759 |
+
{
|
760 |
+
if (!empty($value)) {
|
761 |
+
$array[$key] = $value;
|
762 |
+
return true;
|
763 |
+
}
|
764 |
+
return false;
|
765 |
+
}
|
766 |
+
|
767 |
+
/**
|
768 |
+
* Helper method to add a value to an array even if it is not set.
|
769 |
+
*
|
770 |
+
* @param array $array
|
771 |
+
* @param string $key
|
772 |
+
* @param mixed $value
|
773 |
+
* @param mixed $default
|
774 |
+
*
|
775 |
+
* @return bool
|
776 |
+
* True if the value was not empty and thus has been added, false if the
|
777 |
+
* default has been added.,
|
778 |
+
*/
|
779 |
+
protected function addEmpty(array &$array, $key, $value, $default = '')
|
780 |
+
{
|
781 |
+
if (!empty($value)) {
|
782 |
+
$array[$key] = $value;
|
783 |
+
return true;
|
784 |
+
} else {
|
785 |
+
$array[$key] = $default;
|
786 |
+
return false;
|
787 |
+
}
|
788 |
+
}
|
789 |
+
|
790 |
+
/**
|
791 |
+
* Helper method to add a default (without overwriting) value to an array.
|
792 |
+
*
|
793 |
+
* @param array $array
|
794 |
+
* @param string $key
|
795 |
+
* @param mixed $value
|
796 |
+
*
|
797 |
+
* @return bool
|
798 |
+
* Whether the default was added.
|
799 |
+
*/
|
800 |
+
protected function addDefault(array &$array, $key, $value)
|
801 |
+
{
|
802 |
+
if (empty($array[$key]) && !empty($value)) {
|
803 |
+
$array[$key] = $value;
|
804 |
+
return true;
|
805 |
+
}
|
806 |
+
return false;
|
807 |
+
}
|
808 |
+
|
809 |
+
/**
|
810 |
+
* Adds a meta-line-type tag to the lines.
|
811 |
+
*
|
812 |
+
* @param array $itemLines
|
813 |
+
* @param string $lineType
|
814 |
+
*
|
815 |
+
* @return array
|
816 |
+
*
|
817 |
+
*/
|
818 |
+
protected function addLineType(array $itemLines, $lineType)
|
819 |
+
{
|
820 |
+
foreach ($itemLines as &$itemLine) {
|
821 |
+
$itemLine['meta-line-type'] = $lineType;
|
822 |
+
}
|
823 |
+
return $itemLines;
|
824 |
+
}
|
825 |
+
|
826 |
+
/**
|
827 |
+
* Returns the range in which the vat rate will lie.
|
828 |
+
*
|
829 |
+
* If a webshop does not store the vat rates used in the order, we must
|
830 |
+
* calculate them using a (product) price and the vat on it. But as webshops
|
831 |
+
* often store these numbers rounded to cents, the vat rate calculation
|
832 |
+
* becomes imprecise. Therefore we compute the range in which it will lie and
|
833 |
+
* will let the Completor do a comparison with the actual vat rates that an
|
834 |
+
* order can have (one of the Dutch or, for electronic services, other EU
|
835 |
+
* country VAT rates).
|
836 |
+
*
|
837 |
+
* - If $denominator = 0 the vatrate will be set to NULL and the Completor may
|
838 |
+
* try to get this line listed under the correct vat rate.
|
839 |
+
* - If $numerator = 0 the vatrate will be set to 0 and be treated as if it
|
840 |
+
* is an exact vat rate, not a vat range
|
841 |
+
*
|
842 |
+
* @param float $numerator
|
843 |
+
* @param float $denominator
|
844 |
+
* @param float $precisionNumerator
|
845 |
+
* @param float $precisionDenominator
|
846 |
+
*
|
847 |
+
* @return array
|
848 |
+
* Array with keys vatrate, meta-vatrate-min, meta-vatrate-max, and
|
849 |
+
* meta-vatrate-source.
|
850 |
+
*/
|
851 |
+
public static function getVatRangeTags($numerator, $denominator, $precisionNumerator = 0.01, $precisionDenominator = 0.01)
|
852 |
+
{
|
853 |
+
if (Number::isZero($denominator, 0.0001)) {
|
854 |
+
return array(
|
855 |
+
'vatrate' => null,
|
856 |
+
'meta-vatrate-source' => static::VatRateSource_Completor,
|
857 |
+
);
|
858 |
+
} else if (Number::isZero($numerator, 0.0001)) {
|
859 |
+
return array(
|
860 |
+
'vatrate' => 0,
|
861 |
+
'vatamount' => $numerator,
|
862 |
+
'meta-vatrate-source' => static::VatRateSource_Exact0,
|
863 |
+
);
|
864 |
+
} else {
|
865 |
+
$range = Number::getDivisionRange($numerator, $denominator, $precisionNumerator, $precisionDenominator);
|
866 |
+
return array(
|
867 |
+
'vatrate' => 100.0 * $range['calculated'],
|
868 |
+
'vatamount' => $numerator,
|
869 |
+
'meta-vatrate-min' => 100.0 * $range['min'],
|
870 |
+
'meta-vatrate-max' => 100.0 * $range['max'],
|
871 |
+
'meta-vatrate-source' => static::VatRateSource_Calculated,
|
872 |
+
);
|
873 |
+
}
|
874 |
+
}
|
875 |
+
|
876 |
+
/**
|
877 |
+
* Returns the sign to use for amounts that are always defined as a positive
|
878 |
+
* number, also on credit notes.
|
879 |
+
*
|
880 |
+
* @return float
|
881 |
+
* 1 for orders, -1 for credit notes.
|
882 |
+
*/
|
883 |
+
protected function getSign()
|
884 |
+
{
|
885 |
+
return (float) ($this->invoiceSource->getType() === Source::CreditNote ? -1 : 1);
|
886 |
+
}
|
887 |
+
|
888 |
+
/**
|
889 |
+
* Calls a method constructed of the method name and the source type.
|
890 |
+
*
|
891 |
+
* If the implementation/override of a method depends on the type of invoice
|
892 |
+
* source it might be better to implement 1 method per source type. This
|
893 |
+
* method calls such a method assuming it is named {method}{source-type}.
|
894 |
+
* Example: getLineItem($line) would be very different for an order versus a
|
895 |
+
* credit note: do not override the base method but implement 2 new methods
|
896 |
+
* getLineItemOrder($line) and getLineItemCreditNote($line).
|
897 |
+
*
|
898 |
+
* @param string $method
|
899 |
+
* @param array $args
|
900 |
+
*
|
901 |
+
* @return mixed|void
|
902 |
+
*/
|
903 |
+
protected function callSourceTypeSpecificMethod($method, $args = array())
|
904 |
+
{
|
905 |
+
$method .= $this->invoiceSource->getType();
|
906 |
+
return call_user_func_array(array($this, $method), $args);
|
907 |
+
}
|
908 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/Source.php
ADDED
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Siel\Acumulus\Invoice;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Wraps a source for an invoice, typically an order or a credit note.
|
7 |
+
*
|
8 |
+
* By defining a wrapper around orders from a specific web shop we can more
|
9 |
+
* easily (and in a type safe way) pass them around.
|
10 |
+
*/
|
11 |
+
abstract class Source
|
12 |
+
{
|
13 |
+
// Invoice source type constants.
|
14 |
+
const Order = 'Order';
|
15 |
+
const CreditNote = 'CreditNote';
|
16 |
+
const Other = 'Other';
|
17 |
+
|
18 |
+
/** @var string */
|
19 |
+
protected $type;
|
20 |
+
|
21 |
+
/** @var int */
|
22 |
+
protected $id;
|
23 |
+
|
24 |
+
/** @var array|object */
|
25 |
+
protected $source;
|
26 |
+
|
27 |
+
/**
|
28 |
+
* @param string $type
|
29 |
+
* @param int|string|array|object $idOrSource
|
30 |
+
*/
|
31 |
+
public function __construct($type, $idOrSource)
|
32 |
+
{
|
33 |
+
$this->type = $type;
|
34 |
+
if (is_scalar($idOrSource)) {
|
35 |
+
$this->id = $idOrSource;
|
36 |
+
$this->setSource();
|
37 |
+
} else {
|
38 |
+
$this->source = $idOrSource;
|
39 |
+
$this->setId();
|
40 |
+
}
|
41 |
+
}
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Returns the type of the wrapped source.
|
45 |
+
*
|
46 |
+
* @return string
|
47 |
+
* One of the Source constants Source::Order or Source::CreditNote.
|
48 |
+
*/
|
49 |
+
public function getType()
|
50 |
+
{
|
51 |
+
return $this->type;
|
52 |
+
}
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Sets the source based on type and id.
|
56 |
+
*
|
57 |
+
* Should either be overridden or both setSourceOrder() and
|
58 |
+
* setSourceCreditNote() should be implemented.
|
59 |
+
*/
|
60 |
+
protected function setSource()
|
61 |
+
{
|
62 |
+
return $this->callTypeSpecificMethod(__FUNCTION__);
|
63 |
+
}
|
64 |
+
|
65 |
+
/**
|
66 |
+
* Returns the source for an invoice, either an order or a credit note object.
|
67 |
+
*
|
68 |
+
* @return array|object
|
69 |
+
*/
|
70 |
+
public function getSource()
|
71 |
+
{
|
72 |
+
return $this->source;
|
73 |
+
}
|
74 |
+
|
75 |
+
/**
|
76 |
+
* Sets the id based on type and source.
|
77 |
+
*
|
78 |
+
* Should either be overridden or both setIdOrder() and
|
79 |
+
* setIdCreditNote() should be implemented.
|
80 |
+
*/
|
81 |
+
protected function setId()
|
82 |
+
{
|
83 |
+
return $this->callTypeSpecificMethod(__FUNCTION__);
|
84 |
+
}
|
85 |
+
|
86 |
+
/**
|
87 |
+
* Returns the internal id of this invoice source.
|
88 |
+
*
|
89 |
+
* @return int|string
|
90 |
+
* The internal id.
|
91 |
+
*/
|
92 |
+
public function getId()
|
93 |
+
{
|
94 |
+
return $this->id;
|
95 |
+
}
|
96 |
+
|
97 |
+
/**
|
98 |
+
* Returns the user facing reference for this invoice source.
|
99 |
+
*
|
100 |
+
* Should be overridden when this is not the internal id.
|
101 |
+
*
|
102 |
+
* @return string|int
|
103 |
+
* The user facing id for this invoice source. This is not necessarily the
|
104 |
+
* internal id!
|
105 |
+
*/
|
106 |
+
public function getReference()
|
107 |
+
{
|
108 |
+
return $this->getId();
|
109 |
+
}
|
110 |
+
|
111 |
+
/**
|
112 |
+
* Returns the status for this invoice source.
|
113 |
+
*
|
114 |
+
* Should either be overridden or both getStatusOrder() and
|
115 |
+
* getStatusCreditNote() should be implemented.
|
116 |
+
*
|
117 |
+
* @return mixed
|
118 |
+
* The status for this invoice source.
|
119 |
+
*/
|
120 |
+
public function getStatus()
|
121 |
+
{
|
122 |
+
return $this->callTypeSpecificMethod(__FUNCTION__);
|
123 |
+
}
|
124 |
+
|
125 |
+
/**
|
126 |
+
* Calls a method that typically depends on the type of invoice source.
|
127 |
+
*
|
128 |
+
* @param string $method
|
129 |
+
*
|
130 |
+
* @return void|mixed
|
131 |
+
*/
|
132 |
+
protected function callTypeSpecificMethod($method)
|
133 |
+
{
|
134 |
+
$method .= $this->getType();
|
135 |
+
return $this->$method();
|
136 |
+
}
|
137 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Invoice/Translations.php
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Invoice;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\TranslationCollection;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Contains translations for classes in the \Siel\Acumulus\Invoice namespace.
|
8 |
+
*/
|
9 |
+
class Translations extends TranslationCollection
|
10 |
+
{
|
11 |
+
protected $nl = array(
|
12 |
+
Source::Order => 'bestelling',
|
13 |
+
Source::CreditNote => 'creditnota',
|
14 |
+
Source::Other => 'overig',
|
15 |
+
|
16 |
+
'vat' => 'BTW',
|
17 |
+
'inc_vat' => 'incl. BTW',
|
18 |
+
'ex_vat' => 'excl. BTW',
|
19 |
+
'shipping_costs' => 'Verzendkosten',
|
20 |
+
// @todo: try to better distinguish free shipping and pickup: for now only WC, HS PS do so
|
21 |
+
'free_shipping' => 'Gratis verzending',
|
22 |
+
'pickup' => 'Afhalen',
|
23 |
+
'payment_costs' => 'Betalingskosten',
|
24 |
+
'discount' => 'Korting',
|
25 |
+
'discount_code' => 'Kortingscode',
|
26 |
+
'coupon_code' => 'Cadeaubon',
|
27 |
+
'used' => 'gebruikt',
|
28 |
+
'gift_wrapping' => 'Cadeauverpakking',
|
29 |
+
'fee' => 'Behandelkosten',
|
30 |
+
'refund' => 'Terugbetaling',
|
31 |
+
'refund_adjustment' => 'Aanpassing teruggaafbedrag',
|
32 |
+
'fee_adjustment' => 'Kosten (soort onbekend)',
|
33 |
+
'discount_adjustment' => 'Handmatige korting',
|
34 |
+
|
35 |
+
'message_warning_no_vat' => 'De factuur bevat geen BTW en er kan niet bepaald worden welk factuurtype (https://wiki.acumulus.nl/index.php?page=127) van toepassing is. Daarom is de factuur als concept opgeslagen. Corrigeer de factuur in Acumulus en controleer uw BTW instellingen.',
|
36 |
+
'message_warning_no_vattype' => 'Door een fout in uw instellingen of BTW tarieven, kan het factuurtype (https://wiki.acumulus.nl/index.php?page=127) niet bepaald worden. Daarom is de factuur is als concept opgeslagen. Corrigeer of completeer de factuur in Acumulus.',
|
37 |
+
'message_warning_multiple_vattype_must_split' => 'De factuur heeft meerdere factuurtypes (https://wiki.acumulus.nl/index.php?page=127). Daarom is de factuur als concept opgeslagen. Splits de factuur in Acumulus en verdeel de regels over beide facturen gebaseerd op het BTW type waar de regel onder valt.',
|
38 |
+
'message_warning_multiple_vattype_may_split' => 'De factuur kan meerdere factuurtypes hebben (https://wiki.acumulus.nl/index.php?page=127). Daarom is de factuur als concept opgeslagen. Controleer het BTW type van de factuur in Acumulus en corrigeer deze indien nodig, of splits de factuur en verdeel de regels over beide facturen gebaseerd op het BTW type waar de regel onder valt.',
|
39 |
+
);
|
40 |
+
|
41 |
+
protected $en = array(
|
42 |
+
Source::Order => 'order',
|
43 |
+
Source::CreditNote => 'credit note',
|
44 |
+
Source::Other => 'other',
|
45 |
+
|
46 |
+
'vat' => 'VAT',
|
47 |
+
'inc_vat' => 'incl. VAT',
|
48 |
+
'ex_vat' => 'ex. VAT',
|
49 |
+
'shipping_costs' => 'Shipping costs',
|
50 |
+
'free_shipping' => 'Free shipping',
|
51 |
+
'pickup' => 'In store pick-up',
|
52 |
+
'payment_costs' => 'Payment fee',
|
53 |
+
'discount' => 'Discount',
|
54 |
+
'discount_code' => 'Coupon code',
|
55 |
+
'coupon_code' => 'Voucher',
|
56 |
+
'used' => 'used',
|
57 |
+
'gift_wrapping' => 'Gift wrapping',
|
58 |
+
'fee' => 'Order treatment costs',
|
59 |
+
'refund' => 'Refund',
|
60 |
+
'refund_adjustment' => 'Refund adjustment',
|
61 |
+
'fee_adjustment' => 'Fee (unknown type)',
|
62 |
+
'discount_adjustment' => 'Manual discount',
|
63 |
+
|
64 |
+
'message_warning_no_vat' => 'The invoice has no VAT and it is not possible to determine the invoice type (https://wiki.acumulus.nl/index.php?page=127). The invoice has been saved as concept. Correct the invoice in acumulus and check your VAT settings.',
|
65 |
+
'message_warning_no_vattype' => 'Due to an error in your settings, we cannot determine the invoice type for the invoice (https://wiki.acumulus.nl/index.php?page=127). The invoice has been saved as concept. Correct or complete the invoice in Acumulus.',
|
66 |
+
'message_warning_multiple_vattype_must_split' => 'The invoice has multiple invoice types (https://wiki.acumulus.nl/index.php?page=127). The invoice has been saved as concept. Split the invoice in Acumulus and divide the invoice lines over both invoices based on the VAT type the line belongs to.',
|
67 |
+
'message_warning_multiple_vattype_may_split' => 'The invoice can have multiple invoice types (https://wiki.acumulus.nl/index.php?page=127). The invoice has been saved as concept. Check the VAT type of the invoice in Acumulus and correct if necessary, or split the invoice and divide the invoice lines over both invoices based on the VAT type the line belongs to.',
|
68 |
+
);
|
69 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/Helpers/FormRenderer.php
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Joomla\Helpers;
|
3 |
+
|
4 |
+
use JHtml;
|
5 |
+
use Siel\Acumulus\Helpers\FormRenderer as BaseFormRenderer;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class FormRenderer renders a form in the Joomla/VirtueMart standards.
|
9 |
+
*/
|
10 |
+
class FormRenderer extends BaseFormRenderer
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* Constructor.
|
14 |
+
*/
|
15 |
+
public function __construct()
|
16 |
+
{
|
17 |
+
// Default Joomla template seems to use xhtml.
|
18 |
+
$this->html5 = false;
|
19 |
+
$this->fieldsetWrapperClass = 'adminform';
|
20 |
+
$this->elementWrapperClass = 'control-group';
|
21 |
+
$this->requiredMarkup = '<span class="star"> *</span>';
|
22 |
+
$this->labelWrapperTag = 'div';
|
23 |
+
$this->labelWrapperClass = 'control-label';
|
24 |
+
$this->inputWrapperTag = 'div';
|
25 |
+
$this->inputWrapperClass = 'controls';
|
26 |
+
$this->multiLabelClass = 'control-label';
|
27 |
+
$this->descriptionClass = 'controls';
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* {@inheritdoc}
|
32 |
+
*/
|
33 |
+
public function input($type, $id, $name, $value = '', array $attributes = array())
|
34 |
+
{
|
35 |
+
$output = '';
|
36 |
+
if ($type === 'date') {
|
37 |
+
$output .= $this->getWrapper('input');
|
38 |
+
$output .= JHtml::calendar($value, $name, $id, $this->form->getShopDateFormat()/*, $attributes*/);
|
39 |
+
$output .= $this->getWrapperEnd('input');
|
40 |
+
} else {
|
41 |
+
$output .= parent::input($type, $id, $name, $value, $attributes);
|
42 |
+
}
|
43 |
+
return $output;
|
44 |
+
}
|
45 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/Helpers/Log.php
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Joomla\Helpers;
|
3 |
+
|
4 |
+
use JLog;
|
5 |
+
use Siel\Acumulus\Helpers\Log as BaseLog;
|
6 |
+
use Siel\Acumulus\Web\ConfigInterface;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Extends the base log class to log any library logging to the Joomla log.
|
10 |
+
*/
|
11 |
+
class Log extends BaseLog
|
12 |
+
{
|
13 |
+
public function __construct(ConfigInterface $config)
|
14 |
+
{
|
15 |
+
parent::__construct($config);
|
16 |
+
JLog::addLogger(array('text_file' => 'acumulus.log.php'),
|
17 |
+
JLog::ALL,
|
18 |
+
array('com_acumulus')
|
19 |
+
);
|
20 |
+
}
|
21 |
+
|
22 |
+
/**
|
23 |
+
* {@inheritdoc}
|
24 |
+
*
|
25 |
+
* This override uses JLog.
|
26 |
+
*/
|
27 |
+
protected function write($message, $severity)
|
28 |
+
{
|
29 |
+
jimport('joomla.log.log');
|
30 |
+
JLog::add($message, $this->getJoomlaSeverity($severity), 'com_acumulus');
|
31 |
+
}
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Returns the joomla equivalent of the severity.
|
35 |
+
*
|
36 |
+
* @param int $severity
|
37 |
+
* One of the constants of the base Log class.
|
38 |
+
*
|
39 |
+
* @return int
|
40 |
+
* the Joomla equivalent of the severity.
|
41 |
+
*/
|
42 |
+
protected function getJoomlaSeverity($severity)
|
43 |
+
{
|
44 |
+
switch ($severity) {
|
45 |
+
case Log::Error:
|
46 |
+
return JLog::ERROR;
|
47 |
+
case Log::Warning:
|
48 |
+
return JLog::WARNING;
|
49 |
+
case Log::Notice:
|
50 |
+
return JLog::NOTICE;
|
51 |
+
case Log::Debug:
|
52 |
+
default:
|
53 |
+
return JLog::DEBUG;
|
54 |
+
}
|
55 |
+
}
|
56 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/Helpers/Mailer.php
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Joomla\Helpers;
|
3 |
+
|
4 |
+
use JFactory;
|
5 |
+
use Siel\Acumulus\Helpers\Mailer as BaseMailer;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Extends the base mailer class to send a mail using the Joomla mail features.
|
9 |
+
*/
|
10 |
+
class Mailer extends BaseMailer
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* {@inheritdoc}
|
14 |
+
*/
|
15 |
+
public function sendInvoiceAddMailResult(array $result, array $messages, $invoiceSourceType, $invoiceSourceReference)
|
16 |
+
{
|
17 |
+
$app = JFactory::getApplication();
|
18 |
+
$mailer = JFactory::getMailer();
|
19 |
+
$mailer->isHtml(true);
|
20 |
+
|
21 |
+
$mailer->setSender(array($app->get('mailfrom'), $this->getFromName()));
|
22 |
+
$mailer->addRecipient($this->getToAddress());
|
23 |
+
$mailer->setSubject(html_entity_decode($this->getSubject($result)));
|
24 |
+
$body = $this->getBody($result, $messages, $invoiceSourceType, $invoiceSourceReference);
|
25 |
+
$mailer->setBody($body['html']);
|
26 |
+
|
27 |
+
return $mailer->Send();
|
28 |
+
}
|
29 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/HikaShop/Invoice/Creator.php
ADDED
@@ -0,0 +1,321 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Joomla\HikaShop\Invoice;
|
3 |
+
|
4 |
+
use hikashopConfigClass;
|
5 |
+
use Siel\Acumulus\Helpers\Number;
|
6 |
+
use Siel\Acumulus\Invoice\ConfigInterface;
|
7 |
+
use Siel\Acumulus\Invoice\Creator as BaseCreator;
|
8 |
+
use stdClass;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Allows to create arrays in the Acumulus invoice structure from a HikaShop
|
12 |
+
* order
|
13 |
+
*
|
14 |
+
* Notes:
|
15 |
+
* - HikaShop knows discounts in the form of coupons or unrestricted discounts.
|
16 |
+
* Coupons can be without vat (to be seen as partial payment, which was
|
17 |
+
* probably not meant, thus incorrect) or with a fixed vat rate, independent
|
18 |
+
* from the products in the cart, thus also incorrect.
|
19 |
+
* - When a cart with a coupon contains products with another vat rate, the
|
20 |
+
* shown vat amount breakdown is incorrect. The Acumulus invoice will be
|
21 |
+
* correct, but may differ from the shop invoice, though the overall amount
|
22 |
+
* tends to be equal. It is the meta data in the invoice (as sent to Acumulus)
|
23 |
+
* that shows the differences.
|
24 |
+
* that shows the differences.
|
25 |
+
*/
|
26 |
+
class Creator extends BaseCreator
|
27 |
+
{
|
28 |
+
/**
|
29 |
+
* @var object
|
30 |
+
*/
|
31 |
+
protected $order;
|
32 |
+
|
33 |
+
/**
|
34 |
+
* {@inheritdoc}
|
35 |
+
*
|
36 |
+
* This override also initializes VM specific properties related to the
|
37 |
+
* source.
|
38 |
+
*/
|
39 |
+
protected function setInvoiceSource($source)
|
40 |
+
{
|
41 |
+
parent::setInvoiceSource($source);
|
42 |
+
$this->order = $this->invoiceSource->getSource();
|
43 |
+
}
|
44 |
+
|
45 |
+
/**
|
46 |
+
* {@inheritdoc}
|
47 |
+
*/
|
48 |
+
protected function getCustomer()
|
49 |
+
{
|
50 |
+
$result = array();
|
51 |
+
|
52 |
+
$this->addIfNotEmpty($result, 'contactyourid', $this->order->order_user_id);
|
53 |
+
|
54 |
+
$billingAddress = $this->order->billing_address;
|
55 |
+
if (!empty($billingAddress)) {
|
56 |
+
// @todo: hoe kan een klant dit (en vat#) invullen?
|
57 |
+
$this->addIfNotEmpty($result, 'companyname1', $billingAddress->address_company);
|
58 |
+
if (!empty($result['companyname1'])) {
|
59 |
+
$this->addIfNotEmpty($result, 'vatnumber', $billingAddress->address_vat);
|
60 |
+
}
|
61 |
+
$result['fullname'] = $billingAddress->address_lastname;
|
62 |
+
if (!empty($billingAddress->address_middle_name)) {
|
63 |
+
$result['fullname'] = $billingAddress->address_middle_name . ' ' . $result['fullname'];
|
64 |
+
}
|
65 |
+
if (!empty($billingAddress->address_firstname)) {
|
66 |
+
$result['fullname'] = $billingAddress->address_firstname . ' ' . $result['fullname'];
|
67 |
+
}
|
68 |
+
if (empty($result['fullname'])) {
|
69 |
+
$this->addIfNotEmpty($result, 'fullname', $this->order->customer->name);
|
70 |
+
}
|
71 |
+
$this->addIfNotEmpty($result, 'address1', $billingAddress->address_street);
|
72 |
+
$this->addIfNotEmpty($result, 'address2', $billingAddress->address_street2);
|
73 |
+
$this->addIfNotEmpty($result, 'postalcode', $billingAddress->address_post_code);
|
74 |
+
$this->addIfNotEmpty($result, 'city', $billingAddress->address_city);
|
75 |
+
$this->addIfNotEmpty($result, 'countrycode', $billingAddress->address_country_code_2);
|
76 |
+
// Preference for 1st phone number.
|
77 |
+
$this->addIfNotEmpty($result, 'telephone', $billingAddress->address_telephone2);
|
78 |
+
$this->addIfNotEmpty($result, 'telephone', $billingAddress->address_telephone);
|
79 |
+
$this->addIfNotEmpty($result, 'fax', $billingAddress->address_fax);
|
80 |
+
} else {
|
81 |
+
$this->addIfNotEmpty($result, 'fullname', $this->order->customer->name);
|
82 |
+
}
|
83 |
+
|
84 |
+
// Preference for the user email, not the registration email.
|
85 |
+
$this->addIfNotEmpty($result, 'email', $this->order->customer->email);
|
86 |
+
$this->addIfNotEmpty($result, 'email', $this->order->customer->user_email);
|
87 |
+
|
88 |
+
return $result;
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* {@inheritdoc}
|
93 |
+
*/
|
94 |
+
protected function searchProperty($property)
|
95 |
+
{
|
96 |
+
$value = @$this->getProperty($property, $this->order);
|
97 |
+
if (empty($value)) {
|
98 |
+
$value = @$this->getProperty($property, $this->order->billing_address);
|
99 |
+
if (empty($value)) {
|
100 |
+
$value = @$this->getProperty($property, $this->order->customer);
|
101 |
+
}
|
102 |
+
}
|
103 |
+
return $value;
|
104 |
+
}
|
105 |
+
|
106 |
+
/**
|
107 |
+
* {@inheritdoc}
|
108 |
+
*/
|
109 |
+
protected function getInvoiceNumber($invoiceNumberSource)
|
110 |
+
{
|
111 |
+
$result = $this->invoiceSource->getReference();
|
112 |
+
if ($invoiceNumberSource == ConfigInterface::InvoiceNrSource_ShopInvoice && !empty($this->order->order_invoice_number)) {
|
113 |
+
$result = $this->order->order_invoice_number;
|
114 |
+
}
|
115 |
+
return $result;
|
116 |
+
}
|
117 |
+
|
118 |
+
/**
|
119 |
+
* {@inheritdoc}
|
120 |
+
*/
|
121 |
+
protected function getInvoiceDate($dateToUse)
|
122 |
+
{
|
123 |
+
$result = date('Y-m-d', $this->order->order_created);
|
124 |
+
if ($dateToUse == ConfigInterface::InvoiceDate_InvoiceCreate && !empty($this->order->order_invoice_created)) {
|
125 |
+
$result = date('Y-m-d', $this->order->order_invoice_created);
|
126 |
+
}
|
127 |
+
return $result;
|
128 |
+
}
|
129 |
+
|
130 |
+
/**
|
131 |
+
* {@inheritdoc}
|
132 |
+
*/
|
133 |
+
protected function getPaymentState()
|
134 |
+
{
|
135 |
+
/** @var hikashopConfigClass $config */
|
136 |
+
$config = hikashop_config();
|
137 |
+
$unpaidStatuses = explode(',', $config->get('order_unpaid_statuses', 'created'));
|
138 |
+
return in_array($this->order->order_status, $unpaidStatuses)
|
139 |
+
? ConfigInterface::PaymentStatus_Due
|
140 |
+
: ConfigInterface::PaymentStatus_Paid;
|
141 |
+
}
|
142 |
+
|
143 |
+
/**
|
144 |
+
* {@inheritdoc}
|
145 |
+
*/
|
146 |
+
protected function getPaymentDate()
|
147 |
+
{
|
148 |
+
// Scan through the history and look for a non empty history_payment_id.
|
149 |
+
// The order of this array is by history_created DESC, we take the one that
|
150 |
+
// is furthest away in time.
|
151 |
+
$date = null;
|
152 |
+
foreach ($this->order->history as $history) {
|
153 |
+
if (!empty($history->history_payment_id)) {
|
154 |
+
$date = $history->history_created;
|
155 |
+
}
|
156 |
+
}
|
157 |
+
if (!$date) {
|
158 |
+
// Scan through the history and look for a non unpaid order status.
|
159 |
+
// We take the one that is furthest away in time.
|
160 |
+
/** @var hikashopConfigClass $config */
|
161 |
+
$config = hikashop_config();
|
162 |
+
$unpaidStatuses = explode(',', $config->get('order_unpaid_statuses', 'created'));
|
163 |
+
foreach ($this->order->history as $history) {
|
164 |
+
if (!empty($history->history_new_status) && !in_array($history->history_new_status, $unpaidStatuses)) {
|
165 |
+
$date = $history->history_created;
|
166 |
+
}
|
167 |
+
}
|
168 |
+
}
|
169 |
+
return $date ? date('Y-m-d', $date) : $date;
|
170 |
+
}
|
171 |
+
|
172 |
+
/**
|
173 |
+
* {@inheritdoc}
|
174 |
+
*
|
175 |
+
* This override provides the values meta-invoice-amountinc and
|
176 |
+
* meta-invoice-vatamount.
|
177 |
+
*/
|
178 |
+
protected function getInvoiceTotals()
|
179 |
+
{
|
180 |
+
$vatAmount = 0.0;
|
181 |
+
// No order_taxinfo => no tax (?) => vatamount = 0.
|
182 |
+
if (!empty($this->order->order_tax_info)) {
|
183 |
+
foreach ($this->order->order_tax_info as $taxInfo) {
|
184 |
+
$vatAmount += $taxInfo->tax_amount;
|
185 |
+
}
|
186 |
+
}
|
187 |
+
return array(
|
188 |
+
'meta-invoice-amountinc' => $this->order->order_full_price,
|
189 |
+
'meta-invoice-vatamount' => $vatAmount,
|
190 |
+
);
|
191 |
+
}
|
192 |
+
|
193 |
+
/**
|
194 |
+
* {@inheritdoc}
|
195 |
+
*/
|
196 |
+
protected function getItemLines()
|
197 |
+
{
|
198 |
+
$result = array_map(array($this, 'getItemLine'), $this->order->products);
|
199 |
+
return $result;
|
200 |
+
}
|
201 |
+
|
202 |
+
/**
|
203 |
+
* Returns 1 item line for 1 product line.
|
204 |
+
*
|
205 |
+
* @param stdClass $item
|
206 |
+
*
|
207 |
+
* @return array
|
208 |
+
*/
|
209 |
+
protected function getItemLine(stdClass $item)
|
210 |
+
{
|
211 |
+
$productPriceEx = (float) $item->order_product_price;
|
212 |
+
$productVat = (float) $item->order_product_tax;
|
213 |
+
|
214 |
+
// Note that this info remains correct when rates are changed as upon order
|
215 |
+
// creation this info is stored in the order_product table.
|
216 |
+
if (is_array($item->order_product_tax_info) && count($item->order_product_tax_info) === 1) {
|
217 |
+
$productVatInfo = reset($item->order_product_tax_info);
|
218 |
+
if (!empty($productVatInfo->tax_rate)) {
|
219 |
+
$vatRate = $productVatInfo->tax_rate;
|
220 |
+
}
|
221 |
+
}
|
222 |
+
if (isset($vatRate)) {
|
223 |
+
$vatInfo = array(
|
224 |
+
'vatrate' => 100.0 * $vatRate,
|
225 |
+
'meta-vatrate-source' => static::VatRateSource_Exact,
|
226 |
+
);
|
227 |
+
} else {
|
228 |
+
$vatInfo = $this->getVatRangeTags($productVat, $productPriceEx, 0.0001, 0.0001);
|
229 |
+
}
|
230 |
+
// @todo: options/variations: $item->order_product_options?
|
231 |
+
$result = array(
|
232 |
+
'itemnumber' => $item->order_product_code,
|
233 |
+
'product' => $item->order_product_name,
|
234 |
+
'unitprice' => $productPriceEx,
|
235 |
+
'meta-line-price' => $item->order_product_total_price_no_vat,
|
236 |
+
'meta-line-priceinc' => $item->order_product_total_price,
|
237 |
+
'quantity' => $item->order_product_quantity,
|
238 |
+
'vatamount' => $productVat,
|
239 |
+
) + $vatInfo;
|
240 |
+
|
241 |
+
return $result;
|
242 |
+
}
|
243 |
+
|
244 |
+
/**
|
245 |
+
* {@inheritdoc}
|
246 |
+
*/
|
247 |
+
protected function getShippingLine()
|
248 |
+
{
|
249 |
+
$result = array();
|
250 |
+
// Check if there is a shipping id attached to the order.
|
251 |
+
if (!empty($this->order->order_shipping_id)) {
|
252 |
+
// Check for free shipping on a credit note.
|
253 |
+
if (!Number::isZero($this->order->order_shipping_price) || $this->invoiceSource->getType() !== Source::CreditNote) {
|
254 |
+
$shippingInc = (float) $this->order->order_shipping_price;
|
255 |
+
$shippingVat = (float) $this->order->order_shipping_tax;
|
256 |
+
$shippingEx = $shippingInc - $shippingVat;
|
257 |
+
$vatInfo = $this->getVatRangeTags($shippingVat, $shippingEx, 0.0001, 0.0002);
|
258 |
+
$description = $this->t('shipping_costs');
|
259 |
+
|
260 |
+
$result = array(
|
261 |
+
'product' => $description,
|
262 |
+
'unitpriceinc' => $shippingInc,
|
263 |
+
'quantity' => 1,
|
264 |
+
'vatamount' => $shippingVat,
|
265 |
+
) + $vatInfo;
|
266 |
+
}
|
267 |
+
}
|
268 |
+
return $result;
|
269 |
+
}
|
270 |
+
|
271 |
+
/**
|
272 |
+
* {@inheritdoc}
|
273 |
+
*/
|
274 |
+
protected function getDiscountLines()
|
275 |
+
{
|
276 |
+
$result = array();
|
277 |
+
|
278 |
+
if (!Number::isZero($this->order->order_discount_price)) {
|
279 |
+
$discountInc = (float) $this->order->order_discount_price;
|
280 |
+
$discountVat = (float) $this->order->order_discount_tax;
|
281 |
+
$discountEx = $discountInc - $discountVat;
|
282 |
+
$vatInfo = $this->getVatRangeTags($discountVat, $discountEx, 0.0001, 0.0002);
|
283 |
+
$description = empty($this->order->order_discount_code)
|
284 |
+
? $this->t('discount')
|
285 |
+
: $this->t('discount_code') . ' ' . $this->order->order_discount_code;
|
286 |
+
|
287 |
+
$result[] = array(
|
288 |
+
'product' => $description,
|
289 |
+
'unitpriceinc' => -$discountInc,
|
290 |
+
'quantity' => 1,
|
291 |
+
'vatamount' => -$discountVat,
|
292 |
+
) + $vatInfo;
|
293 |
+
}
|
294 |
+
|
295 |
+
return $result;
|
296 |
+
}
|
297 |
+
|
298 |
+
/**
|
299 |
+
* {@inheritdoc}
|
300 |
+
*/
|
301 |
+
protected function getPaymentFeeLine()
|
302 |
+
{
|
303 |
+
// @todo check (return on refund?)
|
304 |
+
$result = array();
|
305 |
+
if (!Number::isZero($this->order->order_payment_price)) {
|
306 |
+
$paymentInc = (float) $this->order->order_payment_price;
|
307 |
+
$paymentVat = (float) $this->order->order_payment_tax;
|
308 |
+
$paymentEx = $paymentInc - $paymentVat;
|
309 |
+
$vatInfo = $this->getVatRangeTags($paymentVat, $paymentEx, 0.0001, 0.0002);
|
310 |
+
$description = $this->t('payment_costs');
|
311 |
+
|
312 |
+
$result = array(
|
313 |
+
'product' => $description,
|
314 |
+
'unitpriceinc' => $paymentInc,
|
315 |
+
'quantity' => 1,
|
316 |
+
'vatamount' => $paymentVat,
|
317 |
+
) + $vatInfo;
|
318 |
+
}
|
319 |
+
return $result;
|
320 |
+
}
|
321 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/HikaShop/Invoice/Source.php
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Joomla\HikaShop\Invoice;
|
3 |
+
|
4 |
+
use hikashopOrderClass;
|
5 |
+
use Siel\Acumulus\Invoice\Source as BaseSource;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Wraps a HikaShop order in an invoice source object.
|
9 |
+
*/
|
10 |
+
class Source extends BaseSource
|
11 |
+
{
|
12 |
+
// More specifically typed properties.
|
13 |
+
/** @var object */
|
14 |
+
protected $source;
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Loads an Order source for the set id.
|
18 |
+
*/
|
19 |
+
protected function setSourceOrder()
|
20 |
+
{
|
21 |
+
/** @var hikashopOrderClass $class */
|
22 |
+
$class = hikashop_get('class.order');
|
23 |
+
$this->source = $class->loadFullOrder($this->id, true, false);
|
24 |
+
}
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Sets the id based on the loaded Order.
|
28 |
+
*/
|
29 |
+
protected function setIdOrder()
|
30 |
+
{
|
31 |
+
$this->id = $this->source->order_id;
|
32 |
+
}
|
33 |
+
|
34 |
+
/**
|
35 |
+
* {@inheritdoc}
|
36 |
+
*/
|
37 |
+
public function getReference()
|
38 |
+
{
|
39 |
+
return $this->source->order_number;
|
40 |
+
}
|
41 |
+
|
42 |
+
/**
|
43 |
+
* {@inheritdoc}
|
44 |
+
*
|
45 |
+
* @return string
|
46 |
+
*/
|
47 |
+
public function getStatus()
|
48 |
+
{
|
49 |
+
return $this->source->order_status;
|
50 |
+
}
|
51 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/HikaShop/Shop/ConfigForm.php
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Joomla\HikaShop\Shop;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Joomla\Shop\ConfigForm as BaseConfigForm;
|
5 |
+
use Siel\Acumulus\Shop\ConfigInterface;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class ConfigForm processes and builds the settings form page for the
|
9 |
+
* HikaShop Acumulus module.
|
10 |
+
*/
|
11 |
+
class ConfigForm extends BaseConfigForm
|
12 |
+
{
|
13 |
+
/**
|
14 |
+
* {@inheritdoc}
|
15 |
+
*/
|
16 |
+
protected function getShopOrderStatuses()
|
17 |
+
{
|
18 |
+
/** @var \hikashopCategoryClass $class */
|
19 |
+
$class = hikashop_get('class.category');
|
20 |
+
$statuses = $class->loadAllWithTrans('status');
|
21 |
+
|
22 |
+
$orderStatuses = array();
|
23 |
+
foreach ($statuses as $state) {
|
24 |
+
$orderStatuses[$state->category_name] = $state->translation;
|
25 |
+
}
|
26 |
+
return $orderStatuses;
|
27 |
+
}
|
28 |
+
|
29 |
+
/**
|
30 |
+
* {@inheritdoc}
|
31 |
+
*/
|
32 |
+
protected function getTriggerInvoiceSendEventOptions()
|
33 |
+
{
|
34 |
+
$result = parent::getTriggerInvoiceSendEventOptions();
|
35 |
+
// HikaShop does not have separate invoice entities, let alone a create
|
36 |
+
// event for that.
|
37 |
+
unset($result[ConfigInterface::TriggerInvoiceSendEvent_InvoiceCreate]);
|
38 |
+
return $result;
|
39 |
+
}
|
40 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/HikaShop/Shop/InvoiceManager.php
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Joomla\HikaShop\Shop;
|
3 |
+
|
4 |
+
use DateTime;
|
5 |
+
use Siel\Acumulus\Invoice\Source as Source;
|
6 |
+
use Siel\Acumulus\Joomla\Shop\InvoiceManager as BaseInvoiceManager;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* {@inheritdoc}
|
10 |
+
*
|
11 |
+
* This override provides HikaShop specific queries.
|
12 |
+
*/
|
13 |
+
class InvoiceManager extends BaseInvoiceManager
|
14 |
+
{
|
15 |
+
/**
|
16 |
+
* {@inheritdoc}
|
17 |
+
*
|
18 |
+
* @todo: this override only returns order as supported invoice source type.
|
19 |
+
*/
|
20 |
+
public function getSupportedInvoiceSourceTypes()
|
21 |
+
{
|
22 |
+
return array(
|
23 |
+
Source::Order,
|
24 |
+
//Source::CreditNote,
|
25 |
+
);
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* {@inheritdoc}
|
30 |
+
*/
|
31 |
+
public function getInvoiceSourcesByIdRange($invoiceSourceType, $InvoiceSourceIdFrom, $InvoiceSourceIdTo)
|
32 |
+
{
|
33 |
+
if ($invoiceSourceType === Source::Order) {
|
34 |
+
$query = sprintf("select order_id
|
35 |
+
from #__hikashop_order
|
36 |
+
where order_id between %d and %d",
|
37 |
+
$InvoiceSourceIdFrom, $InvoiceSourceIdTo);
|
38 |
+
return $this->getSourcesByQuery($invoiceSourceType, $query);
|
39 |
+
}
|
40 |
+
return array();
|
41 |
+
}
|
42 |
+
|
43 |
+
/**
|
44 |
+
* {@inheritdoc}
|
45 |
+
*
|
46 |
+
* By default, HikaShop order numbers are non sequential random strings.
|
47 |
+
* So getting a range is not logical. However, extensions may exists that do
|
48 |
+
* introduce sequential order numbers in which case this query should be
|
49 |
+
* adapted.
|
50 |
+
*/
|
51 |
+
public function getInvoiceSourcesByReferenceRange($invoiceSourceType, $InvoiceSourceReferenceFrom, $InvoiceSourceReferenceTo)
|
52 |
+
{
|
53 |
+
if ($invoiceSourceType === Source::Order) {
|
54 |
+
$query = sprintf("select order_id
|
55 |
+
from #__hikashop_order
|
56 |
+
where order_number between '%s' and '%s'",
|
57 |
+
$this->getDb()->escape($InvoiceSourceReferenceFrom),
|
58 |
+
$this->getDb()->escape($InvoiceSourceReferenceTo)
|
59 |
+
);
|
60 |
+
return $this->getSourcesByQuery($invoiceSourceType, $query);
|
61 |
+
}
|
62 |
+
return array();
|
63 |
+
}
|
64 |
+
|
65 |
+
/**
|
66 |
+
* {@inheritdoc}
|
67 |
+
*/
|
68 |
+
public function getInvoiceSourcesByDateRange($invoiceSourceType, DateTime $dateFrom, DateTime $dateTo)
|
69 |
+
{
|
70 |
+
if ($invoiceSourceType === Source::Order) {
|
71 |
+
$query = sprintf("select order_id
|
72 |
+
from #__hikashop_order
|
73 |
+
where order_modified between %u and %u",
|
74 |
+
$dateFrom->getTimestamp(), $dateTo->getTimestamp());
|
75 |
+
return $this->getSourcesByQuery($invoiceSourceType, $query);
|
76 |
+
}
|
77 |
+
return array();
|
78 |
+
}
|
79 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/Shop/AcumulusEntryModel.php
ADDED
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Joomla\Shop;
|
3 |
+
|
4 |
+
use DateTimeZone;
|
5 |
+
use JDate;
|
6 |
+
use JFactory;
|
7 |
+
use JTable;
|
8 |
+
use Siel\Acumulus\Shop\AcumulusEntryModel as BaseAcumulusEntryModel;
|
9 |
+
use Siel\Acumulus\Joomla\Helpers\Log;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Implements the VirtueMart specific acumulus entry model class.
|
13 |
+
*/
|
14 |
+
class AcumulusEntryModel extends BaseAcumulusEntryModel
|
15 |
+
{
|
16 |
+
/**
|
17 |
+
* @return \AcumulusTableAcumulusEntry
|
18 |
+
*/
|
19 |
+
protected function newTable()
|
20 |
+
{
|
21 |
+
$table = JTable::getInstance('AcumulusEntry', 'AcumulusTable');
|
22 |
+
if ($table === false) {
|
23 |
+
Log::getInstance()->error('AcumulusEntryModel::newTable(): table not created');
|
24 |
+
}
|
25 |
+
return $table;
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* {@inheritdoc}
|
30 |
+
*/
|
31 |
+
public function getByEntryId($entryId)
|
32 |
+
{
|
33 |
+
$table = $this->newTable();
|
34 |
+
$result = $table->load(array('entry_id' => $entryId), true);
|
35 |
+
return $result ? $table : null;
|
36 |
+
}
|
37 |
+
|
38 |
+
/**
|
39 |
+
* {@inheritdoc}
|
40 |
+
*/
|
41 |
+
public function getByInvoiceSourceId($invoiceSourceType, $invoiceSourceId)
|
42 |
+
{
|
43 |
+
$table = $this->newTable();
|
44 |
+
$result = $table->load(array('source_type' => $invoiceSourceType, 'source_id' => $invoiceSourceId), true);
|
45 |
+
return $result ? $table : null;
|
46 |
+
}
|
47 |
+
|
48 |
+
/**
|
49 |
+
* {@inheritdoc}
|
50 |
+
*/
|
51 |
+
protected function insert($invoiceSource, $entryId, $token, $created)
|
52 |
+
{
|
53 |
+
// Start with new table class to not overwrite any loaded record.
|
54 |
+
$table = $this->newTable();
|
55 |
+
$table->entry_id = $entryId;
|
56 |
+
$table->token = $token;
|
57 |
+
$table->source_type = $invoiceSource->getType();
|
58 |
+
$table->source_id = $invoiceSource->getId();
|
59 |
+
$table->created = $created;
|
60 |
+
$table->updated = $created;
|
61 |
+
return $table->store();
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* {@inheritdoc}
|
66 |
+
*/
|
67 |
+
protected function update($record, $entryId, $token, $updated)
|
68 |
+
{
|
69 |
+
// Continue with existing table object with already loaded record.
|
70 |
+
$table = $record;
|
71 |
+
$table->entry_id = $entryId;
|
72 |
+
$table->token = $token;
|
73 |
+
$table->updated = $updated;
|
74 |
+
return $table->store(false);
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* {@inheritdoc}
|
79 |
+
*/
|
80 |
+
protected function sqlNow()
|
81 |
+
{
|
82 |
+
$tz = new DateTimeZone(JFactory::getApplication()->get('offset'));
|
83 |
+
$date = new JDate();
|
84 |
+
$date->setTimezone($tz);
|
85 |
+
return $date->toSql(true);
|
86 |
+
}
|
87 |
+
|
88 |
+
/**
|
89 |
+
* {@inheritdoc}
|
90 |
+
*
|
91 |
+
* Joomla has separate install scripts, so nothing has to be done here.
|
92 |
+
*/
|
93 |
+
public function install()
|
94 |
+
{
|
95 |
+
return false;
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* {@inheritdoc}
|
100 |
+
*
|
101 |
+
* Joomla has separate install scripts, so nothing has to be done here.
|
102 |
+
*/
|
103 |
+
public function uninstall()
|
104 |
+
{
|
105 |
+
return false;
|
106 |
+
}
|
107 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/Shop/BatchForm.php
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Joomla\Shop;
|
3 |
+
|
4 |
+
use JSession;
|
5 |
+
use JText;
|
6 |
+
use Siel\Acumulus\Shop\BatchForm as BaseBatchForm;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Provides the Batch send form handling for the VirtueMart Acumulus module.
|
10 |
+
*/
|
11 |
+
class BatchForm extends BaseBatchForm
|
12 |
+
{
|
13 |
+
/**
|
14 |
+
* {@inheritdoc}
|
15 |
+
*
|
16 |
+
* This override checks the Joomla form token:
|
17 |
+
* https://docs.joomla.org/How_to_add_CSRF_anti-spoofing_to_forms
|
18 |
+
*/
|
19 |
+
protected function systemValidate()
|
20 |
+
{
|
21 |
+
return JSession::checkToken();
|
22 |
+
}
|
23 |
+
|
24 |
+
/**
|
25 |
+
* {@inheritdoc}
|
26 |
+
*/
|
27 |
+
public function getDateFormat()
|
28 |
+
{
|
29 |
+
return JText::_('DATE_FORMAT_LC4');
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* {@inheritdoc}
|
34 |
+
*/
|
35 |
+
public function getShopDateFormat()
|
36 |
+
{
|
37 |
+
return str_replace(array('Y', 'm', 'd'), array('%Y', '%m', '%d'), $this->getDateFormat());
|
38 |
+
}
|
39 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/Shop/ConfigForm.php
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Joomla\Shop;
|
3 |
+
|
4 |
+
use JSession;
|
5 |
+
use Siel\Acumulus\Shop\ConfigForm as BaseConfigForm;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class ConfigForm adds features for the settings form page for the Joomla
|
9 |
+
* based shop modules.
|
10 |
+
*/
|
11 |
+
abstract class ConfigForm extends BaseConfigForm
|
12 |
+
{
|
13 |
+
/**
|
14 |
+
* {@inheritdoc}
|
15 |
+
*
|
16 |
+
* This override checks the Joomla form token:
|
17 |
+
* https://docs.joomla.org/How_to_add_CSRF_anti-spoofing_to_forms
|
18 |
+
*/
|
19 |
+
protected function systemValidate()
|
20 |
+
{
|
21 |
+
return JSession::checkToken();
|
22 |
+
}
|
23 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/Shop/ConfigStore.php
ADDED
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Joomla\Shop;
|
3 |
+
|
4 |
+
use JComponentHelper;
|
5 |
+
use JLoader;
|
6 |
+
use JModelLegacy;
|
7 |
+
use JTable;
|
8 |
+
use JTableExtension;
|
9 |
+
use Siel\Acumulus\Shop\ConfigStore as BaseConfigStore;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Implements the connection to the Joomla config component.
|
13 |
+
*/
|
14 |
+
class ConfigStore extends BaSeConfigStore
|
15 |
+
{
|
16 |
+
/** @var array */
|
17 |
+
protected $savedValues = array();
|
18 |
+
|
19 |
+
/**
|
20 |
+
* {@inheritdoc}
|
21 |
+
*/
|
22 |
+
public function getShopEnvironment()
|
23 |
+
{
|
24 |
+
/** @var JTableExtension $extension */
|
25 |
+
$extension = JTable::getInstance('extension');
|
26 |
+
|
27 |
+
$id = $extension->find(array('element' => 'com_acumulus', 'type' => 'component'));
|
28 |
+
$extension->load($id);
|
29 |
+
/** @noinspection PhpUndefinedFieldInspection */
|
30 |
+
$componentInfo = json_decode($extension->manifest_cache, true);
|
31 |
+
$moduleVersion = $componentInfo['version'];
|
32 |
+
|
33 |
+
$id = $extension->find(array('element' => 'com_' . strtolower($this->shopName), 'type' => 'component'));
|
34 |
+
$extension->load($id);
|
35 |
+
/** @noinspection PhpUndefinedFieldInspection */
|
36 |
+
$componentInfo = json_decode($extension->manifest_cache, true);
|
37 |
+
$shopVersion = $componentInfo['version'];
|
38 |
+
|
39 |
+
$joomlaVersion = JVERSION;
|
40 |
+
|
41 |
+
$environment = array(
|
42 |
+
'moduleVersion' => $moduleVersion,
|
43 |
+
'shopName' => $this->shopName,
|
44 |
+
'shopVersion' => "$shopVersion (CMS: Joomla $joomlaVersion)",
|
45 |
+
);
|
46 |
+
|
47 |
+
return $environment;
|
48 |
+
}
|
49 |
+
|
50 |
+
/**
|
51 |
+
* {@inheritdoc}
|
52 |
+
*/
|
53 |
+
public function load(array $keys)
|
54 |
+
{
|
55 |
+
$result = array();
|
56 |
+
$params = JComponentHelper::getParams('com_acumulus');
|
57 |
+
foreach ($keys as $key) {
|
58 |
+
$value = $params->get($key, null);;
|
59 |
+
if (isset($value)) {
|
60 |
+
$result[$key] = $value;
|
61 |
+
}
|
62 |
+
// Overwrite with values saved during this request.
|
63 |
+
if (isset($this->savedValues[$key])) {
|
64 |
+
$result[$key] = $this->savedValues[$key];
|
65 |
+
}
|
66 |
+
}
|
67 |
+
return $result;
|
68 |
+
}
|
69 |
+
|
70 |
+
/**
|
71 |
+
* {@inheritdoc}
|
72 |
+
*/
|
73 |
+
public function save(array $values)
|
74 |
+
{
|
75 |
+
$values = $this->saveCommon($values);
|
76 |
+
|
77 |
+
// When the values are loaded in the same request, the new values are not
|
78 |
+
// retrieved: store a copy of them here to merge them when loading.
|
79 |
+
$this->savedValues = $values;
|
80 |
+
|
81 |
+
// Place the values in the component record.
|
82 |
+
/** @var \stdClass|array $component */
|
83 |
+
$component = JComponentHelper::getComponent('com_acumulus');
|
84 |
+
$component = (array) $component;
|
85 |
+
$component['params'] = empty($component['params']['data']) ? $values : array_merge($component['params']['data'], $values);
|
86 |
+
|
87 |
+
// Use save method of com_config component model.
|
88 |
+
JLoader::registerPrefix('Config', JPATH_ROOT . '/components/com_config');
|
89 |
+
JModelLegacy::addIncludePath(JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'com_config' . DIRECTORY_SEPARATOR . 'model');
|
90 |
+
/** @var \ConfigModelComponent $model */
|
91 |
+
$model = JModelLegacy::getInstance('Component', 'ConfigModel', array('ignore_request' => true));
|
92 |
+
return $model->save($component);
|
93 |
+
}
|
94 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/Shop/InvoiceManager.php
ADDED
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Joomla\Shop;
|
3 |
+
|
4 |
+
use DateTimeZone;
|
5 |
+
use JDate;
|
6 |
+
use JEventDispatcher;
|
7 |
+
use JFactory;
|
8 |
+
use JPluginHelper;
|
9 |
+
use Siel\Acumulus\Invoice\Source as Source;
|
10 |
+
use Siel\Acumulus\Shop\InvoiceManager as BaseInvoiceManager;
|
11 |
+
|
12 |
+
/**
|
13 |
+
* {@inheritdoc}
|
14 |
+
*
|
15 |
+
* This override provides Joomla specific db helper methods and defines
|
16 |
+
* and dispatches Joomla events for the events defined by our library.
|
17 |
+
*/
|
18 |
+
abstract class InvoiceManager extends BaseInvoiceManager
|
19 |
+
{
|
20 |
+
/**
|
21 |
+
* Helper method that executes a query to retrieve a list of invoice source
|
22 |
+
* ids and returns a list of invoice sources for these ids.
|
23 |
+
*
|
24 |
+
* @param string $invoiceSourceType
|
25 |
+
* @param string $query
|
26 |
+
*
|
27 |
+
* @return \Siel\Acumulus\Invoice\Source[]
|
28 |
+
* A non keyed array with invoice Sources.
|
29 |
+
*/
|
30 |
+
protected function getSourcesByQuery($invoiceSourceType, $query)
|
31 |
+
{
|
32 |
+
$sourceIds = $this->loadColumn($query);
|
33 |
+
return $this->getSourcesByIdsOrSources($invoiceSourceType, $sourceIds);
|
34 |
+
}
|
35 |
+
|
36 |
+
/**
|
37 |
+
* Helper method to execute a query and return the 1st column from the
|
38 |
+
* results.
|
39 |
+
*
|
40 |
+
* @param string $query
|
41 |
+
*
|
42 |
+
* @return int[]
|
43 |
+
* A non keyed array with the values of the 1st results of the query result.
|
44 |
+
*/
|
45 |
+
protected function loadColumn($query)
|
46 |
+
{
|
47 |
+
return $this->getDb()->setQuery($query)->loadColumn();
|
48 |
+
}
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Helper method to get the db object.
|
52 |
+
*
|
53 |
+
* @return \JDatabaseDriver
|
54 |
+
*/
|
55 |
+
protected function getDb()
|
56 |
+
{
|
57 |
+
return JFactory::getDbo();
|
58 |
+
}
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Helper method that returns a date in the correct and escaped sql format.
|
62 |
+
*
|
63 |
+
* @param string $date
|
64 |
+
* Date in yyyy-mm-dd format.
|
65 |
+
*
|
66 |
+
* @return string
|
67 |
+
*/
|
68 |
+
protected function toSql($date)
|
69 |
+
{
|
70 |
+
$tz = new DateTimeZone(JFactory::getApplication()->get('offset'));
|
71 |
+
$date = new JDate($date);
|
72 |
+
$date->setTimezone($tz);
|
73 |
+
return $date->toSql(true);
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* {@inheritdoc}
|
78 |
+
*
|
79 |
+
* This Joomla override dispatches the 'onAcumulusInvoiceCreated' event.
|
80 |
+
*/
|
81 |
+
protected function triggerInvoiceCreated(array &$invoice, Source $invoiceSource)
|
82 |
+
{
|
83 |
+
JPluginHelper::importPlugin('acumulus');
|
84 |
+
$results = JEventDispatcher::getInstance()->trigger('onAcumulusInvoiceCreated', array(&$invoice, $invoiceSource));
|
85 |
+
if (count(array_filter($results, function ($value) {
|
86 |
+
return $value === false;
|
87 |
+
})) > 1
|
88 |
+
) {
|
89 |
+
$invoice = null;
|
90 |
+
}
|
91 |
+
}
|
92 |
+
|
93 |
+
/**
|
94 |
+
* {@inheritdoc}
|
95 |
+
*
|
96 |
+
* This Joomla override dispatches the 'onAcumulusInvoiceCompleted' event.
|
97 |
+
*/
|
98 |
+
protected function triggerInvoiceCompleted(array &$invoice, Source $invoiceSource)
|
99 |
+
{
|
100 |
+
JPluginHelper::importPlugin('acumulus');
|
101 |
+
$results = JEventDispatcher::getInstance()->trigger('onAcumulusInvoiceCompleted', array(&$invoice, $invoiceSource));
|
102 |
+
if (count(array_filter($results, function ($value) {
|
103 |
+
return $value === false;
|
104 |
+
})) > 1
|
105 |
+
) {
|
106 |
+
$invoice = null;
|
107 |
+
}
|
108 |
+
}
|
109 |
+
|
110 |
+
/**
|
111 |
+
* {@inheritdoc}
|
112 |
+
*
|
113 |
+
* This Joomla override dispatches the 'onAcumulusInvoiceSent' event.
|
114 |
+
*/
|
115 |
+
protected function triggerInvoiceSent(array $invoice, Source $invoiceSource, array $result)
|
116 |
+
{
|
117 |
+
JPluginHelper::importPlugin('acumulus');
|
118 |
+
JEventDispatcher::getInstance()->trigger('onAcumulusInvoiceSent', array($invoice, $invoiceSource, $result));
|
119 |
+
}
|
120 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/VirtueMart/Invoice/Creator.php
ADDED
@@ -0,0 +1,448 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Joomla\VirtueMart\Invoice;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\Number;
|
5 |
+
use Siel\Acumulus\Invoice\ConfigInterface;
|
6 |
+
use Siel\Acumulus\Invoice\Creator as BaseCreator;
|
7 |
+
use stdClass;
|
8 |
+
use VmModel;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Allows to create arrays in the Acumulus invoice structure from a VirtueMart
|
12 |
+
* order
|
13 |
+
*
|
14 |
+
* Notes:
|
15 |
+
* - Calculation rules used, e.g, to give a certain customer group, a discount
|
16 |
+
* (fixed amount or percentage) should always have "price modifier before tax"
|
17 |
+
* or "price modifier before tax per bill" for the Type of Arithmetic
|
18 |
+
* Operation. Otherwise the VAT computations won't comply with Dutch
|
19 |
+
* regulations.
|
20 |
+
* - "price modifier before tax per bill" will show normal product prices with a
|
21 |
+
* separate discount line indicating the name of the discount rule.
|
22 |
+
* - "price modifier before tax per bill" will show discounted product prices
|
23 |
+
* without a separate discount line and thus also no mention of the applied
|
24 |
+
* discount. In general this option will be a bit more accurate, but IMO that
|
25 |
+
* does not weigh up against the loss of information on the invoice.
|
26 |
+
* - The VMInvoice extension offers credit notes, but for now we do not support
|
27 |
+
* this.
|
28 |
+
*/
|
29 |
+
class Creator extends BaseCreator
|
30 |
+
{
|
31 |
+
/** @var \VirtueMartModelOrders */
|
32 |
+
protected $orderModel;
|
33 |
+
|
34 |
+
/**
|
35 |
+
* @var array
|
36 |
+
* Array with keys:
|
37 |
+
* [details]
|
38 |
+
* [BT]: stdClass (BillTo details)
|
39 |
+
* [ST]: stdClass (ShipTo details, if available)
|
40 |
+
* [history]
|
41 |
+
* [0]: stdClass (virtuemart_order_histories table record)
|
42 |
+
* ...
|
43 |
+
* [items]
|
44 |
+
* [0]: stdClass (virtuemart_order_items table record)
|
45 |
+
* ...
|
46 |
+
* [calc_rules]
|
47 |
+
* [0]: stdClass (virtuemart_order_calc_rules table record)
|
48 |
+
* ...
|
49 |
+
*/
|
50 |
+
protected $order;
|
51 |
+
|
52 |
+
/** @var array */
|
53 |
+
protected $shopInvoice = array();
|
54 |
+
|
55 |
+
/** @var stdClass
|
56 |
+
* Object with properties:
|
57 |
+
* [...]: virtuemart_vmusers table record columns
|
58 |
+
* [shopper_groups]: array of stdClass virtuemart_vmuser_shoppergroups table
|
59 |
+
* records
|
60 |
+
* [JUser]: JUser object
|
61 |
+
* [userInfo]: array of stdClass virtuemart_userinfos table records
|
62 |
+
*/
|
63 |
+
protected $user;
|
64 |
+
|
65 |
+
/** @var int */
|
66 |
+
protected $userBtUid;
|
67 |
+
|
68 |
+
/**
|
69 |
+
* {@inheritdoc}
|
70 |
+
*
|
71 |
+
* This override also initializes VM specific properties related to the
|
72 |
+
* source.
|
73 |
+
*/
|
74 |
+
protected function setInvoiceSource($source)
|
75 |
+
{
|
76 |
+
parent::setInvoiceSource($source);
|
77 |
+
$this->order = $this->invoiceSource->getSource();
|
78 |
+
$this->orderModel = VmModel::getModel('orders');
|
79 |
+
/** @var \TableInvoices $invoiceTable */
|
80 |
+
if ($invoiceTable = $this->orderModel->getTable('invoices')->load($this->order['details']['BT']->virtuemart_order_id, 'virtuemart_order_id')) {
|
81 |
+
$this->shopInvoice = $invoiceTable->getProperties();
|
82 |
+
}
|
83 |
+
if (!empty($this->order['details']['BT']->virtuemart_user_id)) {
|
84 |
+
/** @var \VirtueMartModelUser $userModel */
|
85 |
+
$userModel = VmModel::getModel('user');
|
86 |
+
$userModel->setId($this->order['details']['BT']->virtuemart_user_id);
|
87 |
+
$this->user = $userModel->getUser();
|
88 |
+
$this->userBtUid = null;
|
89 |
+
|
90 |
+
foreach ($this->user->userInfo as $uid => $userInfo) {
|
91 |
+
if ($userInfo->address_type === 'BT') {
|
92 |
+
$this->userBtUid = $uid;
|
93 |
+
break;
|
94 |
+
}
|
95 |
+
}
|
96 |
+
}
|
97 |
+
}
|
98 |
+
|
99 |
+
/**
|
100 |
+
* {@inheritdoc}
|
101 |
+
*/
|
102 |
+
protected function getCustomer()
|
103 |
+
{
|
104 |
+
$result = array();
|
105 |
+
|
106 |
+
$this->addIfSetAndNotEmpty($result, 'contactyourid', $this->order['details']['BT'], 'virtuemart_user_id');
|
107 |
+
$this->addIfSetAndNotEmpty($result, 'companyname1', $this->order['details']['BT'], 'company');
|
108 |
+
if (!empty($result['companyname1']) && !empty($this->userBtUid)) {
|
109 |
+
// @todo: there's also a (paid) EU VAT checker extension that probably does not use the field 'tax_exemption_number'.
|
110 |
+
$this->addIfSetAndNotEmpty($result, 'vatnumber', $this->user->userInfo[$this->userBtUid], 'tax_exemption_number');
|
111 |
+
}
|
112 |
+
$result['fullname'] = $this->order['details']['BT']->last_name;
|
113 |
+
if (!empty($this->order['details']['BT']->middle_name)) {
|
114 |
+
$result['fullname'] = $this->order['details']['BT']->middle_name . ' ' . $result['fullname'];
|
115 |
+
}
|
116 |
+
if (!empty($this->order['details']['BT']->first_name)) {
|
117 |
+
$result['fullname'] = $this->order['details']['BT']->first_name . ' ' . $result['fullname'];
|
118 |
+
}
|
119 |
+
$this->addIfSetAndNotEmpty($result, 'address1', $this->order['details']['BT'], 'address_1');
|
120 |
+
$this->addIfSetAndNotEmpty($result, 'address2', $this->order['details']['BT'], 'address_2');
|
121 |
+
$this->addIfSetAndNotEmpty($result, 'postalcode', $this->order['details']['BT'], 'zip');
|
122 |
+
$this->addIfSetAndNotEmpty($result, 'city', $this->order['details']['BT'], 'city');
|
123 |
+
if (!empty($this->order['details']['BT']->virtuemart_country_id)) {
|
124 |
+
/** @var \VirtueMartModelCountry $countryModel */
|
125 |
+
$countryModel = VmModel::getModel('country');
|
126 |
+
$country = $countryModel->getData($this->order['details']['BT']->virtuemart_country_id);
|
127 |
+
$this->addIfSetAndNotEmpty($result, 'countrycode', $country, 'country_2_code');
|
128 |
+
}
|
129 |
+
$this->addIfSetAndNotEmpty($result, 'telephone', $this->order['details']['BT'], 'phone_2');
|
130 |
+
$this->addIfSetAndNotEmpty($result, 'telephone', $this->order['details']['BT'], 'phone_1');
|
131 |
+
$this->addIfSetAndNotEmpty($result, 'fax', $this->order['details']['BT'], 'fax');
|
132 |
+
$this->addIfSetAndNotEmpty($result, 'email', $this->order['details']['BT'], 'email');
|
133 |
+
|
134 |
+
return $result;
|
135 |
+
}
|
136 |
+
|
137 |
+
/**
|
138 |
+
* {@inheritdoc}
|
139 |
+
*/
|
140 |
+
protected function searchProperty($property)
|
141 |
+
{
|
142 |
+
$value = @$this->getProperty($property, $this->order['details']['BT']);
|
143 |
+
if (empty($value)) {
|
144 |
+
$value = @$this->getProperty($property, $this->user);
|
145 |
+
}
|
146 |
+
if (empty($value)) {
|
147 |
+
$value = @$this->getProperty($property, $this->user->userInfo[$this->userBtUid]);
|
148 |
+
}
|
149 |
+
return $value;
|
150 |
+
}
|
151 |
+
|
152 |
+
/**
|
153 |
+
* {@inheritdoc}
|
154 |
+
*/
|
155 |
+
protected function getInvoiceNumber($invoiceNumberSource)
|
156 |
+
{
|
157 |
+
$result = $this->invoiceSource->getReference();
|
158 |
+
if ($invoiceNumberSource == ConfigInterface::InvoiceNrSource_ShopInvoice && !empty($this->shopInvoice['invoice_number'])) {
|
159 |
+
$result = $this->shopInvoice['invoice_number'];
|
160 |
+
}
|
161 |
+
return $result;
|
162 |
+
}
|
163 |
+
|
164 |
+
/**
|
165 |
+
* {@inheritdoc}
|
166 |
+
*/
|
167 |
+
protected function getInvoiceDate($dateToUse)
|
168 |
+
{
|
169 |
+
$result = date('Y-m-d', strtotime($this->order['details']['BT']->created_on));
|
170 |
+
if ($dateToUse == ConfigInterface::InvoiceDate_InvoiceCreate && !empty($this->shopInvoice['created_on'])) {
|
171 |
+
$result = date('Y-m-d', strtotime($this->shopInvoice['created_on']));
|
172 |
+
}
|
173 |
+
return $result;
|
174 |
+
}
|
175 |
+
|
176 |
+
/**
|
177 |
+
* {@inheritdoc}
|
178 |
+
*/
|
179 |
+
protected function getPaymentState()
|
180 |
+
{
|
181 |
+
$order = $this->invoiceSource->getSource();
|
182 |
+
return in_array($order['details']['BT']->order_status, $this->getPaidStates())
|
183 |
+
? ConfigInterface::PaymentStatus_Paid
|
184 |
+
: ConfigInterface::PaymentStatus_Due;
|
185 |
+
}
|
186 |
+
|
187 |
+
/**
|
188 |
+
* {@inheritdoc}
|
189 |
+
*/
|
190 |
+
protected function getPaymentDate()
|
191 |
+
{
|
192 |
+
$date = null;
|
193 |
+
$previousStatus = '';
|
194 |
+
foreach ($this->order['history'] as $orderHistory) {
|
195 |
+
if (in_array($orderHistory->order_status_code, $this->getPaidStates()) && !in_array($previousStatus, $this->getPaidStates())) {
|
196 |
+
$date = $orderHistory->created_on;
|
197 |
+
}
|
198 |
+
$previousStatus = $orderHistory->order_status_code;
|
199 |
+
}
|
200 |
+
return $date ? date('Y-m-d', strtotime($date)) : $date;
|
201 |
+
}
|
202 |
+
|
203 |
+
/**
|
204 |
+
* Returns a list of order states that indicate that the order has been paid.
|
205 |
+
*
|
206 |
+
* @return array
|
207 |
+
*/
|
208 |
+
protected function getPaidStates()
|
209 |
+
{
|
210 |
+
return array('C', 'S', 'R');
|
211 |
+
}
|
212 |
+
|
213 |
+
/**
|
214 |
+
* {@inheritdoc}
|
215 |
+
*
|
216 |
+
* This override provides the values meta-invoice-amountinc and
|
217 |
+
* meta-invoice-vatamount as they may be needed by the Completor.
|
218 |
+
*/
|
219 |
+
protected function getInvoiceTotals()
|
220 |
+
{
|
221 |
+
return array(
|
222 |
+
'meta-invoice-amountinc' => $this->order['details']['BT']->order_total,
|
223 |
+
'meta-invoice-vatamount' => $this->order['details']['BT']->order_billTaxAmount,
|
224 |
+
);
|
225 |
+
}
|
226 |
+
|
227 |
+
/**
|
228 |
+
* {@inheritdoc}
|
229 |
+
*/
|
230 |
+
protected function getItemLines()
|
231 |
+
{
|
232 |
+
$result = array_map(array($this, 'getItemLine'), $this->order['items']);
|
233 |
+
return $result;
|
234 |
+
}
|
235 |
+
|
236 |
+
/**
|
237 |
+
* Returns 1 item line for 1 product line.
|
238 |
+
*
|
239 |
+
* @param stdClass $item
|
240 |
+
*
|
241 |
+
* @return array
|
242 |
+
*/
|
243 |
+
protected function getItemLine(stdClass $item)
|
244 |
+
{
|
245 |
+
$productPriceEx = (float) $item->product_discountedPriceWithoutTax;
|
246 |
+
$productPriceInc = (float) $item->product_final_price;
|
247 |
+
$productVat = (float) $item->product_tax;
|
248 |
+
|
249 |
+
$calcRule = $this->getCalcRule('VatTax', $item->virtuemart_order_item_id);
|
250 |
+
if (!empty($calcRule->calc_value)) {
|
251 |
+
$vatInfo = array(
|
252 |
+
'vatrate' => (float) $calcRule->calc_value,
|
253 |
+
'meta-vatrate-source' => static::VatRateSource_Exact,
|
254 |
+
);
|
255 |
+
} else {
|
256 |
+
$vatInfo = $this->getVatRangeTags($productVat, $productPriceEx, 0.0001, 0.0001);
|
257 |
+
}
|
258 |
+
|
259 |
+
$result = array(
|
260 |
+
'itemnumber' => $item->order_item_sku,
|
261 |
+
'product' => $item->order_item_name,
|
262 |
+
'unitprice' => $productPriceEx,
|
263 |
+
'unitpriceinc' => $productPriceInc,
|
264 |
+
'quantity' => $item->product_quantity,
|
265 |
+
'vatamount' => $productVat,
|
266 |
+
) + $vatInfo;
|
267 |
+
|
268 |
+
return $result;
|
269 |
+
}
|
270 |
+
|
271 |
+
/**
|
272 |
+
* {@inheritdoc}
|
273 |
+
*/
|
274 |
+
protected function getShippingLine()
|
275 |
+
{
|
276 |
+
$result = array();
|
277 |
+
// We are checking on empty, assuming that a null value will be used to
|
278 |
+
// indicate no shipping at all (downloadable product) and that free shipping
|
279 |
+
// will be represented as the string '0.00' which is not considered empty.
|
280 |
+
if (!empty($this->order['details']['BT']->order_shipment)) {
|
281 |
+
$shippingEx = (float) $this->order['details']['BT']->order_shipment;
|
282 |
+
$shippingVat = (float) $this->order['details']['BT']->order_shipment_tax;
|
283 |
+
|
284 |
+
$calcRule = $this->getCalcRule('shipment');
|
285 |
+
if (!empty($calcRule->calc_value)) {
|
286 |
+
$vatInfo = array(
|
287 |
+
'vatrate' => (float) $calcRule->calc_value,
|
288 |
+
'meta-vatrate-source' => static::VatRateSource_Exact,
|
289 |
+
);
|
290 |
+
} else {
|
291 |
+
$vatInfo = $this->getVatRangeTags($shippingVat, $shippingEx, 0.0001, 0.01);
|
292 |
+
}
|
293 |
+
|
294 |
+
$result = array(
|
295 |
+
'product' => $this->t('shipping_costs'),
|
296 |
+
'unitprice' => $shippingEx,
|
297 |
+
'quantity' => 1,
|
298 |
+
'vatamount' => $shippingVat,
|
299 |
+
) + $vatInfo;
|
300 |
+
}
|
301 |
+
return $result;
|
302 |
+
}
|
303 |
+
|
304 |
+
/**
|
305 |
+
* {@inheritdoc}
|
306 |
+
*/
|
307 |
+
protected function getDiscountLines()
|
308 |
+
{
|
309 |
+
$result = array();
|
310 |
+
|
311 |
+
// We do have several discount related fields in the order details:
|
312 |
+
// - order_billDiscountAmount
|
313 |
+
// - order_discountAmount
|
314 |
+
// - coupon_discount
|
315 |
+
// - order_discount
|
316 |
+
// However, these fields seem to be totals based on applied non-tax
|
317 |
+
// calculation rules. So it is better to add a line per calc rule with a
|
318 |
+
// negative amount: this gives us descriptions of the discounts as well.
|
319 |
+
$result = array_merge($result, array_map(array($this, 'getCalcRuleDiscountLine'),
|
320 |
+
array_filter($this->order['calc_rules'], array($this, 'isDiscountCalcRule'))));
|
321 |
+
|
322 |
+
// Coupon codes are not stored in a calc rules, so handle them separately.
|
323 |
+
if (!Number::isZero($this->order['details']['BT']->coupon_discount)) {
|
324 |
+
$result[] = $this->getCouponCodeDiscountLine();
|
325 |
+
}
|
326 |
+
|
327 |
+
return $result;
|
328 |
+
}
|
329 |
+
|
330 |
+
/**
|
331 |
+
* Returns whether the calculation rule is a discount rule.
|
332 |
+
*
|
333 |
+
* @param \stdClass $calcRule
|
334 |
+
*
|
335 |
+
* @return bool
|
336 |
+
* True if the calculation rule is a discount rule.
|
337 |
+
*/
|
338 |
+
protected function isDiscountCalcRule(stdClass $calcRule)
|
339 |
+
{
|
340 |
+
return $calcRule->calc_amount < 0.0
|
341 |
+
&& !in_array($calcRule->calc_kind, array('VatTax', 'shipment', 'payment'));
|
342 |
+
}
|
343 |
+
|
344 |
+
/*
|
345 |
+
* Returns a discount item line for the discount calculation rule.
|
346 |
+
*
|
347 |
+
* The returned line will only contain a discount amount including tax.
|
348 |
+
* The completor will have to divide this amount over vat rates that are used
|
349 |
+
* in this invoice.
|
350 |
+
*
|
351 |
+
* @param \stdClass $calcRule
|
352 |
+
*
|
353 |
+
* @return array
|
354 |
+
* An item line for the invoice.
|
355 |
+
*/
|
356 |
+
protected function getCalcRuleDiscountLine(stdClass $calcRule)
|
357 |
+
{
|
358 |
+
$result = array(
|
359 |
+
'product' => $calcRule->calc_rule_name,
|
360 |
+
'unitprice' => null,
|
361 |
+
'unitpriceinc' => (float) $calcRule->calc_amount,
|
362 |
+
'vatrate' => null,
|
363 |
+
'quantity' => 1,
|
364 |
+
'meta-vatrate-source' => static::VatRateSource_Strategy,
|
365 |
+
'meta-strategy-split' => true,
|
366 |
+
);
|
367 |
+
|
368 |
+
return $result;
|
369 |
+
}
|
370 |
+
|
371 |
+
/**
|
372 |
+
* Returns an item line for the coupon code discount on this order.
|
373 |
+
*
|
374 |
+
* @return array
|
375 |
+
* An item line array.
|
376 |
+
*/
|
377 |
+
protected function getCouponCodeDiscountLine()
|
378 |
+
{
|
379 |
+
// @todo: standardize coupon lines: use itemnumber?, use shop specific names?, etc.
|
380 |
+
$result = array(
|
381 |
+
'itemnumber' => $this->order['details']['BT']->coupon_code,
|
382 |
+
'product' => $this->t('discount'),
|
383 |
+
'quantity' => 1,
|
384 |
+
'unitpriceinc' => (float) $this->order['details']['BT']->coupon_discount,
|
385 |
+
'vatrate' => null,
|
386 |
+
'meta-vatrate-source' => static::VatRateSource_Strategy,
|
387 |
+
'meta-strategy-split' => true,
|
388 |
+
);
|
389 |
+
|
390 |
+
return $result;
|
391 |
+
}
|
392 |
+
|
393 |
+
/**
|
394 |
+
* {@inheritdoc}
|
395 |
+
*/
|
396 |
+
protected function getPaymentFeeLine()
|
397 |
+
{
|
398 |
+
$result = array();
|
399 |
+
if (!empty($this->order['details']['BT']->order_payment)) {
|
400 |
+
$paymentEx = (float) $this->order['details']['BT']->order_payment;
|
401 |
+
if (!Number::isZero($paymentEx)) {
|
402 |
+
$paymentVat = (float) $this->order['details']['BT']->order_payment_tax;
|
403 |
+
|
404 |
+
$calcRule = $this->getCalcRule('payment');
|
405 |
+
if (!empty($calcRule->calc_value)) {
|
406 |
+
$vatInfo = array(
|
407 |
+
'vatrate' => (float) $calcRule->calc_value,
|
408 |
+
'meta-vatrate-source' => static::VatRateSource_Exact,
|
409 |
+
);
|
410 |
+
} else {
|
411 |
+
$vatInfo = $this->getVatRangeTags($paymentVat, $paymentEx, 0.0001, 0.01);
|
412 |
+
}
|
413 |
+
|
414 |
+
$result = array(
|
415 |
+
'product' => $this->t('payment_costs'),
|
416 |
+
'unitprice' => $paymentEx,
|
417 |
+
'quantity' => 1,
|
418 |
+
'vatamount' => $paymentVat,
|
419 |
+
) + $vatInfo;
|
420 |
+
}
|
421 |
+
}
|
422 |
+
return $result;
|
423 |
+
}
|
424 |
+
|
425 |
+
/**
|
426 |
+
* Returns a calculation rule identified by the given reference
|
427 |
+
*
|
428 |
+
* @param string $calcKind
|
429 |
+
* The value for the kind of calc rule.
|
430 |
+
* @param int $orderItemId
|
431 |
+
* The value for the order item id, or 0 for special lines.
|
432 |
+
*
|
433 |
+
* @return null|object
|
434 |
+
* The (1st) calculation rule for the given reference, or null if none
|
435 |
+
* found.
|
436 |
+
*/
|
437 |
+
protected function getCalcRule($calcKind, $orderItemId = 0)
|
438 |
+
{
|
439 |
+
foreach ($this->order['calc_rules'] as $calcRule) {
|
440 |
+
if ($calcRule->calc_kind == $calcKind) {
|
441 |
+
if (empty($orderItemId) || $calcRule->virtuemart_order_item_id == $orderItemId) {
|
442 |
+
return $calcRule;
|
443 |
+
}
|
444 |
+
}
|
445 |
+
}
|
446 |
+
return null;
|
447 |
+
}
|
448 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/VirtueMart/Invoice/Source.php
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Joomla\VirtueMart\Invoice;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Invoice\Source as BaseSource;
|
5 |
+
use VmModel;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Wraps a VirtueMart order in an invoice source object.
|
9 |
+
*/
|
10 |
+
class Source extends BaseSource
|
11 |
+
{
|
12 |
+
// More specifically typed properties.
|
13 |
+
/** @var array */
|
14 |
+
protected $source;
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Loads an Order source for the set id.
|
18 |
+
*/
|
19 |
+
protected function setSourceOrder()
|
20 |
+
{
|
21 |
+
/** @var \VirtueMartModelOrders $orders */
|
22 |
+
$orders = VmModel::getModel('orders');
|
23 |
+
$this->source = $orders->getOrder($this->id);
|
24 |
+
}
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Sets the id based on the loaded Order.
|
28 |
+
*/
|
29 |
+
protected function setIdOrder()
|
30 |
+
{
|
31 |
+
$this->id = $this->source['details']['BT']->virtuemart_order_id;
|
32 |
+
}
|
33 |
+
|
34 |
+
/**
|
35 |
+
* {@inheritdoc}
|
36 |
+
*/
|
37 |
+
public function getReference()
|
38 |
+
{
|
39 |
+
return $this->source['details']['BT']->order_number;
|
40 |
+
}
|
41 |
+
|
42 |
+
/**
|
43 |
+
* {@inheritDoc}
|
44 |
+
*
|
45 |
+
* @return string
|
46 |
+
* A single character indicating the order status.
|
47 |
+
*/
|
48 |
+
public function getStatus()
|
49 |
+
{
|
50 |
+
return $this->source['details']['BT']->order_status;
|
51 |
+
}
|
52 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/VirtueMart/Shop/ConfigForm.php
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Joomla\VirtueMart\Shop;
|
3 |
+
|
4 |
+
use JText;
|
5 |
+
use Siel\Acumulus\Joomla\Shop\ConfigForm as BaseConfigForm;
|
6 |
+
use Siel\Acumulus\Shop\ConfigInterface;
|
7 |
+
use VirtueMartModelOrderstatus;
|
8 |
+
use VmModel;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Class ConfigForm processes and builds the settings form page for the
|
12 |
+
* VirtueMart Acumulus module.
|
13 |
+
*/
|
14 |
+
class ConfigForm extends BaseConfigForm
|
15 |
+
{
|
16 |
+
/**
|
17 |
+
* {@inheritdoc}
|
18 |
+
*/
|
19 |
+
protected function getShopOrderStatuses()
|
20 |
+
{
|
21 |
+
/** @var VirtueMartModelOrderstatus $orderStatusModel */
|
22 |
+
$orderStatusModel = VmModel::getModel('orderstatus');
|
23 |
+
/** @var array[] $orderStates Method getOrderStatusNames() has an incorrect @return type ... */
|
24 |
+
$orderStates = $orderStatusModel->getOrderStatusNames();
|
25 |
+
foreach ($orderStates as $code => &$value) {
|
26 |
+
$value = \JText::_($value['order_status_name']);
|
27 |
+
}
|
28 |
+
return $orderStates;
|
29 |
+
}
|
30 |
+
|
31 |
+
/**
|
32 |
+
* {@inheritdoc}
|
33 |
+
*/
|
34 |
+
protected function getTriggerInvoiceSendEventOptions()
|
35 |
+
{
|
36 |
+
$result = parent::getTriggerInvoiceSendEventOptions();
|
37 |
+
// @todo: find out if there's something like an invoice create event.
|
38 |
+
unset($result[ConfigInterface::TriggerInvoiceSendEvent_InvoiceCreate]);
|
39 |
+
return $result;
|
40 |
+
}
|
41 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Joomla/VirtueMart/Shop/InvoiceManager.php
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Joomla\VirtueMart\Shop;
|
3 |
+
|
4 |
+
use DateTime;
|
5 |
+
use Siel\Acumulus\Invoice\Source as Source;
|
6 |
+
use Siel\Acumulus\Joomla\Shop\InvoiceManager as BaseInvoiceManager;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* {@inheritdoc}
|
10 |
+
*
|
11 |
+
* This override provides the VirtueMart specific queries.
|
12 |
+
*/
|
13 |
+
class InvoiceManager extends BaseInvoiceManager
|
14 |
+
{
|
15 |
+
/**
|
16 |
+
* {@inheritdoc}
|
17 |
+
*
|
18 |
+
* @todo: this override only returns order as supported invoice source type.
|
19 |
+
*
|
20 |
+
* Note: the VMInvoice extension seems to offer credit notes, but for now we
|
21 |
+
* do not support them.
|
22 |
+
*/
|
23 |
+
public function getSupportedInvoiceSourceTypes()
|
24 |
+
{
|
25 |
+
return array(
|
26 |
+
Source::Order,
|
27 |
+
//Source::CreditNote,
|
28 |
+
);
|
29 |
+
}
|
30 |
+
|
31 |
+
/**
|
32 |
+
* {@inheritdoc}
|
33 |
+
*/
|
34 |
+
public function getInvoiceSourcesByIdRange($invoiceSourceType, $InvoiceSourceIdFrom, $InvoiceSourceIdTo)
|
35 |
+
{
|
36 |
+
if ($invoiceSourceType === Source::Order) {
|
37 |
+
$query = sprintf("select virtuemart_order_id
|
38 |
+
from #__virtuemart_orders
|
39 |
+
where virtuemart_order_id between %d and %d",
|
40 |
+
$InvoiceSourceIdFrom, $InvoiceSourceIdTo);
|
41 |
+
return $this->getSourcesByQuery($invoiceSourceType, $query);
|
42 |
+
}
|
43 |
+
return array();
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* {@inheritdoc}
|
48 |
+
*
|
49 |
+
* By default, VirtueMart order numbers are non sequential random strings.
|
50 |
+
* So getting a range is not logical. However, extensions exists that do
|
51 |
+
* introduce sequential order numbers, E.g:
|
52 |
+
* http://extensions.joomla.org/profile/extension/extension-specific/virtuemart-extensions/human-readable-order-numbers
|
53 |
+
*/
|
54 |
+
public function getInvoiceSourcesByReferenceRange($invoiceSourceType, $InvoiceSourceReferenceFrom, $InvoiceSourceReferenceTo)
|
55 |
+
{
|
56 |
+
if ($invoiceSourceType === Source::Order) {
|
57 |
+
$query = sprintf("select virtuemart_order_id
|
58 |
+
from #__virtuemart_orders
|
59 |
+
where order_number between '%s' and '%s'",
|
60 |
+
$this->getDb()->escape($InvoiceSourceReferenceFrom),
|
61 |
+
$this->getDb()->escape($InvoiceSourceReferenceTo)
|
62 |
+
);
|
63 |
+
return $this->getSourcesByQuery($invoiceSourceType, $query);
|
64 |
+
}
|
65 |
+
return array();
|
66 |
+
}
|
67 |
+
|
68 |
+
/**
|
69 |
+
* {@inheritdoc}
|
70 |
+
*/
|
71 |
+
public function getInvoiceSourcesByDateRange($invoiceSourceType, DateTime $dateFrom, DateTime $dateTo)
|
72 |
+
{
|
73 |
+
if ($invoiceSourceType === Source::Order) {
|
74 |
+
$dateFrom = $this->getSqlDate($dateFrom);
|
75 |
+
$dateTo = $this->getSqlDate($dateTo);
|
76 |
+
$query = sprintf("select virtuemart_order_id
|
77 |
+
from #__virtuemart_orders
|
78 |
+
where modified_on between '%s' and '%s'",
|
79 |
+
$this->toSql($dateFrom), $this->toSql($dateTo));
|
80 |
+
return $this->getSourcesByQuery($invoiceSourceType, $query);
|
81 |
+
}
|
82 |
+
return array();
|
83 |
+
}
|
84 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Helpers/FormMapper.php
ADDED
@@ -0,0 +1,222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Magento\Helpers;
|
3 |
+
|
4 |
+
use Mage;
|
5 |
+
use Varien_Data_Form_Abstract;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class FormMapper maps an Acumulus form definition to a Magento form
|
9 |
+
* definition.
|
10 |
+
*/
|
11 |
+
class FormMapper
|
12 |
+
{
|
13 |
+
/** @var bool */
|
14 |
+
protected $hasRadios = false;
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Maps a set of field definitions.
|
18 |
+
*
|
19 |
+
* @param \Varien_Data_Form_Abstract $form
|
20 |
+
* @param array[] $fields
|
21 |
+
*/
|
22 |
+
public function map(Varien_Data_Form_Abstract $form, array $fields)
|
23 |
+
{
|
24 |
+
$this->fields($form, $fields);
|
25 |
+
if ($this->hasRadios) {
|
26 |
+
$form->addField('radio-styling',
|
27 |
+
'note',
|
28 |
+
array('text' => '<style> input[type=radio] { float: left; clear: both; margin-top: 0.2em;} .value label.inline {float: left !important; max-width: 95%; padding-left: 1em;} .note {clear: both;}</style>'),
|
29 |
+
'^');
|
30 |
+
}
|
31 |
+
}
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Maps a set of field definitions.
|
35 |
+
*
|
36 |
+
* @param \Varien_Data_Form_Abstract $parent
|
37 |
+
* @param array[] $fields
|
38 |
+
*/
|
39 |
+
public function fields($parent, array $fields)
|
40 |
+
{
|
41 |
+
foreach ($fields as $id => $field) {
|
42 |
+
if (!isset($field['id'])) {
|
43 |
+
$field['id'] = $id;
|
44 |
+
}
|
45 |
+
if (!isset($field['name'])) {
|
46 |
+
$field['name'] = $id;
|
47 |
+
}
|
48 |
+
$this->field($parent, $field);
|
49 |
+
}
|
50 |
+
}
|
51 |
+
|
52 |
+
/**
|
53 |
+
* Maps a single field definition.
|
54 |
+
*
|
55 |
+
* @param \Varien_Data_Form_Abstract $parent
|
56 |
+
* @param array $field
|
57 |
+
*/
|
58 |
+
public function field($parent, array $field)
|
59 |
+
{
|
60 |
+
if (!isset($field['attributes'])) {
|
61 |
+
$field['attributes'] = array();
|
62 |
+
}
|
63 |
+
$element = $parent->addField($field['id'], $this->getMagentoType($field), $this->getMagentoElementSettings($field));
|
64 |
+
if ($field['type'] === 'fieldset') {
|
65 |
+
$this->fields($element, $field['fields']);
|
66 |
+
}
|
67 |
+
}
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Returns the Magento form element type for the given Acumulus type string.
|
71 |
+
*
|
72 |
+
* @param array $field
|
73 |
+
*
|
74 |
+
* @return string
|
75 |
+
*/
|
76 |
+
protected function getMagentoType(array $field)
|
77 |
+
{
|
78 |
+
switch ($field['type']) {
|
79 |
+
case 'email':
|
80 |
+
$type = 'text';
|
81 |
+
break;
|
82 |
+
case 'markup':
|
83 |
+
$type = 'note';
|
84 |
+
break;
|
85 |
+
case 'radio':
|
86 |
+
$type = 'radios';
|
87 |
+
$this->hasRadios = true;
|
88 |
+
break;
|
89 |
+
case 'checkbox':
|
90 |
+
$type = 'checkboxes';
|
91 |
+
break;
|
92 |
+
case 'select':
|
93 |
+
$type = empty($field['attributes']['multiple']) ? 'select' : 'multiselect';
|
94 |
+
break;
|
95 |
+
default:
|
96 |
+
$type = $field['type'];
|
97 |
+
break;
|
98 |
+
}
|
99 |
+
return $type;
|
100 |
+
}
|
101 |
+
|
102 |
+
/**
|
103 |
+
* Returns the Magento form element settings.
|
104 |
+
*
|
105 |
+
* @param array $field
|
106 |
+
* The Acumulus field settings.
|
107 |
+
*
|
108 |
+
* @return array
|
109 |
+
* The Magento form element settings.
|
110 |
+
*/
|
111 |
+
protected function getMagentoElementSettings(array $field)
|
112 |
+
{
|
113 |
+
$config = array();
|
114 |
+
|
115 |
+
foreach ($field as $key => $value) {
|
116 |
+
$config += $this->getMagentoProperty($key, $value, $field['type']);
|
117 |
+
}
|
118 |
+
|
119 |
+
return $config;
|
120 |
+
}
|
121 |
+
|
122 |
+
/**
|
123 |
+
* Converts an Acumulus settings to a Magento setting.
|
124 |
+
*
|
125 |
+
* @param string $key
|
126 |
+
* The name of the setting to convert.
|
127 |
+
* @param mixed $value
|
128 |
+
* The value for the setting to convert.
|
129 |
+
* @param string $type
|
130 |
+
* The Acumulus field type.
|
131 |
+
*
|
132 |
+
* @return array
|
133 |
+
* The Magento setting. This will typically contain 1 element, but in some
|
134 |
+
* cases, 1 Acumulus field setting may result in multiple Magento settings.
|
135 |
+
*/
|
136 |
+
protected function getMagentoProperty($key, $value, $type)
|
137 |
+
{
|
138 |
+
switch ($key) {
|
139 |
+
// Fields to ignore:
|
140 |
+
case 'type':
|
141 |
+
$result = array();
|
142 |
+
if ($value === 'date') {
|
143 |
+
$result['image'] = Mage::getDesign()->getSkinUrl('images/grid-cal.gif');
|
144 |
+
}
|
145 |
+
break;
|
146 |
+
case 'id':
|
147 |
+
case 'fields':
|
148 |
+
$result = array();
|
149 |
+
break;
|
150 |
+
// Fields to return unchanged:
|
151 |
+
case 'legend':
|
152 |
+
case 'label':
|
153 |
+
case 'format':
|
154 |
+
$result = array($key => $value);
|
155 |
+
break;
|
156 |
+
case 'name':
|
157 |
+
if ($type === 'checkbox') {
|
158 |
+
// Make it an array for PHP POST processing, in case there are
|
159 |
+
// multiple checkboxes.
|
160 |
+
$value .= '[]';
|
161 |
+
}
|
162 |
+
$result = array($key => $value);
|
163 |
+
break;
|
164 |
+
case 'description':
|
165 |
+
$result = array('after_element_html' => '<p class="note">' . $value . '</p>');
|
166 |
+
break;
|
167 |
+
case 'value':
|
168 |
+
if ($type === 'markup') {
|
169 |
+
$result = array('text' => $value);
|
170 |
+
} else { // $type === 'hidden'
|
171 |
+
$result = array('value' => $value);
|
172 |
+
}
|
173 |
+
break;
|
174 |
+
case 'attributes':
|
175 |
+
// In magento you add pure html attributes at the same level as the
|
176 |
+
// "field attributes" that are for Magento.
|
177 |
+
$result = $value;
|
178 |
+
if (!empty($value['required'])) {
|
179 |
+
if (isset($result['class'])) {
|
180 |
+
$result['class'] .= ' ';
|
181 |
+
} else {
|
182 |
+
$result['class'] = '';
|
183 |
+
}
|
184 |
+
if ($type === 'radio') {
|
185 |
+
unset($result['required']);
|
186 |
+
$result['class'] .= 'validate-one-required-by-name';
|
187 |
+
} else {
|
188 |
+
$result['class'] .= 'required-entry';
|
189 |
+
}
|
190 |
+
}
|
191 |
+
break;
|
192 |
+
case 'options':
|
193 |
+
$result = array('values' => $this->getMagentoOptions($value));
|
194 |
+
break;
|
195 |
+
default:
|
196 |
+
Log::getInstance()->warning(__METHOD__ . "Unknown key '$key'");
|
197 |
+
$result = array($key => $value);
|
198 |
+
break;
|
199 |
+
}
|
200 |
+
return $result;
|
201 |
+
}
|
202 |
+
|
203 |
+
/**
|
204 |
+
* Converts a list of Acumulus field options to a list of Magento options.
|
205 |
+
*
|
206 |
+
* @param array $options
|
207 |
+
*
|
208 |
+
* @return array
|
209 |
+
* A list of Magento form element options.
|
210 |
+
*/
|
211 |
+
protected function getMagentoOptions(array $options)
|
212 |
+
{
|
213 |
+
$result = array();
|
214 |
+
foreach ($options as $value => $label) {
|
215 |
+
$result[] = array(
|
216 |
+
'value' => $value,
|
217 |
+
'label' => $label,
|
218 |
+
);
|
219 |
+
}
|
220 |
+
return $result;
|
221 |
+
}
|
222 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Helpers/Log.php
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Magento\Helpers;
|
3 |
+
|
4 |
+
use Mage;
|
5 |
+
use Siel\Acumulus\Helpers\Log as BaseLog;
|
6 |
+
use Zend_Log;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Extends the base log class to log any library logging to the Magento log.
|
10 |
+
*/
|
11 |
+
class Log extends BaseLog
|
12 |
+
{
|
13 |
+
/**
|
14 |
+
* {@inheritdoc}
|
15 |
+
*
|
16 |
+
* This override uses Mage::log().
|
17 |
+
*/
|
18 |
+
protected function write($message, $severity)
|
19 |
+
{
|
20 |
+
Mage::log($message, $this->getMagentoSeverity($severity));
|
21 |
+
}
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Returns the Magento equivalent of the severity.
|
25 |
+
*
|
26 |
+
* @param int $severity
|
27 |
+
* One of the constants of the base Log class.
|
28 |
+
*
|
29 |
+
* @return int
|
30 |
+
* The Magento equivalent of the severity.
|
31 |
+
*/
|
32 |
+
protected function getMagentoSeverity($severity)
|
33 |
+
{
|
34 |
+
switch ($severity) {
|
35 |
+
case Log::Error:
|
36 |
+
return Zend_Log::ERR;
|
37 |
+
case Log::Warning:
|
38 |
+
return Zend_Log::WARN;
|
39 |
+
case Log::Notice:
|
40 |
+
return Zend_Log::NOTICE;
|
41 |
+
case Log::Debug:
|
42 |
+
default:
|
43 |
+
return Zend_Log::DEBUG;
|
44 |
+
}
|
45 |
+
}
|
46 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Helpers/Mailer.php
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Magento\Helpers;
|
3 |
+
|
4 |
+
use Mage;
|
5 |
+
use Mage_Core_Model_Email_Template;
|
6 |
+
use Siel\Acumulus\Helpers\Mailer as BaseMailer;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Extends the base mailer class to send a mail using the Magento mail features.
|
10 |
+
*/
|
11 |
+
class Mailer extends BaseMailer
|
12 |
+
{
|
13 |
+
/**
|
14 |
+
* {@inheritdoc}
|
15 |
+
*/
|
16 |
+
public function sendInvoiceAddMailResult(array $result, array $messages, $invoiceSourceType, $invoiceSourceReference)
|
17 |
+
{
|
18 |
+
/** @var Mage_Core_Model_Email_Template $emailTemplate */
|
19 |
+
$emailTemplate = Mage::getModel('core/email_template');
|
20 |
+
$emailTemplate->setSenderEmail(Mage::getStoreConfig('trans_email/ident_general/email'));
|
21 |
+
$emailTemplate->setSenderName($this->getFromName());
|
22 |
+
$emailTemplate->setTemplateSubject($this->getSubject($result));
|
23 |
+
$body = $this->getBody($result, $messages, $invoiceSourceType, $invoiceSourceReference);
|
24 |
+
$emailTemplate->setTemplateText($body['html']);
|
25 |
+
return $emailTemplate->send($this->getToAddress(), Mage::getStoreConfig('trans_email/ident_general/name'));
|
26 |
+
}
|
27 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Invoice/Completor.php
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Magento\Invoice;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\Number;
|
5 |
+
use Siel\Acumulus\Invoice\Completor as BaseCompletor;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class Completor
|
9 |
+
*/
|
10 |
+
class Completor extends BaseCompletor
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* {@inheritdoc}
|
14 |
+
*
|
15 |
+
* !Magento bug!
|
16 |
+
* In credit memos, the discount amount from discount lines may differ from
|
17 |
+
* the summed discount amounts per line. This occurs when because refunded
|
18 |
+
* shipping costs do not advertise any discount amount.
|
19 |
+
*
|
20 |
+
* So, if the comparison fails, we correct the discount amount on the shipping
|
21 |
+
* line so that SplitKnownDiscountLine::checkPreconditions() will pass.
|
22 |
+
*/
|
23 |
+
protected function completeLineTotals()
|
24 |
+
{
|
25 |
+
parent::completeLineTotals();
|
26 |
+
|
27 |
+
if ($this->source->getType() === Source::CreditNote) {
|
28 |
+
$discountAmountInc = 0.0;
|
29 |
+
$discountLineAmountInc = 0.0;
|
30 |
+
|
31 |
+
$invoiceLines = $this->invoice['customer']['invoice']['line'];
|
32 |
+
foreach ($invoiceLines as $line) {
|
33 |
+
if (isset($line['meta-line-discount-amountinc'])) {
|
34 |
+
$discountAmountInc += $line['meta-line-discount-amountinc'];
|
35 |
+
}
|
36 |
+
|
37 |
+
if ($line['meta-line-type'] === Creator::LineType_Discount) {
|
38 |
+
if (isset($line['meta-line-priceinc'])) {
|
39 |
+
$discountLineAmountInc += $line['meta-line-priceinc'];
|
40 |
+
} else if (isset($line['unitpriceinc'])) {
|
41 |
+
$discountLineAmountInc += $line['quantity'] * $line['unitpriceinc'];
|
42 |
+
}
|
43 |
+
}
|
44 |
+
}
|
45 |
+
|
46 |
+
if (!Number::floatsAreEqual($discountAmountInc, $discountLineAmountInc)) {
|
47 |
+
foreach ($invoiceLines as $line) {
|
48 |
+
if ($line['meta-line-type'] === Creator::LineType_Shipping && isset($line['meta-line-discount-amountinc'])) {
|
49 |
+
$line['meta-line-discount-amountinc'] += $discountLineAmountInc - $discountAmountInc;
|
50 |
+
}
|
51 |
+
}
|
52 |
+
}
|
53 |
+
}
|
54 |
+
}
|
55 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Invoice/Creator.php
ADDED
@@ -0,0 +1,527 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Magento\Invoice;
|
3 |
+
|
4 |
+
use Mage;
|
5 |
+
use Mage_Core_Model_Resource_Db_Collection_Abstract;
|
6 |
+
use Mage_Customer_Model_Customer;
|
7 |
+
use Mage_Sales_Model_Order;
|
8 |
+
use Mage_Sales_Model_Order_Creditmemo;
|
9 |
+
use Mage_Sales_Model_Order_Creditmemo_Item;
|
10 |
+
use Mage_Sales_Model_Order_Invoice;
|
11 |
+
use Mage_Sales_Model_Order_Item;
|
12 |
+
use Mage_Tax_Model_Config;
|
13 |
+
use Siel\Acumulus\Helpers\Number;
|
14 |
+
use Siel\Acumulus\Invoice\ConfigInterface;
|
15 |
+
use Siel\Acumulus\Invoice\Creator as BaseCreator;
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Allows to create arrays in the Acumulus invoice structure from a Magento
|
19 |
+
* order or credit memo.
|
20 |
+
*
|
21 |
+
* @todo: multi currency: use base values (default store currency) or values
|
22 |
+
* without base in their names (selected store currency). Other fields
|
23 |
+
* involved:
|
24 |
+
* - base_currency_code
|
25 |
+
* - store_to_base_rate
|
26 |
+
* - store_to_order_rate
|
27 |
+
* - order_currency_code
|
28 |
+
*/
|
29 |
+
class Creator extends BaseCreator
|
30 |
+
{
|
31 |
+
/** @var Mage_Sales_Model_Order */
|
32 |
+
protected $order;
|
33 |
+
|
34 |
+
/** @var Mage_Sales_Model_Order_Creditmemo */
|
35 |
+
protected $creditNote;
|
36 |
+
|
37 |
+
/** @var Mage_Core_Model_Resource_Db_Collection_Abstract */
|
38 |
+
protected $shopInvoices;
|
39 |
+
|
40 |
+
/** @var Mage_Sales_Model_Order_Invoice */
|
41 |
+
protected $shopInvoice;
|
42 |
+
|
43 |
+
/**
|
44 |
+
* {@inheritdoc}
|
45 |
+
*
|
46 |
+
* This override also initializes Magento specific properties related to the
|
47 |
+
* source.
|
48 |
+
*/
|
49 |
+
protected function setInvoiceSource($source)
|
50 |
+
{
|
51 |
+
parent::setInvoiceSource($source);
|
52 |
+
switch ($this->invoiceSource->getType()) {
|
53 |
+
case Source::Order:
|
54 |
+
$this->order = $this->invoiceSource->getSource();
|
55 |
+
$this->creditNote = null;
|
56 |
+
break;
|
57 |
+
case Source::CreditNote:
|
58 |
+
$this->creditNote = $this->invoiceSource->getSource();
|
59 |
+
$this->order = $this->creditNote->getOrder();
|
60 |
+
break;
|
61 |
+
}
|
62 |
+
$this->shopInvoices = $this->order->getInvoiceCollection();
|
63 |
+
$this->shopInvoice = count($this->shopInvoices) > 0 ? $this->shopInvoices->getFirstItem() : null;
|
64 |
+
}
|
65 |
+
|
66 |
+
/**
|
67 |
+
* {@inheritdoc}
|
68 |
+
*/
|
69 |
+
protected function getCustomer()
|
70 |
+
{
|
71 |
+
$result = array();
|
72 |
+
|
73 |
+
/** @var Mage_Sales_Model_Order|Mage_Sales_Model_Order_Creditmemo $order */
|
74 |
+
$order = $this->creditNote !== null ? $this->creditNote : $this->order;
|
75 |
+
|
76 |
+
if ($order->getCustomerId()) {
|
77 |
+
/** @var Mage_Customer_Model_Customer $customer */
|
78 |
+
$customer = Mage::getModel('customer/customer')->load($order->getCustomerId());
|
79 |
+
$result['contactyourid'] = $customer->getId();
|
80 |
+
/** @noinspection PhpUndefinedMethodInspection */
|
81 |
+
$this->addIfNotEmpty($result, 'contactyourid', $customer->getIncrementId());
|
82 |
+
}
|
83 |
+
|
84 |
+
$invoiceAddress = $order->getBillingAddress();
|
85 |
+
$this->addEmpty($result, 'companyname1', $invoiceAddress->getCompany());
|
86 |
+
$result['fullname'] = $invoiceAddress->getFirstname() . ' ' . $invoiceAddress->getLastname();
|
87 |
+
$this->addEmpty($result, 'address1', $invoiceAddress->getStreet(1));
|
88 |
+
$this->addEmpty($result, 'address2', $invoiceAddress->getStreet(2));
|
89 |
+
$this->addEmpty($result, 'postalcode', $invoiceAddress->getPostcode());
|
90 |
+
$this->addEmpty($result, 'city', $invoiceAddress->getCity());
|
91 |
+
if ($invoiceAddress->getCountryId()) {
|
92 |
+
$result['countrycode'] = $invoiceAddress->getCountry();
|
93 |
+
}
|
94 |
+
// Magento has 2 VAT numbers:
|
95 |
+
// http://magento.stackexchange.com/questions/42164/there-are-2-vat-fields-in-onepage-checkout-which-one-should-i-be-using
|
96 |
+
$this->addIfNotEmpty($result, 'vatnumber', $order->getCustomerTaxvat());
|
97 |
+
/** @noinspection PhpUndefinedMethodInspection */
|
98 |
+
$this->addIfNotEmpty($result, 'vatnumber', $invoiceAddress->getVatId());
|
99 |
+
$this->addIfNotEmpty($result, 'telephone', $invoiceAddress->getTelephone());
|
100 |
+
$this->addIfNotEmpty($result, 'fax', $invoiceAddress->getFax());
|
101 |
+
$result['email'] = $invoiceAddress->getEmail();
|
102 |
+
|
103 |
+
return $result;
|
104 |
+
}
|
105 |
+
|
106 |
+
/**
|
107 |
+
* {@inheritdoc}
|
108 |
+
*/
|
109 |
+
protected function searchProperty($property)
|
110 |
+
{
|
111 |
+
$invoiceAddress = $this->invoiceSource->getSource()->getBillingAddress();
|
112 |
+
$value = $this->getProperty($property, $invoiceAddress);
|
113 |
+
if (empty($value)) {
|
114 |
+
$customer = Mage::getModel('customer/customer')->load($this->invoiceSource->getSource()->getCustomerId());
|
115 |
+
$value = $this->getProperty($property, $customer);
|
116 |
+
}
|
117 |
+
if (empty($value)) {
|
118 |
+
$value = parent::searchProperty($property);
|
119 |
+
}
|
120 |
+
return $value;
|
121 |
+
}
|
122 |
+
|
123 |
+
/**
|
124 |
+
* {@inheritdoc}
|
125 |
+
*/
|
126 |
+
protected function getInvoiceNumber($invoiceNumberSource)
|
127 |
+
{
|
128 |
+
$result = $this->invoiceSource->getReference();
|
129 |
+
if ($invoiceNumberSource == ConfigInterface::InvoiceNrSource_ShopInvoice && $this->invoiceSource->getType() === Source::Order && $this->shopInvoice !== null) {
|
130 |
+
$result = $this->shopInvoice->getIncrementId();
|
131 |
+
}
|
132 |
+
return $result;
|
133 |
+
}
|
134 |
+
|
135 |
+
/**
|
136 |
+
* {@inheritdoc}
|
137 |
+
*/
|
138 |
+
protected function getInvoiceDate($dateToUse)
|
139 |
+
{
|
140 |
+
// createdAt returns yyyy-mm-dd hh:mm:ss, take date part.
|
141 |
+
$result = substr($this->invoiceSource->getSource()->getCreatedAt(), 0, strlen('yyyy-mm-dd'));
|
142 |
+
// A credit note is to be considered an invoice on its own.
|
143 |
+
if ($dateToUse == ConfigInterface::InvoiceDate_InvoiceCreate && $this->invoiceSource->getType() === Source::Order && $this->shopInvoice !== null) {
|
144 |
+
$result = substr($this->shopInvoice->getCreatedAt(), 0, strlen('yyyy-mm-dd'));
|
145 |
+
}
|
146 |
+
return $result;
|
147 |
+
}
|
148 |
+
|
149 |
+
protected function getPaymentStateOrder()
|
150 |
+
{
|
151 |
+
return Number::isZero($this->order->getBaseTotalDue())
|
152 |
+
? ConfigInterface::PaymentStatus_Paid
|
153 |
+
: ConfigInterface::PaymentStatus_Due;
|
154 |
+
}
|
155 |
+
|
156 |
+
protected function getPaymentStateCreditNote()
|
157 |
+
{
|
158 |
+
return $this->creditNote->getState() == Mage_Sales_Model_Order_Creditmemo::STATE_REFUNDED
|
159 |
+
? ConfigInterface::PaymentStatus_Paid
|
160 |
+
: ConfigInterface::PaymentStatus_Due;
|
161 |
+
}
|
162 |
+
|
163 |
+
protected function getPaymentDateOrder()
|
164 |
+
{
|
165 |
+
// Take date of last payment as payment date.
|
166 |
+
$paymentDate = null;
|
167 |
+
foreach ($this->order->getStatusHistoryCollection() as $statusChange) {
|
168 |
+
/** @var \Mage_Sales_Model_Order_Status_History $statusChange */
|
169 |
+
if (!$paymentDate || $this->isPaidStatus($statusChange->getStatus())) {
|
170 |
+
$createdAt = substr($statusChange->getCreatedAt(), 0, strlen('yyyy-mm-dd'));
|
171 |
+
if (!$paymentDate || $createdAt < $paymentDate) {
|
172 |
+
$paymentDate = $createdAt;
|
173 |
+
}
|
174 |
+
}
|
175 |
+
}
|
176 |
+
return $paymentDate;
|
177 |
+
}
|
178 |
+
|
179 |
+
protected function isPaidStatus($status)
|
180 |
+
{
|
181 |
+
return in_array($status, array('processing', 'closed', 'complete'));
|
182 |
+
}
|
183 |
+
|
184 |
+
protected function getPaymentDateCreditNote()
|
185 |
+
{
|
186 |
+
return substr($this->creditNote->getCreatedAt(), 0, strlen('yyyy-mm-dd'));
|
187 |
+
}
|
188 |
+
|
189 |
+
/**
|
190 |
+
* {@inheritdoc}
|
191 |
+
*
|
192 |
+
* This override provides the values meta-invoice-amountinc and
|
193 |
+
* meta-invoice-vatamount.
|
194 |
+
*/
|
195 |
+
protected function getInvoiceTotals()
|
196 |
+
{
|
197 |
+
$sign = $this->invoiceSource->getType() === Source::CreditNote ? -1.0 : 1.0;
|
198 |
+
return array(
|
199 |
+
'meta-invoice-amountinc' => $sign * $this->invoiceSource->getSource()->getBaseGrandTotal(),
|
200 |
+
'meta-invoice-vatamount' => $sign * $this->invoiceSource->getSource()->getBaseTaxAmount(),
|
201 |
+
);
|
202 |
+
}
|
203 |
+
|
204 |
+
/**
|
205 |
+
* {@inheritdoc}
|
206 |
+
*/
|
207 |
+
protected function getItemLinesOrder()
|
208 |
+
{
|
209 |
+
$result = array();
|
210 |
+
// Items may be composed, so start with all "visible" items.
|
211 |
+
foreach ($this->order->getAllVisibleItems() as $item) {
|
212 |
+
$result = array_merge($result, $this->getItemLineOrder($item));
|
213 |
+
}
|
214 |
+
return $result;
|
215 |
+
}
|
216 |
+
|
217 |
+
/**
|
218 |
+
* {@inheritdoc}
|
219 |
+
*/
|
220 |
+
protected function getItemLinesCreditNote()
|
221 |
+
{
|
222 |
+
$result = array();
|
223 |
+
// Items may be composed, so start with all "visible" items.
|
224 |
+
foreach ($this->creditNote->getAllItems() as $item) {
|
225 |
+
// Only items for which row total is set, are refunded
|
226 |
+
/** @var Mage_Sales_Model_Order_Creditmemo_Item $item */
|
227 |
+
if ((float) $item->getRowTotal() > 0.0) {
|
228 |
+
$result[] = $this->getItemLineCreditNote($item);
|
229 |
+
}
|
230 |
+
}
|
231 |
+
return $result;
|
232 |
+
}
|
233 |
+
|
234 |
+
/**
|
235 |
+
* Returns 1 or more item lines for 1 main product line.
|
236 |
+
*
|
237 |
+
* @param Mage_Sales_Model_Order_Item $item
|
238 |
+
*
|
239 |
+
* @return array
|
240 |
+
*/
|
241 |
+
protected function getItemLineOrder(Mage_Sales_Model_Order_Item $item)
|
242 |
+
{
|
243 |
+
$result = array();
|
244 |
+
$childLines = array();
|
245 |
+
|
246 |
+
$vatRate = (float) $item->getTaxPercent();
|
247 |
+
$productPriceInc = (float) $item->getPriceInclTax();
|
248 |
+
// For higher precision of the unit price, we use the prices as entered by
|
249 |
+
// the admin.
|
250 |
+
$productPriceEx = $this->productPricesIncludeTax() ? (float) $productPriceInc / (100.0 + $vatRate) * 100.0 : (float) $item->getPrice();
|
251 |
+
// Tax amount = VAT over discounted product price.
|
252 |
+
// Hidden tax amount = VAT over discount.
|
253 |
+
// But as discounts get their own lines and the product lines are showing
|
254 |
+
// the normal (not discounted) price we add these 2.
|
255 |
+
$lineVat = (float) $item->getTaxAmount() + (float) $item->getHiddenTaxAmount();
|
256 |
+
|
257 |
+
// Simple products (products without children): add as 1 line.
|
258 |
+
$this->addIfNotEmpty($result, 'itemnumber', $item->getSku());
|
259 |
+
$result += array(
|
260 |
+
'product' => $item->getName(),
|
261 |
+
'unitprice' => $productPriceEx,
|
262 |
+
'unitpriceinc' => $productPriceInc,
|
263 |
+
'vatrate' => $vatRate,
|
264 |
+
'meta-line-vatamount' => $lineVat,
|
265 |
+
'quantity' => $item->getQtyOrdered(),
|
266 |
+
'meta-vatrate-source' => static::VatRateSource_Exact,
|
267 |
+
);
|
268 |
+
if (!Number::isZero($item->getDiscountAmount())) {
|
269 |
+
// Store discount on this item to be able to get correct discount lines.
|
270 |
+
$result['meta-line-discount-amountinc'] = -$item->getDiscountAmount();
|
271 |
+
}
|
272 |
+
|
273 |
+
// Also add child lines for composed products, a.o. to be able to print a
|
274 |
+
// packing slip in Acumulus.
|
275 |
+
foreach ($item->getChildrenItems() as $child) {
|
276 |
+
$childLine = $this->getItemLineOrder($child);
|
277 |
+
$childLines = array_merge($childLines, $childLine);
|
278 |
+
}
|
279 |
+
|
280 |
+
// If:
|
281 |
+
// - there is exactly 1 child line
|
282 |
+
// - for the same item number, and quantity
|
283 |
+
// - with no price info on the child
|
284 |
+
// We seem to be processing a configurable product that for some reason
|
285 |
+
// appears twice: do not add the child, but copy the product description to
|
286 |
+
// the result as it contains more option descriptions.
|
287 |
+
// @todo: refine this: when to add just 1 line, when multiple lines (see OC)
|
288 |
+
if (count($childLines) === 1
|
289 |
+
&& $result['itemnumber'] === $childLines[0]['itemnumber']
|
290 |
+
&& $childLines[0]['unitprice'] == 0
|
291 |
+
&& $result['quantity'] === $childLines[0]['quantity']
|
292 |
+
) {
|
293 |
+
$result['product'] = $childLines[0]['product'];
|
294 |
+
$childLines = array();
|
295 |
+
}
|
296 |
+
// keep price info on bundle level or child level?
|
297 |
+
if (count($childLines) > 0) {
|
298 |
+
if ($item->getPriceInclTax() > 0.0 && ($item->getTaxPercent() > 0 || $item->getTaxAmount() == 0.0)) {
|
299 |
+
// If the bundle line contains valid price and tax info, we remove that
|
300 |
+
// info from all child lines (to prevent accounting amounts twice).
|
301 |
+
foreach ($childLines as &$childLine) {
|
302 |
+
$childLine['unitprice'] = 0;
|
303 |
+
$childLine['vatrate'] = $result['vatrate'];
|
304 |
+
}
|
305 |
+
} else {
|
306 |
+
// Do all children have the same vat?
|
307 |
+
$vatRate = null;
|
308 |
+
foreach ($childLines as $childLine) {
|
309 |
+
// Check if this is not an empty price/vat line.
|
310 |
+
if ($childLine['unitprice'] != 0 && $childLine['vatrate'] !== -1) {
|
311 |
+
// Same vat?
|
312 |
+
if ($vatRate === null || $childLine['vatrate'] === $vatRate) {
|
313 |
+
$vatRate = $childLine['vatrate'];
|
314 |
+
} else {
|
315 |
+
$vatRate = null;
|
316 |
+
break;
|
317 |
+
}
|
318 |
+
}
|
319 |
+
}
|
320 |
+
|
321 |
+
if ($vatRate !== null && $vatRate == $result['vatrate'] && $productPriceEx != 0.0) {
|
322 |
+
// Bundle has price info and same vat as ALL children: use price and
|
323 |
+
// vat info from bundle line and remove it from child lines to prevent
|
324 |
+
// accounting amounts twice.
|
325 |
+
foreach ($childLines as &$childLine) {
|
326 |
+
$childLine['unitprice'] = 0;
|
327 |
+
$childLine['vatrate'] = $result['vatrate'];
|
328 |
+
}
|
329 |
+
} else {
|
330 |
+
// All price and vat info is/remains on the child lines.
|
331 |
+
// Make sure no price and vat info is left on the bundle line.
|
332 |
+
$result['unitprice'] = 0;
|
333 |
+
$result['vatrate'] = -1;
|
334 |
+
}
|
335 |
+
}
|
336 |
+
}
|
337 |
+
|
338 |
+
$result = array_merge(array($result), $childLines);
|
339 |
+
return $result;
|
340 |
+
}
|
341 |
+
|
342 |
+
/**
|
343 |
+
* Returns 1 item line for 1 credit line.
|
344 |
+
*
|
345 |
+
* @param Mage_Sales_Model_Order_Creditmemo_Item $item
|
346 |
+
*
|
347 |
+
* @return array
|
348 |
+
*/
|
349 |
+
protected function getItemLineCreditNote(Mage_Sales_Model_Order_Creditmemo_Item $item)
|
350 |
+
{
|
351 |
+
$result = array();
|
352 |
+
|
353 |
+
$lineVat = -((float) $item->getTaxAmount() + (float) $item->getHiddenTaxAmount());
|
354 |
+
$productPriceEx = -((float) $item->getPrice());
|
355 |
+
|
356 |
+
// On a credit note we only have single lines, no compound lines.
|
357 |
+
$this->addIfNotEmpty($result, 'itemnumber', $item->getSku());
|
358 |
+
$result += array(
|
359 |
+
'product' => $item->getName(),
|
360 |
+
'unitprice' => $productPriceEx,
|
361 |
+
'quantity' => $item->getQty(),
|
362 |
+
'meta-line-vatamount' => $lineVat,
|
363 |
+
);
|
364 |
+
|
365 |
+
if ($this->productPricesIncludeTax()) {
|
366 |
+
$productPriceInc = -((float) $item->getPriceInclTax());
|
367 |
+
$result['unitpriceinc'] = $productPriceInc;
|
368 |
+
}
|
369 |
+
|
370 |
+
$orderItemId = $item->getOrderItemId();
|
371 |
+
if (!empty($orderItemId)) {
|
372 |
+
$orderItem = $item->getOrderItem();
|
373 |
+
$result += array(
|
374 |
+
'vatrate' => $orderItem->getTaxPercent(),
|
375 |
+
'meta-vatrate-source' => static::VatRateSource_Exact,
|
376 |
+
);
|
377 |
+
} else {
|
378 |
+
$result += $this->getVatRangeTags($lineVat / $item->getQty(), $productPriceEx, 0.02, 0.02);
|
379 |
+
$result['meta-calculated-fields'] = 'vatamount';
|
380 |
+
}
|
381 |
+
|
382 |
+
if (!Number::isZero($item->getDiscountAmount())) {
|
383 |
+
// Credit note: discounts are cancelled, thus amount is positive.
|
384 |
+
$result['meta-line-discount-amountinc'] = $item->getDiscountAmount();
|
385 |
+
}
|
386 |
+
|
387 |
+
return $result;
|
388 |
+
}
|
389 |
+
|
390 |
+
/**
|
391 |
+
* {@inheritdoc}
|
392 |
+
*/
|
393 |
+
protected function getShippingLine()
|
394 |
+
{
|
395 |
+
$shippingDescription = $this->order->getShippingDescription();
|
396 |
+
$result = array(
|
397 |
+
'itemnumber' => '',
|
398 |
+
'product' => !empty($shippingDescription) ? $shippingDescription : $this->t('shipping_costs'),
|
399 |
+
'quantity' => 1,
|
400 |
+
);
|
401 |
+
|
402 |
+
// What do the following methods return:
|
403 |
+
// - getShippingAmount(): shipping costs excl VAT excl any discount
|
404 |
+
// - getShippingInclTax(): shipping costs incl VAT excl any discount
|
405 |
+
// - getShippingTaxAmount(): VAT on shipping costs incl discount
|
406 |
+
// - getShippingDiscountAmount(): discount on shipping incl VAT
|
407 |
+
/** @var Mage_Sales_Model_Order|Mage_Sales_Model_Order_Creditmemo $magentoSource */
|
408 |
+
$magentoSource = $this->invoiceSource->getSource();
|
409 |
+
if (!Number::isZero($magentoSource->getShippingAmount())) {
|
410 |
+
// We have 2 ways of calculating the vat rate: first one is based on tax
|
411 |
+
// amount and normal shipping costs corrected with any discount (as the
|
412 |
+
// tax amount is including any discount):
|
413 |
+
// $vatRate1 = $magentoSource->getShippingTaxAmount() / ($magentoSource->getShippingInclTax() - $magentoSource->getShippingDiscountAmount() - $magentoSource->getShippingTaxAmount());
|
414 |
+
// However, we will use the 2nd way as that seems to be more precise and,
|
415 |
+
// thus generally leads to a smaller range:
|
416 |
+
// Get range based on normal shipping costs incl and excl VAT.
|
417 |
+
$sign = $this->getSign();
|
418 |
+
$shippingInc = $sign * $magentoSource->getShippingInclTax();
|
419 |
+
$shippingEx = $sign * $magentoSource->getShippingAmount();
|
420 |
+
$shippingVat = $shippingInc - $shippingEx;
|
421 |
+
$result += array(
|
422 |
+
'unitprice' => $shippingEx,
|
423 |
+
'unitpriceinc' => $shippingInc,
|
424 |
+
) + $this->getVatRangeTags($shippingVat, $shippingEx, 0.02, 0.01);
|
425 |
+
$result['meta-calculated-fields'] = 'vatamount';
|
426 |
+
|
427 |
+
// getShippingDiscountAmount() only exists on Orders.
|
428 |
+
if ($this->invoiceSource->getType() === Source::Order && !Number::isZero($magentoSource->getShippingDiscountAmount())) {
|
429 |
+
$result['meta-line-discount-amountinc'] = -$sign * $magentoSource->getShippingDiscountAmount();
|
430 |
+
} else if ($this->invoiceSource->getType() === Source::CreditNote && !Number::floatsAreEqual($shippingVat, $magentoSource->getShippingTaxAmount(), 0.02)) {
|
431 |
+
// On credit notes, the shipping discount amount is not stored but can
|
432 |
+
// be deduced via the shipping discount tax amount and the shipping vat
|
433 |
+
// rate. To get a more precise 'meta-line-discount-amountinc', we
|
434 |
+
// compute that in the completor when we have corrected the vatrate.
|
435 |
+
$result['meta-line-discount-vatamount'] = $sign * ($shippingVat - $sign * $magentoSource->getShippingTaxAmount());
|
436 |
+
}
|
437 |
+
}
|
438 |
+
|
439 |
+
// Only add a free shipping line on an order, not on a credit note: free
|
440 |
+
// shipping is never refunded...
|
441 |
+
else if ($this->invoiceSource->getType() === Source::Order) {
|
442 |
+
// Free shipping should get a "normal" tax rate. We leave that to the
|
443 |
+
// completor to determine.
|
444 |
+
$result += array(
|
445 |
+
'unitprice' => 0,
|
446 |
+
'vatrate' => null,
|
447 |
+
'meta-vatrate-source' => static::VatRateSource_Completor,
|
448 |
+
);
|
449 |
+
}
|
450 |
+
return $result;
|
451 |
+
}
|
452 |
+
|
453 |
+
/**
|
454 |
+
* {@inheritdoc}
|
455 |
+
*/
|
456 |
+
protected function getDiscountLines()
|
457 |
+
{
|
458 |
+
$result = array();
|
459 |
+
if (!Number::isZero($this->invoiceSource->getSource()->getDiscountAmount())) {
|
460 |
+
$line = array(
|
461 |
+
'itemnumber' => '',
|
462 |
+
'product' => $this->getDiscountDescription(),
|
463 |
+
'vatrate' => null,
|
464 |
+
'meta-vatrate-source' => static::VatRateSource_Strategy,
|
465 |
+
'meta-strategy-split' => true,
|
466 |
+
'quantity' => 1,
|
467 |
+
);
|
468 |
+
// Product prices incl. VAT => discount amount is also incl. VAT
|
469 |
+
if ($this->productPricesIncludeTax()) {
|
470 |
+
$line['unitpriceinc'] = $this->getSign() * $this->invoiceSource->getSource()->getDiscountAmount();
|
471 |
+
} else {
|
472 |
+
$line['unitprice'] = $this->getSign() * $this->invoiceSource->getSource()->getDiscountAmount();
|
473 |
+
}
|
474 |
+
$result[] = $line;
|
475 |
+
}
|
476 |
+
return $result;
|
477 |
+
}
|
478 |
+
|
479 |
+
/**
|
480 |
+
* {@inheritdoc}
|
481 |
+
*
|
482 |
+
* This implementation may return a manual line for a credit memo.
|
483 |
+
*/
|
484 |
+
protected function getManualLines()
|
485 |
+
{
|
486 |
+
$result = array();
|
487 |
+
|
488 |
+
if (isset($this->creditNote) && !Number::isZero($this->creditNote->getAdjustment())) {
|
489 |
+
$line = array(
|
490 |
+
'product' => $this->t('refund_adjustment'),
|
491 |
+
'unitprice' => -$this->creditNote->getAdjustment(),
|
492 |
+
'quantity' => 1,
|
493 |
+
'vatrate' => 0,
|
494 |
+
);
|
495 |
+
$result[] = $line;
|
496 |
+
}
|
497 |
+
return $result;
|
498 |
+
}
|
499 |
+
|
500 |
+
/**
|
501 |
+
* @return string
|
502 |
+
*/
|
503 |
+
protected function getDiscountDescription()
|
504 |
+
{
|
505 |
+
if ($this->order->getDiscountDescription()) {
|
506 |
+
$description = $this->t('discount_code') . ' ' . $this->order->getDiscountDescription();
|
507 |
+
} else if ($this->order->getCouponCode()) {
|
508 |
+
$description = $this->t('discount_code') . ' ' . $this->order->getCouponCode();
|
509 |
+
} else {
|
510 |
+
$description = $this->t('discount');
|
511 |
+
}
|
512 |
+
return $description;
|
513 |
+
}
|
514 |
+
|
515 |
+
/**
|
516 |
+
* Returns if the prices for the products are entered with or without tax.
|
517 |
+
*
|
518 |
+
* @return bool
|
519 |
+
* Whether the prices for the products are entered with or without tax.
|
520 |
+
*/
|
521 |
+
protected function productPricesIncludeTax()
|
522 |
+
{
|
523 |
+
/** @var Mage_Tax_Model_Config $taxConfig */
|
524 |
+
$taxConfig = Mage::getModel('tax/config');
|
525 |
+
return $taxConfig->priceIncludesTax();
|
526 |
+
}
|
527 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Invoice/Source.php
ADDED
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Magento\Invoice;
|
3 |
+
|
4 |
+
use Mage;
|
5 |
+
use Mage_Sales_Model_Order;
|
6 |
+
use Mage_Sales_Model_Order_Creditmemo;
|
7 |
+
use Siel\Acumulus\Invoice\Source as BaseSource;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Wraps a Magento order or credit memo in an invoice source object.
|
11 |
+
*/
|
12 |
+
class Source extends BaseSource
|
13 |
+
{
|
14 |
+
// More specifically typed properties.
|
15 |
+
/** @var Mage_Sales_Model_Order|Mage_Sales_Model_Order_Creditmemo */
|
16 |
+
protected $source;
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Loads an Order source for the set id.
|
20 |
+
*/
|
21 |
+
protected function setSourceOrder()
|
22 |
+
{
|
23 |
+
$this->source = Mage::getModel('sales/order');
|
24 |
+
$this->source->load($this->id);
|
25 |
+
}
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Loads a Credit memo source for the set id.
|
29 |
+
*/
|
30 |
+
protected function setSourceCreditNote()
|
31 |
+
{
|
32 |
+
$this->source = Mage::getModel('sales/order_creditmemo');
|
33 |
+
$this->source->load($this->id);
|
34 |
+
}
|
35 |
+
|
36 |
+
/**
|
37 |
+
* {@inheritdoc}
|
38 |
+
*/
|
39 |
+
protected function setId()
|
40 |
+
{
|
41 |
+
$this->id = $this->source->getId();
|
42 |
+
}
|
43 |
+
|
44 |
+
/**
|
45 |
+
* {@inheritdoc}
|
46 |
+
*/
|
47 |
+
public function getReference()
|
48 |
+
{
|
49 |
+
return $this->callTypeSpecificMethod(__FUNCTION__);
|
50 |
+
}
|
51 |
+
|
52 |
+
/**
|
53 |
+
* Returns the order reference.
|
54 |
+
*
|
55 |
+
* @return string
|
56 |
+
*/
|
57 |
+
protected function getReferenceOrder()
|
58 |
+
{
|
59 |
+
return $this->source->getIncrementId();
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Returns the credit note reference.
|
64 |
+
*
|
65 |
+
* @return string
|
66 |
+
*/
|
67 |
+
protected function getReferenceCreditNote()
|
68 |
+
{
|
69 |
+
return 'CM' . $this->source->getIncrementId();
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Returns the status of this order.
|
74 |
+
*
|
75 |
+
* @return string
|
76 |
+
*/
|
77 |
+
protected function getStatusOrder()
|
78 |
+
{
|
79 |
+
return $this->source->getStatus();
|
80 |
+
}
|
81 |
+
|
82 |
+
/**
|
83 |
+
* Returns the status of this order.
|
84 |
+
*
|
85 |
+
* @return int
|
86 |
+
* 1 of
|
87 |
+
* Mage_Sales_Model_Order_Creditmemo::STATE_OPEN = 1;
|
88 |
+
* Mage_Sales_Model_Order_Creditmemo::STATE_REFUNDED = 2;
|
89 |
+
* Mage_Sales_Model_Order_Creditmemo::STATE_CANCELED = 3;
|
90 |
+
*/
|
91 |
+
protected function getStatusCreditNote()
|
92 |
+
{
|
93 |
+
return $this->source->getState();
|
94 |
+
}
|
95 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Shop/AcumulusEntryModel.php
ADDED
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Magento\Shop;
|
3 |
+
|
4 |
+
use Mage;
|
5 |
+
use Siel\Acumulus\Shop\AcumulusEntryModel as BaseAcumulusEntryModel;
|
6 |
+
use Siel_Acumulus_Model_Entry;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Implements the Magento specific acumulus entry model class.
|
10 |
+
*/
|
11 |
+
class AcumulusEntryModel extends BaseAcumulusEntryModel
|
12 |
+
{
|
13 |
+
/** @var Siel_Acumulus_Model_Entry */
|
14 |
+
protected $model;
|
15 |
+
|
16 |
+
/**
|
17 |
+
* AcumulusEntryModel constructor.
|
18 |
+
*/
|
19 |
+
public function __construct()
|
20 |
+
{
|
21 |
+
$this->model = Mage::getModel('acumulus/entry');
|
22 |
+
}
|
23 |
+
|
24 |
+
/**
|
25 |
+
* @return Siel_Acumulus_Model_Entry
|
26 |
+
*/
|
27 |
+
protected function getModel()
|
28 |
+
{
|
29 |
+
return $this->model;
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* {@inheritdoc}
|
34 |
+
*/
|
35 |
+
public function getByEntryId($entryId)
|
36 |
+
{
|
37 |
+
return $this->getModel()->load($entryId, 'entry_id');
|
38 |
+
}
|
39 |
+
|
40 |
+
/**
|
41 |
+
* {@inheritdoc}
|
42 |
+
*/
|
43 |
+
public function getByInvoiceSourceId($invoiceSourceType, $invoiceSourceId)
|
44 |
+
{
|
45 |
+
/** @var Siel_Acumulus_Model_Entry $result */
|
46 |
+
$result = $this->getModel()->getResourceCollection()
|
47 |
+
->addFieldToFilter('source_type', $invoiceSourceType)
|
48 |
+
->addFieldToFilter('source_id', $invoiceSourceId)
|
49 |
+
->getFirstItem();
|
50 |
+
return $result->getSourceId() == $invoiceSourceId ? $result : null;
|
51 |
+
}
|
52 |
+
|
53 |
+
/**
|
54 |
+
* {@inheritdoc}
|
55 |
+
*/
|
56 |
+
protected function insert($invoiceSource, $entryId, $token, $created)
|
57 |
+
{
|
58 |
+
return $this->getModel()
|
59 |
+
->setEntryId($entryId)
|
60 |
+
->setToken($token)
|
61 |
+
->setSourceType($invoiceSource->getType())
|
62 |
+
->setSourceId($invoiceSource->getId())
|
63 |
+
->save();
|
64 |
+
}
|
65 |
+
|
66 |
+
/**
|
67 |
+
* {@inheritdoc}
|
68 |
+
*/
|
69 |
+
protected function update($record, $entryId, $token, $updated)
|
70 |
+
{
|
71 |
+
return $record
|
72 |
+
->setEntryId($entryId)
|
73 |
+
->setToken($token)
|
74 |
+
->save();
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* {@inheritdoc}
|
79 |
+
*/
|
80 |
+
protected function sqlNow()
|
81 |
+
{
|
82 |
+
return time();
|
83 |
+
}
|
84 |
+
|
85 |
+
/**
|
86 |
+
* {@inheritdoc}
|
87 |
+
*
|
88 |
+
* Magento has separate install scripts, so nothing has to be done here.
|
89 |
+
*/
|
90 |
+
public function install()
|
91 |
+
{
|
92 |
+
return true;
|
93 |
+
}
|
94 |
+
|
95 |
+
/**
|
96 |
+
* {@inheritdoc}
|
97 |
+
*
|
98 |
+
* Magento has separate install scripts, so nothing has to be done here.
|
99 |
+
*/
|
100 |
+
public function uninstall()
|
101 |
+
{
|
102 |
+
return true;
|
103 |
+
}
|
104 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Shop/BatchForm.php
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Magento\Shop;
|
3 |
+
|
4 |
+
use Mage;
|
5 |
+
use Mage_Core_Model_Locale;
|
6 |
+
use Siel\Acumulus\Shop\BatchForm as BaseBatchForm;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Provides the Batch send form handling for the Magento Acumulus module.
|
10 |
+
*/
|
11 |
+
class BatchForm extends BaseBatchForm
|
12 |
+
{
|
13 |
+
/**
|
14 |
+
* {@inheritdoc}
|
15 |
+
*/
|
16 |
+
protected function setFormValues()
|
17 |
+
{
|
18 |
+
parent::setFormValues();
|
19 |
+
|
20 |
+
// Group (checked) checkboxes into their collections.
|
21 |
+
foreach ($this->getCheckboxKeys() as $checkboxName => $collectionName) {
|
22 |
+
if (!empty($this->formValues[$checkboxName])) {
|
23 |
+
// Handle the case where $collectionName and $checkboxName are the same.
|
24 |
+
if (array_key_exists($collectionName, $this->formValues) && is_array($this->formValues[$collectionName])) {
|
25 |
+
$this->formValues[$collectionName][] = $checkboxName;
|
26 |
+
} else {
|
27 |
+
$this->formValues[$collectionName] = array($checkboxName);
|
28 |
+
}
|
29 |
+
}
|
30 |
+
}
|
31 |
+
}
|
32 |
+
|
33 |
+
/**
|
34 |
+
* {@inheritdoc}
|
35 |
+
*
|
36 |
+
* This override returns the default date format as set in the Magento config.
|
37 |
+
*/
|
38 |
+
public function getDateFormat()
|
39 |
+
{
|
40 |
+
$result = $this->getShopDateFormat();
|
41 |
+
$result = str_replace(array('yyyy', 'MM', 'dd'), array('Y', 'm', 'd'), $result);
|
42 |
+
return $result;
|
43 |
+
}
|
44 |
+
|
45 |
+
public function getShopDateFormat()
|
46 |
+
{
|
47 |
+
return Mage::app()->getLocale()->getDateFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT);
|
48 |
+
}
|
49 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Shop/ConfigForm.php
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Magento\Shop;
|
3 |
+
|
4 |
+
use Mage;
|
5 |
+
use Siel\Acumulus\Shop\ConfigForm as BaseConfigForm;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class ConfigForm processes and builds the settings form page for the
|
9 |
+
* Magento Acumulus module.
|
10 |
+
*/
|
11 |
+
class ConfigForm extends BaseConfigForm
|
12 |
+
{
|
13 |
+
/**
|
14 |
+
* {@inheritdoc}
|
15 |
+
*/
|
16 |
+
protected function setFormValues()
|
17 |
+
{
|
18 |
+
parent::setFormValues();
|
19 |
+
|
20 |
+
// Group (checked) checkboxes into their collections.
|
21 |
+
foreach ($this->getCheckboxKeys() as $checkboxName => $collectionName) {
|
22 |
+
if (!empty($this->formValues[$checkboxName])) {
|
23 |
+
// Handle the case where $collectionName and $checkboxName are the same.
|
24 |
+
if (array_key_exists($collectionName, $this->formValues) && is_array($this->formValues[$collectionName])) {
|
25 |
+
$this->formValues[$collectionName][] = $checkboxName;
|
26 |
+
} else {
|
27 |
+
$this->formValues[$collectionName] = array($checkboxName);
|
28 |
+
}
|
29 |
+
}
|
30 |
+
}
|
31 |
+
}
|
32 |
+
|
33 |
+
/**
|
34 |
+
* {@inheritdoc}
|
35 |
+
*/
|
36 |
+
protected function getShopOrderStatuses()
|
37 |
+
{
|
38 |
+
$items = Mage::getModel('sales/order_status')->getResourceCollection()->getData();
|
39 |
+
$result = array();
|
40 |
+
foreach ($items as $item) {
|
41 |
+
$result[reset($item)] = next($item);
|
42 |
+
}
|
43 |
+
return $result;
|
44 |
+
}
|
45 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Shop/ConfigStore.php
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Magento\Shop;
|
3 |
+
|
4 |
+
use Mage;
|
5 |
+
use Siel\Acumulus\Shop\ConfigStore as BaseConfigStore;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Implements the connection to the Magento config component.
|
9 |
+
*/
|
10 |
+
class ConfigStore extends BaSeConfigStore
|
11 |
+
{
|
12 |
+
protected $configKey = 'siel_acumulus/';
|
13 |
+
|
14 |
+
/**
|
15 |
+
* {@inheritdoc}
|
16 |
+
*/
|
17 |
+
public function getShopEnvironment()
|
18 |
+
{
|
19 |
+
/** @noinspection PhpUndefinedFieldInspection */
|
20 |
+
$environment = array(
|
21 |
+
'moduleVersion' => Mage::getConfig()->getModuleConfig("Siel_Acumulus")->version,
|
22 |
+
'shopName' => $this->shopName,
|
23 |
+
'shopVersion' => Mage::getVersion(),
|
24 |
+
);
|
25 |
+
|
26 |
+
return $environment;
|
27 |
+
}
|
28 |
+
|
29 |
+
/**
|
30 |
+
* {@inheritdoc}
|
31 |
+
*/
|
32 |
+
public function load(array $keys)
|
33 |
+
{
|
34 |
+
$result = array();
|
35 |
+
// Load the values from the web shop specific configuration.
|
36 |
+
foreach ($keys as $key) {
|
37 |
+
$value = Mage::getStoreConfig($this->configKey . $key);
|
38 |
+
// Do not overwrite defaults if no value is set.
|
39 |
+
if (isset($value)) {
|
40 |
+
if (is_string($value) && strpos($value, '{') !== false) {
|
41 |
+
$unserialized = @unserialize($value);
|
42 |
+
if ($unserialized !== false) {
|
43 |
+
$value = $unserialized;
|
44 |
+
}
|
45 |
+
}
|
46 |
+
$result[$key] = $value;
|
47 |
+
}
|
48 |
+
}
|
49 |
+
return $result;
|
50 |
+
}
|
51 |
+
|
52 |
+
/**
|
53 |
+
* {@inheritdoc}
|
54 |
+
*/
|
55 |
+
public function save(array $values)
|
56 |
+
{
|
57 |
+
$values = $this->saveCommon($values);
|
58 |
+
|
59 |
+
foreach ($values as $key => $value) {
|
60 |
+
if ($value !== null) {
|
61 |
+
if (is_bool($value)) {
|
62 |
+
$value = $value ? 1 : 0;
|
63 |
+
} elseif (is_array($value)) {
|
64 |
+
$value = serialize($value);
|
65 |
+
}
|
66 |
+
/** @var \Mage_Core_Model_Config $configModel */
|
67 |
+
$configModel = Mage::getModel('core/config');
|
68 |
+
$configModel->saveConfig($this->configKey . $key, $value);
|
69 |
+
}
|
70 |
+
}
|
71 |
+
Mage::getConfig()->reinit();
|
72 |
+
return true;
|
73 |
+
}
|
74 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Magento/Shop/InvoiceManager.php
ADDED
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Magento\Shop;
|
3 |
+
|
4 |
+
use DateTime;
|
5 |
+
use Mage;
|
6 |
+
use Siel\Acumulus\Magento\Invoice\Source;
|
7 |
+
use Varien_Object;
|
8 |
+
use Siel\Acumulus\Invoice\Source as BaseSource;
|
9 |
+
use Siel\Acumulus\Shop\InvoiceManager as BaseInvoiceManager;
|
10 |
+
|
11 |
+
class InvoiceManager extends BaseInvoiceManager
|
12 |
+
{
|
13 |
+
/**
|
14 |
+
* Returns a Magento model for the given source type.
|
15 |
+
*
|
16 |
+
* @param $invoiceSourceType
|
17 |
+
*
|
18 |
+
* @return \Mage_Sales_Model_Abstract
|
19 |
+
*/
|
20 |
+
protected function getInvoiceSourceTypeModel($invoiceSourceType)
|
21 |
+
{
|
22 |
+
return $invoiceSourceType == Source::Order ? Mage::getModel('sales/order') : Mage::getModel('sales/order_creditmemo');
|
23 |
+
}
|
24 |
+
|
25 |
+
/**
|
26 |
+
* {@inheritdoc}
|
27 |
+
*/
|
28 |
+
public function getInvoiceSourcesByIdRange($invoiceSourceType, $InvoiceSourceIdFrom, $InvoiceSourceIdTo)
|
29 |
+
{
|
30 |
+
$field = 'entity_id';
|
31 |
+
$condition = array('from' => $InvoiceSourceIdFrom, 'to' => $InvoiceSourceIdTo);
|
32 |
+
return $this->getByCondition($invoiceSourceType, $field, $condition);
|
33 |
+
}
|
34 |
+
|
35 |
+
/**
|
36 |
+
* {@inheritdoc}
|
37 |
+
*/
|
38 |
+
public function getInvoiceSourcesByReferenceRange($invoiceSourceType, $InvoiceSourceReferenceFrom, $InvoiceSourceReferenceTo)
|
39 |
+
{
|
40 |
+
$field = 'increment_id';
|
41 |
+
$condition = array('from' => $InvoiceSourceReferenceFrom, 'to' => $InvoiceSourceReferenceTo);
|
42 |
+
return $this->getByCondition($invoiceSourceType, $field, $condition);
|
43 |
+
}
|
44 |
+
|
45 |
+
/**
|
46 |
+
* {@inheritdoc}
|
47 |
+
*/
|
48 |
+
public function getInvoiceSourcesByDateRange($invoiceSourceType, DateTime $dateFrom, DateTime $dateTo)
|
49 |
+
{
|
50 |
+
$dateFrom = $this->getSqlDate($dateFrom);
|
51 |
+
$dateTo = $this->getSqlDate($dateTo);
|
52 |
+
$field = 'updated_at';
|
53 |
+
$condition = array('from' => $dateFrom, 'to' => $dateTo);
|
54 |
+
return $this->getByCondition($invoiceSourceType, $field, $condition);
|
55 |
+
}
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Helper method that executes a query to retrieve a list of invoice source
|
59 |
+
* ids and returns a list of invoice sources for these ids.
|
60 |
+
*
|
61 |
+
* @param string $invoiceSourceType
|
62 |
+
* @param string|string[] $field
|
63 |
+
* @param int|string|array $condition
|
64 |
+
*
|
65 |
+
* @return \Siel\Acumulus\Magento\Invoice\Source[]
|
66 |
+
* A non keyed array with invoice Sources.
|
67 |
+
*/
|
68 |
+
protected function getByCondition($invoiceSourceType, $field, $condition)
|
69 |
+
{
|
70 |
+
/** @var \Mage_Core_Model_Resource_Db_Collection_Abstract $collection */
|
71 |
+
$collection = $this->getInvoiceSourceTypeModel($invoiceSourceType)->getResourceCollection();
|
72 |
+
$items = $collection
|
73 |
+
->addFieldToFilter($field, $condition)
|
74 |
+
->getItems();
|
75 |
+
|
76 |
+
// @todo: replace with getSourcesByIds
|
77 |
+
return $this->getSourcesByIdsOrSources($invoiceSourceType, $items);
|
78 |
+
}
|
79 |
+
|
80 |
+
/**
|
81 |
+
* {@inheritdoc}
|
82 |
+
*
|
83 |
+
* This Magento override dispatches the 'acumulus_invoice_created' event.
|
84 |
+
*/
|
85 |
+
protected function triggerInvoiceCreated(array &$invoice, BaseSource $invoiceSource)
|
86 |
+
{
|
87 |
+
$transportObject = new Varien_Object(array('invoice' => $invoice));
|
88 |
+
Mage::dispatchEvent('acumulus_invoice_created', array('transport_object' => $transportObject, 'source' => $invoiceSource));
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* {@inheritdoc}
|
93 |
+
*
|
94 |
+
* This Magento override dispatches the 'acumulus_invoice_completed' event.
|
95 |
+
*/
|
96 |
+
protected function triggerInvoiceCompleted(array &$invoice, BaseSource $invoiceSource)
|
97 |
+
{
|
98 |
+
$transportObject = new Varien_Object(array('invoice' => $invoice));
|
99 |
+
Mage::dispatchEvent('acumulus_invoice_completed', array('transport_object' => $transportObject, 'source' => $invoiceSource));
|
100 |
+
}
|
101 |
+
|
102 |
+
/**
|
103 |
+
* {@inheritdoc}
|
104 |
+
*
|
105 |
+
* This Magento override dispatches the 'acumulus_invoice_sent' event.
|
106 |
+
*/
|
107 |
+
protected function triggerInvoiceSent(array $invoice, BaseSource $invoiceSource, array $result)
|
108 |
+
{
|
109 |
+
Mage::dispatchEvent('acumulus_invoice_sent', array('invoice' => $invoice, 'source' => $invoiceSource, 'result' => $result));
|
110 |
+
}
|
111 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Helpers/Log.php
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\OpenCart\Helpers;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\Log as BaseLog;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Extends the base log class to log any library logging to the PrestaShop log.
|
8 |
+
*/
|
9 |
+
class Log extends BaseLog
|
10 |
+
{
|
11 |
+
/**
|
12 |
+
* {@inheritdoc}
|
13 |
+
*
|
14 |
+
* This override uses the OpenCart Log class.
|
15 |
+
*/
|
16 |
+
protected function write($message, $severity)
|
17 |
+
{
|
18 |
+
$log = new \Log('acumulus.log');
|
19 |
+
$message = sprintf('%s - %s', $this->getSeverityString($severity), $message);
|
20 |
+
$log->write($message);
|
21 |
+
}
|
22 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Helpers/Mailer.php
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\OpenCart\Helpers;
|
3 |
+
|
4 |
+
use Mail;
|
5 |
+
use Siel\Acumulus\Helpers\Mailer as BaseMailer;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Extends the base mailer class to send a mail using the PrestaShop mailer.
|
9 |
+
*/
|
10 |
+
class Mailer extends BaseMailer
|
11 |
+
{
|
12 |
+
/** @var string */
|
13 |
+
protected $templateDir;
|
14 |
+
|
15 |
+
/** @var string */
|
16 |
+
protected $templateName;
|
17 |
+
|
18 |
+
/**
|
19 |
+
* {@inheritdoc}
|
20 |
+
*/
|
21 |
+
public function sendInvoiceAddMailResult(array $result, array $messages, $invoiceSourceType, $invoiceSourceReference)
|
22 |
+
{
|
23 |
+
$config = Registry::getInstance()->config;
|
24 |
+
$mail = new Mail();
|
25 |
+
|
26 |
+
$mail->protocol = $config->get('config_mail_protocol') ? $config->get('config_mail_protocol') : 'mail';
|
27 |
+
$mail->parameter = $config->get('config_mail_parameter');
|
28 |
+
$mail->hostname = $config->get('config_smtp_host');
|
29 |
+
$mail->username = $config->get('config_smtp_username');
|
30 |
+
$mail->password = $config->get('config_smtp_password');
|
31 |
+
$mail->port = $config->get('config_smtp_port');
|
32 |
+
$mail->timeout = $config->get('config_smtp_timeout');
|
33 |
+
$mail->setTo($this->getToAddress());
|
34 |
+
$mail->setFrom($config->get('config_email'));
|
35 |
+
$mail->setSender($this->getFromName());
|
36 |
+
$mail->setSubject($this->getSubject($result));
|
37 |
+
$content = $this->getBody($result, $messages, $invoiceSourceType, $invoiceSourceReference);
|
38 |
+
$mail->setText($content['text']);
|
39 |
+
$mail->setHtml($content['html']);
|
40 |
+
|
41 |
+
$mail->send();
|
42 |
+
}
|
43 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Helpers/OcHelper.php
ADDED
@@ -0,0 +1,319 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\OpenCart\Helpers;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\Requirements;
|
5 |
+
use Siel\Acumulus\Invoice\Source;
|
6 |
+
use Siel\Acumulus\Shop\Config as ShopConfig;
|
7 |
+
use Siel\Acumulus\Shop\ModuleTranslations;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* OcHelper contains functionality shared between the OC1 and OC2 controllers
|
11 |
+
* and models, for both admin and catalog.
|
12 |
+
*/
|
13 |
+
class OcHelper
|
14 |
+
{
|
15 |
+
/** @var \Siel\Acumulus\Shop\Config */
|
16 |
+
protected $acumulusConfig = null;
|
17 |
+
|
18 |
+
/** @var array */
|
19 |
+
public $data;
|
20 |
+
|
21 |
+
/** @var \Siel\Acumulus\Helpers\Form */
|
22 |
+
protected $form;
|
23 |
+
|
24 |
+
/** @var \Registry */
|
25 |
+
protected $registry;
|
26 |
+
|
27 |
+
/** @var string */
|
28 |
+
protected $shopNamespace;
|
29 |
+
|
30 |
+
/**
|
31 |
+
* OcHelper constructor.
|
32 |
+
*
|
33 |
+
* @param \Registry $registry
|
34 |
+
* @param string $shopNamespace
|
35 |
+
*/
|
36 |
+
public function __construct(\Registry $registry, $shopNamespace)
|
37 |
+
{
|
38 |
+
Registry::setRegistry($registry);
|
39 |
+
$this->registry = Registry::getInstance();
|
40 |
+
$this->shopNamespace = $shopNamespace;
|
41 |
+
}
|
42 |
+
|
43 |
+
protected function addError($message)
|
44 |
+
{
|
45 |
+
if (is_array($message)) {
|
46 |
+
$this->data['error_messages'] = array_merge($this->data['error_messages'], $message);
|
47 |
+
} else {
|
48 |
+
$this->data['error_messages'][] = $message;
|
49 |
+
}
|
50 |
+
}
|
51 |
+
|
52 |
+
protected function addSuccess($message)
|
53 |
+
{
|
54 |
+
$this->data['success_messages'][] = $message;
|
55 |
+
}
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Helper method that initializes some object properties:
|
59 |
+
* - language
|
60 |
+
* - model_Setting_Setting
|
61 |
+
* - webAPI
|
62 |
+
* - acumulusConfig
|
63 |
+
*/
|
64 |
+
protected function init()
|
65 |
+
{
|
66 |
+
if ($this->acumulusConfig === null) {
|
67 |
+
$languageCode = $this->registry->language->get('code');
|
68 |
+
if (empty($languageCode)) {
|
69 |
+
$languageCode = 'nl';
|
70 |
+
}
|
71 |
+
$this->acumulusConfig = new ShopConfig($this->shopNamespace, $languageCode);
|
72 |
+
$this->acumulusConfig->getTranslator()->add(new ModuleTranslations());
|
73 |
+
}
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Helper method to translate strings.
|
78 |
+
*
|
79 |
+
* @param string $key
|
80 |
+
* The key to get a translation for.
|
81 |
+
*
|
82 |
+
* @return string
|
83 |
+
* The translation for the given key or the key itself if no translation
|
84 |
+
* could be found.
|
85 |
+
*/
|
86 |
+
protected function t($key)
|
87 |
+
{
|
88 |
+
return $this->acumulusConfig->getTranslator()->get($key);
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Install controller action, called when the module is installed.
|
93 |
+
*
|
94 |
+
* @return bool
|
95 |
+
*/
|
96 |
+
public function install()
|
97 |
+
{
|
98 |
+
// Call the actual install method.
|
99 |
+
$this->doInstall();
|
100 |
+
|
101 |
+
return empty($this->data['error_messages']);
|
102 |
+
}
|
103 |
+
|
104 |
+
/**
|
105 |
+
* Uninstall function, called when the module is uninstalled by an admin.
|
106 |
+
*/
|
107 |
+
public function uninstall()
|
108 |
+
{
|
109 |
+
$this->registry->response->redirect($this->registry->url->link('module/acumulus/confirmUninstall', 'token=' . $this->registry->session->data['token'], 'SSL'));
|
110 |
+
}
|
111 |
+
|
112 |
+
/**
|
113 |
+
* Controller action: show/process the settings form for this module.
|
114 |
+
*/
|
115 |
+
public function config()
|
116 |
+
{
|
117 |
+
$this->displayFormCommon('config');
|
118 |
+
|
119 |
+
// Are we posting? If not so, handle this as a trigger to update.
|
120 |
+
if ($this->registry->request->server['REQUEST_METHOD'] !== 'POST') {
|
121 |
+
$this->doUpgrade();
|
122 |
+
}
|
123 |
+
|
124 |
+
// Add an intermediate level to the breadcrumb.
|
125 |
+
$this->data['breadcrumbs'][] = array(
|
126 |
+
'text' => $this->t('modules'),
|
127 |
+
'href' => Registry::getInstance()->url->link('extension/module', 'token=' . $this->registry->session->data['token'], 'SSL'),
|
128 |
+
'separator' => ' :: '
|
129 |
+
);
|
130 |
+
|
131 |
+
$this->renderFormCommon('config', 'button_save');
|
132 |
+
}
|
133 |
+
|
134 |
+
/**
|
135 |
+
* Controller action: show/process the settings form for this module.
|
136 |
+
*/
|
137 |
+
public function batch()
|
138 |
+
{
|
139 |
+
$this->displayFormCommon('batch');
|
140 |
+
$this->renderFormCommon('batch', 'button_send');
|
141 |
+
}
|
142 |
+
|
143 |
+
/**
|
144 |
+
* Explicit confirmation step to allow to retain the settings.
|
145 |
+
*
|
146 |
+
* The normal uninstall action will unconditionally delete all settings.
|
147 |
+
*/
|
148 |
+
public function confirmUninstall()
|
149 |
+
{
|
150 |
+
$this->displayFormCommon('uninstall');
|
151 |
+
|
152 |
+
// Are we confirming, or should we show the confirm message?
|
153 |
+
if ($this->registry->request->server['REQUEST_METHOD'] === 'POST') {
|
154 |
+
$this->doUninstall();
|
155 |
+
$this->registry->response->redirect($this->registry->url->link('extension/module', 'token=' . $this->registry->session->data['token'],
|
156 |
+
'SSL'));
|
157 |
+
}
|
158 |
+
|
159 |
+
// Add an intermediate level to the breadcrumb.
|
160 |
+
$this->data['breadcrumbs'][] = array(
|
161 |
+
'text' => $this->t('modules'),
|
162 |
+
'href' => $this->registry->url->link('extension/module', 'token=' . $this->registry->session->data['token'], 'SSL'),
|
163 |
+
'separator' => ' :: '
|
164 |
+
);
|
165 |
+
|
166 |
+
$this->renderFormCommon('confirmUninstall', 'button_confirm_uninstall');
|
167 |
+
}
|
168 |
+
|
169 |
+
/**
|
170 |
+
* Event handler that executes on the creation or update of an order.
|
171 |
+
*
|
172 |
+
* @param int $order_id
|
173 |
+
*/
|
174 |
+
public function eventOrderUpdate($order_id) {
|
175 |
+
$this->init();
|
176 |
+
$source = $this->acumulusConfig->getSource(Source::Order, $order_id);
|
177 |
+
$this->acumulusConfig->getManager()->sourceStatusChange($source);
|
178 |
+
}
|
179 |
+
|
180 |
+
/**
|
181 |
+
* Performs the common tasks when displaying a form.
|
182 |
+
*
|
183 |
+
* @param string $task
|
184 |
+
*/
|
185 |
+
protected function displayFormCommon($task)
|
186 |
+
{
|
187 |
+
$this->init();
|
188 |
+
|
189 |
+
$this->form = $this->acumulusConfig->getForm($task);
|
190 |
+
|
191 |
+
$this->registry->document->addStyle('view/stylesheet/acumulus.css');
|
192 |
+
|
193 |
+
$this->data['success_messages'] = array();
|
194 |
+
$this->data['error_messages'] = array();
|
195 |
+
|
196 |
+
// Set the page title.
|
197 |
+
$this->registry->document->setTitle($this->t("{$task}_form_title"));
|
198 |
+
$this->data["page_title"] = $this->t("{$task}_form_title");
|
199 |
+
$this->data["heading_title"] = $this->t("{$task}_form_header");
|
200 |
+
$this->data["text_edit"] = $this->t("{$task}_form_header");
|
201 |
+
|
202 |
+
// Set up breadcrumb.
|
203 |
+
$this->data['breadcrumbs'] = array();
|
204 |
+
$this->data['breadcrumbs'][] = array(
|
205 |
+
'text' => $this->t('text_home'),
|
206 |
+
'href' => $this->registry->url->link('common/dashboard', 'token=' . $this->registry->session->data['token'], 'SSL'),
|
207 |
+
'separator' => false
|
208 |
+
);
|
209 |
+
}
|
210 |
+
|
211 |
+
/**
|
212 |
+
* Performs the common tasks when processing and rendering a form.
|
213 |
+
*
|
214 |
+
* @param string $task
|
215 |
+
* @param string $button
|
216 |
+
*/
|
217 |
+
protected function renderFormCommon($task, $button)
|
218 |
+
{
|
219 |
+
// Process the form if it was submitted and render it again.
|
220 |
+
$this->form->process();
|
221 |
+
|
222 |
+
// Show messages.
|
223 |
+
foreach ($this->form->getSuccessMessages() as $message) {
|
224 |
+
$this->addSuccess($message);
|
225 |
+
}
|
226 |
+
foreach ($this->form->getErrorMessages() as $message) {
|
227 |
+
$this->addError($this->t($message));
|
228 |
+
}
|
229 |
+
|
230 |
+
$this->data['form'] = $this->form;
|
231 |
+
$this->data['formRenderer'] = $this->acumulusConfig->getFormRenderer();
|
232 |
+
|
233 |
+
// Complete the breadcrumb with the current path.
|
234 |
+
$link = 'module/acumulus';
|
235 |
+
if ($task !== 'config') {
|
236 |
+
$link .= "/$task";
|
237 |
+
}
|
238 |
+
$this->data['breadcrumbs'][] = array(
|
239 |
+
'text' => $this->t("{$task}_form_header"),
|
240 |
+
'href' => $this->registry->url->link($link, 'token=' . $this->registry->session->data['token'], 'SSL'),
|
241 |
+
'separator' => ' :: '
|
242 |
+
);
|
243 |
+
|
244 |
+
// Set the action buttons (action + text).
|
245 |
+
$this->data['action'] = $this->registry->url->link($link, 'token=' . $this->registry->session->data['token'], 'SSL');
|
246 |
+
$this->data['button_icon'] = $task === 'batch' ? 'fa-envelope-o' : ($task === 'uninstall' ? 'fa-delete' : 'fa-save');
|
247 |
+
$this->data['button_save'] = $this->t($button);
|
248 |
+
$this->data['cancel'] = $this->registry->url->link('common/dashboard', 'token=' . $this->registry->session->data['token'], 'SSL');
|
249 |
+
$this->data['button_cancel'] = $task === 'uninstall' ? $this->t('button_cancel_uninstall') : $this->t('button_cancel');
|
250 |
+
}
|
251 |
+
|
252 |
+
/**
|
253 |
+
* Checks requirements and installs tables for this module.
|
254 |
+
*
|
255 |
+
* @return bool
|
256 |
+
* Success.
|
257 |
+
*/
|
258 |
+
protected function doInstall()
|
259 |
+
{
|
260 |
+
$this->init();
|
261 |
+
|
262 |
+
$result = true;
|
263 |
+
$this->registry->load->model('setting/setting');
|
264 |
+
$setting = $this->registry->model_setting_setting->getSetting('acumulus_siel');
|
265 |
+
$currentDataModelVersion = isset($setting['acumulus_siel_datamodel_version']) ? $setting['acumulus_siel_datamodel_version'] : '';
|
266 |
+
|
267 |
+
if ($currentDataModelVersion === '' || version_compare($currentDataModelVersion, '4.0', '<')) {
|
268 |
+
// Check requirements (we assume this has been done successfully before
|
269 |
+
// if the data model is at the latest version.
|
270 |
+
$requirements = new Requirements();
|
271 |
+
$messages = $requirements->check();
|
272 |
+
foreach ($messages as $message) {
|
273 |
+
$this->addError($message['message']);
|
274 |
+
}
|
275 |
+
if (!empty($messages)) {
|
276 |
+
return false;
|
277 |
+
}
|
278 |
+
|
279 |
+
// Install tables.
|
280 |
+
$result = $this->acumulusConfig->getAcumulusEntryModel()->install();
|
281 |
+
$setting['acumulus_siel_datamodel_version'] = '4.0';
|
282 |
+
$this->registry->model_setting_setting->editSetting('acumulus_siel', $setting);
|
283 |
+
}
|
284 |
+
|
285 |
+
return $result;
|
286 |
+
}
|
287 |
+
|
288 |
+
/**
|
289 |
+
* Uninstalls data and settings from this module.
|
290 |
+
*
|
291 |
+
* @return bool
|
292 |
+
* Whether the uninstall was successful.
|
293 |
+
*/
|
294 |
+
protected function doUninstall()
|
295 |
+
{
|
296 |
+
$this->init();
|
297 |
+
$this->acumulusConfig->getAcumulusEntryModel()->uninstall();
|
298 |
+
|
299 |
+
// Delete all config values.
|
300 |
+
$this->registry->load->model('setting/setting');
|
301 |
+
$this->registry->model_setting_setting->deleteSetting('acumulus_siel');
|
302 |
+
|
303 |
+
return true;
|
304 |
+
}
|
305 |
+
|
306 |
+
/**
|
307 |
+
* Upgrades the data and settings for this module if needed.
|
308 |
+
*
|
309 |
+
* The install now checks for the data model and can do an upgrade instead of
|
310 |
+
* a clean install.
|
311 |
+
*
|
312 |
+
* @return bool
|
313 |
+
* Whether the upgrade was successful.
|
314 |
+
*/
|
315 |
+
protected function doUpgrade()
|
316 |
+
{
|
317 |
+
return $this->doInstall();
|
318 |
+
}
|
319 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Helpers/Registry.php
ADDED
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\OpenCart\Helpers;
|
3 |
+
|
4 |
+
/** @noinspection PhpUndefinedNamespaceInspection */
|
5 |
+
/** @noinspection PhpUndefinedClassInspection */
|
6 |
+
/**
|
7 |
+
* Registry is a wrapper around the registry instance which is not directly
|
8 |
+
* accessible as the single instance is passed to each constructor in the
|
9 |
+
* OpenCart classes.
|
10 |
+
*
|
11 |
+
* @property \Config config
|
12 |
+
* @property \DBMySQLi|\DB\MySQLi db
|
13 |
+
* @property \Document document
|
14 |
+
* @property \Event event
|
15 |
+
* @property \Language language
|
16 |
+
* @property \Loader load
|
17 |
+
* @property \Request request
|
18 |
+
* @property \Response response
|
19 |
+
* @property \Session session
|
20 |
+
* @property \Url url
|
21 |
+
* @property \ModelAccountOrder model_account_order
|
22 |
+
* @property \ModelCatalogProduct model_catalog_product
|
23 |
+
* @property \ModelCheckoutOrder model_checkout_order
|
24 |
+
* @property \ModelExtensionEvent model_extension_event
|
25 |
+
* @property \ModelSaleOrder model_sale_order
|
26 |
+
* @property \ModelLocalisationOrderStatus model_localisation_order_status
|
27 |
+
* @property \ModelSettingSetting model_setting_setting
|
28 |
+
*/
|
29 |
+
class Registry
|
30 |
+
{
|
31 |
+
/** @var \Siel\Acumulus\OpenCart\Helpers\Registry */
|
32 |
+
protected static $instance;
|
33 |
+
|
34 |
+
/** @var \Registry */
|
35 |
+
protected $registry;
|
36 |
+
|
37 |
+
/** @noinspection PhpUndefinedClassInspection */
|
38 |
+
/** @var \ModelAccountOrder|\ModelSaleOrder */
|
39 |
+
protected $orderModel;
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Sets the OC Registry.
|
43 |
+
*
|
44 |
+
* @param \Registry $registry
|
45 |
+
*/
|
46 |
+
public static function setRegistry(\Registry $registry) {
|
47 |
+
static::$instance = new Registry($registry);
|
48 |
+
}
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Returns the Registry instance.
|
52 |
+
*
|
53 |
+
* @return \Siel\Acumulus\OpenCart\Helpers\Registry
|
54 |
+
*/
|
55 |
+
public static function getInstance()
|
56 |
+
{
|
57 |
+
return static::$instance;
|
58 |
+
}
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Registry constructor.
|
62 |
+
*
|
63 |
+
* @param \Registry $registry
|
64 |
+
*/
|
65 |
+
public function __construct(\Registry $registry)
|
66 |
+
{
|
67 |
+
$this->registry = $registry;
|
68 |
+
$this->orderModel = null;
|
69 |
+
}
|
70 |
+
|
71 |
+
public function __get($key)
|
72 |
+
{
|
73 |
+
return $this->registry->get($key);
|
74 |
+
}
|
75 |
+
|
76 |
+
public function __set($key, $value)
|
77 |
+
{
|
78 |
+
$this->registry->set($key, $value);
|
79 |
+
}
|
80 |
+
|
81 |
+
public function getOrderModel()
|
82 |
+
{
|
83 |
+
if ($this->orderModel === null) {
|
84 |
+
if (strrpos(DIR_APPLICATION, '/catalog/') === strlen(DIR_APPLICATION) - strlen('/catalog/')) {
|
85 |
+
// We are in the catalog section, use the account/order model.
|
86 |
+
$this->load->model('account/order');
|
87 |
+
$this->orderModel = $this->model_account_order;
|
88 |
+
} else {
|
89 |
+
// We are in the admin section, use the sale/order model.
|
90 |
+
Registry::getInstance()->load->model('sale/order');
|
91 |
+
$this->orderModel = Registry::getInstance()->model_sale_order;
|
92 |
+
}
|
93 |
+
}
|
94 |
+
return $this->orderModel;
|
95 |
+
}
|
96 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Invoice/Creator.php
ADDED
@@ -0,0 +1,352 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\OpenCart\Invoice;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Invoice\ConfigInterface;
|
5 |
+
use Siel\Acumulus\Invoice\Creator as BaseCreator;
|
6 |
+
use Siel\Acumulus\OpenCart\Helpers\Registry;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Allows to create arrays in the Acumulus invoice structure from an OpenCart
|
10 |
+
* order.
|
11 |
+
*/
|
12 |
+
class Creator extends BaseCreator
|
13 |
+
{
|
14 |
+
// More specifically typed property.
|
15 |
+
/** @var array */
|
16 |
+
protected $order;
|
17 |
+
|
18 |
+
/** @var array[] List of OpenCart order total records. */
|
19 |
+
protected $orderTotalLines;
|
20 |
+
|
21 |
+
/**
|
22 |
+
* {@inheritdoc}
|
23 |
+
*
|
24 |
+
* This override also initializes WooCommerce specific properties related to
|
25 |
+
* the source.
|
26 |
+
*/
|
27 |
+
protected function setInvoiceSource($invoiceSource)
|
28 |
+
{
|
29 |
+
parent::setInvoiceSource($invoiceSource);
|
30 |
+
|
31 |
+
// Load some models and properties we are going to use.
|
32 |
+
Registry::getInstance()->load->model('catalog/product');
|
33 |
+
$this->orderTotalLines = null;
|
34 |
+
|
35 |
+
switch ($this->invoiceSource->getType()) {
|
36 |
+
case Source::Order:
|
37 |
+
$this->order = $this->invoiceSource->getSource();
|
38 |
+
break;
|
39 |
+
case Source::CreditNote:
|
40 |
+
break;
|
41 |
+
}
|
42 |
+
}
|
43 |
+
|
44 |
+
/**
|
45 |
+
* {@inheritdoc}
|
46 |
+
*/
|
47 |
+
protected function getCustomer()
|
48 |
+
{
|
49 |
+
$order = $this->order;
|
50 |
+
$result = array();
|
51 |
+
|
52 |
+
$this->addIfSetAndNotEmpty($result, 'contactyourid', $order, 'customer_id');
|
53 |
+
$this->addEmpty($result, 'companyname1', $order['payment_company']);
|
54 |
+
if (!empty($result['companyname1'])) {
|
55 |
+
// @todo: Are there VAT number extensions?
|
56 |
+
$this->addIfSetAndNotEmpty($result, 'vatnumber', $order, 'payment_tax_id');
|
57 |
+
}
|
58 |
+
$result['fullname'] = $order['firstname'] . ' ' . $order['lastname'];
|
59 |
+
$this->addEmpty($result, 'address1', $order['payment_address_1']);
|
60 |
+
$this->addEmpty($result, 'address2', $order['payment_address_2']);
|
61 |
+
$this->addEmpty($result, 'postalcode', $order['payment_postcode']);
|
62 |
+
$this->addEmpty($result, 'city', $order['payment_city']);
|
63 |
+
if (!empty($order['payment_iso_code_2'])) {
|
64 |
+
$result['countrycode'] = $order['payment_iso_code_2'];
|
65 |
+
}
|
66 |
+
$this->addIfSetAndNotEmpty($result, 'country', $order, 'payment_country');
|
67 |
+
$this->addIfSetAndNotEmpty($result, 'telephone', $order, 'telephone');
|
68 |
+
$this->addIfSetAndNotEmpty($result, 'fax', $order, 'fax');
|
69 |
+
$result['email'] = $order['email'];
|
70 |
+
|
71 |
+
return $result;
|
72 |
+
}
|
73 |
+
|
74 |
+
/**
|
75 |
+
* {@inheritdoc}
|
76 |
+
*/
|
77 |
+
protected function getInvoiceNumber($invoiceNumberSource)
|
78 |
+
{
|
79 |
+
$result = $this->invoiceSource->getReference();
|
80 |
+
if ($invoiceNumberSource == ConfigInterface::InvoiceNrSource_ShopInvoice && !empty($this->order['invoice_no'])) {
|
81 |
+
$result = $this->order['invoice_prefix'] . $this->order['invoice_no'];
|
82 |
+
}
|
83 |
+
return $result;
|
84 |
+
}
|
85 |
+
|
86 |
+
/**
|
87 |
+
* {@inheritdoc}
|
88 |
+
*/
|
89 |
+
protected function getInvoiceDate($dateToUse)
|
90 |
+
{
|
91 |
+
$result = substr($this->order['date_added'], 0, strlen('2000-01-01'));
|
92 |
+
// There doesn't seem to be an invoice date: stick with order create date,
|
93 |
+
// also when $dateToUse === ConfigInterface::InvoiceDate_InvoiceCreate.
|
94 |
+
return $result;
|
95 |
+
}
|
96 |
+
|
97 |
+
/**
|
98 |
+
* {@inheritdoc}
|
99 |
+
*/
|
100 |
+
protected function getPaymentState()
|
101 |
+
{
|
102 |
+
// @todo: Can we determine this based on payment_method and payment_code?
|
103 |
+
$result = ConfigInterface::PaymentStatus_Paid;
|
104 |
+
return $result;
|
105 |
+
}
|
106 |
+
|
107 |
+
/**
|
108 |
+
* {@inheritdoc}
|
109 |
+
*/
|
110 |
+
protected function getPaymentDate()
|
111 |
+
{
|
112 |
+
// @todo: Can we determine this based on history (and optionally payment_method and payment_code)?
|
113 |
+
// Will default to the issue date.
|
114 |
+
return null;
|
115 |
+
}
|
116 |
+
|
117 |
+
/**
|
118 |
+
* {@inheritdoc}
|
119 |
+
*
|
120 |
+
* This override provides the values meta-invoice-amountinc,
|
121 |
+
* meta-invoice-vatamount and a vat breakdown in meta-invoice-vat.
|
122 |
+
*/
|
123 |
+
protected function getInvoiceTotals()
|
124 |
+
{
|
125 |
+
$result = array(
|
126 |
+
'meta-invoice-amountinc' => $this->order['total'],
|
127 |
+
'meta-invoice-vatamount' => 0.0,
|
128 |
+
'meta-invoice-vat' => array(),
|
129 |
+
);
|
130 |
+
|
131 |
+
$orderTotals = $this->getOrderTotalLines();
|
132 |
+
foreach ($orderTotals as $totalLine) {
|
133 |
+
if ($totalLine['code'] === 'tax') {
|
134 |
+
$result['meta-invoice-vat'][] = $totalLine['title'] . ': ' . $totalLine['value'];
|
135 |
+
$result['meta-invoice-vatamount'] += $totalLine['value'];
|
136 |
+
}
|
137 |
+
}
|
138 |
+
return $result;
|
139 |
+
}
|
140 |
+
|
141 |
+
/**
|
142 |
+
* {@inheritdoc}
|
143 |
+
*/
|
144 |
+
protected function getInvoiceLines()
|
145 |
+
{
|
146 |
+
$itemLines = $this->getItemLines();
|
147 |
+
$itemLines = $this->addLineType($itemLines, static::LineType_Order);
|
148 |
+
|
149 |
+
$totalLines = $this->getTotalLines();
|
150 |
+
|
151 |
+
$result = array_merge($itemLines, $totalLines);
|
152 |
+
return $result;
|
153 |
+
}
|
154 |
+
|
155 |
+
/**
|
156 |
+
* {@inheritdoc}
|
157 |
+
*/
|
158 |
+
protected function getItemLines()
|
159 |
+
{
|
160 |
+
$result = array();
|
161 |
+
|
162 |
+
$orderProducts = $this->getOrderModel()->getOrderProducts($this->invoiceSource->getId());
|
163 |
+
foreach ($orderProducts as $line) {
|
164 |
+
$result = array_merge($result, $this->getItemLine($line));
|
165 |
+
}
|
166 |
+
|
167 |
+
return $result;
|
168 |
+
}
|
169 |
+
|
170 |
+
/**
|
171 |
+
* Returns the item line(s) for 1 product line.
|
172 |
+
*
|
173 |
+
* This method may return multiple lines if there are many options.
|
174 |
+
* These additional lines will be informative, their price will be 0.
|
175 |
+
*
|
176 |
+
* @param array $item
|
177 |
+
*
|
178 |
+
* @return array[]
|
179 |
+
*/
|
180 |
+
protected function getItemLine(array $item)
|
181 |
+
{
|
182 |
+
$result = array();
|
183 |
+
|
184 |
+
$product = Registry::getInstance()->model_catalog_product->getProduct($item['product_id']);
|
185 |
+
$this->addIfSetAndNotEmpty($result, 'itemnumber', $product, 'mpn');
|
186 |
+
$this->addIfSetAndNotEmpty($result, 'itemnumber', $product, 'isbn');
|
187 |
+
$this->addIfSetAndNotEmpty($result, 'itemnumber', $product, 'jan');
|
188 |
+
$this->addIfSetAndNotEmpty($result, 'itemnumber', $product, 'ean');
|
189 |
+
$this->addIfSetAndNotEmpty($result, 'itemnumber', $product, 'upc');
|
190 |
+
$this->addIfSetAndNotEmpty($result, 'itemnumber', $product, 'sku');
|
191 |
+
|
192 |
+
// Product name, model, and option(s).
|
193 |
+
$result['product'] = $item['name'];
|
194 |
+
if (!empty($item['model'])) {
|
195 |
+
$result['product'] .= ' (' . $item['model'] . ')';
|
196 |
+
}
|
197 |
+
|
198 |
+
$productPriceEx = $item['price'];
|
199 |
+
$productVat = $item['tax'];
|
200 |
+
$vatInfo = $this->getVatRangeTags($productVat, $productPriceEx);
|
201 |
+
|
202 |
+
// Options (variants).
|
203 |
+
$optionsLines = array();
|
204 |
+
$optionsTexts = array();
|
205 |
+
$options = $this->getOrderModel()->getOrderOptions($item['order_id'], $item['order_product_id']);
|
206 |
+
foreach ($options as $option) {
|
207 |
+
$optionsTexts[] = "{$option['name']}: {$option['value']}";
|
208 |
+
}
|
209 |
+
if (count($options) > 0) {
|
210 |
+
$optionsText = ' (' . implode(', ', $optionsTexts) . ')';
|
211 |
+
|
212 |
+
$invoiceSettings = $this->config->getInvoiceSettings();
|
213 |
+
if (count($options) <= $invoiceSettings['optionsAllOn1Line']
|
214 |
+
|| (count($options) < $invoiceSettings['optionsAllOnOwnLine'] && strlen($optionsText) <= $invoiceSettings['optionsMaxLength'])
|
215 |
+
) {
|
216 |
+
// All options on 1 item.
|
217 |
+
$result['product'] .= ' (' . implode(', ', $optionsTexts) . ')';
|
218 |
+
} else {
|
219 |
+
// All options on their own item.
|
220 |
+
foreach ($optionsTexts as $optionsText) {
|
221 |
+
$optionsLines[] = array(
|
222 |
+
'product' => " - $optionsText",
|
223 |
+
'unitprice' => 0,
|
224 |
+
// Table order_option does not have a quantity
|
225 |
+
// field, so composite products are apparently not
|
226 |
+
// covered.
|
227 |
+
'quantity' => 1,
|
228 |
+
) + $vatInfo;
|
229 |
+
}
|
230 |
+
}
|
231 |
+
}
|
232 |
+
|
233 |
+
$result['unitprice'] = $productPriceEx;
|
234 |
+
$result['quantity'] = $item['quantity'];
|
235 |
+
$result += $vatInfo;
|
236 |
+
$result['vatamount'] = $productVat;
|
237 |
+
|
238 |
+
return array_merge(array($result), $optionsLines);
|
239 |
+
}
|
240 |
+
|
241 |
+
/**
|
242 |
+
*
|
243 |
+
*
|
244 |
+
*
|
245 |
+
* @return array[]
|
246 |
+
*
|
247 |
+
*/
|
248 |
+
protected function getTotalLines()
|
249 |
+
{
|
250 |
+
$result = array();
|
251 |
+
|
252 |
+
$totalLines = $this->getOrderTotalLines();
|
253 |
+
foreach ($totalLines as $totalLine) {
|
254 |
+
switch ($totalLine['code']) {
|
255 |
+
case 'sub_total':
|
256 |
+
// Sub total of all product lines: ignore.
|
257 |
+
$line = null;
|
258 |
+
break;
|
259 |
+
case 'shipping':
|
260 |
+
$line = $this->getTotalLine($totalLine);
|
261 |
+
$line['meta-line-type'] = static::LineType_Shipping;
|
262 |
+
break;
|
263 |
+
case 'coupon':
|
264 |
+
$line = $this->getTotalLine($totalLine);
|
265 |
+
$line['meta-line-type'] = static::LineType_Discount;
|
266 |
+
break;
|
267 |
+
case 'tax':
|
268 |
+
// Tax line: added to invoice level
|
269 |
+
$line = null;
|
270 |
+
break;
|
271 |
+
case 'voucher':
|
272 |
+
$line = $this->getTotalLine($totalLine);
|
273 |
+
$line['meta-line-type'] = static::LineType_Voucher;
|
274 |
+
break;
|
275 |
+
case 'total':
|
276 |
+
// Overall total: ignore.
|
277 |
+
$line = null;
|
278 |
+
break;
|
279 |
+
default:
|
280 |
+
$line = $this->getTotalLine($totalLine);
|
281 |
+
$line['meta-line-type'] = static::LineType_Other;
|
282 |
+
break;
|
283 |
+
}
|
284 |
+
if ($line) {
|
285 |
+
$result[] = $line;
|
286 |
+
}
|
287 |
+
}
|
288 |
+
|
289 |
+
return $result;
|
290 |
+
}
|
291 |
+
|
292 |
+
/**
|
293 |
+
* Returns a line based on a "order total line".
|
294 |
+
*
|
295 |
+
* @param array $line
|
296 |
+
*
|
297 |
+
* @return array
|
298 |
+
*/
|
299 |
+
protected function getTotalLine(array $line)
|
300 |
+
{
|
301 |
+
$result = array(
|
302 |
+
'product' => $line['title'],
|
303 |
+
// Let's hope that this is the value ex vat...
|
304 |
+
'unitprice' => $line['value'],
|
305 |
+
'quantity' => 1,
|
306 |
+
);
|
307 |
+
|
308 |
+
if ($line['code'] === 'voucher') {
|
309 |
+
// A voucher is to be seen as a partial payment, thus no tax.
|
310 |
+
$result += array(
|
311 |
+
'vatrate' => -1,
|
312 |
+
'meta-vatrate-source' => Creator::VatRateSource_Exact0,
|
313 |
+
);
|
314 |
+
} else {
|
315 |
+
// Other lines do not have a discoverable vatrate, let a strategy try to
|
316 |
+
// compute it.
|
317 |
+
$result += array(
|
318 |
+
'vatrate' => null,
|
319 |
+
'meta-vatrate-source' => Creator::VatRateSource_Strategy,
|
320 |
+
// Coupons may have to be split over various taxes, but shipping and
|
321 |
+
// other fees not.
|
322 |
+
'meta-strategy-split' => $line['code'] === 'coupon',
|
323 |
+
);
|
324 |
+
}
|
325 |
+
|
326 |
+
return $result;
|
327 |
+
}
|
328 |
+
|
329 |
+
/**
|
330 |
+
* Returns a list of OpenCart order total records. These are shipment,
|
331 |
+
* other fee, tax, and discount lines.
|
332 |
+
*
|
333 |
+
* @return array[]
|
334 |
+
*/
|
335 |
+
protected function getOrderTotalLines()
|
336 |
+
{
|
337 |
+
if (!$this->orderTotalLines) {
|
338 |
+
$orderModel = $this->getOrderModel();
|
339 |
+
$this->orderTotalLines = $orderModel->getOrderTotals($this->order['order_id']);
|
340 |
+
}
|
341 |
+
return $this->orderTotalLines;
|
342 |
+
}
|
343 |
+
|
344 |
+
/** @noinspection PhpUndefinedClassInspection */
|
345 |
+
/**
|
346 |
+
* @return \ModelAccountOrder|\ModelSaleOrder
|
347 |
+
*/
|
348 |
+
protected function getOrderModel()
|
349 |
+
{
|
350 |
+
return Registry::getInstance()->getOrderModel();
|
351 |
+
}
|
352 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Invoice/Source.php
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\OpenCart\Invoice;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Invoice\Source as BaseSource;
|
5 |
+
use Siel\Acumulus\OpenCart\Helpers\Registry;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Wraps an OpenCart order in an invoice source object.
|
9 |
+
*/
|
10 |
+
class Source extends BaseSource
|
11 |
+
{
|
12 |
+
// More specifically typed properties.
|
13 |
+
/** @var array */
|
14 |
+
protected $source;
|
15 |
+
|
16 |
+
/**
|
17 |
+
* {@inheritdoc}
|
18 |
+
*/
|
19 |
+
protected function setSource()
|
20 |
+
{
|
21 |
+
$this->source = Registry::getInstance()->getOrderModel()->getOrder($this->id);
|
22 |
+
}
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Sets the id based on the loaded Order.
|
26 |
+
*/
|
27 |
+
protected function setId()
|
28 |
+
{
|
29 |
+
$this->id = $this->source['order_id'];
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* {@inheritdoc}
|
34 |
+
*
|
35 |
+
* @return int
|
36 |
+
*/
|
37 |
+
public function getStatus()
|
38 |
+
{
|
39 |
+
return $this->source['order_status_id'];
|
40 |
+
}
|
41 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/OpenCart1/Helpers/FormRenderer.php
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\OpenCart\OpenCart1\Helpers;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\FormRenderer as BaseFormRenderer;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* FormMapper maps an Acumulus form definition to a OpenCart 1 form definition.
|
8 |
+
*/
|
9 |
+
class FormRenderer extends BaseFormRenderer
|
10 |
+
{
|
11 |
+
/**
|
12 |
+
* Constructor.
|
13 |
+
*/
|
14 |
+
public function __construct()
|
15 |
+
{
|
16 |
+
// Default OpenCart template seems to use html 5.
|
17 |
+
$this->fieldsetWrapperClass = 'adminform';
|
18 |
+
$this->legendWrapperClass = 'form-group';
|
19 |
+
$this->requiredMarkup = '';
|
20 |
+
$this->labelWrapperClass = '';
|
21 |
+
$this->inputWrapperClass = 'form-control';
|
22 |
+
$this->radioWrapperClass = 'form-element-radios';
|
23 |
+
$this->checkboxWrapperClass = 'form-element-checkboxes';
|
24 |
+
$this->multiLabelClass = 'label';
|
25 |
+
$this->descriptionClass = 'desc';
|
26 |
+
}
|
27 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/OpenCart1/Helpers/OcHelper.php
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\OpenCart\OpenCart1\Helpers;
|
3 |
+
|
4 |
+
use Siel\Acumulus\OpenCart\Helpers\OcHelper as BaseOcHelper;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* OcHelper contains functionality shared between the OC1 and OC2 controllers
|
8 |
+
* and models, for both admin and catalog.
|
9 |
+
*/
|
10 |
+
class OcHelper extends BaseOcHelper
|
11 |
+
{
|
12 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/OpenCart1/Shop/InvoiceManager.php
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\OpenCart\OpenCart1\Shop;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Invoice\Source as BaseSource;
|
5 |
+
use Siel\Acumulus\OpenCart\Shop\InvoiceManager as BaseInvoiceManager;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* This OpenCart 1 override allows you to insert your event handler code using
|
9 |
+
* VQMOD.
|
10 |
+
*/
|
11 |
+
class InvoiceManager extends BaseInvoiceManager
|
12 |
+
{
|
13 |
+
/**
|
14 |
+
* {@inheritdoc}
|
15 |
+
*/
|
16 |
+
protected function triggerInvoiceCreated(array &$invoice, BaseSource $invoiceSource)
|
17 |
+
{
|
18 |
+
// VQMOD: insert your 'acumulus.invoice.created' event code here.
|
19 |
+
// END VQMOD: insert your 'acumulus.invoice.created' event code here.
|
20 |
+
}
|
21 |
+
|
22 |
+
/**
|
23 |
+
* {@inheritdoc}
|
24 |
+
*/
|
25 |
+
protected function triggerInvoiceCompleted(array &$invoice, BaseSource $invoiceSource)
|
26 |
+
{
|
27 |
+
// VQMOD: insert your 'acumulus.invoice.completed' event code here.
|
28 |
+
// END VQMOD: insert your 'acumulus.invoice.completed' event code here.
|
29 |
+
}
|
30 |
+
|
31 |
+
/**
|
32 |
+
* {@inheritdoc}
|
33 |
+
*/
|
34 |
+
protected function triggerInvoiceSent(array $invoice, BaseSource $invoiceSource, array $result)
|
35 |
+
{
|
36 |
+
// VQMOD: insert your 'acumulus.invoice.sent' event code here.
|
37 |
+
// END VQMOD: insert your 'acumulus.invoice.sent' event code here.
|
38 |
+
}
|
39 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/OpenCart2/Helpers/FormRenderer.php
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\OpenCart\OpenCart2\Helpers;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\FormRenderer as BaseFormRenderer;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* FormMapper maps an Acumulus form definition to a OpenCart 2 form definition.
|
8 |
+
*/
|
9 |
+
class FormRenderer extends BaseFormRenderer
|
10 |
+
{
|
11 |
+
/**
|
12 |
+
* Constructor.
|
13 |
+
*/
|
14 |
+
public function __construct()
|
15 |
+
{
|
16 |
+
// Default OpenCart template seems to use html 5.
|
17 |
+
$this->fieldsetWrapperClass = 'adminform';
|
18 |
+
$this->legendWrapperClass = 'form-group';
|
19 |
+
$this->elementWrapperClass = 'form-group';
|
20 |
+
$this->requiredMarkup = '';
|
21 |
+
$this->labelWrapperClass = 'form-group';
|
22 |
+
$this->inputWrapperClass = 'form-control';
|
23 |
+
$this->multiLabelClass = 'control-label';
|
24 |
+
$this->descriptionClass = 'description';
|
25 |
+
}
|
26 |
+
|
27 |
+
protected function renderField(array $field)
|
28 |
+
{
|
29 |
+
$oldElementWrapperClass = $this->elementWrapperClass;
|
30 |
+
if (!empty($field['attributes']['required'])) {
|
31 |
+
if (empty($this->elementWrapperClass)) {
|
32 |
+
$this->elementWrapperClass = '';
|
33 |
+
} else {
|
34 |
+
$this->elementWrapperClass .= ' ';
|
35 |
+
}
|
36 |
+
$this->elementWrapperClass .= 'required';
|
37 |
+
}
|
38 |
+
$result = parent::renderField($field);
|
39 |
+
$this->elementWrapperClass = $oldElementWrapperClass;
|
40 |
+
return $result;
|
41 |
+
}
|
42 |
+
|
43 |
+
|
44 |
+
protected function addLabelAttributes(array $attributes, $id)
|
45 |
+
{
|
46 |
+
$attributes = parent::addLabelAttributes($attributes, $id);
|
47 |
+
$attributes = $this->addAttribute($attributes, 'class', 'col-sm-2');
|
48 |
+
$attributes = $this->addAttribute($attributes, 'class', 'control-label');
|
49 |
+
return $attributes;
|
50 |
+
}
|
51 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/OpenCart2/Helpers/OcHelper.php
ADDED
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\OpenCart\OpenCart2\Helpers;
|
3 |
+
|
4 |
+
use Siel\Acumulus\OpenCart\Helpers\OcHelper as BaseOcHelper;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* OcHelper contains functionality shared between the OC1 and OC2 controllers
|
8 |
+
* and models, for both admin and catalog.
|
9 |
+
*/
|
10 |
+
class OcHelper extends BaseOcHelper
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* Uninstall function, called when the module is uninstalled by an admin.
|
14 |
+
*/
|
15 |
+
public function uninstall()
|
16 |
+
{
|
17 |
+
// "Disable" (delete) events, regardless the confirmation answer.
|
18 |
+
$this->uninstallEvents();
|
19 |
+
parent::uninstall();
|
20 |
+
}
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Performs the common tasks when displaying a form.
|
24 |
+
*
|
25 |
+
* @param string $task
|
26 |
+
*/
|
27 |
+
protected function displayFormCommon($task)
|
28 |
+
{
|
29 |
+
parent::displayFormCommon($task);
|
30 |
+
|
31 |
+
// Render the common parts.
|
32 |
+
$this->data['header'] = $this->registry->load->controller('common/header');
|
33 |
+
$this->data['column_left'] = $this->registry->load->controller('common/column_left');
|
34 |
+
$this->data['footer'] = $this->registry->load->controller('common/footer');
|
35 |
+
}
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Performs the common tasks when processing and rendering a form.
|
39 |
+
*
|
40 |
+
* @param string $task
|
41 |
+
* @param string $button
|
42 |
+
*/
|
43 |
+
protected function renderFormCommon($task, $button)
|
44 |
+
{
|
45 |
+
parent::renderFormCommon($task, $button);
|
46 |
+
|
47 |
+
// Send the output.
|
48 |
+
$this->registry->response->setOutput($this->registry->load->view('module/acumulus-form.tpl', $this->data));
|
49 |
+
}
|
50 |
+
|
51 |
+
/**
|
52 |
+
* Checks requirements and installs tables for this module.
|
53 |
+
*
|
54 |
+
* @return bool
|
55 |
+
* Success.
|
56 |
+
*/
|
57 |
+
protected function doInstall()
|
58 |
+
{
|
59 |
+
$result = parent::doInstall();
|
60 |
+
|
61 |
+
// Install events
|
62 |
+
if (empty($this->data['error_warnings'])) {
|
63 |
+
$this->installEvents();
|
64 |
+
}
|
65 |
+
|
66 |
+
return $result;
|
67 |
+
}
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Installs our events.
|
71 |
+
*
|
72 |
+
* This will add them to the table 'event' from where they are registered on
|
73 |
+
* the start of each request. The controller actions can be found in the
|
74 |
+
* catalog controller.
|
75 |
+
*
|
76 |
+
* To support updating, this will also be called by the index function.
|
77 |
+
* Therefore we will first remove any existing events from our module.
|
78 |
+
*/
|
79 |
+
protected function installEvents()
|
80 |
+
{
|
81 |
+
$this->uninstallEvents();
|
82 |
+
$this->registry->model_extension_event->addEvent('acumulus', 'post.order.add', 'module/acumulus/eventOrderUpdate');
|
83 |
+
$this->registry->model_extension_event->addEvent('acumulus', 'post.order.history.add',
|
84 |
+
'module/acumulus/eventOrderUpdate');
|
85 |
+
}
|
86 |
+
|
87 |
+
/**
|
88 |
+
* Removes the Acumulus event handlers from the event table.
|
89 |
+
*/
|
90 |
+
protected function uninstallEvents()
|
91 |
+
{
|
92 |
+
$this->registry->load->model('extension/event');
|
93 |
+
$this->registry->model_extension_event->deleteEvent('acumulus');
|
94 |
+
}
|
95 |
+
|
96 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/OpenCart2/Shop/InvoiceManager.php
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\OpenCart\OpenCart2\Shop;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Invoice\Source as BaseSource;
|
5 |
+
use Siel\Acumulus\OpenCart\Helpers\Registry;
|
6 |
+
use Siel\Acumulus\OpenCart\Shop\InvoiceManager as BaseInvoiceManager;
|
7 |
+
|
8 |
+
class InvoiceManager extends BaseInvoiceManager
|
9 |
+
{
|
10 |
+
/**
|
11 |
+
* {@inheritdoc}
|
12 |
+
*
|
13 |
+
* This OpenCart 2 override triggers the 'acumulus.invoice.created' event.
|
14 |
+
*/
|
15 |
+
protected function triggerInvoiceCreated(array &$invoice, BaseSource $invoiceSource)
|
16 |
+
{
|
17 |
+
$args = array('invoice' => &$invoice, 'source' => $invoiceSource);
|
18 |
+
$this->getEvent()->trigger('acumulus.invoice.created', $args);
|
19 |
+
}
|
20 |
+
|
21 |
+
/**
|
22 |
+
* {@inheritdoc}
|
23 |
+
*
|
24 |
+
* This OpenCart 2 override triggers the 'acumulus.invoice.completed' event.
|
25 |
+
*/
|
26 |
+
protected function triggerInvoiceCompleted(array &$invoice, BaseSource $invoiceSource)
|
27 |
+
{
|
28 |
+
$args = array('invoice' => &$invoice, 'source' => $invoiceSource);
|
29 |
+
$this->getEvent()->trigger('acumulus.invoice.completed', $args);
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* {@inheritdoc}
|
34 |
+
*
|
35 |
+
* This OpenCart 2 override triggers the 'acumulus.invoice.sent' event.
|
36 |
+
*/
|
37 |
+
protected function triggerInvoiceSent(array $invoice, BaseSource $invoiceSource, array $result)
|
38 |
+
{
|
39 |
+
$args = array('invoice' => $invoice, 'source' => $invoiceSource, 'result' => $result);
|
40 |
+
$this->getEvent()->trigger('acumulus.invoice.sent', $args);
|
41 |
+
}
|
42 |
+
|
43 |
+
/**
|
44 |
+
* @return \Event
|
45 |
+
*/
|
46 |
+
private function getEvent()
|
47 |
+
{
|
48 |
+
return Registry::getInstance()->event;
|
49 |
+
}
|
50 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Shop/AcumulusEntryModel.php
ADDED
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\OpenCart\Shop;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Invoice\Source;
|
5 |
+
use Siel\Acumulus\OpenCart\Helpers\Registry;
|
6 |
+
use Siel\Acumulus\Shop\AcumulusEntryModel as BaseAcumulusEntryModel;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Implements the OpenCart specific acumulus entry model class.
|
10 |
+
*
|
11 |
+
* In WooCommerce this data is stored as metadata. As such, the "records"
|
12 |
+
* returned here are an array of all metadata values, thus not filtered by
|
13 |
+
* Acumulus keys.
|
14 |
+
*/
|
15 |
+
class AcumulusEntryModel extends BaseAcumulusEntryModel
|
16 |
+
{
|
17 |
+
/** @var string */
|
18 |
+
protected $tableName;
|
19 |
+
|
20 |
+
/**
|
21 |
+
* AcumulusEntryModel constructor.
|
22 |
+
*/
|
23 |
+
public function __construct()
|
24 |
+
{
|
25 |
+
$this->tableName = DB_PREFIX . 'acumulus_entry';
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* {@inheritdoc}
|
30 |
+
*/
|
31 |
+
public function getByEntryId($entryId)
|
32 |
+
{
|
33 |
+
$result = $this->getDb()->query(sprintf('SELECT * FROM %s WHERE entry_id = %u', $this->tableName, $entryId));
|
34 |
+
return empty($result->row) ? $result->row : null;
|
35 |
+
}
|
36 |
+
|
37 |
+
/**
|
38 |
+
* {@inheritdoc}
|
39 |
+
*/
|
40 |
+
public function getByInvoiceSourceId($invoiceSourceType, $invoiceSourceId)
|
41 |
+
{
|
42 |
+
$result = $this->getDb()->query(sprintf("SELECT * FROM `%s` WHERE source_type = '%s' AND source_id = %u", $this->tableName, $invoiceSourceType, $invoiceSourceId));
|
43 |
+
return !empty($result->row) ? $result->row : null;
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* {@inheritdoc}
|
48 |
+
*/
|
49 |
+
protected function insert($invoiceSource, $entryId, $token, $created)
|
50 |
+
{
|
51 |
+
if ($invoiceSource->getType() === Source::Order) {
|
52 |
+
$storeId = $invoiceSource->getSource();
|
53 |
+
$storeId = $storeId['store_id'];
|
54 |
+
} else {
|
55 |
+
$storeId = 0;
|
56 |
+
}
|
57 |
+
return (bool) $this->getDb()->query(sprintf("INSERT INTO `%s` (store_id, entry_id, token, source_type, source_id, updated) VALUES (%d, %d, '%s', '%s', %d, '%s')",
|
58 |
+
$this->tableName, $storeId, $entryId, $token, $invoiceSource->getType(), $invoiceSource->getId(), $created));
|
59 |
+
}
|
60 |
+
|
61 |
+
/**
|
62 |
+
* {@inheritdoc}
|
63 |
+
*/
|
64 |
+
protected function update($record, $entryId, $token, $updated)
|
65 |
+
{
|
66 |
+
return (bool) $this->getDb()->query(sprintf("UPDATE `%s` SET entry_id = %u, token = '%s', updated = '%s' WHERE id = %u",
|
67 |
+
$this->tableName, $entryId, $token, $updated, $record['id']));
|
68 |
+
}
|
69 |
+
|
70 |
+
/** @noinspection PhpUndefinedNamespaceInspection */
|
71 |
+
/** @noinspection PhpUndefinedClassInspection */
|
72 |
+
/**
|
73 |
+
* Helper method to get the db object.
|
74 |
+
*
|
75 |
+
* @return \DBMySQLi|\DB\MySQLi
|
76 |
+
*/
|
77 |
+
protected function getDb()
|
78 |
+
{
|
79 |
+
return Registry::getInstance()->db;
|
80 |
+
}
|
81 |
+
|
82 |
+
/**
|
83 |
+
* {@inheritdoc}
|
84 |
+
*/
|
85 |
+
protected function sqlNow()
|
86 |
+
{
|
87 |
+
return date('Y-m-d H:i:s');
|
88 |
+
}
|
89 |
+
|
90 |
+
/**
|
91 |
+
* {@inheritdoc}
|
92 |
+
*/
|
93 |
+
public function install()
|
94 |
+
{
|
95 |
+
$queryResult = $this->getDb()->query("show tables like '{$this->tableName}'");
|
96 |
+
$tableExists = !empty($queryResult->num_rows);
|
97 |
+
if (!$tableExists) {
|
98 |
+
// Table does not exist: create it.
|
99 |
+
return $this->createTable();
|
100 |
+
} else {
|
101 |
+
// Table does exist: but in old or current data model?
|
102 |
+
$columnExists = $this->getDb()->query("show columns from `{$this->tableName}` like 'source_type'");
|
103 |
+
$columnExists = !empty($columnExists->num_rows);
|
104 |
+
if (!$columnExists) {
|
105 |
+
// Table exists but in old data model: alter table
|
106 |
+
// Rename currently existing table.
|
107 |
+
$oldTableName = $this->tableName . '_old';
|
108 |
+
$result = $this->getDb()->query("ALTER TABLE `{$this->tableName}` RENAME `$oldTableName`;");
|
109 |
+
|
110 |
+
// Create table in new data model.
|
111 |
+
$result = $this->createTable() && $result;
|
112 |
+
|
113 |
+
// Copy data from old to new table.
|
114 |
+
// Orders only, credit slips were not supported in that version.
|
115 |
+
// Nor did we support multi store shops (though a join could add that).
|
116 |
+
$result = $result && $this->getDb()->query("insert into `{$this->tableName}`
|
117 |
+
(entry_id, token, source_type, source_id, created, updated)
|
118 |
+
select entry_id, token, 'Order' as source_type, order_id as source_id, created, updated
|
119 |
+
from `$oldTableName``;");
|
120 |
+
|
121 |
+
// Delete old table.
|
122 |
+
$result = $result && $this->getDb()->query("DROP TABLE `$oldTableName`");
|
123 |
+
|
124 |
+
return $result;
|
125 |
+
} else {
|
126 |
+
// Table exists in current data model.
|
127 |
+
return true;
|
128 |
+
}
|
129 |
+
}
|
130 |
+
}
|
131 |
+
|
132 |
+
/**
|
133 |
+
* {@inheritdoc}
|
134 |
+
*/
|
135 |
+
public function uninstall()
|
136 |
+
{
|
137 |
+
return (bool) $this->getDb()->query("DROP TABLE `{$this->tableName}`");
|
138 |
+
}
|
139 |
+
|
140 |
+
/**
|
141 |
+
*
|
142 |
+
*
|
143 |
+
*
|
144 |
+
* @return bool
|
145 |
+
*
|
146 |
+
*/
|
147 |
+
protected function createTable()
|
148 |
+
{
|
149 |
+
return (bool) $this->getDb()->query("CREATE TABLE IF NOT EXISTS `{$this->tableName}` (
|
150 |
+
`id` int(11) NOT NULL AUTO_INCREMENT,
|
151 |
+
`store_id` int(11) NOT NULL DEFAULT '0',
|
152 |
+
`entry_id` int(11) NOT NULL,
|
153 |
+
`token` char(32) NOT NULL,
|
154 |
+
`source_type` varchar(32) NOT NULL,
|
155 |
+
`source_id` int(11) NOT NULL,
|
156 |
+
`created` timestamp DEFAULT CURRENT_TIMESTAMP,
|
157 |
+
`updated` timestamp NOT NULL,
|
158 |
+
PRIMARY KEY (`id`),
|
159 |
+
UNIQUE INDEX `acumulus_idx_entry_id` (`entry_id`),
|
160 |
+
UNIQUE INDEX `acumulus_idx_source` (`source_id`, `source_type`)
|
161 |
+
)");
|
162 |
+
}
|
163 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Shop/BatchForm.php
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\OpenCart\Shop;
|
3 |
+
|
4 |
+
use Siel\Acumulus\OpenCart\Helpers\Registry;
|
5 |
+
use Siel\Acumulus\Shop\BatchForm as BaseBatchForm;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Provides the Batch send form handling for the VirtueMart Acumulus module.
|
9 |
+
*/
|
10 |
+
class BatchForm extends BaseBatchForm
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* {@inheritdoc}
|
14 |
+
*/
|
15 |
+
public function isSubmitted()
|
16 |
+
{
|
17 |
+
return $this->getRequest()->server['REQUEST_METHOD'] == 'POST';
|
18 |
+
}
|
19 |
+
|
20 |
+
/**
|
21 |
+
* return \Request
|
22 |
+
*/
|
23 |
+
private function getRequest()
|
24 |
+
{
|
25 |
+
return Registry::getInstance()->request;
|
26 |
+
}
|
27 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Shop/ConfigForm.php
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\OpenCart\Shop;
|
3 |
+
|
4 |
+
use Siel\Acumulus\OpenCart\Helpers\Registry;
|
5 |
+
use Siel\Acumulus\Shop\ConfigForm as BaseConfigForm;
|
6 |
+
use Siel\Acumulus\Shop\ConfigInterface;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Class ConfigForm processes and builds the settings form page for the
|
10 |
+
* PrestaShop Acumulus module.
|
11 |
+
*/
|
12 |
+
class ConfigForm extends BaseConfigForm
|
13 |
+
{
|
14 |
+
/**
|
15 |
+
* {@inheritdoc}
|
16 |
+
*/
|
17 |
+
public function isSubmitted()
|
18 |
+
{
|
19 |
+
return $this->getRequest()->server['REQUEST_METHOD'] == 'POST';
|
20 |
+
}
|
21 |
+
|
22 |
+
/**
|
23 |
+
* return \Request
|
24 |
+
*/
|
25 |
+
private function getRequest()
|
26 |
+
{
|
27 |
+
return Registry::getInstance()->request;
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* {@inheritdoc}
|
32 |
+
*/
|
33 |
+
protected function getShopOrderStatuses()
|
34 |
+
{
|
35 |
+
Registry::getInstance()->load->model('localisation/order_status');
|
36 |
+
$states = Registry::getInstance()->model_localisation_order_status->getOrderStatuses();
|
37 |
+
$result = array();
|
38 |
+
foreach ($states as $state) {
|
39 |
+
list($optionValue, $optionText) = array_values($state);
|
40 |
+
$result[$optionValue] = $optionText;
|
41 |
+
}
|
42 |
+
return $result;
|
43 |
+
}
|
44 |
+
|
45 |
+
/**
|
46 |
+
* {@inheritdoc}
|
47 |
+
*
|
48 |
+
* This override removes the 'Use invoice sent' option as OpenCart does not
|
49 |
+
* have an event on creating/sending the invoice.
|
50 |
+
*
|
51 |
+
* @todo: What does OC have?
|
52 |
+
*/
|
53 |
+
protected function getTriggerInvoiceSendEventOptions()
|
54 |
+
{
|
55 |
+
$result = parent::getTriggerInvoiceSendEventOptions();
|
56 |
+
unset($result[ConfigInterface::TriggerInvoiceSendEvent_InvoiceCreate]);
|
57 |
+
return $result;
|
58 |
+
}
|
59 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Shop/ConfigStore.php
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\OpenCart\Shop;
|
3 |
+
|
4 |
+
use Siel\Acumulus\OpenCart\Helpers\Registry;
|
5 |
+
use Siel\Acumulus\Shop\ConfigStore as BaseConfigStore;
|
6 |
+
use Siel\Acumulus\Web\ConfigInterface as ServiceConfigInterface;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Implements the connection to the PrestaShop config component.
|
10 |
+
*/
|
11 |
+
class ConfigStore extends BaSeConfigStore
|
12 |
+
{
|
13 |
+
const CONFIG_KEY = 'ACUMULUS_';
|
14 |
+
|
15 |
+
/**
|
16 |
+
* {@inheritdoc}
|
17 |
+
*/
|
18 |
+
public function getShopEnvironment()
|
19 |
+
{
|
20 |
+
$environment = array(
|
21 |
+
// Module has same version as library.
|
22 |
+
'moduleVersion' => ServiceConfigInterface::libraryVersion,
|
23 |
+
'shopName' => $this->shopName,
|
24 |
+
'shopVersion' => VERSION,
|
25 |
+
);
|
26 |
+
return $environment;
|
27 |
+
}
|
28 |
+
|
29 |
+
/** @noinspection PhpUndefinedClassInspection */
|
30 |
+
/**
|
31 |
+
* @return \ModelSettingSetting
|
32 |
+
*/
|
33 |
+
protected function getSettings()
|
34 |
+
{
|
35 |
+
Registry::getInstance()->load->model('setting/setting');
|
36 |
+
return Registry::getInstance()->model_setting_setting;
|
37 |
+
}
|
38 |
+
|
39 |
+
/**
|
40 |
+
* {@inheritdoc}
|
41 |
+
*/
|
42 |
+
public function load(array $keys)
|
43 |
+
{
|
44 |
+
$result = array();
|
45 |
+
// Load the values from the web shop specific configuration.
|
46 |
+
$configurationValues = $this->getSettings()->getSetting('acumulus_siel');
|
47 |
+
$configurationValues = isset($configurationValues['acumulus_siel_module']) ? $configurationValues['acumulus_siel_module'] : array();
|
48 |
+
foreach ($keys as $key) {
|
49 |
+
if (array_key_exists($key, $configurationValues)) {
|
50 |
+
$result[$key] = $configurationValues[$key];
|
51 |
+
}
|
52 |
+
}
|
53 |
+
return $result;
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* {@inheritdoc}
|
58 |
+
*/
|
59 |
+
public function save(array $values)
|
60 |
+
{
|
61 |
+
$values = $this->saveCommon($values);
|
62 |
+
|
63 |
+
$setting = $this->getSettings()->getSetting('acumulus_siel');
|
64 |
+
if (!isset($setting['acumulus_siel_module'])) {
|
65 |
+
$setting['acumulus_siel_module'] = array();
|
66 |
+
}
|
67 |
+
$setting['acumulus_siel_module'] = array_merge($setting['acumulus_siel_module'], $values);
|
68 |
+
$this->getSettings()->editSetting('acumulus_siel', $setting);
|
69 |
+
return true;
|
70 |
+
}
|
71 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/OpenCart/Shop/InvoiceManager.php
ADDED
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\OpenCart\Shop;
|
3 |
+
|
4 |
+
use DateTime;
|
5 |
+
use Siel\Acumulus\OpenCart\Helpers\Registry;
|
6 |
+
use Siel\Acumulus\PrestaShop\Invoice\Source;
|
7 |
+
use Siel\Acumulus\Shop\Config;
|
8 |
+
use Siel\Acumulus\Shop\InvoiceManager as BaseInvoiceManager;
|
9 |
+
|
10 |
+
class InvoiceManager extends BaseInvoiceManager
|
11 |
+
{
|
12 |
+
/** @var array */
|
13 |
+
protected $tableInfo;
|
14 |
+
|
15 |
+
/** @var string */
|
16 |
+
protected $orderTableName;
|
17 |
+
|
18 |
+
/** @var string */
|
19 |
+
protected $returnTableName;
|
20 |
+
|
21 |
+
/**
|
22 |
+
* {@inheritdoc}
|
23 |
+
*/
|
24 |
+
public function __construct(Config $config)
|
25 |
+
{
|
26 |
+
parent::__construct($config);
|
27 |
+
$this->tableInfo = array(
|
28 |
+
Source::Order => array(
|
29 |
+
'table' => DB_PREFIX . 'order',
|
30 |
+
'key' => 'order_id',
|
31 |
+
),
|
32 |
+
Source::CreditNote => array(
|
33 |
+
'table' => DB_PREFIX . 'return',
|
34 |
+
'key' => 'return_id',
|
35 |
+
),
|
36 |
+
);
|
37 |
+
}
|
38 |
+
|
39 |
+
public function getSupportedInvoiceSourceTypes()
|
40 |
+
{
|
41 |
+
return array(
|
42 |
+
Source::Order,
|
43 |
+
//Source::CreditNote,
|
44 |
+
);
|
45 |
+
}
|
46 |
+
|
47 |
+
|
48 |
+
/** @noinspection PhpUndefinedNamespaceInspection */
|
49 |
+
/** @noinspection PhpUndefinedClassInspection */
|
50 |
+
/**
|
51 |
+
* Helper method to get the db object.
|
52 |
+
*
|
53 |
+
* @return \DBMySQLi|\DB\MySQLi
|
54 |
+
*/
|
55 |
+
protected function getDb()
|
56 |
+
{
|
57 |
+
return Registry::getInstance()->db;
|
58 |
+
}
|
59 |
+
|
60 |
+
/**
|
61 |
+
* {@inheritdoc}
|
62 |
+
*/
|
63 |
+
public function getInvoiceSourcesByIdRange($invoiceSourceType, $InvoiceSourceIdFrom, $InvoiceSourceIdTo)
|
64 |
+
{
|
65 |
+
$key = $this->tableInfo[$invoiceSourceType]['key'];
|
66 |
+
$table = $this->tableInfo[$invoiceSourceType]['table'];
|
67 |
+
$query = sprintf("SELECT `%s` FROM `%s` WHERE `%s` BETWEEN %u AND %u", $key, $table, $key, $InvoiceSourceIdFrom, $InvoiceSourceIdTo);
|
68 |
+
$result = $this->getDb()->query($query);
|
69 |
+
return $this->getSourcesByIdsOrSources($invoiceSourceType, $this->getCol($result->rows, $key));
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* {@inheritdoc}
|
74 |
+
*/
|
75 |
+
public function getInvoiceSourcesByDateRange($invoiceSourceType, DateTime $dateFrom, DateTime $dateTo)
|
76 |
+
{
|
77 |
+
$dateFrom = $this->getSqlDate($dateFrom);
|
78 |
+
$dateTo = $this->getSqlDate($dateTo);
|
79 |
+
$dateField = 'date_modified';
|
80 |
+
$key = $this->tableInfo[$invoiceSourceType]['key'];
|
81 |
+
$table = $this->tableInfo[$invoiceSourceType]['table'];
|
82 |
+
$query = sprintf("SELECT `%s` FROM `%s` WHERE `%s` BETWEEN '%s' AND '%s'", $key, $table, $dateField, $dateFrom, $dateTo);
|
83 |
+
$result = $this->getDb()->query($query);
|
84 |
+
return $this->getSourcesByIdsOrSources($invoiceSourceType, $this->getCol($result->rows, $key));
|
85 |
+
}
|
86 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Helpers/FormMapper.php
ADDED
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\PrestaShop\Helpers;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\Form;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* FormMapper maps an Acumulus form definition to a PrestaShop form definition.
|
8 |
+
*/
|
9 |
+
class FormMapper
|
10 |
+
{
|
11 |
+
/**
|
12 |
+
* Maps a set of field definitions.
|
13 |
+
*
|
14 |
+
* @param Form $form
|
15 |
+
*
|
16 |
+
* @return array[]
|
17 |
+
*/
|
18 |
+
public function map(Form $form)
|
19 |
+
{
|
20 |
+
return $this->fields($form->getFields());
|
21 |
+
}
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Maps a set of field definitions.
|
25 |
+
*
|
26 |
+
* @param array[] $fields
|
27 |
+
*
|
28 |
+
* @return array[]
|
29 |
+
*/
|
30 |
+
protected function fields(array $fields)
|
31 |
+
{
|
32 |
+
$result = array();
|
33 |
+
foreach ($fields as $id => $field) {
|
34 |
+
if (!isset($field['id'])) {
|
35 |
+
$field['id'] = $id;
|
36 |
+
}
|
37 |
+
if (!isset($field['name'])) {
|
38 |
+
$field['name'] = $id;
|
39 |
+
}
|
40 |
+
$result[$id] = $this->field($field);
|
41 |
+
}
|
42 |
+
return $result;
|
43 |
+
}
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Maps a single field definition, possibly a fieldset.
|
47 |
+
*
|
48 |
+
* @param array $field
|
49 |
+
* Field(set) definition.
|
50 |
+
*
|
51 |
+
* @return array
|
52 |
+
*/
|
53 |
+
protected function field(array $field)
|
54 |
+
{
|
55 |
+
if ($field['type'] === 'fieldset') {
|
56 |
+
$result = $this->fieldset($field);
|
57 |
+
} else {
|
58 |
+
$result = $this->element($field);
|
59 |
+
}
|
60 |
+
return $result;
|
61 |
+
}
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Returns a mapped fieldset.
|
65 |
+
*
|
66 |
+
* @param array $field
|
67 |
+
*
|
68 |
+
* @return array[]
|
69 |
+
*/
|
70 |
+
protected function fieldset(array $field)
|
71 |
+
{
|
72 |
+
$result = array(
|
73 |
+
'form' => array(
|
74 |
+
'legend' => array(
|
75 |
+
'title' => $field['legend'],
|
76 |
+
),
|
77 |
+
'input' => $this->fields($field['fields']),
|
78 |
+
),
|
79 |
+
);
|
80 |
+
if (isset($field['icon'])) {
|
81 |
+
$result['form']['legend']['icon'] = $field['icon'];
|
82 |
+
}
|
83 |
+
return $result;
|
84 |
+
}
|
85 |
+
|
86 |
+
/**
|
87 |
+
* Returns a mapped simple element.
|
88 |
+
*
|
89 |
+
* @param array $field
|
90 |
+
*
|
91 |
+
* @return array
|
92 |
+
*
|
93 |
+
*/
|
94 |
+
protected function element(array $field)
|
95 |
+
{
|
96 |
+
$result = array(
|
97 |
+
'type' => $this->getPrestaShopType($field['type']),
|
98 |
+
'label' => isset($field['label']) ? $field['label'] : '',
|
99 |
+
'name' => $field['name'],
|
100 |
+
'required' => isset($field['attributes']['required']) ? $field['attributes']['required'] : false,
|
101 |
+
'multiple' => isset($field['attributes']['multiple']) ? $field['attributes']['multiple'] : false,
|
102 |
+
);
|
103 |
+
|
104 |
+
if (isset($field['attributes'])) {
|
105 |
+
$result['attributes'] = $field['attributes'];
|
106 |
+
}
|
107 |
+
if (isset($field['description'])) {
|
108 |
+
$result['desc'] = $field['description'];
|
109 |
+
}
|
110 |
+
|
111 |
+
if ($field['type'] === 'radio') {
|
112 |
+
$result['values'] = $this->getPrestaShopValues($field['name'], $field['options']);
|
113 |
+
} else if ($field['type'] === 'checkbox') {
|
114 |
+
$result['values'] = $this->getPrestaShopOptions($field['options']);
|
115 |
+
} else if ($field['type'] === 'select') {
|
116 |
+
$result['options'] = $this->getPrestaShopOptions($field['options']);
|
117 |
+
if ($result['multiple']) {
|
118 |
+
$result['size'] = $field['attributes']['size'];
|
119 |
+
}
|
120 |
+
}
|
121 |
+
|
122 |
+
return $result;
|
123 |
+
}
|
124 |
+
|
125 |
+
/**
|
126 |
+
* Returns the PrestaShop form element type for the given Acumulus type string.
|
127 |
+
*
|
128 |
+
* @param string $type
|
129 |
+
*
|
130 |
+
* @return string
|
131 |
+
*/
|
132 |
+
protected function getPrestaShopType($type)
|
133 |
+
{
|
134 |
+
switch ($type) {
|
135 |
+
case 'fieldset':
|
136 |
+
case 'markup':
|
137 |
+
$type = 'free';
|
138 |
+
break;
|
139 |
+
case 'email':
|
140 |
+
$type = 'text';
|
141 |
+
break;
|
142 |
+
default:
|
143 |
+
// Return as is: text, password, textarea, radio, select, checkbox,
|
144 |
+
// date. PrestaShop accepts these as are.
|
145 |
+
break;
|
146 |
+
}
|
147 |
+
return $type;
|
148 |
+
}
|
149 |
+
|
150 |
+
/**
|
151 |
+
* Converts a list of Acumulus field options to a list of PrestaShop radio
|
152 |
+
* button values.
|
153 |
+
*
|
154 |
+
* @param string $id
|
155 |
+
* @param array $options
|
156 |
+
*
|
157 |
+
* @return array A list of PrestaShop radio button options.
|
158 |
+
* A list of PrestaShop radio button options.
|
159 |
+
*/
|
160 |
+
protected function getPrestaShopValues($id, array $options)
|
161 |
+
{
|
162 |
+
$result = array();
|
163 |
+
foreach ($options as $value => $label) {
|
164 |
+
$result[] = array(
|
165 |
+
'id' => $id . $value,
|
166 |
+
'value' => $value,
|
167 |
+
'label' => $label,
|
168 |
+
);
|
169 |
+
}
|
170 |
+
return $result;
|
171 |
+
}
|
172 |
+
|
173 |
+
/**
|
174 |
+
* Converts a list of Acumulus field options to a list of PrestaShop radio
|
175 |
+
* button values.
|
176 |
+
*
|
177 |
+
* @param array $options
|
178 |
+
*
|
179 |
+
* @return array A list of PrestaShop radio button options.
|
180 |
+
* A list of PrestaShop radio button options.
|
181 |
+
*/
|
182 |
+
protected function getPrestaShopOptions(array $options)
|
183 |
+
{
|
184 |
+
$result = array(
|
185 |
+
'query' => array(),
|
186 |
+
'id' => 'id',
|
187 |
+
'name' => 'name',
|
188 |
+
);
|
189 |
+
|
190 |
+
foreach ($options as $value => $label) {
|
191 |
+
$result['query'][] = array(
|
192 |
+
'id' => $value,
|
193 |
+
'name' => $label,
|
194 |
+
);
|
195 |
+
}
|
196 |
+
|
197 |
+
return $result;
|
198 |
+
}
|
199 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Helpers/Log.php
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\PrestaShop\Helpers;
|
3 |
+
|
4 |
+
use PrestaShopLogger;
|
5 |
+
use Siel\Acumulus\Helpers\Log as BaseLog;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Extends the base log class to log any library logging to the PrestaShop log.
|
9 |
+
*/
|
10 |
+
class Log extends BaseLog
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* {@inheritdoc}
|
14 |
+
*
|
15 |
+
* This override uses the PrestaShopLogger.
|
16 |
+
*/
|
17 |
+
protected function write($message, $severity)
|
18 |
+
{
|
19 |
+
PrestaShopLogger::addLog($message, $this->getPrestaShopSeverity($severity));
|
20 |
+
}
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Returns the PrestaShop equivalent of the severity.
|
24 |
+
*
|
25 |
+
* @param int $severity
|
26 |
+
* One of the constants of the base Log class.
|
27 |
+
*
|
28 |
+
* @return int
|
29 |
+
* The PrestaShop equivalent of the severity.
|
30 |
+
*/
|
31 |
+
protected function getPrestaShopSeverity($severity)
|
32 |
+
{
|
33 |
+
switch ($severity) {
|
34 |
+
case Log::Error:
|
35 |
+
return 3;
|
36 |
+
case Log::Warning:
|
37 |
+
return 2;
|
38 |
+
case Log::Notice:
|
39 |
+
return 1;
|
40 |
+
case Log::Debug:
|
41 |
+
default:
|
42 |
+
return 1;
|
43 |
+
}
|
44 |
+
}
|
45 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Helpers/Mailer.php
ADDED
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\PrestaShop\Helpers;
|
3 |
+
|
4 |
+
use Configuration;
|
5 |
+
use Language;
|
6 |
+
use Mail;
|
7 |
+
use Siel\Acumulus\Helpers\Mailer as BaseMailer;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Extends the base mailer class to send a mail using the PrestaShop mailer.
|
11 |
+
*/
|
12 |
+
class Mailer extends BaseMailer
|
13 |
+
{
|
14 |
+
/** @var string */
|
15 |
+
protected $templateDir;
|
16 |
+
|
17 |
+
/** @var string */
|
18 |
+
protected $templateName;
|
19 |
+
|
20 |
+
/**
|
21 |
+
* {@inheritdoc}
|
22 |
+
*/
|
23 |
+
public function sendInvoiceAddMailResult(array $result, array $messages, $invoiceSourceType, $invoiceSourceReference)
|
24 |
+
{
|
25 |
+
$this->templateDir = dirname(__FILE__) . '/mails/';
|
26 |
+
$this->templateName = 'message';
|
27 |
+
$body = $this->getBody($result, $messages, $invoiceSourceType, $invoiceSourceReference);
|
28 |
+
$this->writeTemplateFile($body);
|
29 |
+
|
30 |
+
$languageId = Language::getIdByIso($this->translator->getLanguage());
|
31 |
+
$title = $this->getSubject($result);
|
32 |
+
$templateVars = array();
|
33 |
+
$toEmail = $this->getToAddress();
|
34 |
+
$toName = Configuration::get('PS_SHOP_NAME');
|
35 |
+
$from = Configuration::get('PS_SHOP_EMAIL');
|
36 |
+
$fromName = Configuration::get('PS_SHOP_NAME');
|
37 |
+
|
38 |
+
$result = Mail::Send($languageId, $this->templateName, $title, $templateVars, $toEmail, $toName, $from, $fromName, null, null, $this->templateDir);
|
39 |
+
|
40 |
+
// Clear the template files as they contain privacy sensitive data.
|
41 |
+
$this->writeTemplateFiles(array('body' => '', 'text' => ''));
|
42 |
+
|
43 |
+
return $result;
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* Writes the mail bodies (html and text) to template files as used by the
|
48 |
+
* PrestaShop mailer.
|
49 |
+
*
|
50 |
+
* @param array $body
|
51 |
+
*/
|
52 |
+
protected function writeTemplateFile(array $body)
|
53 |
+
{
|
54 |
+
$languageIso = $this->translator->getLanguage();
|
55 |
+
$templateBaseName = $this->templateDir . $languageIso . '/' . $this->templateName;
|
56 |
+
file_put_contents($templateBaseName . '.html', !empty($body['html']) ? $body['html'] : '');
|
57 |
+
file_put_contents($templateBaseName . '.txt', !empty($body['text']) ? $body['text'] : '');
|
58 |
+
}
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Writes the mail bodies (html and text) to template files as used by the
|
62 |
+
* PrestaShop mailer.
|
63 |
+
*
|
64 |
+
* @param array $body
|
65 |
+
*/
|
66 |
+
protected function writeTemplateFiles(array $body)
|
67 |
+
{
|
68 |
+
$languageIso = $this->translator->getLanguage();
|
69 |
+
$templateBaseName = $this->templateDir . $languageIso . '/' . $this->templateName;
|
70 |
+
file_put_contents($templateBaseName . '.html', !empty($body['html']) ? $body['html'] : '');
|
71 |
+
file_put_contents($templateBaseName . '.txt', !empty($body['text']) ? $body['text'] : '');
|
72 |
+
}
|
73 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Helpers/mails/.htaccess
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#
|
2 |
+
# Apache/PHP/Drupal settings:
|
3 |
+
#
|
4 |
+
|
5 |
+
# Protect files and directories from prying eyes.
|
6 |
+
<FilesMatch "\.(html|txt)$">
|
7 |
+
Order allow,deny
|
8 |
+
</FilesMatch>
|
9 |
+
|
10 |
+
# Don't show directory listings for URLs which map to a directory.
|
11 |
+
Options -Indexes
|
12 |
+
|
13 |
+
# Follow symbolic links in this directory.
|
14 |
+
Options +FollowSymLinks
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Helpers/mails/en/message.html
ADDED
File without changes
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Helpers/mails/en/message.txt
ADDED
File without changes
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Helpers/mails/nl/message.html
ADDED
File without changes
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Helpers/mails/nl/message.txt
ADDED
File without changes
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Invoice/Creator.php
ADDED
@@ -0,0 +1,499 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\PrestaShop\Invoice;
|
3 |
+
|
4 |
+
use Address;
|
5 |
+
use Carrier;
|
6 |
+
use Configuration;
|
7 |
+
use Country;
|
8 |
+
use Customer;
|
9 |
+
use Order;
|
10 |
+
use OrderPayment;
|
11 |
+
use OrderSlip;
|
12 |
+
use Siel\Acumulus\Helpers\Number;
|
13 |
+
use Siel\Acumulus\Invoice\ConfigInterface;
|
14 |
+
use Siel\Acumulus\Invoice\Creator as BaseCreator;
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Allows to create arrays in the Acumulus invoice structure from a PrestaShop
|
18 |
+
* order or order slip.
|
19 |
+
*
|
20 |
+
* Notes:
|
21 |
+
* - If needed, PrestaShop allows us to get tax rates by querying the tax table
|
22 |
+
* because as soon as an existing tax rate gets updated it will get a new id,
|
23 |
+
* so old order details still point to a tax record with the tax rate as was
|
24 |
+
* used at the moment the order was placed.
|
25 |
+
* - Fixed in 1.6.1.1: bug in partial refund, not executed the hook
|
26 |
+
* actionOrderSlipAdd #PSCSX-6287. So before 1.6.1.1, partial refunds will not
|
27 |
+
* be automatically sent to Acumulus.
|
28 |
+
* - Credit notes can get a correction line. They get one if the total amount
|
29 |
+
* does not match the sum of the lines added so far. This can happen if an
|
30 |
+
* amount was entered manually, or if discount(s) applied during the sale were
|
31 |
+
* subtracted from the credit amount but we could not find which discounts
|
32 |
+
* this were. However:
|
33 |
+
* - amount is excl vat if not manually entered.
|
34 |
+
* - amount is incl vat if manually entered (assuming administrators enter
|
35 |
+
* amounts incl tax, and this is what gets listed on the credit PDF.
|
36 |
+
* - shipping_cost_amount is excl vat.
|
37 |
+
* So this is never going to work in all situations!!!
|
38 |
+
*
|
39 |
+
* @todo: So, can we get a tax amount/rate over the manually entered refund?
|
40 |
+
*/
|
41 |
+
class Creator extends BaseCreator
|
42 |
+
{
|
43 |
+
// More specifically typed property.
|
44 |
+
/** @var Source */
|
45 |
+
protected $invoiceSource;
|
46 |
+
|
47 |
+
/** @var Order */
|
48 |
+
protected $order;
|
49 |
+
|
50 |
+
/** @var OrderSlip */
|
51 |
+
protected $creditSlip;
|
52 |
+
|
53 |
+
/**
|
54 |
+
* {@inheritdoc}
|
55 |
+
*
|
56 |
+
* This override also initializes WooCommerce specific properties related to
|
57 |
+
* the source.
|
58 |
+
*/
|
59 |
+
protected function setInvoiceSource($invoiceSource)
|
60 |
+
{
|
61 |
+
parent::setInvoiceSource($invoiceSource);
|
62 |
+
switch ($this->invoiceSource->getType()) {
|
63 |
+
case Source::Order:
|
64 |
+
$this->order = $this->invoiceSource->getSource();
|
65 |
+
break;
|
66 |
+
case Source::CreditNote:
|
67 |
+
$this->creditSlip = $this->invoiceSource->getSource();
|
68 |
+
$this->order = new Order($this->creditSlip->id_order);
|
69 |
+
break;
|
70 |
+
}
|
71 |
+
}
|
72 |
+
|
73 |
+
/**
|
74 |
+
* {@inheritdoc}
|
75 |
+
*/
|
76 |
+
protected function getCustomer()
|
77 |
+
{
|
78 |
+
$customer = new Customer($this->invoiceSource->getSource()->id_customer);
|
79 |
+
$invoiceAddress = new Address($this->order->id_address_invoice);
|
80 |
+
|
81 |
+
$result = array();
|
82 |
+
|
83 |
+
$this->addIfNotEmpty($result, 'contactyourid', $customer->id);
|
84 |
+
$this->addEmpty($result, 'companyname1', $invoiceAddress->company);
|
85 |
+
$result['fullname'] = $invoiceAddress->firstname . ' ' . $invoiceAddress->lastname;
|
86 |
+
$this->addEmpty($result, 'address1', $invoiceAddress->address1);
|
87 |
+
$this->addEmpty($result, 'address2', $invoiceAddress->address2);
|
88 |
+
$this->addEmpty($result, 'postalcode', $invoiceAddress->postcode);
|
89 |
+
$this->addEmpty($result, 'city', $invoiceAddress->city);
|
90 |
+
if ($invoiceAddress->id_country) {
|
91 |
+
$result['countrycode'] = Country::getIsoById($invoiceAddress->id_country);
|
92 |
+
}
|
93 |
+
$this->addIfNotEmpty($result, 'vatnumber', $invoiceAddress->vat_number);
|
94 |
+
// Add either mobile or phone number to 'telephone'.
|
95 |
+
$this->addIfNotEmpty($result, 'telephone', $invoiceAddress->phone);
|
96 |
+
$this->addIfNotEmpty($result, 'telephone', $invoiceAddress->phone_mobile);
|
97 |
+
$result['email'] = $customer->email;
|
98 |
+
|
99 |
+
return $result;
|
100 |
+
}
|
101 |
+
|
102 |
+
/**
|
103 |
+
* {@inheritdoc}
|
104 |
+
*/
|
105 |
+
protected function searchProperty($property)
|
106 |
+
{
|
107 |
+
$invoiceAddress = new Address($this->order->id_address_invoice);
|
108 |
+
$value = $this->getProperty($property, $invoiceAddress);
|
109 |
+
if (empty($value)) {
|
110 |
+
$customer = new Customer($this->invoiceSource->getSource()->id_customer);
|
111 |
+
$value = $this->getProperty($property, $customer);
|
112 |
+
}
|
113 |
+
if (empty($value)) {
|
114 |
+
$value = parent::searchProperty($property);
|
115 |
+
}
|
116 |
+
return $value;
|
117 |
+
}
|
118 |
+
|
119 |
+
/**
|
120 |
+
* {@inheritdoc}
|
121 |
+
*/
|
122 |
+
protected function getInvoiceNumber($invoiceNumberSource)
|
123 |
+
{
|
124 |
+
$result = $this->invoiceSource->getReference();
|
125 |
+
if ($invoiceNumberSource === ConfigInterface::InvoiceNrSource_ShopInvoice && $this->invoiceSource->getType() === Source::Order && !empty($this->order->invoice_number)) {
|
126 |
+
$result = Configuration::get('PS_INVOICE_PREFIX', (int) $this->order->id_lang, null, $this->order->id_shop) . sprintf('%06d', $this->order->invoice_number);
|
127 |
+
}
|
128 |
+
return $result;
|
129 |
+
}
|
130 |
+
|
131 |
+
/**
|
132 |
+
* {@inheritdoc}
|
133 |
+
*/
|
134 |
+
protected function getInvoiceDate($dateToUse)
|
135 |
+
{
|
136 |
+
$result = substr($this->invoiceSource->getSource()->date_add, 0, strlen('2000-01-01'));
|
137 |
+
// Invoice_date is filled with "0000-00-00 00:00:00", so use invoice
|
138 |
+
// number instead to check for existence of the invoice.
|
139 |
+
if ($dateToUse == ConfigInterface::InvoiceDate_InvoiceCreate && $this->invoiceSource->getType() === Source::Order && !empty($this->order->invoice_number)) {
|
140 |
+
$result = substr($this->order->invoice_date, 0, strlen('2000-01-01'));
|
141 |
+
}
|
142 |
+
return $result;
|
143 |
+
}
|
144 |
+
|
145 |
+
/**
|
146 |
+
* {@inheritdoc}
|
147 |
+
*/
|
148 |
+
protected function getPaymentState()
|
149 |
+
{
|
150 |
+
// Assumption: credit slips are always in a paid state.
|
151 |
+
if (($this->invoiceSource->getType() === Source::Order && $this->order->hasBeenPaid()) || $this->invoiceSource->getType() === Source::CreditNote) {
|
152 |
+
$result = ConfigInterface::PaymentStatus_Paid;
|
153 |
+
} else {
|
154 |
+
$result = ConfigInterface::PaymentStatus_Due;
|
155 |
+
}
|
156 |
+
return $result;
|
157 |
+
}
|
158 |
+
|
159 |
+
/**
|
160 |
+
* {@inheritdoc}
|
161 |
+
*/
|
162 |
+
protected function getPaymentDate()
|
163 |
+
{
|
164 |
+
if ($this->invoiceSource->getType() === Source::Order) {
|
165 |
+
$paymentDate = null;
|
166 |
+
foreach ($this->order->getOrderPaymentCollection() as $payment) {
|
167 |
+
/** @var OrderPayment $payment */
|
168 |
+
if ($payment->date_add && (!$paymentDate || $payment->date_add > $paymentDate)) {
|
169 |
+
$paymentDate = $payment->date_add;
|
170 |
+
}
|
171 |
+
}
|
172 |
+
} else {
|
173 |
+
// Assumption: last modified date is date of actual reimbursement.
|
174 |
+
$paymentDate = $this->creditSlip->date_upd;
|
175 |
+
}
|
176 |
+
|
177 |
+
$result = $paymentDate ? substr($paymentDate, 0, strlen('2000-01-01')) : null;
|
178 |
+
return $result;
|
179 |
+
}
|
180 |
+
|
181 |
+
/**
|
182 |
+
* {@inheritdoc}
|
183 |
+
*
|
184 |
+
* This override provides the values meta-invoice-amountinc and
|
185 |
+
* meta-invoice-amount.
|
186 |
+
*/
|
187 |
+
protected function getInvoiceTotals()
|
188 |
+
{
|
189 |
+
$sign = $this->getSign();
|
190 |
+
if ($this->invoiceSource->getType() === Source::Order) {
|
191 |
+
$amount = $this->order->getTotalProductsWithoutTaxes()
|
192 |
+
+ $this->order->total_shipping_tax_excl
|
193 |
+
+ $this->order->total_wrapping_tax_excl
|
194 |
+
- $this->order->total_discounts_tax_excl;
|
195 |
+
$amountInc = $this->order->getTotalProductsWithTaxes()
|
196 |
+
+ $this->order->total_shipping_tax_incl
|
197 |
+
+ $this->order->total_wrapping_tax_incl
|
198 |
+
- $this->order->total_discounts_tax_incl;
|
199 |
+
} else {
|
200 |
+
// On credit notes, the amount ex VAT will not have been corrected for
|
201 |
+
// discounts that are subtracted from the refund. This will be corrected
|
202 |
+
// later in getDiscountLinesCreditNote().
|
203 |
+
/** @noinspection PhpUndefinedFieldInspection */
|
204 |
+
$amount = $this->creditSlip->total_products_tax_excl
|
205 |
+
+ $this->creditSlip->total_shipping_tax_excl;
|
206 |
+
/** @noinspection PhpUndefinedFieldInspection */
|
207 |
+
$amountInc = $this->creditSlip->total_products_tax_incl
|
208 |
+
+ $this->creditSlip->total_shipping_tax_incl;
|
209 |
+
}
|
210 |
+
|
211 |
+
return array(
|
212 |
+
'meta-invoice-amountinc' => $sign * $amountInc,
|
213 |
+
'meta-invoice-amount' => $sign * $amount,
|
214 |
+
);
|
215 |
+
}
|
216 |
+
|
217 |
+
/**
|
218 |
+
* {@inheritdoc}
|
219 |
+
*/
|
220 |
+
protected function getItemLines()
|
221 |
+
{
|
222 |
+
$result = array();
|
223 |
+
if ($this->invoiceSource->getType() === Source::Order) {
|
224 |
+
// Note: getOrderDetailTaxes() is new in 1.6.1.0.
|
225 |
+
$lines = method_exists($this->order, 'getOrderDetailTaxes')
|
226 |
+
? $this->mergeProductLines($this->order->getProductsDetail(), $this->order->getOrderDetailTaxes())
|
227 |
+
: $this->order->getProductsDetail();
|
228 |
+
} else {
|
229 |
+
$lines = $this->creditSlip->getOrdersSlipProducts($this->invoiceSource->getId(), $this->order);
|
230 |
+
}
|
231 |
+
|
232 |
+
foreach ($lines as $line) {
|
233 |
+
$result[] = $this->getItemLine($line);
|
234 |
+
}
|
235 |
+
return $result;
|
236 |
+
}
|
237 |
+
|
238 |
+
/**
|
239 |
+
* Merges the product and tax details arrays.
|
240 |
+
*
|
241 |
+
* @param array $productLines
|
242 |
+
* @param array $taxLines
|
243 |
+
*
|
244 |
+
* @return array
|
245 |
+
*/
|
246 |
+
public function mergeProductLines(array $productLines, array $taxLines)
|
247 |
+
{
|
248 |
+
$result = array();
|
249 |
+
// Key the product lines on id_order_detail, so we can easily add the tax
|
250 |
+
// lines in the 2nd loop.
|
251 |
+
foreach ($productLines as $productLine) {
|
252 |
+
$result[$productLine['id_order_detail']] = $productLine;
|
253 |
+
}
|
254 |
+
// Add the tax lines without overwriting existing entries (though in a
|
255 |
+
// consistent db the same keys should contain the same values).
|
256 |
+
foreach ($taxLines as $taxLine) {
|
257 |
+
$result[$taxLine['id_order_detail']] += $taxLine;
|
258 |
+
}
|
259 |
+
return $result;
|
260 |
+
}
|
261 |
+
|
262 |
+
/**
|
263 |
+
* Returns 1 item line, both for an order or credit slip.
|
264 |
+
*
|
265 |
+
* @param array $item
|
266 |
+
* An array of an OrderDetail line combined with a tax detail line OR
|
267 |
+
* an array with an OrderSlipDetail line.
|
268 |
+
*
|
269 |
+
* @return array
|
270 |
+
*/
|
271 |
+
protected function getItemLine(array $item)
|
272 |
+
{
|
273 |
+
$result = array();
|
274 |
+
$sign = $this->getSign();
|
275 |
+
|
276 |
+
$this->addIfNotEmpty($result, 'itemnumber', $item['product_upc']);
|
277 |
+
$this->addIfNotEmpty($result, 'itemnumber', $item['product_ean13']);
|
278 |
+
$this->addIfNotEmpty($result, 'itemnumber', $item['product_supplier_reference']);
|
279 |
+
$this->addIfNotEmpty($result, 'itemnumber', $item['product_reference']);
|
280 |
+
$result['product'] = $item['product_name'];
|
281 |
+
|
282 |
+
// Prestashop does not support the margin scheme. So in a standard install
|
283 |
+
// this method will always return false. But if this method happens to
|
284 |
+
// return true anyway (customisation, hook), the costprice will trigger
|
285 |
+
// vattype = 5 for Acumulus.
|
286 |
+
if ($this->allowMarginScheme() && !empty($item['purchase_supplier_price'])) {
|
287 |
+
// Margin scheme:
|
288 |
+
// - Do not put VAT on invoice: send price incl VAT as unitprice.
|
289 |
+
// - But still send the VAT rate to Acumulus.
|
290 |
+
$result['unitprice'] = $sign * $item['unit_price_tax_incl'];
|
291 |
+
// Costprice > 0 is the trigger for Acumulus to use the margin scheme.
|
292 |
+
$result['costprice'] = $sign * $item['purchase_supplier_price'];
|
293 |
+
} else {
|
294 |
+
// Unit price is without VAT, so use product_price, not product_price_wt.
|
295 |
+
$result['unitprice'] = $sign * $item['unit_price_tax_excl'];
|
296 |
+
$result['unitpriceinc'] = $sign * $item['unit_price_tax_incl'];
|
297 |
+
$result['meta-line-price'] = $sign * $item['total_price_tax_excl'];
|
298 |
+
$result['meta-line-priceinc'] = $sign * $item['total_price_tax_incl'];
|
299 |
+
}
|
300 |
+
$result['quantity'] = $item['product_quantity'];
|
301 |
+
// The field 'rate' comes from order->getOrderDetailTaxes() and is only
|
302 |
+
// defined for orders and were not filled in or before PS1.6.1.1. So, check
|
303 |
+
// if the field is available.
|
304 |
+
// The fields 'unit_amount' and 'total_amount' (table order_detail_tax) are
|
305 |
+
// based on the discounted product price and thus cannot be used.
|
306 |
+
if (isset($item['rate'])) {
|
307 |
+
$result['vatrate'] = $item['rate'];
|
308 |
+
$result['meta-vatrate-source'] = Creator::VatRateSource_Exact;
|
309 |
+
if (!Number::floatsAreEqual($item['unit_amount'], $result['unitpriceinc'] - $result['unitprice'])) {
|
310 |
+
$result['meta-line-discount-vatamount'] = $item['unit_amount'] - ($result['unitpriceinc'] - $result['unitprice']);
|
311 |
+
}
|
312 |
+
} else {
|
313 |
+
$result += $this->getVatRangeTags($sign * ($item['unit_price_tax_incl'] - $item['unit_price_tax_excl']), $sign * $item['unit_price_tax_excl'], 0.02);
|
314 |
+
}
|
315 |
+
$result['meta-calculated-fields'] = 'vatamount';
|
316 |
+
|
317 |
+
return $result;
|
318 |
+
}
|
319 |
+
|
320 |
+
/**
|
321 |
+
* {@inheritdoc}
|
322 |
+
*/
|
323 |
+
protected function getShippingLine()
|
324 |
+
{
|
325 |
+
$sign = $this->getSign();
|
326 |
+
$shippingEx = $sign * $this->invoiceSource->getSource()->total_shipping_tax_excl;
|
327 |
+
$shippingInc = $sign * $this->invoiceSource->getSource()->total_shipping_tax_incl;
|
328 |
+
$shippingVat = $shippingInc - $shippingEx;
|
329 |
+
|
330 |
+
if (!Number::isZero($shippingInc)) {
|
331 |
+
$result = array(
|
332 |
+
'product' => $this->t('shipping_costs'),
|
333 |
+
'unitprice' => $shippingEx,
|
334 |
+
'unitpriceinc' => $shippingInc,
|
335 |
+
'quantity' => 1,
|
336 |
+
) + $this->getVatRangeTags($shippingVat, $shippingEx, 0.02);
|
337 |
+
$result['meta-calculated-fields'] = 'vatamount';
|
338 |
+
} else {
|
339 |
+
$carrier = new Carrier($this->order->id_carrier);
|
340 |
+
$description = $carrier->id_reference == 1 ? $this->t('pickup') : $this->t('free_shipping');
|
341 |
+
$result = array(
|
342 |
+
'product' => $description,
|
343 |
+
'unitprice' => 0,
|
344 |
+
'unitpriceinc' => 0,
|
345 |
+
'quantity' => 1,
|
346 |
+
'vatamount' => 0,
|
347 |
+
'vatrate' => null,
|
348 |
+
'meta-vatrate-source' => Creator::VatRateSource_Completor,
|
349 |
+
);
|
350 |
+
}
|
351 |
+
|
352 |
+
return $result;
|
353 |
+
}
|
354 |
+
|
355 |
+
/**
|
356 |
+
* {@inheritdoc}
|
357 |
+
*
|
358 |
+
* This override returns can return an invoice line for orders. Credit slips
|
359 |
+
* cannot have a wrapping line.
|
360 |
+
*/
|
361 |
+
protected function getGiftWrappingLine()
|
362 |
+
{
|
363 |
+
$result = array();
|
364 |
+
if ($this->invoiceSource->getType() === Source::Order && $this->order->gift && !Number::isZero($this->order->total_wrapping_tax_incl)) {
|
365 |
+
$wrappingEx = $this->order->total_wrapping_tax_excl;
|
366 |
+
$wrappingInc = $this->order->total_wrapping_tax_incl;
|
367 |
+
$wrappingVat = $wrappingInc - $wrappingEx;
|
368 |
+
$result = array(
|
369 |
+
'product' => $this->t('gift_wrapping'),
|
370 |
+
'unitprice' => $wrappingEx,
|
371 |
+
'unitpriceinc' => $wrappingInc,
|
372 |
+
'quantity' => 1,
|
373 |
+
) + $this->getVatRangeTags($wrappingVat, $wrappingEx, 0.02);
|
374 |
+
$result['meta-calculated-fields'] = 'vatamount';
|
375 |
+
}
|
376 |
+
return $result;
|
377 |
+
}
|
378 |
+
|
379 |
+
/**
|
380 |
+
* In a Prestashop order the discount lines are specified in Order cart rules
|
381 |
+
*
|
382 |
+
* @return array[]
|
383 |
+
*/
|
384 |
+
protected function getDiscountLinesOrder()
|
385 |
+
{
|
386 |
+
$result = array();
|
387 |
+
|
388 |
+
foreach ($this->order->getCartRules() as $line) {
|
389 |
+
$result[] = $this->getDiscountLineOrder($line);
|
390 |
+
}
|
391 |
+
|
392 |
+
return $result;
|
393 |
+
}
|
394 |
+
|
395 |
+
/**
|
396 |
+
* In a Prestashop order the discount lines are specified in Order cart rules
|
397 |
+
* that have, a.o, the following fields:
|
398 |
+
* - value: total amount inc VAT
|
399 |
+
* - value_tax_excl: total amount ex VAT
|
400 |
+
*
|
401 |
+
* @param array $line
|
402 |
+
*
|
403 |
+
* @return array
|
404 |
+
*/
|
405 |
+
protected function getDiscountLineOrder(array $line)
|
406 |
+
{
|
407 |
+
$sign = $this->getSign();
|
408 |
+
$discountInc = -$sign * $line['value'];
|
409 |
+
$discountEx = -$sign * $line['value_tax_excl'];
|
410 |
+
$discountVat = $discountInc - $discountEx;
|
411 |
+
$result = array(
|
412 |
+
'itemnumber' => $line['id_cart_rule'],
|
413 |
+
'product' => $this->t('discount_code') . ' ' . $line['name'],
|
414 |
+
'unitprice' => $discountEx,
|
415 |
+
'unitpriceinc' => $discountInc,
|
416 |
+
'quantity' => 1,
|
417 |
+
// If no match is found, this line may be split.
|
418 |
+
'meta-strategy-split' => true,
|
419 |
+
// Assuming that the fixed discount amount was entered:
|
420 |
+
// - including VAT, the precision would be 0.01, 0.01.
|
421 |
+
// - excluding VAT, the precision would be 0.01, 0
|
422 |
+
// However, for a % discount, it will be: 0.02, 0.01, so use this.
|
423 |
+
) + $this->getVatRangeTags($discountVat, $discountEx, 0.02);
|
424 |
+
$result['meta-calculated-fields'] = 'vatamount';
|
425 |
+
|
426 |
+
return $result;
|
427 |
+
}
|
428 |
+
|
429 |
+
/**
|
430 |
+
* In a Prestashop credit slip, the discounts are not visible anymore, but
|
431 |
+
* can be computed by looking at the difference between the value of
|
432 |
+
* total_products_tax_incl and the sum of the OrderSlipDetail amounts.
|
433 |
+
*
|
434 |
+
* @return array[]
|
435 |
+
*/
|
436 |
+
protected function getDiscountLinesCreditNote()
|
437 |
+
{
|
438 |
+
$result = array();
|
439 |
+
|
440 |
+
// Get total amount credited.
|
441 |
+
/** @noinspection PhpUndefinedFieldInspection */
|
442 |
+
$creditSlipAmountInc = $this->creditSlip->total_products_tax_incl;
|
443 |
+
|
444 |
+
// Get sum of product lines.
|
445 |
+
$lines = $this->creditSlip->getOrdersSlipProducts($this->invoiceSource->getId(), $this->order);
|
446 |
+
$detailsAmountInc = array_reduce($lines, function ($sum, $item) {
|
447 |
+
$sum += $item['total_price_tax_incl'];
|
448 |
+
return $sum;
|
449 |
+
}, 0.0);
|
450 |
+
|
451 |
+
// We assume that if the total is smaller than the sum, a discount given on
|
452 |
+
// the original order has now been subtracted from the amount credited.
|
453 |
+
if (!Number::floatsAreEqual($creditSlipAmountInc, $detailsAmountInc, 0.05)
|
454 |
+
&& $creditSlipAmountInc < $detailsAmountInc
|
455 |
+
) {
|
456 |
+
// PS Error: total_products_tax_excl is not adjusted (whereas
|
457 |
+
// total_products_tax_incl is) when a discount is subtracted from the
|
458 |
+
// amount to be credited.
|
459 |
+
// So we cannot calculate the discount ex VAT ourselves.
|
460 |
+
// What we can try is the following: Get the order cart rules to see if
|
461 |
+
// 1 or all of those match the discount amount here.
|
462 |
+
$discountAmountInc = $detailsAmountInc - $creditSlipAmountInc;
|
463 |
+
$totalOrderDiscountInc = 0.0;
|
464 |
+
// Note: The sign of the entries in $orderDiscounts will be correct.
|
465 |
+
$orderDiscounts = $this->getDiscountLinesOrder();
|
466 |
+
|
467 |
+
foreach ($orderDiscounts as $key => $orderDiscount) {
|
468 |
+
if (Number::floatsAreEqual($orderDiscount['unitpriceinc'], $discountAmountInc)) {
|
469 |
+
// Return this single line.
|
470 |
+
$from = $to = $key;
|
471 |
+
break;
|
472 |
+
}
|
473 |
+
$totalOrderDiscountInc += $orderDiscount['unitpriceinc'];
|
474 |
+
if (Number::floatsAreEqual($totalOrderDiscountInc, $discountAmountInc)) {
|
475 |
+
// Return all lines up to here.
|
476 |
+
$from = 0;
|
477 |
+
$to = $key;
|
478 |
+
break;
|
479 |
+
}
|
480 |
+
}
|
481 |
+
|
482 |
+
if (isset($from) && isset($to)) {
|
483 |
+
$result = array_slice($orderDiscounts, $from, $to - $from + 1);
|
484 |
+
// Correct meta-invoice-amount.
|
485 |
+
$totalOrderDiscountEx = array_reduce($result, function ($sum, $item) {
|
486 |
+
$sum += $item['quantity'] * $item['unitprice'];
|
487 |
+
return $sum;
|
488 |
+
}, 0.0);
|
489 |
+
$this->invoice['customer']['invoice']['meta-invoice-amount'] += $totalOrderDiscountEx;
|
490 |
+
}
|
491 |
+
//else {
|
492 |
+
// We could not match a discount with the difference between the total
|
493 |
+
// amount credited and the sum of the products returned. A manual line
|
494 |
+
// will correct the invoice.
|
495 |
+
//}
|
496 |
+
}
|
497 |
+
return $result;
|
498 |
+
}
|
499 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Invoice/Source.php
ADDED
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\PrestaShop\Invoice;
|
3 |
+
|
4 |
+
use Configuration;
|
5 |
+
use Context;
|
6 |
+
use Db;
|
7 |
+
use Order;
|
8 |
+
use OrderSlip;
|
9 |
+
use Siel\Acumulus\Invoice\Source as BaseSource;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Wraps a PrestaShop order in an invoice source object.
|
13 |
+
*/
|
14 |
+
class Source extends BaseSource
|
15 |
+
{
|
16 |
+
// More specifically typed properties.
|
17 |
+
/** @var Order|OrderSLip */
|
18 |
+
protected $source;
|
19 |
+
|
20 |
+
/**
|
21 |
+
* {@inheritdoc}
|
22 |
+
*/
|
23 |
+
protected function setSource()
|
24 |
+
{
|
25 |
+
if ($this->getType() === Source::Order) {
|
26 |
+
$this->source = new Order($this->id);
|
27 |
+
} else {
|
28 |
+
$this->source = new OrderSlip($this->id);
|
29 |
+
$this->addProperties();
|
30 |
+
}
|
31 |
+
}
|
32 |
+
|
33 |
+
/**
|
34 |
+
* {@inheritdoc}
|
35 |
+
*
|
36 |
+
* This override returns the order reference or order slip id.
|
37 |
+
*/
|
38 |
+
public function getReference()
|
39 |
+
{
|
40 |
+
return $this->getType() === Source::Order
|
41 |
+
? $this->source->reference
|
42 |
+
: Configuration::get('PS_CREDIT_SLIP_PREFIX', Context::getContext()->language->id) . sprintf('%06d', $this->source->id);
|
43 |
+
}
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Sets the id based on the loaded Order.
|
47 |
+
*/
|
48 |
+
protected function setId()
|
49 |
+
{
|
50 |
+
$this->id = $this->source->id;
|
51 |
+
if ($this->getType() === Source::CreditNote) {
|
52 |
+
$this->addProperties();
|
53 |
+
}
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Returns the status of this order.
|
58 |
+
*
|
59 |
+
* @return int
|
60 |
+
*/
|
61 |
+
protected function getStatusOrder()
|
62 |
+
{
|
63 |
+
return $this->source->current_state;
|
64 |
+
}
|
65 |
+
|
66 |
+
/**
|
67 |
+
* Returns the status of this credit note.
|
68 |
+
*
|
69 |
+
* @return null
|
70 |
+
*/
|
71 |
+
protected function getStatusCreditNote()
|
72 |
+
{
|
73 |
+
return null;
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* OrderSlip does store but not load the values total_products_tax_excl,
|
78 |
+
* total_shipping_tax_excl, total_products_tax_incl, and
|
79 |
+
* total_shipping_tax_incl. As we need them, we load them ourselves.
|
80 |
+
*
|
81 |
+
* @throws \PrestaShopDatabaseException
|
82 |
+
*/
|
83 |
+
protected function addProperties()
|
84 |
+
{
|
85 |
+
$row = Db::getInstance()->executeS(sprintf("SELECT * FROM `%s` WHERE `%s` = %u",
|
86 |
+
_DB_PREFIX_ . OrderSlip::$definition['table'], OrderSlip::$definition['primary'], $this->id));
|
87 |
+
// Get 1st (and only) result.
|
88 |
+
$row = reset($row);
|
89 |
+
foreach ($row as $key => $value) {
|
90 |
+
if (!isset($this->source->$key)) {
|
91 |
+
$this->source->$key = $value;
|
92 |
+
}
|
93 |
+
}
|
94 |
+
}
|
95 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Shop/AcumulusEntryModel.php
ADDED
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\PrestaShop\Shop;
|
3 |
+
|
4 |
+
use Db;
|
5 |
+
use Siel\Acumulus\PrestaShop\Invoice\Source;
|
6 |
+
use Siel\Acumulus\Shop\AcumulusEntryModel as BaseAcumulusEntryModel;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Implements the PrestaShop specific acumulus entry model class.
|
10 |
+
*
|
11 |
+
* In WooCommerce this data is stored as metadata. As such, the "records"
|
12 |
+
* returned here are an array of all metadata values, thus not filtered by
|
13 |
+
* Acumulus keys.
|
14 |
+
*/
|
15 |
+
class AcumulusEntryModel extends BaseAcumulusEntryModel
|
16 |
+
{
|
17 |
+
/** @var string */
|
18 |
+
protected $tableName;
|
19 |
+
|
20 |
+
/**
|
21 |
+
* AcumulusEntryModel constructor.
|
22 |
+
*/
|
23 |
+
public function __construct()
|
24 |
+
{
|
25 |
+
$this->tableName = _DB_PREFIX_ . 'acumulus_entry';
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* {@inheritdoc}
|
30 |
+
*/
|
31 |
+
public function getByEntryId($entryId)
|
32 |
+
{
|
33 |
+
$result = Db::getInstance()->executeS(sprintf("SELECT * FROM `%s` WHERE id_entry = %u", $this->tableName, $entryId));
|
34 |
+
return count($result) === 1 ? reset($result) : null;
|
35 |
+
}
|
36 |
+
|
37 |
+
/**
|
38 |
+
* {@inheritdoc}
|
39 |
+
*/
|
40 |
+
public function getByInvoiceSourceId($invoiceSourceType, $invoiceSourceId)
|
41 |
+
{
|
42 |
+
$result = Db::getInstance()->executeS(sprintf("SELECT * FROM `%s` WHERE source_type = '%s' AND source_id = %u", $this->tableName, $invoiceSourceType, $invoiceSourceId));
|
43 |
+
return count($result) === 1 ? reset($result) : null;
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* {@inheritdoc}
|
48 |
+
*/
|
49 |
+
protected function insert($invoiceSource, $entryId, $token, $created)
|
50 |
+
{
|
51 |
+
if ($invoiceSource->getType() === Source::Order) {
|
52 |
+
$shopId = $invoiceSource->getSource()->id_shop;
|
53 |
+
$shopGroupId = $invoiceSource->getSource()->id_shop_group;
|
54 |
+
} else {
|
55 |
+
$shopId = 0;
|
56 |
+
$shopGroupId = 0;
|
57 |
+
}
|
58 |
+
return Db::getInstance()->execute(sprintf("INSERT INTO `%s` (id_shop, id_shop_group, id_entry, token, source_type, source_id, updated) VALUES (%u, %u, %u, '%s', '%s', %u, '%s')",
|
59 |
+
$this->tableName, $shopId, $shopGroupId, $entryId, $token, $invoiceSource->getType(), $invoiceSource->getId(), $created));
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* {@inheritdoc}
|
64 |
+
*/
|
65 |
+
protected function update($record, $entryId, $token, $updated)
|
66 |
+
{
|
67 |
+
return Db::getInstance()->execute(sprintf("UPDATE `%s` SET id_entry = %u, token = '%s', updated = '%s' WHERE id = %u",
|
68 |
+
$this->tableName, $entryId, $token, $updated, $record['id']));
|
69 |
+
}
|
70 |
+
|
71 |
+
/**
|
72 |
+
* {@inheritdoc}
|
73 |
+
*/
|
74 |
+
protected function sqlNow()
|
75 |
+
{
|
76 |
+
return date('Y-m-d H:i:s');
|
77 |
+
}
|
78 |
+
|
79 |
+
/**
|
80 |
+
* {@inheritdoc}
|
81 |
+
*/
|
82 |
+
public function install()
|
83 |
+
{
|
84 |
+
return Db::getInstance()->execute("CREATE TABLE IF NOT EXISTS `{$this->tableName}` (
|
85 |
+
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
|
86 |
+
`id_shop` int(11) UNSIGNED NOT NULL DEFAULT '1',
|
87 |
+
`id_shop_group` int(11) UNSIGNED NOT NULL DEFAULT '1',
|
88 |
+
`id_entry` int(11) UNSIGNED NOT NULL,
|
89 |
+
`token` char(32) NOT NULL,
|
90 |
+
`source_type` varchar(32) NOT NULL,
|
91 |
+
`source_id` int(11) UNSIGNED NOT NULL,
|
92 |
+
`created` timestamp DEFAULT CURRENT_TIMESTAMP,
|
93 |
+
`updated` timestamp NOT NULL,
|
94 |
+
PRIMARY KEY (`id`),
|
95 |
+
UNIQUE INDEX `acumulus_idx_entry_id` (`id_entry`),
|
96 |
+
UNIQUE INDEX `acumulus_idx_source` (`source_id`, `source_type`)
|
97 |
+
)");
|
98 |
+
}
|
99 |
+
|
100 |
+
/**
|
101 |
+
* {@inheritdoc}
|
102 |
+
*/
|
103 |
+
public function uninstall()
|
104 |
+
{
|
105 |
+
return Db::getInstance()->execute("DROP TABLE `{$this->tableName}`");
|
106 |
+
}
|
107 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Shop/BatchForm.php
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\PrestaShop\Shop;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Shop\BatchForm as BaseBatchForm;
|
5 |
+
use Tools;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Provides the Batch send form handling for the VirtueMart Acumulus module.
|
9 |
+
*/
|
10 |
+
class BatchForm extends BaseBatchForm
|
11 |
+
{
|
12 |
+
public function isSubmitted()
|
13 |
+
{
|
14 |
+
return Tools::isSubmit('submitAdd');
|
15 |
+
}
|
16 |
+
|
17 |
+
/**
|
18 |
+
* {@inheritdoc}
|
19 |
+
*/
|
20 |
+
protected function setFormValues()
|
21 |
+
{
|
22 |
+
parent::setFormValues();
|
23 |
+
|
24 |
+
// Prepend (checked) checkboxes with their collection name.
|
25 |
+
foreach ($this->getCheckboxKeys() as $checkboxName => $collectionName) {
|
26 |
+
if (isset($this->formValues[$checkboxName])) {
|
27 |
+
$this->formValues["{$collectionName}_{$checkboxName}"] = $this->formValues[$checkboxName];
|
28 |
+
}
|
29 |
+
}
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* {@inheritdoc}
|
34 |
+
*/
|
35 |
+
public function getFieldDefinitions()
|
36 |
+
{
|
37 |
+
$result = parent::getFieldDefinitions();
|
38 |
+
$result['batchFieldsHeader']['icon'] = 'icon-envelope-alt';
|
39 |
+
if (isset($result['batchLogHeader'])) {
|
40 |
+
$result['batchLogHeader']['icon'] = 'icon-list';
|
41 |
+
}
|
42 |
+
$result['batchInfoHeader']['icon'] = 'icon-info-circle';
|
43 |
+
return $result;
|
44 |
+
}
|
45 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Shop/ConfigForm.php
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\PrestaShop\Shop;
|
3 |
+
|
4 |
+
use Context;
|
5 |
+
use OrderState;
|
6 |
+
use Siel\Acumulus\Helpers\TranslatorInterface;
|
7 |
+
use Siel\Acumulus\Shop\Config;
|
8 |
+
use Siel\Acumulus\Shop\ConfigForm as BaseConfigForm;
|
9 |
+
use Siel\Acumulus\Shop\ConfigInterface;
|
10 |
+
use Tools;
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Class ConfigForm processes and builds the settings form page for the
|
14 |
+
* PrestaShop Acumulus module.
|
15 |
+
*/
|
16 |
+
class ConfigForm extends BaseConfigForm
|
17 |
+
{
|
18 |
+
/** @var string */
|
19 |
+
protected $moduleName;
|
20 |
+
|
21 |
+
/**
|
22 |
+
* ConfigForm constructor.
|
23 |
+
*
|
24 |
+
* @param \Siel\Acumulus\Helpers\TranslatorInterface $translator
|
25 |
+
* @param \Siel\Acumulus\Shop\Config $config
|
26 |
+
*/
|
27 |
+
public function __construct(TranslatorInterface $translator, Config $config)
|
28 |
+
{
|
29 |
+
parent::__construct($translator, $config);
|
30 |
+
$this->moduleName = 'acumulus';
|
31 |
+
}
|
32 |
+
|
33 |
+
/**
|
34 |
+
* {@inheritdoc}
|
35 |
+
*
|
36 |
+
* This override uses the PS way of checking if a form is submitted.
|
37 |
+
*/
|
38 |
+
public function isSubmitted()
|
39 |
+
{
|
40 |
+
return Tools::isSubmit('submit' . $this->moduleName);
|
41 |
+
}
|
42 |
+
|
43 |
+
/**
|
44 |
+
* {@inheritdoc}
|
45 |
+
*
|
46 |
+
* This override ensures that array values are passed with the correct key
|
47 |
+
* to the PS form renderer.
|
48 |
+
*/
|
49 |
+
public function getFormValues()
|
50 |
+
{
|
51 |
+
$result = parent::getFormValues();
|
52 |
+
$result['triggerOrderStatus[]'] = $result['triggerOrderStatus'];
|
53 |
+
return $result;
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* {@inheritdoc}
|
58 |
+
*/
|
59 |
+
protected function setFormValues()
|
60 |
+
{
|
61 |
+
parent::setFormValues();
|
62 |
+
|
63 |
+
// Prepend (checked) checkboxes with their collection name.
|
64 |
+
foreach ($this->getCheckboxKeys() as $checkboxName => $collectionName) {
|
65 |
+
if (isset($this->formValues[$checkboxName])) {
|
66 |
+
$this->formValues["{$collectionName}_{$checkboxName}"] = $this->formValues[$checkboxName];
|
67 |
+
}
|
68 |
+
}
|
69 |
+
}
|
70 |
+
|
71 |
+
/**
|
72 |
+
* {@inheritdoc}
|
73 |
+
*/
|
74 |
+
public function getFieldDefinitions()
|
75 |
+
{
|
76 |
+
$result = parent::getFieldDefinitions();
|
77 |
+
$result['accountSettingsHeader']['icon'] = 'icon-user';
|
78 |
+
if (isset($result['invoiceSettingsHeader'])) {
|
79 |
+
$result['invoiceSettingsHeader']['icon'] = 'icon-AdminParentPreferences';
|
80 |
+
$result['emailAsPdfSettingsHeader']['icon'] = 'icon-file-pdf-o';
|
81 |
+
}
|
82 |
+
$result['versionInformationHeader']['icon'] = 'icon-info-circle';
|
83 |
+
return $result;
|
84 |
+
}
|
85 |
+
|
86 |
+
/**
|
87 |
+
* {@inheritdoc}
|
88 |
+
*/
|
89 |
+
protected function getShopOrderStatuses()
|
90 |
+
{
|
91 |
+
$states = OrderState::getOrderStates((int) Context::getContext()->language->id);
|
92 |
+
$result = array();
|
93 |
+
foreach ($states as $state) {
|
94 |
+
$result[$state['id_order_state']] = $state['name'];
|
95 |
+
}
|
96 |
+
return $result;
|
97 |
+
}
|
98 |
+
|
99 |
+
/**
|
100 |
+
* {@inheritdoc}
|
101 |
+
*
|
102 |
+
* This override removes the 'Use invoice sent' option as PS does not have
|
103 |
+
* an event on creating/sending the invoice.
|
104 |
+
*
|
105 |
+
* @todo: PS has the 'actionSetInvoice' event, can we use that?
|
106 |
+
* This event fires when the order state changes to a state that allows an
|
107 |
+
* invoice and on manually creating one via the adminOrdersController page.
|
108 |
+
*/
|
109 |
+
protected function getTriggerInvoiceSendEventOptions()
|
110 |
+
{
|
111 |
+
$result = parent::getTriggerInvoiceSendEventOptions();
|
112 |
+
unset($result[ConfigInterface::TriggerInvoiceSendEvent_InvoiceCreate]);
|
113 |
+
return $result;
|
114 |
+
}
|
115 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Shop/ConfigStore.php
ADDED
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\PrestaShop\Shop;
|
3 |
+
|
4 |
+
use Acumulus;
|
5 |
+
use Configuration;
|
6 |
+
use Siel\Acumulus\Shop\ConfigStore as BaseConfigStore;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Implements the connection to the PrestaShop config component.
|
10 |
+
*/
|
11 |
+
class ConfigStore extends BaSeConfigStore
|
12 |
+
{
|
13 |
+
const CONFIG_KEY = 'ACUMULUS_';
|
14 |
+
|
15 |
+
/**
|
16 |
+
* {@inheritdoc}
|
17 |
+
*/
|
18 |
+
public function getShopEnvironment()
|
19 |
+
{
|
20 |
+
$environment = array(
|
21 |
+
'moduleVersion' => Acumulus::$module_version,
|
22 |
+
'shopName' => $this->shopName,
|
23 |
+
'shopVersion' => _PS_VERSION_,
|
24 |
+
);
|
25 |
+
return $environment;
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* {@inheritdoc}
|
30 |
+
*/
|
31 |
+
public function load(array $keys)
|
32 |
+
{
|
33 |
+
$result = array();
|
34 |
+
// Load the values from the web shop specific configuration.
|
35 |
+
foreach ($keys as $key) {
|
36 |
+
$dbKey = substr(static::CONFIG_KEY . $key, 0, 32);
|
37 |
+
$value = Configuration::get($dbKey);
|
38 |
+
// Do not overwrite defaults if no value is stored.
|
39 |
+
if ($value !== false) {
|
40 |
+
if (is_string($value) && strpos($value, '{') !== false) {
|
41 |
+
$unserialized = @unserialize($value);
|
42 |
+
if ($unserialized !== false) {
|
43 |
+
$value = $unserialized;
|
44 |
+
}
|
45 |
+
}
|
46 |
+
$result[$key] = $value;
|
47 |
+
}
|
48 |
+
}
|
49 |
+
return $result;
|
50 |
+
}
|
51 |
+
|
52 |
+
/**
|
53 |
+
* {@inheritdoc}
|
54 |
+
*/
|
55 |
+
public function save(array $values)
|
56 |
+
{
|
57 |
+
$values = $this->saveCommon($values);
|
58 |
+
|
59 |
+
$result = true;
|
60 |
+
foreach ($values as $key => $value) {
|
61 |
+
if ($value !== null) {
|
62 |
+
$dbKey = substr(static::CONFIG_KEY . $key, 0, 32);
|
63 |
+
if (is_bool($value)) {
|
64 |
+
$value = $value ? 1 : 0;
|
65 |
+
} elseif (is_array($value)) {
|
66 |
+
$value = serialize($value);
|
67 |
+
}
|
68 |
+
$result = Configuration::updateValue($dbKey, $value) && $result;
|
69 |
+
}
|
70 |
+
}
|
71 |
+
return $result;
|
72 |
+
}
|
73 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/PrestaShop/Shop/InvoiceManager.php
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\PrestaShop\Shop;
|
3 |
+
|
4 |
+
use DateTime;
|
5 |
+
use Db;
|
6 |
+
use Hook;
|
7 |
+
use Order;
|
8 |
+
use OrderSlip;
|
9 |
+
use Siel\Acumulus\Invoice\Source as BaseSource;
|
10 |
+
use Siel\Acumulus\PrestaShop\Invoice\Source; // @todo: still needed?
|
11 |
+
use Siel\Acumulus\Shop\Config;
|
12 |
+
use Siel\Acumulus\Shop\InvoiceManager as BaseInvoiceManager;
|
13 |
+
|
14 |
+
class InvoiceManager extends BaseInvoiceManager
|
15 |
+
{
|
16 |
+
/** @var string */
|
17 |
+
protected $orderTableName;
|
18 |
+
|
19 |
+
/** @var string */
|
20 |
+
protected $orderSlipTableName;
|
21 |
+
|
22 |
+
/**
|
23 |
+
* {@inheritdoc}
|
24 |
+
*/
|
25 |
+
public function __construct(Config $config)
|
26 |
+
{
|
27 |
+
parent::__construct($config);
|
28 |
+
$this->orderTableName = _DB_PREFIX_ . Order::$definition['table'];
|
29 |
+
$this->orderSlipTableName = _DB_PREFIX_ . OrderSlip::$definition['table'];
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* {@inheritdoc}
|
34 |
+
*/
|
35 |
+
public function getInvoiceSourcesByIdRange($invoiceSourceType, $InvoiceSourceIdFrom, $InvoiceSourceIdTo)
|
36 |
+
{
|
37 |
+
switch ($invoiceSourceType) {
|
38 |
+
case Source::Order:
|
39 |
+
$key = Order::$definition['primary'];
|
40 |
+
$ids = Db::getInstance()->executeS(sprintf("SELECT `%s` FROM `%s` WHERE `%s` BETWEEN %u AND %u", $key, $this->orderTableName, $key, $InvoiceSourceIdFrom, $InvoiceSourceIdTo));
|
41 |
+
return $this->getSourcesByIdsOrSources($invoiceSourceType, $this->getCol($ids, $key));
|
42 |
+
case Source::CreditNote:
|
43 |
+
$key = OrderSlip::$definition['primary'];
|
44 |
+
$ids = Db::getInstance()->executeS(sprintf("SELECT `%s` FROM `%s` WHERE `%s` BETWEEN %u AND %u", $key, $this->orderSlipTableName, $key, $InvoiceSourceIdFrom, $InvoiceSourceIdTo));
|
45 |
+
return $this->getSourcesByIdsOrSources($invoiceSourceType, $this->getCol($ids, $key));
|
46 |
+
}
|
47 |
+
return array();
|
48 |
+
}
|
49 |
+
|
50 |
+
/**
|
51 |
+
* {@inheritdoc}
|
52 |
+
*/
|
53 |
+
public function getInvoiceSourcesByReferenceRange($invoiceSourceType, $InvoiceSourceReferenceFrom, $InvoiceSourceReferenceTo)
|
54 |
+
{
|
55 |
+
switch ($invoiceSourceType) {
|
56 |
+
case Source::Order:
|
57 |
+
$key = Order::$definition['primary'];
|
58 |
+
$reference = 'reference';
|
59 |
+
$ids = Db::getInstance()->executeS(sprintf("SELECT `%s` FROM `%s` WHERE `%s` BETWEEN '%s' AND '%s'", $key, $this->orderTableName, $reference, pSQL($InvoiceSourceReferenceFrom), pSQL($InvoiceSourceReferenceTo)));
|
60 |
+
return $this->getSourcesByIdsOrSources($invoiceSourceType, $this->getCol($ids, $key));
|
61 |
+
case Source::CreditNote:
|
62 |
+
return $this->getInvoiceSourcesByIdRange($invoiceSourceType, $InvoiceSourceReferenceFrom, $InvoiceSourceReferenceTo);
|
63 |
+
}
|
64 |
+
return array();
|
65 |
+
}
|
66 |
+
|
67 |
+
/**
|
68 |
+
* {@inheritdoc}
|
69 |
+
*/
|
70 |
+
public function getInvoiceSourcesByDateRange($invoiceSourceType, DateTime $dateFrom, DateTime $dateTo)
|
71 |
+
{
|
72 |
+
$dateFrom = $dateFrom->format('c');
|
73 |
+
$dateTo = $dateTo->format('c');
|
74 |
+
switch ($invoiceSourceType) {
|
75 |
+
case Source::Order:
|
76 |
+
$ids = Order::getOrdersIdByDate($dateFrom, $dateTo);
|
77 |
+
return $this->getSourcesByIdsOrSources($invoiceSourceType, $ids);
|
78 |
+
case Source::CreditNote:
|
79 |
+
$ids = OrderSlip::getSlipsIdByDate($dateFrom, $dateTo);
|
80 |
+
return $this->getSourcesByIdsOrSources($invoiceSourceType, $ids);
|
81 |
+
}
|
82 |
+
return array();
|
83 |
+
}
|
84 |
+
|
85 |
+
/**
|
86 |
+
* {@inheritdoc}
|
87 |
+
*
|
88 |
+
* This PrestaShop override executes the 'actionAcumulusInvoiceCreated' hook.
|
89 |
+
*/
|
90 |
+
protected function triggerInvoiceCreated(array &$invoice, BaseSource $invoiceSource)
|
91 |
+
{
|
92 |
+
Hook::exec('actionAcumulusInvoiceCreated', array('invoice' => &$invoice, 'source' => $invoiceSource), null, true);
|
93 |
+
}
|
94 |
+
|
95 |
+
/**
|
96 |
+
* {@inheritdoc}
|
97 |
+
*
|
98 |
+
* This PrestaShop override executes the 'actionAcumulusInvoiceCompleted' hook.
|
99 |
+
*/
|
100 |
+
protected function triggerInvoiceCompleted(array &$invoice, BaseSource $invoiceSource)
|
101 |
+
{
|
102 |
+
Hook::exec('actionAcumulusInvoiceCompleted', array('invoice' => &$invoice, 'source' => $invoiceSource), null, true);
|
103 |
+
}
|
104 |
+
|
105 |
+
/**
|
106 |
+
* {@inheritdoc}
|
107 |
+
*
|
108 |
+
* This PrestaShop override executes the 'actionAcumulusInvoiceSent' hook.
|
109 |
+
*/
|
110 |
+
protected function triggerInvoiceSent(array $invoice, BaseSource $invoiceSource, array $result)
|
111 |
+
{
|
112 |
+
// @todo: not by ref + also pass result.
|
113 |
+
Hook::exec('actionAcumulusInvoiceSent', array('invoice' => &$invoice, 'source' => $invoiceSource), null, true);
|
114 |
+
}
|
115 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/AcumulusEntryModel.php
ADDED
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Shop;
|
3 |
+
|
4 |
+
/**
|
5 |
+
* Represents acumulus entry records that ties orders or credit notes from the
|
6 |
+
* web shop to entries in Acumulus.
|
7 |
+
*
|
8 |
+
* Acumulus identifies entries by their entry id (boekstuknummer in het
|
9 |
+
* Nederlands). To access an entry via the API, one must also supply a token
|
10 |
+
* that is generated based on the contents of the entry. This entry id and token
|
11 |
+
* are stored together with an id for the order or credit note from the web
|
12 |
+
* shop.
|
13 |
+
*
|
14 |
+
* Usages (not (all of them are) yet implemented):
|
15 |
+
* - Prevent that an invoice for a given order or credit note is sent twice.
|
16 |
+
* - Show additional information on order list screens
|
17 |
+
* - Update payment status
|
18 |
+
* - Resend Acumulus invoice PDF.
|
19 |
+
*/
|
20 |
+
abstract class AcumulusEntryModel
|
21 |
+
{
|
22 |
+
/**
|
23 |
+
* Returns the Acumulus entry record for the given entry id.
|
24 |
+
*
|
25 |
+
* @param string $entryId
|
26 |
+
* the entry id to look up.
|
27 |
+
*
|
28 |
+
* @return array|object|null
|
29 |
+
* Acumulus entry record for the given entry id or null if the entry id is
|
30 |
+
* unknown.
|
31 |
+
*/
|
32 |
+
abstract public function getByEntryId($entryId);
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Returns the Acumulus entry record for the given invoice source.
|
36 |
+
*
|
37 |
+
* @param \Siel\Acumulus\Invoice\Source $invoiceSource
|
38 |
+
* The source object (order, credit note) for which the invoice was created.
|
39 |
+
*
|
40 |
+
* @return array|object|null
|
41 |
+
* Acumulus entry record for the given invoice source or null if no invoice
|
42 |
+
* has yet been created in Acumulus for this invoice source.
|
43 |
+
*/
|
44 |
+
public function getByInvoiceSource($invoiceSource)
|
45 |
+
{
|
46 |
+
return $this->getByInvoiceSourceId($invoiceSource->getType(), $invoiceSource->getId());
|
47 |
+
}
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Returns the Acumulus entry record for the given invoice source.
|
51 |
+
*
|
52 |
+
* @param string $invoiceSourceType
|
53 |
+
* The type of the invoice source
|
54 |
+
* @param string $invoiceSourceId
|
55 |
+
* The id of the invoice source for which the invoice was created.
|
56 |
+
*
|
57 |
+
* @return array|object|null
|
58 |
+
* Acumulus entry record for the given invoice source or null if no invoice
|
59 |
+
* has yet been created in Acumulus for this invoice source.
|
60 |
+
*/
|
61 |
+
abstract public function getByInvoiceSourceId($invoiceSourceType, $invoiceSourceId);
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Saves the Acumulus entry for the given order in the web shop's database.
|
65 |
+
*
|
66 |
+
* This default implementation calls getByInvoiceSource() to determine whether
|
67 |
+
* to subsequently call insert() or update().
|
68 |
+
*
|
69 |
+
* So normally, a child class should implement insert() and update() and not
|
70 |
+
* override this method.
|
71 |
+
*
|
72 |
+
* @param \Siel\Acumulus\Invoice\Source $invoiceSource
|
73 |
+
* The source object (order, credit note) for which the invoice was created.
|
74 |
+
* @param $entryId
|
75 |
+
* The Acumulus entry Id assigned to the invoice for this order.
|
76 |
+
* @param $token
|
77 |
+
* The Acumulus token to be used to access the invoice for this order via
|
78 |
+
* the Acumulus API.
|
79 |
+
*
|
80 |
+
* @return bool
|
81 |
+
* Success.
|
82 |
+
*/
|
83 |
+
public function save($invoiceSource, $entryId, $token)
|
84 |
+
{
|
85 |
+
$now = $this->sqlNow();
|
86 |
+
$record = $this->getByInvoiceSource($invoiceSource);
|
87 |
+
if ($record == null) {
|
88 |
+
$this->insert($invoiceSource, $entryId, $token, $now);
|
89 |
+
} else {
|
90 |
+
$this->update($record, $entryId, $token, $now);
|
91 |
+
}
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Returns the current time in a format that the actual database layer accepts
|
96 |
+
* as a timestamp.
|
97 |
+
*
|
98 |
+
* @return int|string
|
99 |
+
*/
|
100 |
+
abstract protected function sqlNow();
|
101 |
+
|
102 |
+
/**
|
103 |
+
* Inserts an Acumulus entry for the given order in the web shop's database.
|
104 |
+
*
|
105 |
+
* @param \Siel\Acumulus\Invoice\Source $invoiceSource
|
106 |
+
* The source object (order, credit note) for which the invoice was created.
|
107 |
+
* @param $entryId
|
108 |
+
* The Acumulus entry Id assigned to the invoice for this order.
|
109 |
+
* @param $token
|
110 |
+
* The Acumulus token to be used to access the invoice for this order via
|
111 |
+
* the Acumulus API.
|
112 |
+
* @param int|string $created
|
113 |
+
* The creation time (= current time), in the format as the actual database
|
114 |
+
* layer expects for a timestamp.
|
115 |
+
*
|
116 |
+
* @return bool
|
117 |
+
* Success.
|
118 |
+
*/
|
119 |
+
abstract protected function insert($invoiceSource, $entryId, $token, $created);
|
120 |
+
|
121 |
+
/**
|
122 |
+
* Updates the Acumulus entry for the given invoice source.
|
123 |
+
*
|
124 |
+
* @param array|object $record
|
125 |
+
* The existing record for the invoice source to be updated.
|
126 |
+
* @param string $entryId
|
127 |
+
* The new Acumulus entry id for the invoice source.
|
128 |
+
* @param string $token
|
129 |
+
* The new Acumulus token for the invoice source.
|
130 |
+
* @param int|string $updated
|
131 |
+
* The update time (= current time), in the format as the actual database
|
132 |
+
* layer expects for a timestamp.
|
133 |
+
*
|
134 |
+
* @return bool
|
135 |
+
* Success.
|
136 |
+
*/
|
137 |
+
abstract protected function update($record, $entryId, $token, $updated);
|
138 |
+
|
139 |
+
/**
|
140 |
+
* @return bool
|
141 |
+
*/
|
142 |
+
abstract public function install();
|
143 |
+
|
144 |
+
/**
|
145 |
+
* @return bool
|
146 |
+
*/
|
147 |
+
abstract public function uninstall();
|
148 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/BatchForm.php
ADDED
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Shop;
|
3 |
+
|
4 |
+
use DateTime;
|
5 |
+
use Siel\Acumulus\Helpers\Form;
|
6 |
+
use Siel\Acumulus\Helpers\TranslatorInterface;
|
7 |
+
use Siel\Acumulus\Invoice\Translations as InvoiceTranslations;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Provides batch form handling.
|
11 |
+
*
|
12 |
+
* Shop specific overrides should - of course - implement the abstract method:
|
13 |
+
* - none
|
14 |
+
* Should typically override:
|
15 |
+
* - none
|
16 |
+
* And may optionally (have to) override:
|
17 |
+
* - systemValidate()
|
18 |
+
* - getDateFormat
|
19 |
+
* - getShopDateFormat()
|
20 |
+
* - isSubmitted()
|
21 |
+
* - setSubmittedValues()
|
22 |
+
*/
|
23 |
+
class BatchForm extends Form
|
24 |
+
{
|
25 |
+
/** @var \Siel\Acumulus\Shop\InvoiceManager */
|
26 |
+
protected $invoiceManager;
|
27 |
+
|
28 |
+
/** @var array */
|
29 |
+
protected $log;
|
30 |
+
|
31 |
+
/**
|
32 |
+
* @param \Siel\Acumulus\Helpers\TranslatorInterface $translator
|
33 |
+
* @param \Siel\Acumulus\Shop\InvoiceManager $invoiceManager
|
34 |
+
*/
|
35 |
+
public function __construct(TranslatorInterface $translator, InvoiceManager $invoiceManager)
|
36 |
+
{
|
37 |
+
parent::__construct($translator);
|
38 |
+
|
39 |
+
$translations = new InvoiceTranslations();
|
40 |
+
$this->translator->add($translations);
|
41 |
+
|
42 |
+
$translations = new BatchFormTranslations();
|
43 |
+
$this->translator->add($translations);
|
44 |
+
|
45 |
+
$this->log = array();
|
46 |
+
$this->invoiceManager = $invoiceManager;
|
47 |
+
}
|
48 |
+
|
49 |
+
protected function getDefaultFormValues()
|
50 |
+
{
|
51 |
+
$result = parent::getDefaultFormValues();
|
52 |
+
if (!empty($this->log)) {
|
53 |
+
$result['log'] = implode("\n", $this->log);
|
54 |
+
}
|
55 |
+
return $result;
|
56 |
+
}
|
57 |
+
|
58 |
+
protected function validate()
|
59 |
+
{
|
60 |
+
$invoiceSourceTypes = $this->invoiceManager->getSupportedInvoiceSourceTypes();
|
61 |
+
if (empty($this->submittedValues['invoice_source_type'])) {
|
62 |
+
$this->errorMessages['invoice_source_type'] = $this->t('message_validate_batch_source_type_required');
|
63 |
+
} else if (!in_array($this->submittedValues['invoice_source_type'], $invoiceSourceTypes)) {
|
64 |
+
$this->errorMessages['invoice_source_type'] = $this->t('message_validate_batch_source_type_invalid');
|
65 |
+
}
|
66 |
+
|
67 |
+
if ($this->submittedValues['invoice_source_reference_from'] === '' && $this->submittedValues['date_from'] === '') {
|
68 |
+
// Either a range of order id's or a range of dates should be entered.
|
69 |
+
$this->errorMessages['invoice_source_reference_from'] = $this->t(count($invoiceSourceTypes) === 1 ? 'message_validate_batch_reference_or_date_1' : 'message_validate_batch_reference_or_date_2');
|
70 |
+
} else if ($this->submittedValues['invoice_source_reference_from'] !== '' && $this->submittedValues['date_from'] !== '') {
|
71 |
+
// Not both ranges should be entered.
|
72 |
+
$this->errorMessages['date_from'] = $this->t(count($invoiceSourceTypes) === 1 ? 'message_validate_batch_reference_and_date_1' : 'message_validate_batch_reference_and_date_2');
|
73 |
+
} else if ($this->submittedValues['invoice_source_reference_from'] !== '') {
|
74 |
+
// Date from is empty, we go for a range of order ids.
|
75 |
+
// (We ignore any date to value.)
|
76 |
+
// Single id or range of ids?
|
77 |
+
if ($this->submittedValues['invoice_source_reference_to'] !== '' && $this->submittedValues['invoice_source_reference_to'] < $this->submittedValues['invoice_source_reference_from']) {
|
78 |
+
// order id to is smaller than order id from.
|
79 |
+
$this->errorMessages['invoice_source_reference_to'] = $this->t('message_validate_batch_bad_order_range');
|
80 |
+
}
|
81 |
+
} else /*if ($this->submittedValues['date_to'] !== '') */ {
|
82 |
+
// Range of dates has been filled in.
|
83 |
+
// We ignore any order # to value.
|
84 |
+
$dateFormat = $this->getDateFormat();
|
85 |
+
if (!DateTime::createFromFormat($dateFormat, $this->submittedValues['date_from'])) {
|
86 |
+
// Date from not a valid date.
|
87 |
+
$this->errorMessages['date_from'] = sprintf($this->t('message_validate_batch_bad_date_from'), $this->getShopDateFormat());
|
88 |
+
}
|
89 |
+
if ($this->submittedValues['date_to']) {
|
90 |
+
if (!DateTime::createFromFormat($dateFormat, $this->submittedValues['date_to'])) {
|
91 |
+
// Date to not a valid date.
|
92 |
+
$this->errorMessages['date_to'] = sprintf($this->t('message_validate_batch_bad_date_to'), $this->getShopDateFormat());
|
93 |
+
} else if ($this->submittedValues['date_to'] < $this->submittedValues['date_from']) {
|
94 |
+
// date to is smaller than date from
|
95 |
+
$this->errorMessages['date_to'] = $this->t('message_validate_batch_bad_date_range');
|
96 |
+
}
|
97 |
+
}
|
98 |
+
}
|
99 |
+
}
|
100 |
+
|
101 |
+
/**
|
102 |
+
* {@inheritdoc}
|
103 |
+
*
|
104 |
+
* Sends the invoices as defined by the form values to Acumulus.
|
105 |
+
*/
|
106 |
+
protected function execute()
|
107 |
+
{
|
108 |
+
$type = $this->getFormValue('invoice_source_type');
|
109 |
+
if ($this->getFormValue('invoice_source_reference_from') !== '') {
|
110 |
+
// Retrieve by order reference range.
|
111 |
+
$from = $this->getFormValue('invoice_source_reference_from');
|
112 |
+
$to = $this->getFormValue('invoice_source_reference_to') ? $this->getFormValue('invoice_source_reference_to') : $from;
|
113 |
+
$invoiceSources = $this->invoiceManager->getInvoiceSourcesByReferenceRange($type, $from, $to);
|
114 |
+
} else {
|
115 |
+
// Retrieve by order date.
|
116 |
+
$dateFormat = $this->getDateFormat();
|
117 |
+
$from = DateTime::createFromFormat($dateFormat, $this->getFormValue('date_from'));
|
118 |
+
$from->setTime(0, 0, 0);
|
119 |
+
$to = $this->getFormValue('date_to') ? DateTime::createFromFormat($dateFormat, $this->getFormValue('date_to')) : clone $from;
|
120 |
+
$to->setTime(23, 59, 59);
|
121 |
+
$invoiceSources = $this->invoiceManager->getInvoiceSourcesByDateRange($type, $from, $to);
|
122 |
+
}
|
123 |
+
|
124 |
+
if (count($invoiceSources) === 0) {
|
125 |
+
$this->log[$type] = sprintf($this->t('message_form_empty_range'), $this->t($type));
|
126 |
+
$this->setFormValue('result', $this->log[$type]);
|
127 |
+
$result = true;
|
128 |
+
} else {
|
129 |
+
$result = $this->invoiceManager->sendMultiple($invoiceSources, (bool) $this->getFormValue('force_send'), $this->log);
|
130 |
+
}
|
131 |
+
|
132 |
+
// Set formValue for log in case form values are already queried.
|
133 |
+
$logText = implode("\n", $this->log);
|
134 |
+
$this->setFormValue('log', $logText);
|
135 |
+
return $result;
|
136 |
+
}
|
137 |
+
|
138 |
+
/**
|
139 |
+
* {@inheritdoc}
|
140 |
+
*/
|
141 |
+
public function getFieldDefinitions()
|
142 |
+
{
|
143 |
+
$fields = array();
|
144 |
+
|
145 |
+
$invoiceSourceTypes = $this->invoiceManager->getSupportedInvoiceSourceTypes();
|
146 |
+
if (count($invoiceSourceTypes) === 1) {
|
147 |
+
// Make it a hidden field.
|
148 |
+
$invoiceSourceTypeField = array(
|
149 |
+
'type' => 'hidden',
|
150 |
+
'value' => reset($invoiceSourceTypes),
|
151 |
+
);
|
152 |
+
} else {
|
153 |
+
$options = array();
|
154 |
+
foreach ($invoiceSourceTypes as $invoiceSourceType) {
|
155 |
+
$options[$invoiceSourceType] = ucfirst($this->t($invoiceSourceType));
|
156 |
+
}
|
157 |
+
$invoiceSourceTypeField = array(
|
158 |
+
'type' => 'radio',
|
159 |
+
'label' => $this->t('field_invoice_source_type'),
|
160 |
+
'options' => $options,
|
161 |
+
'attributes' => array(
|
162 |
+
'required' => true,
|
163 |
+
),
|
164 |
+
);
|
165 |
+
}
|
166 |
+
// 1st fieldset: Batch options.
|
167 |
+
$fields['batchFieldsHeader'] = array(
|
168 |
+
'type' => 'fieldset',
|
169 |
+
'legend' => $this->t('batchFieldsHeader'),
|
170 |
+
'fields' => array(
|
171 |
+
'invoice_source_type' => $invoiceSourceTypeField,
|
172 |
+
'invoice_source_reference_from' => array(
|
173 |
+
'type' => 'text',
|
174 |
+
'label' => $this->t('field_invoice_source_reference_from'),
|
175 |
+
),
|
176 |
+
'invoice_source_reference_to' => array(
|
177 |
+
'type' => 'text',
|
178 |
+
'label' => $this->t('field_invoice_source_reference_to'),
|
179 |
+
'description' => count($invoiceSourceTypes) === 1 ? $this->t('desc_invoice_source_reference_from_to_1') : $this->t('desc_invoice_source_reference_from_to_2'),
|
180 |
+
),
|
181 |
+
'date_from' => array(
|
182 |
+
'type' => 'date',
|
183 |
+
'label' => $this->t('field_date_from'),
|
184 |
+
'format' => $this->getShopDateFormat(),
|
185 |
+
),
|
186 |
+
'date_to' => array(
|
187 |
+
'type' => 'date',
|
188 |
+
'label' => $this->t('field_date_to'),
|
189 |
+
'description' => sprintf($this->t('desc_date_from_to'), $this->getShopDateFormat()),
|
190 |
+
'format' => $this->getShopDateFormat(),
|
191 |
+
),
|
192 |
+
'force_send' => array(
|
193 |
+
'type' => 'checkbox',
|
194 |
+
'label' => $this->t('field_options'),
|
195 |
+
'description' => $this->t('desc_batch_options'),
|
196 |
+
'options' => array(
|
197 |
+
'force_send' => $this->t('option_force_send'),
|
198 |
+
),
|
199 |
+
),
|
200 |
+
),
|
201 |
+
);
|
202 |
+
|
203 |
+
// 2nd fieldset: Batch log.
|
204 |
+
if ($this->isSubmitted() && !empty($this->submittedValues) && $this->isValid()) {
|
205 |
+
// Set formValue for log as value in case form values are not yet queried.
|
206 |
+
$fields['batchLogHeader'] = array(
|
207 |
+
'type' => 'fieldset',
|
208 |
+
'legend' => $this->t('batchLogHeader'),
|
209 |
+
'fields' => array(
|
210 |
+
'log' => array(
|
211 |
+
'type' => 'textarea',
|
212 |
+
'attributes' => array(
|
213 |
+
'readonly' => true,
|
214 |
+
'rows' => max(5, min(10, count($this->log))),
|
215 |
+
'style' => 'box-sizing: border-box; width: 100%; min-width: 48em;',
|
216 |
+
),
|
217 |
+
),
|
218 |
+
),
|
219 |
+
);
|
220 |
+
if (!empty($this->log)) {
|
221 |
+
$logText = implode("\n", $this->log);
|
222 |
+
$this->formValues['log'] = $logText;
|
223 |
+
$fields['batchLogHeader']['fields']['log']['value'] = $logText;
|
224 |
+
}
|
225 |
+
}
|
226 |
+
|
227 |
+
// 3rd fieldset: Batch info.
|
228 |
+
$fields['batchInfoHeader'] = array(
|
229 |
+
'type' => 'fieldset',
|
230 |
+
'legend' => $this->t('batchInfoHeader'),
|
231 |
+
'fields' => array(
|
232 |
+
'info' => array(
|
233 |
+
'type' => 'markup',
|
234 |
+
'value' => $this->t('batch_info'),
|
235 |
+
'attributes' => array(
|
236 |
+
'readonly' => true,
|
237 |
+
),
|
238 |
+
),
|
239 |
+
),
|
240 |
+
);
|
241 |
+
|
242 |
+
return $fields;
|
243 |
+
}
|
244 |
+
|
245 |
+
/**
|
246 |
+
* {@inheritdoc}
|
247 |
+
*/
|
248 |
+
protected function getCheckboxKeys()
|
249 |
+
{
|
250 |
+
return array(
|
251 |
+
'force_send' => 'force_send',
|
252 |
+
);
|
253 |
+
}
|
254 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/BatchFormTranslations.php
ADDED
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Shop;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\TranslationCollection;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Contains translations for the batch form.
|
8 |
+
*/
|
9 |
+
class BatchFormTranslations extends TranslationCollection
|
10 |
+
{
|
11 |
+
protected $nl = array(
|
12 |
+
'batch_form_title' => 'Acumulus | Batch verzending',
|
13 |
+
'batch_form_header' => 'Verzend facturen naar Acumulus',
|
14 |
+
|
15 |
+
'button_send' => 'Verzenden',
|
16 |
+
'button_cancel' => 'Annuleren',
|
17 |
+
|
18 |
+
'batchFieldsHeader' => 'Batchgewijs verzenden van facturen naar Acumulus',
|
19 |
+
'field_invoice_source_type' => 'Factuurtype',
|
20 |
+
'field_invoice_source_reference_from' => '# van',
|
21 |
+
'field_invoice_source_reference_to' => '# tot',
|
22 |
+
'desc_invoice_source_reference_from_to_1' => 'Vul de reeks bestelnummers in die u naar Acumulus wilt verzenden. Als u slechts 1 factuur wilt verzenden hoeft u alleen het \'# van\' in te vullen. Laat beide velden leeg als u op datum wilt verzenden.',
|
23 |
+
'desc_invoice_source_reference_from_to_2' => 'Vul de reeks bestel of creditnotanummers in die u naar Acumulus wilt verzenden. Als u slechts 1 factuur wilt verzenden hoeft u alleen het \'# van\' in te vullen. Laat beide velden leeg als u op datum wilt verzenden.',
|
24 |
+
'field_date_from' => 'Datum van',
|
25 |
+
'field_date_to' => 'Datum tot',
|
26 |
+
'desc_date_from_to' => 'Vul de periode in waarvan u de facuren naar Acumulus wilt verzenden (verwacht formaat %1$s). De selectie vindt plaats op basis van de datum van de meest recente wijziging aan de bestelling of creditnota. Als u slechts de facturen van 1 dag wilt verzenden hoeft u alleen de \'Datum van\' in te vullen. Laat beide velden leeg als u op nummer wilt verzenden.',
|
27 |
+
'field_options' => 'Opties',
|
28 |
+
'option_force_send' => 'Forceer verzenden',
|
29 |
+
'desc_batch_options' => 'Facturen die binnen de reeks vallen maar al naar Acumulus verstuurd zijn, worden standaard niet verzonden. Door deze optie aan te vinken forceert u het nogmaals versturen van deze facturen.',
|
30 |
+
'batchLogHeader' => 'Resultaten',
|
31 |
+
'batchInfoHeader' => 'Uitgebreide informatie',
|
32 |
+
'batch_info' => <<<LONGSTRING
|
33 |
+
<p>Met dit formulier kunt u de facturen van een aantal orders of creditnota's in
|
34 |
+
één keer versturen.
|
35 |
+
Dit is vooral handig als u deze koppeling net heeft geïnstalleerd want normaal
|
36 |
+
gesproken heeft het automatisch versturen de voorkeur.</p>
|
37 |
+
<p><strong>Performance: het versturen van een factuur kan tot enige seconden
|
38 |
+
duren.
|
39 |
+
Geef daarom niet teveel facturen in één keer op.
|
40 |
+
U kunt dan een time-out krijgen, waardoor het resultaat van de laatst verstuurde
|
41 |
+
factuur niet opgeslagen wordt.</strong></p>
|
42 |
+
<p><strong>LET OP: Het gebruik van de optie 'Forceer verzenden' is op eigen
|
43 |
+
risico. Door het nogmaals handmatig versturen van facturen kan uw administratie
|
44 |
+
ontregeld raken.</strong></p>
|
45 |
+
<p>Het versturen van orders gaat net als het automatisch versturen:</p>
|
46 |
+
<ul style="list-style: inside disc;">
|
47 |
+
<li>De factuur wordt op exact dezelfde wijze aangemaakt als bij het automatisch
|
48 |
+
versturen.</li>
|
49 |
+
<li>Als er facturen zijn die fouten bevatten ontvangt u een mail per factuur.
|
50 |
+
</li>
|
51 |
+
<li>Als u een event handler heeft geregistreerd voor het 'AcumulusInvoiceAdd"
|
52 |
+
event (of hook of actie) wordt die voor alle facturen die verzonden gaan worden
|
53 |
+
uitgevoerd.</li>
|
54 |
+
</ul>
|
55 |
+
<p>Dit formulier bevindt zich in een experimentele status.
|
56 |
+
Het werkt in zijn huidige vorm, maar als u de behoefte heeft om de reeks van
|
57 |
+
facturen op een andere manier te willen aangeven, laat dit ons dan weten.</p>
|
58 |
+
<p>Standaard worden facuren die al naar Acumulus verzonden zijn, niet opnieuw
|
59 |
+
verstuurd. Dit kunt u forceren door die optie aan te vinken, maar let op: in
|
60 |
+
Acumulus wordt dit als een nieuwe factuur gezien. U dient zelf uw boekhouding te
|
61 |
+
ontdubbelen.
|
62 |
+
Merk ook nog op dat deze beveiliging alleen werkt voor facturen die sinds de
|
63 |
+
installatie van de versie met uitgiftedatum begin september 2014 van deze
|
64 |
+
koppeling verstuurd zijn.
|
65 |
+
In oudere versies werd nog niet bijgehouden voor welke orders en creditnota's al
|
66 |
+
een factuur verzonden was.</p>
|
67 |
+
LONGSTRING
|
68 |
+
,
|
69 |
+
|
70 |
+
'message_validate_batch_source_type_required' => 'U dient een Factuurtype te selecteren.',
|
71 |
+
'message_validate_batch_source_type_invalid' => 'U dient een bestaand factuurtype te selecteren.',
|
72 |
+
'message_validate_batch_reference_or_date_1' => 'U dient of een reeks van bestelnummers of een reeks van datums in te vullen.',
|
73 |
+
'message_validate_batch_reference_or_date_2' => 'U dient of een reeks van bestel of creditnotanummers of een reeks van datums in te vullen.',
|
74 |
+
'message_validate_batch_reference_and_date_1' => 'U kunt niet en een reeks van bestelnummers en een reeks van datums invullen.',
|
75 |
+
'message_validate_batch_reference_and_date_2' => 'U kunt niet en een reeks van bestel of creditnotanummers en een reeks van datums invullen.',
|
76 |
+
'message_validate_batch_bad_date_from' => 'U dient een correcte \'Datum van\' in te vullen (verwacht formaat: %1$s).',
|
77 |
+
'message_validate_batch_bad_date_to' => 'U dient een correcte \'Datum tot\' in te vullen (verwacht formaat %1$s).',
|
78 |
+
'message_validate_batch_bad_date_range' => '\'Datum tot\' dient na \'Datum van\' te liggen.',
|
79 |
+
'message_validate_batch_bad_order_range' => '\'# tot\' dient groter te zijn dan \'# van\'.',
|
80 |
+
|
81 |
+
'message_form_empty_range' => 'De door u opgegeven reeks bevat geen enkele %1$s.',
|
82 |
+
'message_form_success' => 'De facturen zijn succesvol verzonden. Zie het resultatenoverzicht voor eventuele opmerkingen en waarschuwingen.',
|
83 |
+
'message_form_error' => 'Er zijn fouten opgetreden bij het versturen van de facturen. Zie het resultatenoverzicht voor meer informatie over de fouten.',
|
84 |
+
|
85 |
+
);
|
86 |
+
|
87 |
+
protected $en = array(
|
88 |
+
'batch_form_title' => 'Acumulus | Send batch',
|
89 |
+
'batch_form_header' => 'Send invoices to Acumulus',
|
90 |
+
|
91 |
+
'button_send' => 'Send',
|
92 |
+
'button_cancel' => 'Cancel',
|
93 |
+
|
94 |
+
'batchFieldsHeader' => 'Send a batch of invoices to Acumulus',
|
95 |
+
'field_invoice_source_type' => 'Invoice type',
|
96 |
+
'field_invoice_source_reference_from' => '# from',
|
97 |
+
'field_invoice_source_reference_to' => '# to',
|
98 |
+
'desc_invoice_source_reference_from_to_1' => 'Enter the range of order numbers you want to send to Acumulus. If you only want to send 1 invoice, you only have to fill in the \'# from\' field. Leave empty if you want to send by date.',
|
99 |
+
'desc_invoice_source_reference_from_to_2' => 'Enter the range of order or credit note numbers you want to send to Acumulus. If you only want to send 1 invoice, you only have to fill in the \'# from\' field. Leave empty if you want to send by date.',
|
100 |
+
'field_date_from' => 'Date from',
|
101 |
+
'field_date_to' => 'Date to',
|
102 |
+
'desc_date_from_to' => 'Enter the period over which you want to send invoices to Acumulus (expected format: %1$s). If you want to send the invoices of 1 day, only fill in the \'Date from\' field. Leave empty if you want to send by id.',
|
103 |
+
'field_options' => 'Options',
|
104 |
+
'option_force_send' => 'Force sending',
|
105 |
+
'desc_batch_options' => 'Invoices that fall within the range but are already sent to Acumulus will normally not be sent again. By checking this option these orders will be sent again.',
|
106 |
+
'batchLogHeader' => 'Results',
|
107 |
+
'batchInfoHeader' => 'Additional information',
|
108 |
+
|
109 |
+
'message_validate_batch_source_type_required' => 'Please select an invoice type.',
|
110 |
+
'message_validate_batch_source_type_invalid' => 'Please select an existing invoice type.',
|
111 |
+
'message_validate_batch_reference_or_date_1' => 'Fill in a range of order numbers or a range of dates.',
|
112 |
+
'message_validate_batch_reference_or_date_2' => 'Fill in a range of order/credit note numbers or a range of dates.',
|
113 |
+
'message_validate_batch_reference_and_date_1' => 'Either fill in a range of order numbers OR a range of dates, not both.',
|
114 |
+
'message_validate_batch_reference_and_date_2' => 'Either fill in a range of order/credit note numbers OR a range of dates, not both.',
|
115 |
+
'message_validate_batch_bad_date_from' => 'Incorrect \'Date from\' (expected format: %1$s).',
|
116 |
+
'message_validate_batch_bad_date_to' => 'Incorrect \'Date to\' (expected format: %1$s).',
|
117 |
+
'message_validate_batch_bad_date_range' => '\'Date to\' should be after \'Date from\'.',
|
118 |
+
'message_validate_batch_bad_order_range' => '\'# to\' should to be greater than \'# from\'.',
|
119 |
+
|
120 |
+
'message_form_empty_range' => 'The range you defined does not contain any %1$s.',
|
121 |
+
'message_form_success' => 'The invoices were sent successfully. See the results overview for any remarks or warnings.',
|
122 |
+
'message_form_error' => 'Errors during sending the invoices. See the results overview for more information on the errors.',
|
123 |
+
|
124 |
+
);
|
125 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/BatchTranslations.php
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Shop;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\TranslationCollection;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Contains translations for classes in the \Siel\Acumulus\Shop namespace:
|
8 |
+
* - InvoiceManager
|
9 |
+
*/
|
10 |
+
class BatchTranslations extends TranslationCollection
|
11 |
+
{
|
12 |
+
protected $nl = array(
|
13 |
+
// Batch log messages.
|
14 |
+
'message_batch_send_1_success' => 'Factuur voor %1$s %2$s is succesvol verzonden.',
|
15 |
+
'message_batch_send_1_errors' => 'Fout bij het versturen van de factuur voor %1$s %2$s.',
|
16 |
+
'message_batch_send_1_warnings' => 'Waarschuwingen bij het versturen van de factuur voor %1$s %2$s.',
|
17 |
+
'message_batch_send_1_skipped' => 'Factuur voor %1$s %2$s is overgeslagen omdat deze al verstuurd is.',
|
18 |
+
'message_batch_send_1_prevented_invoiceCreated' => 'Factuur voor %1$s %2$s is overgeslagen omdat het versturen tegengehouden is door het event \'AcumulusInvoiceCreated\'.',
|
19 |
+
'message_batch_send_1_prevented_invoiceCompleted' => 'Factuur voor %1$s %2$s is overgeslagen omdat het versturen tegengehouden is door het event \'AcumulusInvoiceCompleted\'.',
|
20 |
+
|
21 |
+
);
|
22 |
+
|
23 |
+
protected $en = array(
|
24 |
+
'message_batch_send_1_success' => 'Successfully sent invoice for %1$s %2$s.',
|
25 |
+
'message_batch_send_1_errors' => 'Error while sending invoice for %1$s %2$s.',
|
26 |
+
'message_batch_send_1_warnings' => 'Warnings while sending invoice for %1$s %2$s.',
|
27 |
+
'message_batch_send_1_skipped' => 'Skipped invoice for %1$s %2$s (already sent).',
|
28 |
+
'message_batch_send_1_prevented_invoiceCreated' => 'Skipped invoice for %1$s %2$s (sending prevented by event \'AcumulusInvoiceCreated\').',
|
29 |
+
'message_batch_send_1_prevented_invoiceCompleted' => 'Skipped invoice for %1$s %2$s (sending prevented by event \'AcumulusInvoiceCompleted\').',
|
30 |
+
|
31 |
+
);
|
32 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/Config.php
ADDED
@@ -0,0 +1,808 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Shop;
|
3 |
+
|
4 |
+
use ReflectionClass;
|
5 |
+
use Siel\Acumulus\Helpers\Log;
|
6 |
+
use Siel\Acumulus\Invoice\Completor;
|
7 |
+
use Siel\Acumulus\Invoice\ConfigInterface as InvoiceConfigInterface;
|
8 |
+
use Siel\Acumulus\Web\ConfigInterface as ServiceConfigInterface;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Gives common code in this package uniform access to the settings for this
|
12 |
+
* extension, hiding all the web shop specific implementations of their config
|
13 |
+
* store.
|
14 |
+
*
|
15 |
+
* This class implements all <...ConfigInterface>s and makes, via a ConfigStore,
|
16 |
+
* use of the shop specific configuration functionality to store this
|
17 |
+
* configuration in a persistent way.
|
18 |
+
*
|
19 |
+
* This class als implements the injector interface to allow other classes to
|
20 |
+
* easily get the correct derived classes of the base classes.
|
21 |
+
*/
|
22 |
+
class Config implements ConfigInterface, InvoiceConfigInterface, ServiceConfigInterface, InjectorInterface
|
23 |
+
{
|
24 |
+
/** @const string */
|
25 |
+
const baseNamespace = '\\Siel\\Acumulus';
|
26 |
+
|
27 |
+
/** @var array[]|null */
|
28 |
+
protected $keyInfo;
|
29 |
+
|
30 |
+
/** @var bool */
|
31 |
+
protected $isLoaded;
|
32 |
+
|
33 |
+
/** @var array */
|
34 |
+
protected $values;
|
35 |
+
|
36 |
+
/** @var array */
|
37 |
+
protected $instances;
|
38 |
+
|
39 |
+
/** @var string The namespace for the current shop. */
|
40 |
+
protected $shopNamespace;
|
41 |
+
|
42 |
+
/** @var string The language to display texts in. */
|
43 |
+
protected $language;
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Constructor.
|
47 |
+
*
|
48 |
+
* @param string $shopNamespace
|
49 |
+
* @param string $language
|
50 |
+
*/
|
51 |
+
public function __construct($shopNamespace, $language)
|
52 |
+
{
|
53 |
+
$this->keyInfo = null;
|
54 |
+
$this->isLoaded = false;
|
55 |
+
$this->values = array();
|
56 |
+
$this->instances = array();
|
57 |
+
$this->shopNamespace = static::baseNamespace . '\\' . $shopNamespace;
|
58 |
+
$this->language = $language;
|
59 |
+
$this->getLog();
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Helper method to translate strings.
|
64 |
+
*
|
65 |
+
* @param string $key
|
66 |
+
* The key to get a translation for.
|
67 |
+
*
|
68 |
+
* @return string
|
69 |
+
* The translation for the given key or the key itself if no translation
|
70 |
+
* could be found.
|
71 |
+
*/
|
72 |
+
protected function t($key)
|
73 |
+
{
|
74 |
+
return $this->getTranslator()->get($key);
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* {@inheritdoc}
|
79 |
+
*/
|
80 |
+
public function getTranslator()
|
81 |
+
{
|
82 |
+
return $this->getInstance('Translator', 'Helpers', array($this->language));
|
83 |
+
}
|
84 |
+
|
85 |
+
/**
|
86 |
+
* {@inheritdoc}
|
87 |
+
*/
|
88 |
+
public function getLog()
|
89 |
+
{
|
90 |
+
return $this->getInstance('Log', 'Helpers', array($this));
|
91 |
+
}
|
92 |
+
|
93 |
+
/**
|
94 |
+
* {@inheritdoc}
|
95 |
+
*/
|
96 |
+
public function getService()
|
97 |
+
{
|
98 |
+
return $this->getInstance('Service', 'Web', array($this, $this->getTranslator()));
|
99 |
+
}
|
100 |
+
|
101 |
+
/**
|
102 |
+
* {@inheritdoc}
|
103 |
+
*/
|
104 |
+
public function getSource($invoiceSourceType, $invoiceSourceOrId)
|
105 |
+
{
|
106 |
+
return $this->getInstance('Source', 'Invoice', array($invoiceSourceType, $invoiceSourceOrId), true);
|
107 |
+
}
|
108 |
+
|
109 |
+
/**
|
110 |
+
* {@inheritdoc}
|
111 |
+
*/
|
112 |
+
public function getCompletor()
|
113 |
+
{
|
114 |
+
return new Completor($this, $this->getTranslator(), $this->getService());
|
115 |
+
}
|
116 |
+
|
117 |
+
/**
|
118 |
+
* {@inheritdoc}
|
119 |
+
*/
|
120 |
+
public function getCreator()
|
121 |
+
{
|
122 |
+
return $this->getInstance('Creator', 'Invoice', array($this, $this->getTranslator()));
|
123 |
+
}
|
124 |
+
|
125 |
+
/**
|
126 |
+
* {@inheritdoc}
|
127 |
+
*/
|
128 |
+
public function getMailer()
|
129 |
+
{
|
130 |
+
return $this->getInstance('Mailer', 'Helpers', array($this, $this->getTranslator(), $this->getService()));
|
131 |
+
}
|
132 |
+
|
133 |
+
/**
|
134 |
+
* {@inheritdoc}
|
135 |
+
*/
|
136 |
+
public function getConfigStore()
|
137 |
+
{
|
138 |
+
return $this->getInstance('ConfigStore', 'Shop', array($this->shopNamespace));
|
139 |
+
}
|
140 |
+
|
141 |
+
/**
|
142 |
+
* {@inheritdoc}
|
143 |
+
*/
|
144 |
+
public function getManager()
|
145 |
+
{
|
146 |
+
return $this->getInstance('InvoiceManager', 'Shop', array($this));
|
147 |
+
}
|
148 |
+
|
149 |
+
/**
|
150 |
+
* {@inheritdoc}
|
151 |
+
*/
|
152 |
+
public function getAcumulusEntryModel()
|
153 |
+
{
|
154 |
+
return $this->getInstance('AcumulusEntryModel', 'Shop');
|
155 |
+
}
|
156 |
+
|
157 |
+
/**
|
158 |
+
* {@inheritdoc}
|
159 |
+
*/
|
160 |
+
public function getForm($type)
|
161 |
+
{
|
162 |
+
$arguments = array($this->getTranslator());
|
163 |
+
switch (strtolower($type)) {
|
164 |
+
case 'config':
|
165 |
+
$arguments[] = $this;
|
166 |
+
break;
|
167 |
+
case 'batch':
|
168 |
+
$arguments[] = $this->getManager();
|
169 |
+
break;
|
170 |
+
default:
|
171 |
+
$this->getLog()->error('Config::getForm(%s): unknown form type', $type);
|
172 |
+
break;
|
173 |
+
}
|
174 |
+
return $this->getInstance(ucfirst($type) . 'Form', 'Shop', $arguments);
|
175 |
+
}
|
176 |
+
|
177 |
+
/**
|
178 |
+
* {@inheritdoc}
|
179 |
+
*/
|
180 |
+
public function getFormRenderer()
|
181 |
+
{
|
182 |
+
return $this->getInstance('FormRenderer', 'Helpers');
|
183 |
+
}
|
184 |
+
|
185 |
+
/**
|
186 |
+
* Returns an instance of the given class.
|
187 |
+
*
|
188 |
+
* The class is taken from the same namespace as the configStore property.
|
189 |
+
* Only 1 instance is created per class.
|
190 |
+
*
|
191 |
+
* @param string $class
|
192 |
+
* The name of the class without namespace. The namespace is taken from the
|
193 |
+
* configStore object.
|
194 |
+
* @param string $subNamespace
|
195 |
+
* The sub namespace (within the shop namespace) in which the class resides.
|
196 |
+
* @param array $constructorArgs
|
197 |
+
* An array of arguments to pass to the constructor, may be an empty array.
|
198 |
+
* @param bool $newInstance
|
199 |
+
* Whether to create a new instance or reuse an already existing instance
|
200 |
+
*
|
201 |
+
* @return object
|
202 |
+
*
|
203 |
+
* @throws \ReflectionException
|
204 |
+
*/
|
205 |
+
protected function getInstance($class, $subNamespace, array $constructorArgs = array(), $newInstance = false)
|
206 |
+
{
|
207 |
+
if (!isset($this->instances[$class]) || $newInstance) {
|
208 |
+
// Try shop namespace.
|
209 |
+
$fqClass = $this->tryNsInstance($class, $subNamespace, $this->shopNamespace);
|
210 |
+
|
211 |
+
// Try CMS namespace if it exists.
|
212 |
+
if (empty($fqClass)) {
|
213 |
+
$cmsNamespace = $this->getCmsNamespace();
|
214 |
+
if (!empty($cmsNamespace)) {
|
215 |
+
$fqClass = $this->tryNsInstance($class, $subNamespace, $cmsNamespace);
|
216 |
+
}
|
217 |
+
}
|
218 |
+
|
219 |
+
// Try base namespace.
|
220 |
+
if (empty($fqClass)) {
|
221 |
+
$fqClass = $this->tryNsInstance($class, $subNamespace, static::baseNamespace);
|
222 |
+
}
|
223 |
+
|
224 |
+
// Use ReflectionClass to pass an argument list.
|
225 |
+
if (empty($constructorArgs)) {
|
226 |
+
// PHP5.3: exception when class has no constructor and
|
227 |
+
// newInstanceArgs() is called.
|
228 |
+
$this->instances[$class] = new $fqClass();
|
229 |
+
}
|
230 |
+
else {
|
231 |
+
$reflector = new ReflectionClass($fqClass);
|
232 |
+
$this->instances[$class] = $reflector->newInstanceArgs($constructorArgs);
|
233 |
+
}
|
234 |
+
}
|
235 |
+
return $this->instances[$class];
|
236 |
+
}
|
237 |
+
|
238 |
+
protected function tryNsInstance($class, $subNamespace, $namespace)
|
239 |
+
{
|
240 |
+
$fqClass = $namespace . '\\' . $subNamespace . '\\' . $class;
|
241 |
+
return class_exists($fqClass) ? $fqClass : '';
|
242 |
+
}
|
243 |
+
|
244 |
+
/**
|
245 |
+
* Returns the namespace for the current cms.
|
246 |
+
*
|
247 |
+
* @return string
|
248 |
+
* The namespace for the current cms or the empty string if the current shop
|
249 |
+
* is not contained in a CMS namespace.
|
250 |
+
*/
|
251 |
+
protected function getCmsNamespace()
|
252 |
+
{
|
253 |
+
// Get parent namespace of the shop namespace.
|
254 |
+
$cmsNamespaceEnd = strrpos($this->shopNamespace, '\\');
|
255 |
+
$cmsNamespace = substr($this->shopNamespace, 0, (int) $cmsNamespaceEnd);
|
256 |
+
// But if that is Acumulus there's no CMS namespace.
|
257 |
+
if (substr($cmsNamespace, -strlen('\\Acumulus')) === '\\Acumulus') {
|
258 |
+
$cmsNamespace = '';
|
259 |
+
}
|
260 |
+
return $cmsNamespace;
|
261 |
+
}
|
262 |
+
|
263 |
+
/**
|
264 |
+
* Loads the configuration from the actual configuration provider.
|
265 |
+
*/
|
266 |
+
protected function load()
|
267 |
+
{
|
268 |
+
if (!$this->isLoaded) {
|
269 |
+
$this->values = array_merge($this->getDefaults(), $this->getConfigStore()->load($this->getKeys()));
|
270 |
+
$this->castValues();
|
271 |
+
$this->isLoaded = true;
|
272 |
+
}
|
273 |
+
}
|
274 |
+
|
275 |
+
/**
|
276 |
+
* Saves the configuration to the actual configuration provider.
|
277 |
+
*
|
278 |
+
* @param array $values
|
279 |
+
* A keyed array that contains the values to store, this may be a subset of
|
280 |
+
* the possible keys.
|
281 |
+
*
|
282 |
+
* @return bool
|
283 |
+
* Success.
|
284 |
+
*/
|
285 |
+
public function save(array $values)
|
286 |
+
{
|
287 |
+
$result = $this->getConfigStore()->save($values);
|
288 |
+
$this->isLoaded = false;
|
289 |
+
// Sync internal values.
|
290 |
+
$this->load();
|
291 |
+
return $result;
|
292 |
+
}
|
293 |
+
|
294 |
+
/**
|
295 |
+
* Returns the value of the specified configuration value.
|
296 |
+
*
|
297 |
+
* @param string $key
|
298 |
+
* The requested configuration value
|
299 |
+
*
|
300 |
+
* @return mixed
|
301 |
+
* The value of the given configuration value or null if not defined. This
|
302 |
+
* will be a simple type (string, int, bool) or a keyed array with simple
|
303 |
+
* values.
|
304 |
+
*/
|
305 |
+
protected function get($key)
|
306 |
+
{
|
307 |
+
$this->load();
|
308 |
+
return isset($this->values[$key]) ? $this->values[$key] : null;
|
309 |
+
}
|
310 |
+
|
311 |
+
/**
|
312 |
+
* Sets the internal value of the specified configuration key.
|
313 |
+
*
|
314 |
+
* This value will not be stored, use save() for that.
|
315 |
+
*
|
316 |
+
* @param string $key
|
317 |
+
* The configuration value to set.
|
318 |
+
* @param mixed $value
|
319 |
+
* The new value for the configuration key.
|
320 |
+
*
|
321 |
+
* @return mixed
|
322 |
+
* The old value.
|
323 |
+
*/
|
324 |
+
protected function set($key, $value)
|
325 |
+
{
|
326 |
+
$this->load();
|
327 |
+
$oldValue = isset($this->values[$key]) ? $this->values[$key] : null;
|
328 |
+
$this->values[$key] = $value;
|
329 |
+
return $oldValue;
|
330 |
+
}
|
331 |
+
|
332 |
+
/**
|
333 |
+
* Sets the value for the debug setting.
|
334 |
+
*
|
335 |
+
* @param int $debug
|
336 |
+
*
|
337 |
+
* @return int
|
338 |
+
* The old value.
|
339 |
+
*/
|
340 |
+
public function setDebug($debug)
|
341 |
+
{
|
342 |
+
return $this->set('debug', (int) $debug);
|
343 |
+
}
|
344 |
+
|
345 |
+
/**
|
346 |
+
* Sets the log level.
|
347 |
+
*
|
348 |
+
* @param int $logLevel
|
349 |
+
*
|
350 |
+
* @return int
|
351 |
+
* The old value.
|
352 |
+
*/
|
353 |
+
public function setLogLevel($logLevel)
|
354 |
+
{
|
355 |
+
return $this->set('logLevel', (int) $logLevel);
|
356 |
+
}
|
357 |
+
|
358 |
+
/**
|
359 |
+
* @inheritdoc
|
360 |
+
*/
|
361 |
+
public function getBaseUri()
|
362 |
+
{
|
363 |
+
return $this->get('baseUri');
|
364 |
+
}
|
365 |
+
|
366 |
+
/**
|
367 |
+
* @inheritdoc
|
368 |
+
*/
|
369 |
+
public function getApiVersion()
|
370 |
+
{
|
371 |
+
return $this->get('apiVersion');
|
372 |
+
}
|
373 |
+
|
374 |
+
/**
|
375 |
+
* @inheritdoc
|
376 |
+
*/
|
377 |
+
public function getEnvironment()
|
378 |
+
{
|
379 |
+
return $this->getSettingsByGroup('environment');
|
380 |
+
}
|
381 |
+
|
382 |
+
/**
|
383 |
+
* @inheritdoc
|
384 |
+
*/
|
385 |
+
public function getDebug()
|
386 |
+
{
|
387 |
+
return $this->get('debug');
|
388 |
+
}
|
389 |
+
|
390 |
+
/**
|
391 |
+
* @inheritdoc
|
392 |
+
*/
|
393 |
+
public function getLogLevel()
|
394 |
+
{
|
395 |
+
return $this->get('logLevel');
|
396 |
+
}
|
397 |
+
|
398 |
+
/**
|
399 |
+
* @inheritdoc
|
400 |
+
*/
|
401 |
+
public function getOutputFormat()
|
402 |
+
{
|
403 |
+
return $this->get('outputFormat');
|
404 |
+
}
|
405 |
+
|
406 |
+
/**
|
407 |
+
* @inheritdoc
|
408 |
+
*/
|
409 |
+
public function getCredentials()
|
410 |
+
{
|
411 |
+
$result = $this->getSettingsByGroup('credentials');
|
412 |
+
// No separate key for now.
|
413 |
+
$result['emailonwarning'] = $result['emailonerror'];
|
414 |
+
return $result;
|
415 |
+
}
|
416 |
+
|
417 |
+
/**
|
418 |
+
* @inheritdoc
|
419 |
+
*/
|
420 |
+
public function getCustomerSettings()
|
421 |
+
{
|
422 |
+
return $this->getSettingsByGroup('customer');
|
423 |
+
}
|
424 |
+
|
425 |
+
/**
|
426 |
+
* @inheritdoc
|
427 |
+
*/
|
428 |
+
public function getInvoiceSettings()
|
429 |
+
{
|
430 |
+
return $this->getSettingsByGroup('invoice');
|
431 |
+
}
|
432 |
+
|
433 |
+
/**
|
434 |
+
* @inheritdoc
|
435 |
+
*/
|
436 |
+
public function getEmailAsPdfSettings()
|
437 |
+
{
|
438 |
+
return $this->getSettingsByGroup('emailaspdf');
|
439 |
+
}
|
440 |
+
|
441 |
+
/**
|
442 |
+
* @inheritdoc
|
443 |
+
*/
|
444 |
+
public function getShopSettings()
|
445 |
+
{
|
446 |
+
return $this->getSettingsByGroup('shop');
|
447 |
+
}
|
448 |
+
|
449 |
+
/**
|
450 |
+
* @inheritdoc
|
451 |
+
*/
|
452 |
+
public function getShopEventSettings()
|
453 |
+
{
|
454 |
+
return $this->getSettingsByGroup('event');
|
455 |
+
}
|
456 |
+
|
457 |
+
/**
|
458 |
+
* @inheritdoc
|
459 |
+
*/
|
460 |
+
public function getOtherSettings()
|
461 |
+
{
|
462 |
+
return $this->getSettingsByGroup('other');
|
463 |
+
}
|
464 |
+
|
465 |
+
/**
|
466 |
+
* Get all settings belonging to the same group.
|
467 |
+
*
|
468 |
+
* @param string $group
|
469 |
+
*
|
470 |
+
* @return array
|
471 |
+
* An array of settings.
|
472 |
+
*/
|
473 |
+
protected function getSettingsByGroup($group)
|
474 |
+
{
|
475 |
+
$result = array();
|
476 |
+
foreach ($this->getKeyInfo() as $key => $keyInfo) {
|
477 |
+
if ($keyInfo['group'] === $group) {
|
478 |
+
$result[$key] = $this->get($key);
|
479 |
+
}
|
480 |
+
}
|
481 |
+
return $result;
|
482 |
+
}
|
483 |
+
|
484 |
+
/**
|
485 |
+
* Casts the values to their correct types.
|
486 |
+
*
|
487 |
+
* Values that come from a submitted form are all strings. Values that come
|
488 |
+
* from the config store might be NULL. However, internally we work with
|
489 |
+
* booleans or integers. So after reading from the config store or form, we
|
490 |
+
* cast the values to their expected types.
|
491 |
+
*/
|
492 |
+
protected function castValues()
|
493 |
+
{
|
494 |
+
foreach ($this->getKeyInfo() as $key => $keyInfo) {
|
495 |
+
if (array_key_exists($key, $this->values)) {
|
496 |
+
switch ($keyInfo['type']) {
|
497 |
+
case 'string':
|
498 |
+
if (!is_string($this->values[$key])) {
|
499 |
+
$this->values[$key] = (string) $this->values[$key];
|
500 |
+
}
|
501 |
+
break;
|
502 |
+
case 'int':
|
503 |
+
if (!is_int($this->values[$key])) {
|
504 |
+
$this->values[$key] = (int) $this->values[$key];
|
505 |
+
}
|
506 |
+
break;
|
507 |
+
case 'bool':
|
508 |
+
if (!is_bool($this->values[$key])) {
|
509 |
+
$this->values[$key] = (bool) $this->values[$key];
|
510 |
+
}
|
511 |
+
break;
|
512 |
+
case 'array':
|
513 |
+
if (!is_array($this->values[$key])) {
|
514 |
+
$this->values[$key] = array($this->values[$key]);
|
515 |
+
}
|
516 |
+
break;
|
517 |
+
}
|
518 |
+
}
|
519 |
+
}
|
520 |
+
}
|
521 |
+
|
522 |
+
/**
|
523 |
+
* Returns a list of keys that are stored in the shop specific config store.
|
524 |
+
*
|
525 |
+
* @return array
|
526 |
+
*/
|
527 |
+
public function getKeys()
|
528 |
+
{
|
529 |
+
$result = $this->getKeyInfo();
|
530 |
+
array_filter($result, function ($item) {
|
531 |
+
return $item['group'] != 'environment';
|
532 |
+
});
|
533 |
+
return array_keys($result);
|
534 |
+
}
|
535 |
+
|
536 |
+
/**
|
537 |
+
* Returns a set of default values for the various config settings.
|
538 |
+
*
|
539 |
+
* @return array
|
540 |
+
*/
|
541 |
+
protected function getDefaults()
|
542 |
+
{
|
543 |
+
$result = $this->getKeyInfo();
|
544 |
+
$result = array_map(function ($item) {
|
545 |
+
return $item['default'];
|
546 |
+
}, $result);
|
547 |
+
return $result;
|
548 |
+
}
|
549 |
+
|
550 |
+
/**
|
551 |
+
* The hostname of the current server.
|
552 |
+
*
|
553 |
+
* Used for a default email address.
|
554 |
+
*
|
555 |
+
* @return string
|
556 |
+
*/
|
557 |
+
public function getHostName()
|
558 |
+
{
|
559 |
+
$hostName = parse_url($_SERVER['REQUEST_URI'], PHP_URL_HOST);
|
560 |
+
if ($hostName) {
|
561 |
+
if ($pos = strpos($hostName, 'www.') !== false) {
|
562 |
+
$hostName = substr($hostName, $pos + strlen('www.'));
|
563 |
+
}
|
564 |
+
} else {
|
565 |
+
$hostName = 'example.com';
|
566 |
+
}
|
567 |
+
return $hostName;
|
568 |
+
}
|
569 |
+
|
570 |
+
/**
|
571 |
+
* Returns information (group and type) about the keys that are stored in the
|
572 |
+
* store config.
|
573 |
+
*
|
574 |
+
* @return array
|
575 |
+
* A keyed array with information (group and type) about the keys that are
|
576 |
+
* stored in the store config.
|
577 |
+
*/
|
578 |
+
protected function getKeyInfo()
|
579 |
+
{
|
580 |
+
if ($this->keyInfo === null) {
|
581 |
+
$hostName = $this->getHostName();
|
582 |
+
$curlVersion = curl_version();
|
583 |
+
$shopDefaults = $this->getConfigStore()->getShopEnvironment();
|
584 |
+
|
585 |
+
$this->keyInfo = array(
|
586 |
+
'baseUri' => array(
|
587 |
+
'group' => 'environment',
|
588 |
+
'type' => 'string',
|
589 |
+
'default' => ServiceConfigInterface::baseUri,
|
590 |
+
),
|
591 |
+
'apiVersion' => array(
|
592 |
+
'group' => 'environment',
|
593 |
+
'type' => 'string',
|
594 |
+
'default' => ServiceConfigInterface::apiVersion,
|
595 |
+
),
|
596 |
+
'outputFormat' => array(
|
597 |
+
'group' => 'environment',
|
598 |
+
'type' => 'string',
|
599 |
+
'default' => ServiceConfigInterface::outputFormat,
|
600 |
+
),
|
601 |
+
'libraryVersion' => array(
|
602 |
+
'group' => 'environment',
|
603 |
+
'type' => 'string',
|
604 |
+
'default' => ServiceConfigInterface::libraryVersion,
|
605 |
+
),
|
606 |
+
'moduleVersion' => array(
|
607 |
+
'group' => 'environment',
|
608 |
+
'type' => 'string',
|
609 |
+
'default' => $shopDefaults['moduleVersion'],
|
610 |
+
),
|
611 |
+
'shopName' => array(
|
612 |
+
'group' => 'environment',
|
613 |
+
'type' => 'string',
|
614 |
+
'default' => $shopDefaults['shopName'],
|
615 |
+
),
|
616 |
+
'shopVersion' => array(
|
617 |
+
'group' => 'environment',
|
618 |
+
'type' => 'string',
|
619 |
+
'default' => $shopDefaults['shopVersion'],
|
620 |
+
),
|
621 |
+
'phpVersion' => array(
|
622 |
+
'group' => 'environment',
|
623 |
+
'type' => 'string',
|
624 |
+
'default' => phpversion(),
|
625 |
+
),
|
626 |
+
'os' => array(
|
627 |
+
'group' => 'environment',
|
628 |
+
'type' => 'string',
|
629 |
+
'default' => php_uname(),
|
630 |
+
),
|
631 |
+
'curlVersion' => array(
|
632 |
+
'group' => 'environment',
|
633 |
+
'type' => 'string',
|
634 |
+
'default' => "{$curlVersion['version']} (ssl: {$curlVersion['ssl_version']}; zlib: {$curlVersion['libz_version']})",
|
635 |
+
),
|
636 |
+
'jsonVersion' => array(
|
637 |
+
'group' => 'environment',
|
638 |
+
'type' => 'string',
|
639 |
+
'default' => phpversion('json'),
|
640 |
+
),
|
641 |
+
'contractcode' => array(
|
642 |
+
'group' => 'credentials',
|
643 |
+
'type' => 'string',
|
644 |
+
'default' => '',
|
645 |
+
),
|
646 |
+
'username' => array(
|
647 |
+
'group' => 'credentials',
|
648 |
+
'type' => 'string',
|
649 |
+
'default' => '',
|
650 |
+
),
|
651 |
+
'password' => array(
|
652 |
+
'group' => 'credentials',
|
653 |
+
'type' => 'string',
|
654 |
+
'default' => '',
|
655 |
+
),
|
656 |
+
'emailonerror' => array(
|
657 |
+
'group' => 'credentials',
|
658 |
+
'type' => 'string',
|
659 |
+
'default' => '',
|
660 |
+
),
|
661 |
+
'defaultCustomerType' => array(
|
662 |
+
'group' => 'customer',
|
663 |
+
'type' => 'int',
|
664 |
+
'default' => 0,
|
665 |
+
),
|
666 |
+
'sendCustomer' => array(
|
667 |
+
'group' => 'customer',
|
668 |
+
'type' => 'bool',
|
669 |
+
'default' => true,
|
670 |
+
),
|
671 |
+
'genericCustomerEmail' => array(
|
672 |
+
'group' => 'customer',
|
673 |
+
'type' => 'string',
|
674 |
+
'default' => 'consumer@' . $hostName,
|
675 |
+
),
|
676 |
+
'overwriteIfExists' => array(
|
677 |
+
'group' => 'customer',
|
678 |
+
'type' => 'bool',
|
679 |
+
'default' => true,
|
680 |
+
),
|
681 |
+
'salutation' => array(
|
682 |
+
'group' => 'customer',
|
683 |
+
'type' => 'string',
|
684 |
+
'default' => '',
|
685 |
+
),
|
686 |
+
'defaultAccountNumber' => array(
|
687 |
+
'group' => 'invoice',
|
688 |
+
'type' => 'int',
|
689 |
+
'default' => 0,
|
690 |
+
),
|
691 |
+
'defaultCostCenter' => array(
|
692 |
+
'group' => 'invoice',
|
693 |
+
'type' => 'int',
|
694 |
+
'default' => 0,
|
695 |
+
),
|
696 |
+
'defaultInvoiceTemplate' => array(
|
697 |
+
'group' => 'invoice',
|
698 |
+
'type' => 'int',
|
699 |
+
'default' => 0,
|
700 |
+
),
|
701 |
+
'defaultInvoicePaidTemplate' => array(
|
702 |
+
'group' => 'invoice',
|
703 |
+
'type' => 'int',
|
704 |
+
'default' => 0,
|
705 |
+
),
|
706 |
+
'removeEmptyShipping' => array(
|
707 |
+
'group' => 'invoice',
|
708 |
+
'type' => 'bool',
|
709 |
+
'default' => false,
|
710 |
+
),
|
711 |
+
// @todo: add to UI?
|
712 |
+
'addMissingAmountLine' => array(
|
713 |
+
'group' => 'invoice',
|
714 |
+
'type' => 'bool',
|
715 |
+
'default' => true,
|
716 |
+
),
|
717 |
+
// @todo: add to UI if shop does support it (PS?, WC?).
|
718 |
+
'useMargin' => array(
|
719 |
+
'group' => 'invoice',
|
720 |
+
'type' => 'bool',
|
721 |
+
'default' => false,
|
722 |
+
),
|
723 |
+
// @todo: add these 3 to UI for shops where this is used (OC2, could others use this?).
|
724 |
+
'optionsAllOn1Line' => array(
|
725 |
+
'group' => 'invoice',
|
726 |
+
'type' => 'int',
|
727 |
+
'default' => 2,
|
728 |
+
),
|
729 |
+
'optionsAllOnOwnLine' => array(
|
730 |
+
'group' => 'invoice',
|
731 |
+
'type' => 'int',
|
732 |
+
'default' => 4,
|
733 |
+
),
|
734 |
+
'optionsMaxLength' => array(
|
735 |
+
'group' => 'invoice',
|
736 |
+
'type' => 'int',
|
737 |
+
'default' => 120,
|
738 |
+
),
|
739 |
+
'digitalServices' => array(
|
740 |
+
'group' => 'shop',
|
741 |
+
'type' => 'int',
|
742 |
+
'default' => InvoiceConfigInterface::DigitalServices_Unknown,
|
743 |
+
),
|
744 |
+
'vatFreeProducts' => array(
|
745 |
+
'group' => 'shop',
|
746 |
+
'type' => 'int',
|
747 |
+
'default' => InvoiceConfigInterface::VatFreeProducts_Unknown,
|
748 |
+
),
|
749 |
+
'invoiceNrSource' => array(
|
750 |
+
'group' => 'shop',
|
751 |
+
'type' => 'int',
|
752 |
+
'default' => InvoiceConfigInterface::InvoiceNrSource_ShopInvoice,
|
753 |
+
),
|
754 |
+
'dateToUse' => array(
|
755 |
+
'group' => 'shop',
|
756 |
+
'type' => 'int',
|
757 |
+
'default' => InvoiceConfigInterface::InvoiceDate_InvoiceCreate,
|
758 |
+
),
|
759 |
+
'triggerInvoiceSendEvent' => array(
|
760 |
+
'group' => 'event',
|
761 |
+
'type' => 'int',
|
762 |
+
'default' => ConfigInterface::TriggerInvoiceSendEvent_None,
|
763 |
+
),
|
764 |
+
'triggerOrderStatus' => array(
|
765 |
+
'group' => 'event',
|
766 |
+
'type' => 'array',
|
767 |
+
'default' => array(),
|
768 |
+
),
|
769 |
+
'emailAsPdf' => array(
|
770 |
+
'group' => 'emailaspdf',
|
771 |
+
'type' => 'bool',
|
772 |
+
'default' => false,
|
773 |
+
),
|
774 |
+
'emailBcc' => array(
|
775 |
+
'group' => 'emailaspdf',
|
776 |
+
'type' => 'string',
|
777 |
+
'default' => '',
|
778 |
+
),
|
779 |
+
'emailFrom' => array(
|
780 |
+
'group' => 'emailaspdf',
|
781 |
+
'type' => 'string',
|
782 |
+
'default' => '',
|
783 |
+
),
|
784 |
+
'subject' => array(
|
785 |
+
'group' => 'emailaspdf',
|
786 |
+
'type' => 'string',
|
787 |
+
'default' => '',
|
788 |
+
),
|
789 |
+
'confirmReading' => array(
|
790 |
+
'group' => 'emailaspdf',
|
791 |
+
'type' => 'bool',
|
792 |
+
'default' => false,
|
793 |
+
),
|
794 |
+
'debug' => array(
|
795 |
+
'group' => 'other',
|
796 |
+
'type' => 'int',
|
797 |
+
'default' => ServiceConfigInterface::Debug_None,
|
798 |
+
),
|
799 |
+
'logLevel' => array(
|
800 |
+
'group' => 'other',
|
801 |
+
'type' => 'int',
|
802 |
+
'default' => Log::Error,
|
803 |
+
),
|
804 |
+
);
|
805 |
+
}
|
806 |
+
return $this->keyInfo;
|
807 |
+
}
|
808 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/ConfigForm.php
ADDED
@@ -0,0 +1,647 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Shop;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\Form;
|
5 |
+
use Siel\Acumulus\Helpers\Log;
|
6 |
+
use Siel\Acumulus\Helpers\TranslatorInterface;
|
7 |
+
use Siel\Acumulus\Invoice\ConfigInterface as InvoiceConfigInterface;
|
8 |
+
use Siel\Acumulus\Web\ConfigInterface as WebConfigInterface;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Provides basic config form handling.
|
12 |
+
*
|
13 |
+
* Shop specific overrides should - of course - implement the abstract method:
|
14 |
+
* - getShopOrderStatuses()
|
15 |
+
* Should typically override:
|
16 |
+
* - getInvoiceNrSourceOptions()
|
17 |
+
* - getDateToUseOptions()
|
18 |
+
* - getTriggerInvoiceSendEventOptions()
|
19 |
+
* And may optionally (have to) override:
|
20 |
+
* - systemValidate()
|
21 |
+
* - isSubmitted()
|
22 |
+
* - setSubmittedValues()
|
23 |
+
*/
|
24 |
+
abstract class ConfigForm extends Form
|
25 |
+
{
|
26 |
+
/** @var \Siel\Acumulus\Shop\Config */
|
27 |
+
protected $acumulusConfig;
|
28 |
+
|
29 |
+
/**
|
30 |
+
* @var array
|
31 |
+
* Contact types picklist result, used to test the connection, storing it in
|
32 |
+
* this property prevents another webservice call.
|
33 |
+
*/
|
34 |
+
protected $contactTypes;
|
35 |
+
|
36 |
+
/**
|
37 |
+
* Constructor.
|
38 |
+
*
|
39 |
+
* @param \Siel\Acumulus\Helpers\TranslatorInterface $translator
|
40 |
+
* @param Config $config
|
41 |
+
*/
|
42 |
+
public function __construct(TranslatorInterface $translator, Config $config)
|
43 |
+
{
|
44 |
+
parent::__construct($translator);
|
45 |
+
|
46 |
+
$translations = new ConfigFormTranslations();
|
47 |
+
$this->translator->add($translations);
|
48 |
+
|
49 |
+
$this->acumulusConfig = $config;
|
50 |
+
}
|
51 |
+
|
52 |
+
/**
|
53 |
+
* {@inheritdoc}
|
54 |
+
*
|
55 |
+
* This is the set of values as are stored in the config.
|
56 |
+
*/
|
57 |
+
protected function getDefaultFormValues()
|
58 |
+
{
|
59 |
+
return $this->acumulusConfig->getCredentials() + $this->acumulusConfig->getShopSettings() + $this->acumulusConfig->getShopEventSettings() + $this->acumulusConfig->getCustomerSettings() + $this->acumulusConfig->getInvoiceSettings() + $this->acumulusConfig->getEmailAsPdfSettings() + $this->acumulusConfig->getOtherSettings();
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* {@inheritdoc}
|
64 |
+
*
|
65 |
+
* The results are restricted to the known config keys.
|
66 |
+
*/
|
67 |
+
protected function setSubmittedValues()
|
68 |
+
{
|
69 |
+
$postedValues = $this->getPostedValues();
|
70 |
+
// Check if the full form was displayed or only the account details.
|
71 |
+
$fullForm = array_key_exists('salutation', $postedValues);
|
72 |
+
foreach ($this->acumulusConfig->getKeys() as $key) {
|
73 |
+
if (!$this->addIfIsset($this->submittedValues, $key, $postedValues)) {
|
74 |
+
// Add unchecked checkboxes, but only if the full form was displayed as
|
75 |
+
// all checkboxes on this form appear in the full form only.
|
76 |
+
if ($fullForm && $this->isCheckboxKey($key)) {
|
77 |
+
$this->submittedValues[$key] = '';
|
78 |
+
}
|
79 |
+
}
|
80 |
+
}
|
81 |
+
}
|
82 |
+
|
83 |
+
/**
|
84 |
+
* {@inheritdoc}
|
85 |
+
*/
|
86 |
+
protected function validate()
|
87 |
+
{
|
88 |
+
$regexpEmail = '/^[^@<>,; "\']+@([^.@ ,;]+\.)+[^.@ ,;]+$/';
|
89 |
+
$regexpMultiEmail = '/^[^@<>,; "\']+@([^.@ ,;]+\.)+[^.@ ,;]+([,;][^@<>,; "\']+@([^.@ ,;]+\.)+[^.@ ,;]+)*$/';
|
90 |
+
|
91 |
+
if (empty($this->submittedValues['contractcode'])) {
|
92 |
+
$this->errorMessages['contractcode'] = $this->t('message_validate_contractcode_0');
|
93 |
+
} elseif (!is_numeric($this->submittedValues['contractcode'])) {
|
94 |
+
$this->errorMessages['contractcode'] = $this->t('message_validate_contractcode_1');
|
95 |
+
}
|
96 |
+
if (empty($this->submittedValues['username'])) {
|
97 |
+
$this->errorMessages['username'] = $this->t('message_validate_username_0');
|
98 |
+
}
|
99 |
+
if (empty($this->submittedValues['password'])) {
|
100 |
+
$this->errorMessages['password'] = $this->t('message_validate_password_0');
|
101 |
+
}
|
102 |
+
if (empty($this->submittedValues['emailonerror'])) {
|
103 |
+
$this->errorMessages['emailonerror'] = $this->t('message_validate_email_1');
|
104 |
+
} else if (!preg_match($regexpEmail, $this->submittedValues['emailonerror'])) {
|
105 |
+
$this->errorMessages['emailonerror'] = $this->t('message_validate_email_0');
|
106 |
+
}
|
107 |
+
if (!empty($this->submittedValues['genericCustomerEmail']) && !preg_match($regexpEmail, $this->submittedValues['genericCustomerEmail'])) {
|
108 |
+
$this->errorMessages['genericCustomerEmail'] = $this->t('message_validate_email_2');
|
109 |
+
}
|
110 |
+
if (!empty($this->submittedValues['emailFrom']) && !preg_match($regexpEmail, $this->submittedValues['emailFrom'])) {
|
111 |
+
$this->errorMessages['emailFrom'] = $this->t('message_validate_email_4');
|
112 |
+
}
|
113 |
+
if (!empty($this->submittedValues['emailBcc']) && !preg_match($regexpMultiEmail, $this->submittedValues['emailBcc'])) {
|
114 |
+
$this->errorMessages['emailBcc'] = $this->t('message_validate_email_3');
|
115 |
+
}
|
116 |
+
if (isset($this->submittedValues['emailAsPdf']) && (bool) $this->submittedValues['emailAsPdf'] && (!array_key_exists('sendCustomer', $this->submittedValues) || !(bool) $this->submittedValues['sendCustomer'])) {
|
117 |
+
$this->errorMessages['conflicting_options'] = $this->t('message_validate_conflicting_options');
|
118 |
+
}
|
119 |
+
}
|
120 |
+
|
121 |
+
/**
|
122 |
+
* {@inheritdoc}
|
123 |
+
*
|
124 |
+
* Saves the submitted and validated form values in the configuration store.
|
125 |
+
*/
|
126 |
+
protected function execute()
|
127 |
+
{
|
128 |
+
return $this->acumulusConfig->save($this->submittedValues);
|
129 |
+
}
|
130 |
+
|
131 |
+
/**
|
132 |
+
* {@inheritdoc}
|
133 |
+
*
|
134 |
+
* This override returns the config form. At the minimum, this includes the
|
135 |
+
* account settings. If these are OK, the other settings are included as well.
|
136 |
+
*/
|
137 |
+
public function getFieldDefinitions()
|
138 |
+
{
|
139 |
+
$fields = array();
|
140 |
+
|
141 |
+
// 1st fieldset: Acumulus account settings.
|
142 |
+
$fields['accountSettingsHeader'] = array(
|
143 |
+
'type' => 'fieldset',
|
144 |
+
'legend' => $this->t('accountSettingsHeader'),
|
145 |
+
// Account fields.
|
146 |
+
'fields' => array(
|
147 |
+
'contractcode' => array(
|
148 |
+
'type' => 'text',
|
149 |
+
'label' => $this->t('field_code'),
|
150 |
+
'attributes' => array(
|
151 |
+
'required' => true,
|
152 |
+
'size' => 20,
|
153 |
+
),
|
154 |
+
),
|
155 |
+
'username' => array(
|
156 |
+
'type' => 'text',
|
157 |
+
'label' => $this->t('field_username'),
|
158 |
+
'attributes' => array(
|
159 |
+
'required' => true,
|
160 |
+
'size' => 20,
|
161 |
+
),
|
162 |
+
),
|
163 |
+
'password' => array(
|
164 |
+
'type' => 'password',
|
165 |
+
'label' => $this->t('field_password'),
|
166 |
+
'attributes' => array(
|
167 |
+
'required' => true,
|
168 |
+
'size' => 20,
|
169 |
+
),
|
170 |
+
),
|
171 |
+
'emailonerror' => array(
|
172 |
+
'type' => 'email',
|
173 |
+
'label' => $this->t('field_email'),
|
174 |
+
'description' => $this->t('desc_email'),
|
175 |
+
'attributes' => array(
|
176 |
+
'required' => true,
|
177 |
+
'size' => 20,
|
178 |
+
),
|
179 |
+
),
|
180 |
+
),
|
181 |
+
);
|
182 |
+
|
183 |
+
$message = $this->checkAccountSettings();
|
184 |
+
$accountOk = empty($message);
|
185 |
+
|
186 |
+
// 2nd fieldset: message or invoice sending related fields.
|
187 |
+
$fields['invoiceSettingsHeader'] = array(
|
188 |
+
'type' => 'fieldset',
|
189 |
+
'legend' => $this->t('invoiceSettingsHeader'),
|
190 |
+
);
|
191 |
+
if (!$accountOk) {
|
192 |
+
$fields['invoiceSettingsHeader']['fields'] = array(
|
193 |
+
'invoiceMessage' => array(
|
194 |
+
'type' => 'markup',
|
195 |
+
'value' => $message,
|
196 |
+
),
|
197 |
+
);
|
198 |
+
} else {
|
199 |
+
$invoiceNrSourceOptions = $this->getInvoiceNrSourceOptions();
|
200 |
+
if (count($invoiceNrSourceOptions) === 1) {
|
201 |
+
// Make it a hidden field.
|
202 |
+
$invoiceNrSourceField = array(
|
203 |
+
'type' => 'hidden',
|
204 |
+
'value' => reset($invoiceNrSourceOptions),
|
205 |
+
);
|
206 |
+
} else {
|
207 |
+
$invoiceNrSourceField = array(
|
208 |
+
'type' => 'radio',
|
209 |
+
'label' => $this->t('field_invoiceNrSource'),
|
210 |
+
'description' => $this->t('desc_invoiceNrSource'),
|
211 |
+
'options' => $invoiceNrSourceOptions,
|
212 |
+
'attributes' => array(
|
213 |
+
'required' => true,
|
214 |
+
),
|
215 |
+
);
|
216 |
+
}
|
217 |
+
|
218 |
+
$dateToUseOptions = $this->getDateToUseOptions();
|
219 |
+
if (count($dateToUseOptions) === 1) {
|
220 |
+
// Make it a hidden field.
|
221 |
+
$dateToUseField = array(
|
222 |
+
'type' => 'hidden',
|
223 |
+
'value' => reset($dateToUseOptions),
|
224 |
+
);
|
225 |
+
} else {
|
226 |
+
$dateToUseField = array(
|
227 |
+
'type' => 'radio',
|
228 |
+
'label' => $this->t('field_dateToUse'),
|
229 |
+
'description' => $this->t($this->t('desc_dateToUse')),
|
230 |
+
'options' => $dateToUseOptions,
|
231 |
+
'attributes' => array(
|
232 |
+
'required' => true,
|
233 |
+
),
|
234 |
+
);
|
235 |
+
}
|
236 |
+
|
237 |
+
$triggerInvoiceSendEventOptions = $this->getTriggerInvoiceSendEventOptions();
|
238 |
+
if (count($triggerInvoiceSendEventOptions) === 1) {
|
239 |
+
// Make it a hidden field.
|
240 |
+
$triggerInvoiceSendEventField = array(
|
241 |
+
'type' => 'hidden',
|
242 |
+
'value' => reset($triggerInvoiceSendEventOptions),
|
243 |
+
);
|
244 |
+
} else {
|
245 |
+
$triggerInvoiceSendEventField = array(
|
246 |
+
'type' => 'radio',
|
247 |
+
'label' => $this->t('field_triggerInvoiceSendEvent'),
|
248 |
+
'description' => $this->t($this->t('desc_triggerInvoiceSendEvent')),
|
249 |
+
'options' => $triggerInvoiceSendEventOptions,
|
250 |
+
'attributes' => array(
|
251 |
+
'required' => true,
|
252 |
+
),
|
253 |
+
);
|
254 |
+
}
|
255 |
+
|
256 |
+
$fields['invoiceSettingsHeader']['fields'] = array(
|
257 |
+
'digitalServices' => array(
|
258 |
+
'type' => 'radio',
|
259 |
+
'label' => $this->t('field_digitalServices'),
|
260 |
+
'description' => $this->t('desc_digitalServices'),
|
261 |
+
'options' => $this->getDigitalServicesOptions(),
|
262 |
+
'attributes' => array(
|
263 |
+
'required' => true,
|
264 |
+
),
|
265 |
+
),
|
266 |
+
'vatFreeProducts' => array(
|
267 |
+
'type' => 'radio',
|
268 |
+
'label' => $this->t('field_vatFreeProducts'),
|
269 |
+
'description' => $this->t('desc_vatFreeProducts'),
|
270 |
+
'options' => $this->getVatFreeProductsOptions(),
|
271 |
+
'attributes' => array(
|
272 |
+
'required' => true,
|
273 |
+
),
|
274 |
+
),
|
275 |
+
'invoiceNrSource' => $invoiceNrSourceField,
|
276 |
+
'dateToUse' => $dateToUseField,
|
277 |
+
'defaultCustomerType' => array(
|
278 |
+
'type' => 'select',
|
279 |
+
'label' => $this->t('field_defaultCustomerType'),
|
280 |
+
'options' => $this->picklistToOptions($this->contactTypes, 'contacttypes', 0, $this->t('option_empty')),
|
281 |
+
),
|
282 |
+
'salutation' => array(
|
283 |
+
'type' => 'text',
|
284 |
+
'label' => $this->t('field_salutation'),
|
285 |
+
'description' => $this->t('desc_salutation'),
|
286 |
+
'attributes' => array(
|
287 |
+
'size' => 30,
|
288 |
+
),
|
289 |
+
),
|
290 |
+
'clientData' => array(
|
291 |
+
'type' => 'checkbox',
|
292 |
+
'label' => $this->t('field_clientData'),
|
293 |
+
'description' => $this->t('desc_clientData'),
|
294 |
+
'options' => array(
|
295 |
+
'sendCustomer' => $this->t('option_sendCustomer'),
|
296 |
+
'overwriteIfExists' => $this->t('option_overwriteIfExists'),
|
297 |
+
),
|
298 |
+
),
|
299 |
+
'defaultAccountNumber' => array(
|
300 |
+
'type' => 'select',
|
301 |
+
'label' => $this->t('field_defaultAccountNumber'),
|
302 |
+
'description' => $this->t('desc_defaultAccountNumber'),
|
303 |
+
'options' => $this->picklistToOptions($this->acumulusConfig->getService()->getPicklistAccounts(), 'accounts', 0, $this->t('option_empty')),
|
304 |
+
),
|
305 |
+
'defaultCostCenter' => array(
|
306 |
+
'type' => 'select',
|
307 |
+
'label' => $this->t('field_defaultCostCenter'),
|
308 |
+
'description' => $this->t('desc_defaultCostCenter'),
|
309 |
+
'options' => $this->picklistToOptions($this->acumulusConfig->getService()->getPicklistCostCenters(), 'costcenters', 0, $this->t('option_empty')),
|
310 |
+
),
|
311 |
+
'defaultInvoiceTemplate' => array(
|
312 |
+
'type' => 'select',
|
313 |
+
'label' => $this->t('field_defaultInvoiceTemplate'),
|
314 |
+
'options' => $this->picklistToOptions($invoiceTemplates = $this->acumulusConfig->getService()->getPicklistInvoiceTemplates(), 'invoicetemplates', 0, $this->t('option_empty')),
|
315 |
+
),
|
316 |
+
'defaultInvoicePaidTemplate' => array(
|
317 |
+
'type' => 'select',
|
318 |
+
'label' => $this->t('field_defaultInvoicePaidTemplate'),
|
319 |
+
'description' => $this->t('desc_defaultInvoiceTemplates'),
|
320 |
+
'options' => $this->picklistToOptions($invoiceTemplates, 'invoicetemplates', 0, $this->t('option_same_template')),
|
321 |
+
),
|
322 |
+
'removeEmptyShipping' => array(
|
323 |
+
'type' => 'checkbox',
|
324 |
+
'label' => $this->t('field_removeEmptyShipping'),
|
325 |
+
'description' => $this->t('desc_removeEmptyShipping'),
|
326 |
+
'options' => array(
|
327 |
+
'removeEmptyShipping' => $this->t('option_removeEmptyShipping'),
|
328 |
+
),
|
329 |
+
),
|
330 |
+
'triggerInvoiceSendEvent' => $triggerInvoiceSendEventField,
|
331 |
+
);
|
332 |
+
if (array_key_exists(ConfigInterface::TriggerInvoiceSendEvent_OrderStatus, $triggerInvoiceSendEventOptions)) {
|
333 |
+
$options = $this->getOrderStatusesList();
|
334 |
+
$fields['invoiceSettingsHeader']['fields']['triggerOrderStatus'] = array(
|
335 |
+
'name' => 'triggerOrderStatus[]',
|
336 |
+
'type' => 'select',
|
337 |
+
'label' => $this->t('field_triggerOrderStatus'),
|
338 |
+
'description' => $this->t('desc_triggerOrderStatus'),
|
339 |
+
'options' => $options,
|
340 |
+
'attributes' => array(
|
341 |
+
'multiple' => true,
|
342 |
+
'size' => min(count($options), 8),
|
343 |
+
),
|
344 |
+
);
|
345 |
+
}
|
346 |
+
}
|
347 |
+
|
348 |
+
// 3rd fieldset: email as PDF settings.
|
349 |
+
if ($accountOk) {
|
350 |
+
$fields['emailAsPdfSettingsHeader'] = array(
|
351 |
+
'type' => 'fieldset',
|
352 |
+
'legend' => $this->t('emailAsPdfSettingsHeader'),
|
353 |
+
'description' => $this->t('desc_emailAsPdfInformation'),
|
354 |
+
'fields' => array(
|
355 |
+
'emailAsPdf' => array(
|
356 |
+
'type' => 'checkbox',
|
357 |
+
'label' => $this->t('field_emailAsPdf'),
|
358 |
+
'description' => $this->t('desc_emailAsPdf'),
|
359 |
+
'options' => array(
|
360 |
+
'emailAsPdf' => $this->t('option_emailAsPdf'),
|
361 |
+
),
|
362 |
+
),
|
363 |
+
'emailFrom' => array(
|
364 |
+
'type' => 'email',
|
365 |
+
'label' => $this->t('field_emailFrom'),
|
366 |
+
'description' => $this->t('desc_emailFrom'),
|
367 |
+
'attributes' => array(
|
368 |
+
'size' => 30,
|
369 |
+
),
|
370 |
+
),
|
371 |
+
'emailBcc' => array(
|
372 |
+
'type' => 'email',
|
373 |
+
'label' => $this->t('field_emailBcc'),
|
374 |
+
'description' => $this->t('desc_emailBcc'),
|
375 |
+
'attributes' => array(
|
376 |
+
'multiple' => true,
|
377 |
+
'size' => 30,
|
378 |
+
),
|
379 |
+
),
|
380 |
+
'subject' => array(
|
381 |
+
'type' => 'text',
|
382 |
+
'label' => $this->t('field_subject'),
|
383 |
+
'description' => $this->t('desc_subject'),
|
384 |
+
'attributes' => array(
|
385 |
+
'size' => 60,
|
386 |
+
),
|
387 |
+
),
|
388 |
+
),
|
389 |
+
);
|
390 |
+
}
|
391 |
+
|
392 |
+
// 4th fieldset: Acumulus version information.
|
393 |
+
$env = $this->acumulusConfig->getEnvironment();
|
394 |
+
$fields['versionInformationHeader'] = array(
|
395 |
+
'type' => 'fieldset',
|
396 |
+
'legend' => $this->t('versionInformationHeader'),
|
397 |
+
'fields' => array(
|
398 |
+
'debug' => array(
|
399 |
+
'type' => 'radio',
|
400 |
+
'label' => $this->t('field_debug'),
|
401 |
+
'description' => $this->t('desc_debug'),
|
402 |
+
'options' => array(
|
403 |
+
WebConfigInterface::Debug_None => $this->t('option_debug_1'),
|
404 |
+
WebConfigInterface::Debug_SendAndLog => $this->t('option_debug_2'),
|
405 |
+
WebConfigInterface::Debug_TestMode => $this->t('option_debug_4'),
|
406 |
+
WebConfigInterface::Debug_StayLocal => $this->t('option_debug_3'),
|
407 |
+
),
|
408 |
+
'attributes' => array(
|
409 |
+
'required' => true,
|
410 |
+
),
|
411 |
+
),
|
412 |
+
'logLevel' => array(
|
413 |
+
'type' => 'radio',
|
414 |
+
'label' => $this->t('field_logLevel'),
|
415 |
+
'description' => $this->t('desc_logLevel'),
|
416 |
+
'options' => array(
|
417 |
+
Log::None => $this->t('option_logLevel_0'),
|
418 |
+
Log::Error => $this->t('option_logLevel_1'),
|
419 |
+
Log::Warning => $this->t('option_logLevel_2'),
|
420 |
+
Log::Notice => $this->t('option_logLevel_3'),
|
421 |
+
Log::Debug => $this->t('option_logLevel_4'),
|
422 |
+
),
|
423 |
+
'attributes' => array(
|
424 |
+
'required' => true,
|
425 |
+
),
|
426 |
+
),
|
427 |
+
'versionInformation' => array(
|
428 |
+
'type' => 'markup',
|
429 |
+
'value' => "<p>Application: Acumulus module {$env['moduleVersion']}; Library: {$env['libraryVersion']}; Shop: {$env['shopName']} {$env['shopVersion']};<br>" .
|
430 |
+
"Environment: PHP {$env['phpVersion']}; Curl: {$env['curlVersion']}; JSON: {$env['jsonVersion']}; OS: {$env['os']}.</p>",
|
431 |
+
),
|
432 |
+
'versionInformationDesc' => array(
|
433 |
+
'type' => 'markup',
|
434 |
+
'value' => $this->t('desc_versionInformation'),
|
435 |
+
),
|
436 |
+
|
437 |
+
),
|
438 |
+
);
|
439 |
+
|
440 |
+
return $fields;
|
441 |
+
}
|
442 |
+
|
443 |
+
/**
|
444 |
+
* Checks if the account settings are known and correct by trying to download
|
445 |
+
* a picklist.
|
446 |
+
*
|
447 |
+
* @return string
|
448 |
+
* Message to show in the 2nd and 3rd fieldset. Empty if successful.
|
449 |
+
*/
|
450 |
+
protected function checkAccountSettings()
|
451 |
+
{
|
452 |
+
// Check if we can retrieve a picklist. This indicates if the account
|
453 |
+
// settings are known and correct.
|
454 |
+
$message = '';
|
455 |
+
$this->contactTypes = null;
|
456 |
+
$credentials = $this->acumulusConfig->getCredentials();
|
457 |
+
if (!empty($credentials['contractcode']) && !empty($credentials['username']) && !empty($credentials['password'])) {
|
458 |
+
$this->contactTypes = $this->acumulusConfig->getService()->getPicklistContactTypes();
|
459 |
+
if (!empty($this->contactTypes['errors'])) {
|
460 |
+
$message = $this->t($this->contactTypes['errors'][0]['code'] == 401 ? 'message_error_auth' : 'message_error_comm');
|
461 |
+
$this->errorMessages += $this->acumulusConfig->getService()->resultToMessages($this->contactTypes, false);
|
462 |
+
}
|
463 |
+
} else {
|
464 |
+
// First fill in your account details.
|
465 |
+
$message = $this->t('message_auth_unknown');
|
466 |
+
}
|
467 |
+
return $message;
|
468 |
+
}
|
469 |
+
|
470 |
+
/**
|
471 |
+
* Converts a picklist response into an options list.
|
472 |
+
*
|
473 |
+
* @param array $picklist
|
474 |
+
* The picklist result structure.
|
475 |
+
* @param string $key
|
476 |
+
* The key in the picklist result structure under which the actual results
|
477 |
+
* can be found.
|
478 |
+
* @param string|null $emptyValue
|
479 |
+
* The value to use for an empty selection.
|
480 |
+
* @param string|null $emptyText
|
481 |
+
* The label to use for an empty selection.
|
482 |
+
*
|
483 |
+
* @return array
|
484 |
+
*/
|
485 |
+
protected function picklistToOptions(array $picklist, $key, $emptyValue = null, $emptyText = null)
|
486 |
+
{
|
487 |
+
$result = array();
|
488 |
+
|
489 |
+
if ($emptyValue !== null) {
|
490 |
+
$result[$emptyValue] = $emptyText;
|
491 |
+
}
|
492 |
+
if (!empty($key)) {
|
493 |
+
// Take the results under the key. This is to be able to follow the
|
494 |
+
// structure returned by the picklist services.
|
495 |
+
$picklist = $picklist[$key];
|
496 |
+
}
|
497 |
+
array_walk($picklist, function ($value) use (&$result) {
|
498 |
+
list($optionValue, $optionText) = array_values($value);
|
499 |
+
$result[$optionValue] = $optionText;
|
500 |
+
});
|
501 |
+
|
502 |
+
return $result;
|
503 |
+
}
|
504 |
+
|
505 |
+
/**
|
506 |
+
* {@inheritdoc}
|
507 |
+
*/
|
508 |
+
protected function getCheckboxKeys()
|
509 |
+
{
|
510 |
+
return array(
|
511 |
+
'sendCustomer' => 'clientData',
|
512 |
+
'overwriteIfExists' => 'clientData',
|
513 |
+
'removeEmptyShipping' => 'removeEmptyShipping',
|
514 |
+
'emailAsPdf' => 'emailAsPdf',
|
515 |
+
);
|
516 |
+
}
|
517 |
+
|
518 |
+
/**
|
519 |
+
* Returns an option list of all order statuses including an empty choice.
|
520 |
+
*
|
521 |
+
* Do not override this method but implement getShopOrderStatuses() instead.
|
522 |
+
*
|
523 |
+
* @return array
|
524 |
+
* An options array of all order statuses.
|
525 |
+
*/
|
526 |
+
protected function getOrderStatusesList()
|
527 |
+
{
|
528 |
+
$result = array();
|
529 |
+
|
530 |
+
$result['0'] = $this->t('option_empty_triggerOrderStatus');
|
531 |
+
$result += $this->getShopOrderStatuses();
|
532 |
+
|
533 |
+
return $result;
|
534 |
+
}
|
535 |
+
|
536 |
+
/**
|
537 |
+
* Returns an option list of all shop order statuses.
|
538 |
+
*
|
539 |
+
* @return array
|
540 |
+
* An array of all shop order statuses, with the key being the ID for
|
541 |
+
* the dropdown item and the value being the label for the dropdown item.
|
542 |
+
*/
|
543 |
+
abstract protected function getShopOrderStatuses();
|
544 |
+
|
545 |
+
/**
|
546 |
+
* Returns a list of valid sources that can be used as invoice number.
|
547 |
+
*
|
548 |
+
* This may differ per shop as not all shops support invoices as a separate
|
549 |
+
* entity.
|
550 |
+
*
|
551 |
+
* Overrides should typically return a subset of the constants defined in this
|
552 |
+
* base implementation, but including at least
|
553 |
+
* ConfigInterface::InvoiceNrSource_Acumulus.
|
554 |
+
*
|
555 |
+
* @return array
|
556 |
+
* An array keyed by the option values and having translated descriptions as
|
557 |
+
* values.
|
558 |
+
*/
|
559 |
+
protected function getInvoiceNrSourceOptions()
|
560 |
+
{
|
561 |
+
return array(
|
562 |
+
InvoiceConfigInterface::InvoiceNrSource_ShopInvoice => $this->t('option_invoiceNrSource_1'),
|
563 |
+
InvoiceConfigInterface::InvoiceNrSource_ShopOrder => $this->t('option_invoiceNrSource_2'),
|
564 |
+
InvoiceConfigInterface::InvoiceNrSource_Acumulus => $this->t('option_invoiceNrSource_3'),
|
565 |
+
);
|
566 |
+
}
|
567 |
+
|
568 |
+
/**
|
569 |
+
* Returns a list of valid date sources that can be used as invoice date.
|
570 |
+
*
|
571 |
+
* This may differ per shop as not all shops support invoices as a separate
|
572 |
+
* entity.
|
573 |
+
*
|
574 |
+
* Overrides should typically return a subset of the constants defined in this
|
575 |
+
* base implementation, but including at least
|
576 |
+
* ConfigInterface::InvoiceDate_Transfer.
|
577 |
+
*
|
578 |
+
* @return array
|
579 |
+
* An array keyed by the option values and having translated descriptions as
|
580 |
+
* values.
|
581 |
+
*/
|
582 |
+
protected function getDateToUseOptions()
|
583 |
+
{
|
584 |
+
return array(
|
585 |
+
InvoiceConfigInterface::InvoiceDate_InvoiceCreate => $this->t('option_dateToUse_1'),
|
586 |
+
InvoiceConfigInterface::InvoiceDate_OrderCreate => $this->t('option_dateToUse_2'),
|
587 |
+
InvoiceConfigInterface::InvoiceDate_Transfer => $this->t('option_dateToUse_3'),
|
588 |
+
);
|
589 |
+
}
|
590 |
+
|
591 |
+
/**
|
592 |
+
* Returns a list of events that can trigger the automatic sending of an
|
593 |
+
* invoice.
|
594 |
+
*
|
595 |
+
* This may differ per shop as not all shops define events for all moments
|
596 |
+
* that can be used to trigger the sending of an invoice.
|
597 |
+
*
|
598 |
+
* Overrides should typically return a subset of the constants defined in this
|
599 |
+
* base implementation. The return array may be empty or only contain
|
600 |
+
* ConfigInterface::TriggerInvoiceSendEvent_None, to indicate that no
|
601 |
+
* automatic sending is possible (shop does not define any event like model).
|
602 |
+
*
|
603 |
+
* @return array
|
604 |
+
* An array keyed by the option values and having translated descriptions as
|
605 |
+
* values.
|
606 |
+
*/
|
607 |
+
protected function getTriggerInvoiceSendEventOptions()
|
608 |
+
{
|
609 |
+
return array(
|
610 |
+
ConfigInterface::TriggerInvoiceSendEvent_None => $this->t('option_triggerInvoiceSendEvent_0'),
|
611 |
+
ConfigInterface::TriggerInvoiceSendEvent_OrderStatus => $this->t('option_triggerInvoiceSendEvent_1'),
|
612 |
+
ConfigInterface::TriggerInvoiceSendEvent_InvoiceCreate => $this->t('option_triggerInvoiceSendEvent_2'),
|
613 |
+
);
|
614 |
+
}
|
615 |
+
|
616 |
+
/**
|
617 |
+
* Returns a list of options for the digital services field.
|
618 |
+
*
|
619 |
+
* @return array
|
620 |
+
* An array keyed by the option values and having translated descriptions as
|
621 |
+
* values.
|
622 |
+
*/
|
623 |
+
protected function getDigitalServicesOptions()
|
624 |
+
{
|
625 |
+
return array(
|
626 |
+
InvoiceConfigInterface::DigitalServices_Both => $this->t('option_digitalServices_1'),
|
627 |
+
InvoiceConfigInterface::DigitalServices_No => $this->t('option_digitalServices_2'),
|
628 |
+
InvoiceConfigInterface::DigitalServices_Only => $this->t('option_digitalServices_3'),
|
629 |
+
);
|
630 |
+
}
|
631 |
+
|
632 |
+
/**
|
633 |
+
* Returns a list of options for the vat free products field.
|
634 |
+
*
|
635 |
+
* @return array
|
636 |
+
* An array keyed by the option values and having translated descriptions as
|
637 |
+
* values.
|
638 |
+
*/
|
639 |
+
protected function getVatFreeProductsOptions()
|
640 |
+
{
|
641 |
+
return array(
|
642 |
+
InvoiceConfigInterface::VatFreeProducts_Both => $this->t('option_vatFreeProducts_1'),
|
643 |
+
InvoiceConfigInterface::VatFreeProducts_No => $this->t('option_vatFreeProducts_2'),
|
644 |
+
InvoiceConfigInterface::VatFreeProducts_Only => $this->t('option_vatFreeProducts_3'),
|
645 |
+
);
|
646 |
+
}
|
647 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/ConfigFormTranslations.php
ADDED
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Shop;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\TranslationCollection;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Contains translations for the configuration form.
|
8 |
+
*/
|
9 |
+
class ConfigFormTranslations extends TranslationCollection
|
10 |
+
{
|
11 |
+
protected $nl = array(
|
12 |
+
'config_form_title' => 'Acumulus | Instellingen',
|
13 |
+
'config_form_header' => 'Acumulus instellingen',
|
14 |
+
|
15 |
+
'message_validate_contractcode_0' => 'Het veld Contractcode is verplicht, vul de contractcode in die u ook gebruikt om in te loggen op Acumulus.',
|
16 |
+
'message_validate_contractcode_1' => 'Het veld Contractcode is een numeriek veld, vul de contractcode in die u ook gebruikt om in te loggen op Acumulus.',
|
17 |
+
'message_validate_username_0' => 'Het veld Gebruikersnaam is verplicht, vul de gebruikersnaam in die u ook gebruikt om in te loggen op Acumulus.',
|
18 |
+
'message_validate_password_0' => 'Het veld Wachtwoord is verplicht, vul het wachtwoord in dat u ook gebruikt om in te loggen op Acumulus.',
|
19 |
+
'message_validate_email_0' => 'Het veld Email is geen valide e-mailadres, vul uw eigen e-mailadres in.',
|
20 |
+
'message_validate_email_1' => 'Het veld Email is verplicht, vul uw eigen e-mailadres in.',
|
21 |
+
'message_validate_email_2' => 'Het veld (fictieve klant) Email is geen valide e-mailadres, vul een correct e-mailadres in.',
|
22 |
+
'message_validate_email_3' => 'Het veld BCC is geen valide e-mailadres, vul een correct e-mailadres in.',
|
23 |
+
'message_validate_email_4' => 'Het veld Afzender is geen valide e-mailadres, vul een correct e-mailadres in.',
|
24 |
+
'message_validate_conflicting_options' => 'Als u geen klantgegevens naar Acumulus verstuurt, kunt u Acumulus geen PDF factuur laten versturen. Pas één van beide opties aan.',
|
25 |
+
|
26 |
+
'message_form_success' => 'De instellingen zijn opgeslagen.',
|
27 |
+
'message_form_error' => 'Er is een fout opgetreden bij het opslaan van de instellingen',
|
28 |
+
'message_uninstall' => 'Wilt u de configuratie-instellingen verwijderen?',
|
29 |
+
|
30 |
+
'message_error_auth' => 'Uw Acumulus account gegevens zijn onjuist. Zodra u de correcte gevens hebt ingevuld, worden hier de overige instellingen getoond.',
|
31 |
+
'message_error_comm' => 'Er is een fout opgetreden bij het ophalen van uw gegevens van Acumulus. Probeer het later nog eens. Zodra de verbinding hersteld is worden hier de overige instellingen getoond.',
|
32 |
+
'message_auth_unknown' => 'Zodra u uw Acumulus accountgegevens hebt ingevuld, worden hier de overige instellingen getoond.',
|
33 |
+
|
34 |
+
'accountSettingsHeader' => 'Uw Acumulus account',
|
35 |
+
'field_code' => 'Contractcode',
|
36 |
+
'field_username' => 'Gebruikersnaam',
|
37 |
+
'field_password' => 'Wachtwoord',
|
38 |
+
'field_email' => 'E-mail',
|
39 |
+
'desc_email' => 'Het e-mailadres waarop u geïnformeerd wordt over fouten die zijn opgetreden tijdens het versturen van facturen. Omdat deze module niet kan weten of het vanuit een beheerdersscherm is aangeroepen, zal het geen berichten op het scherm plaatsen. Daarom is het invullen van een e-mailadres verplicht.',
|
40 |
+
|
41 |
+
'invoiceSettingsHeader' => 'Uw factuurinstellingen',
|
42 |
+
'option_empty' => 'Maak uw keuze',
|
43 |
+
|
44 |
+
'field_digitalServices' => 'Verkoopt u digitale diensten?',
|
45 |
+
'option_digitalServices_1' => 'Zowel digitale diensten als normale producten.',
|
46 |
+
'option_digitalServices_2' => 'Alleen producten die onder Nederlandse BTW vallen.',
|
47 |
+
'option_digitalServices_3' => 'Alleen digitale diensten die met buitenlandse BTW belast moeten worden voor buitenlandse klanten.',
|
48 |
+
'desc_digitalServices' => 'Geef aan of u in uw winkel digitale diensten aanbiedt waarbij u buitenlandse BTW moet hanteren voor EU klanten.
|
49 |
+
Zie <a href="http://www.belastingdienst.nl/wps/wcm/connect/bldcontentnl/belastingdienst/zakelijk/btw/zakendoen_met_het_buitenland/goederen_en_diensten_naar_andere_eu_landen/btw_berekenen_bij_diensten/wijziging_in_digitale_diensten_vanaf_2015/wijziging_in_digitale_diensten_vanaf_2015">Belastingdienst: diensten naar andere EU landen</a>.
|
50 |
+
Met behulp van deze instelling kan de koppeling beter: het type factuur bepalen; controles uitvoeren; en BTW tarieven, voor zover berekend op basis van afgeronde bedragen, terugrekenen.',
|
51 |
+
|
52 |
+
'field_vatFreeProducts' => 'Verkoopt u van BTW vrijgestelde producten of diensten?',
|
53 |
+
'option_vatFreeProducts_1' => 'Zowel BTW vrije als aan BTW onderhevige producten en/of diensten.',
|
54 |
+
'option_vatFreeProducts_2' => 'Alleen aan BTW onderhevige producten en/of diensten.',
|
55 |
+
'option_vatFreeProducts_3' => 'Alleen producten of diensten die van BTW vrijgesteld zijn.',
|
56 |
+
'desc_vatFreeProducts' => 'Geef aan of u in uw winkel producten en/of diensten aanbiedt die vrijgesteld zijn van BTW, bv. onderwijs.
|
57 |
+
Met behulp van deze instelling kan de koppeling beter het type factuur bepalen en controles uitvoeren.',
|
58 |
+
|
59 |
+
'field_invoiceNrSource' => 'Factuurnummer',
|
60 |
+
'option_invoiceNrSource_1' => 'Gebruik het factuurnummer van uw webwinkel. Let op: als er nog geen factuur aan een bestelling gekoppeld is, zal het bestelnummer gebruikt worden!',
|
61 |
+
'option_invoiceNrSource_2' => 'Gebruik het bestelnummer van uw webwinkel.',
|
62 |
+
'option_invoiceNrSource_3' => 'Laat Acumulus het factuurnummer bepalen.',
|
63 |
+
'desc_invoiceNrSource' => 'U kunt hier kiezen welk nummer Acumulus als factuurnummer moet gebruiken.',
|
64 |
+
|
65 |
+
'field_dateToUse' => 'Factuurdatum',
|
66 |
+
'option_dateToUse_1' => 'Gebruik de aanmaakdatum van de factuur. Let op: als er nog geen factuur aan uw bestelling gekoppeld is, zal de aanmaakdatum van de bestelling gebruikt worden!',
|
67 |
+
'option_dateToUse_2' => 'Gebruik de aanmaakdatum van de bestelling.',
|
68 |
+
'option_dateToUse_3' => 'Gebruik de datum van het overzetten.',
|
69 |
+
'desc_dateToUse' => 'U kunt hier kiezen welke datum de factuur in Acumulus moet krijgen.',
|
70 |
+
|
71 |
+
'field_salutation' => 'Aanhef',
|
72 |
+
'desc_salutation' => 'U kunt hier de aanhef specificeren zoals u die op de Acumulus factuur wilt hebben. U kunt [#property] gebruiken om een waarde uit de order of klantgegevens te gebruiken, b.v. "Beste [#firstName],".',
|
73 |
+
|
74 |
+
'field_clientData' => 'Klantadresgegevens',
|
75 |
+
'option_sendCustomer' => 'Uw (niet zakelijke) klanten automatisch aan uw relaties in Acumulus toevoegen.',
|
76 |
+
'option_overwriteIfExists' => 'Overschrijf bestaande adresgegevens.',
|
77 |
+
'desc_clientData' => 'Binnen Acumulus is het mogelijk om uw klantrelaties te beheren.
|
78 |
+
Deze koppeling voegt automatisch uw klanten aan het relatieoverzicht van Acumulus toe.
|
79 |
+
Dit is niet altijd gewenst en kunt u voorkomen door de eerste optie uit te zetten.
|
80 |
+
Hierdoor worden alle transacties van consumenten binnen uw webwinkel onder 1 vaste fictieve relatie ingeboekt in Acumulus.
|
81 |
+
De tweede optie moet u alleen uitzetten als u direct in Acumulus adresgegevens van uw webwinkel-klanten bijwerkt.
|
82 |
+
Als u de eerste optie heeft uitgezet, geldt de tweede optie alleen voor uw zakelijke klanten.',
|
83 |
+
|
84 |
+
'field_defaultCustomerType' => 'Importeer klanten als',
|
85 |
+
|
86 |
+
'field_defaultAccountNumber' => 'Bankrekeningnummer',
|
87 |
+
'desc_defaultAccountNumber' => 'Maakt u binnen Acumulus gebruik van meerdere rekeningen en wilt u alle bestellingen uit uw webwinkel op een specifieke rekening binnen laten komen, kies dan hier het bankrekeningnummer.',
|
88 |
+
|
89 |
+
'field_defaultCostCenter' => 'Kostenplaats',
|
90 |
+
'desc_defaultCostCenter' => 'Maakt u binnen Acumulus gebruik van meerdere kostenplaatsen en wilt u alle bestellingen uit uw webwinkel op een specifieke kostenplaats binnen laten komen, kies dan hier de kostenplaats.',
|
91 |
+
|
92 |
+
'field_defaultInvoiceTemplate' => 'Factuur-sjabloon (niet betaald)',
|
93 |
+
'field_defaultInvoicePaidTemplate' => 'Factuur-sjabloon (betaald)',
|
94 |
+
'option_same_template' => 'Zelfde sjabloon als voor niet betaald',
|
95 |
+
'desc_defaultInvoiceTemplates' => 'Maakt u binnen Acumulus gebruik van meerdere factuur-sjablonen en wilt u alle bestellingen uit uw webwinkel op een specifieke factuursjabloon printen, kies dan hier de factuur-sjablonen voor niet betaalde respectievelijk betaalde bestellingen.',
|
96 |
+
|
97 |
+
'field_removeEmptyShipping' => 'Verzendkosten',
|
98 |
+
'option_removeEmptyShipping' => 'Verstuur geen "gratis verzending" of "zelf afhalen" regels.',
|
99 |
+
'desc_removeEmptyShipping' => 'Omdat Acumulus pakbonnen kan printen, verstuurt deze extensie normaal gesproken altijd een factuurregel met de verzendkosten/methode, zelfs met gratis verzending of zelf afhalen.
|
100 |
+
Vink deze optie aan als u geen regel op uw factuur of pakbon wil voor gratis verzending of zelf afhalen.',
|
101 |
+
|
102 |
+
'field_triggerInvoiceSendEvent' => 'Moment van versturen',
|
103 |
+
'option_triggerInvoiceSendEvent_0' => 'Niet automatisch versturen.',
|
104 |
+
'option_triggerInvoiceSendEvent_1' => 'Als een bestelling de hieronder door u gekozen status bereikt.',
|
105 |
+
'option_triggerInvoiceSendEvent_2' => 'Als de factuur wordt aangemaakt voor deze bestelling.',
|
106 |
+
'desc_triggerInvoiceSendEvent' => 'U kunt hier kiezen op welk moment de factuur wordt verstuurd. Als u voor "Niet automatisch overzetten" kiest, moet u de facturen zelf overzetten m.b.v. het batchformulier.',
|
107 |
+
'option_empty_triggerOrderStatus' => 'Niet automatisch versturen',
|
108 |
+
'field_triggerOrderStatus' => 'Bestelstatus',
|
109 |
+
'desc_triggerOrderStatus' => 'U kunt hier kiezen bij welke bestelstatussen facturen worden overgezet naar Acumulus. Als u meerdere statussen kiest wordt een order overgezet zodra deze één van de gekozen statussen bereikt. Een order wordt maximaal 1 keer overgezet. Deze koppeling gebruikt alleen gegevens van de bestelling, dus u kunt elke status kiezen. De factuur hoeft dus nog niet aangemaakt te zijn.',
|
110 |
+
|
111 |
+
'emailAsPdfSettingsHeader' => 'PDF Factuur',
|
112 |
+
'desc_emailAsPdfInformation' => 'Bij het versturen van bestellinggegevens naar Acumulus, kunt u Acumulus een PDF factuur laten versturen naar uw klant. Deze wordt direct verstuurd naar het door de klant opgegeven emailadres.',
|
113 |
+
|
114 |
+
'field_emailAsPdf' => 'Optie inschakelen',
|
115 |
+
'option_emailAsPdf' => 'Verstuur de factuur als PDF vanuit Acumulus.',
|
116 |
+
'desc_emailAsPdf' => 'Als u deze optie aanvinkt, kunt u de overige opties gebruiken om de emailverzending aan uw wensen aan te passen. Het bericht in de email body kunt u niet hier instellen, dat kunt u in Acumulus doen onder "Beheer - Factuur-sjablonen".',
|
117 |
+
|
118 |
+
'field_emailFrom' => 'Afzender',
|
119 |
+
'desc_emailFrom' => 'Het email adres dat als afzender gebruikt moet worden. Als u dit leeg laat wordt het emailadres uit het Acumulus sjabloon gebruikt. Wij adviseren dit veld leeg te laten.',
|
120 |
+
|
121 |
+
'field_emailBcc' => 'BCC',
|
122 |
+
'desc_emailBcc' => 'Additionele emailadressen om de factuur naar toe te sturen, bv. het emailadres van uw eigen administratie-afdeling. Als u dit leeg laat wordt de factuur alleen naar de klant verstuurd.',
|
123 |
+
|
124 |
+
'field_subject' => 'Onderwerp',
|
125 |
+
'desc_subject' => 'Het onderwerp van de email. Als u dit leeg laat wordt "Factuur [factuurnummer] Bestelling [bestelnummer]" gebruikt. U kunt [#b] gebruiken om het bestelnummer in de onderwerpregel te plaatsen en [#f] voor het factuurnummer (van de webshop, niet van Acumulus).',
|
126 |
+
|
127 |
+
'versionInformationHeader' => 'Informatie over deze module',
|
128 |
+
'desc_versionInformation' => 'Vermeld aub deze gegevens bij een supportverzoek.',
|
129 |
+
|
130 |
+
'field_debug' => 'Support en debug',
|
131 |
+
'option_debug_1' => 'Verzend berichten naar Acumulus en ontvang alleen een mail bij fouten of waarschuwingen.',
|
132 |
+
'option_debug_2' => 'Verzend berichten naar Acumulus en ontvang een mail met het verzonden en ontvangen bericht.',
|
133 |
+
'option_debug_3' => 'Verzend geen berichten naar Acumulus, verstuur alleen een mail met het bericht dat verstuurd zou worden.',
|
134 |
+
'option_debug_4' => 'Verzend berichten naar Acumulus maar Acumulus zal alleen de invoer controleren op fouten en waarschuwingen en geen veranderingen opslaan.',
|
135 |
+
'desc_debug' => 'U kunt hier een support mode kiezen. Kies voor de eerste optie tenzij u i.v.m. een supportverzoek bent geïnstrueerd om iets anders te kiezen.',
|
136 |
+
|
137 |
+
'field_logLevel' => 'Logniveau',
|
138 |
+
'option_logLevel_0' => 'Log geen enkel bericht.',
|
139 |
+
'option_logLevel_1' => 'Log alleen foutmeldingen.',
|
140 |
+
'option_logLevel_2' => 'Log foutmeldingen en waarschuwingen.',
|
141 |
+
'option_logLevel_3' => 'Log foutmeldingen, waarschuwingen en mededelingen.',
|
142 |
+
'option_logLevel_4' => 'Log foutmeldingen, waarschuwingen, mededelingen en communicatieberichten.',
|
143 |
+
'desc_logLevel' => 'U kunt hier een logniveau kiezen. Kies voor de 1e of 2e optie tenzij u i.v.m. een supportverzoek bent geïnstrueerd om iets anders te kiezen.',
|
144 |
+
|
145 |
+
);
|
146 |
+
|
147 |
+
protected $en = array(
|
148 |
+
'config_form_title' => 'Acumulus | Settings',
|
149 |
+
'config_form_header' => 'Acumulus settings',
|
150 |
+
|
151 |
+
'button_save' => 'Save',
|
152 |
+
'button_cancel' => 'Cancel',
|
153 |
+
|
154 |
+
'message_validate_contractcode_0' => 'The field Contract code is required, please fill in the contract code you use to log in to Acumulus.',
|
155 |
+
'message_validate_contractcode_1' => 'The field Contract code is a numeric field, please fill in the contract code you use to log in to Acumulus.',
|
156 |
+
'message_validate_username_0' => 'The field User name is required, please fill in the user name you use to log in to Acumulus.',
|
157 |
+
'message_validate_password_0' => 'The field Password is required, please fill in the password you use to log in to Acumulus.',
|
158 |
+
'message_validate_email_0' => 'The field Email is not a valid e-mail address, please fill in your own e-mail address.',
|
159 |
+
'message_validate_email_1' => 'The field Email is required, please fill in your own e-mail address.',
|
160 |
+
'message_validate_email_2' => 'The field (fictitious customer) Email is not a valid e-mail address, please fill in a correct e-mail address.',
|
161 |
+
'message_validate_email_3' => 'The field BCC is not a valid e-mail address, please fill in a valid e-mail address.',
|
162 |
+
'message_validate_email_4' => 'The field Sender is not a valid e-mail address, please fill in a valid e-mail address.',
|
163 |
+
'message_validate_conflicting_options' => 'If you don\'t send customer data to Acumulus, Acumulus cannot send PDF invoices. Change one of the options.',
|
164 |
+
|
165 |
+
'message_form_success' => 'The settings are saved.',
|
166 |
+
'message_form_error' => 'an error occurred wile saving the settings.',
|
167 |
+
'message_uninstall' => 'Are you sure to delete the configuration settings?',
|
168 |
+
|
169 |
+
'message_error_auth' => 'Your Acumulus connection settings are incorrect. Please check them. After you have entered the correct connection settings the other settings will be shown as well.',
|
170 |
+
'message_error_comm' => 'The module encountered an error retrieving your Acumulus configuration. Please try again. When the connection is restored the other settings will be shown as well.',
|
171 |
+
'message_auth_unknown' => 'When your Acumulus connection settings are filled in, the other settings will be shown as well.',
|
172 |
+
|
173 |
+
'accountSettingsHeader' => 'Your Acumulus connection settings',
|
174 |
+
'field_code' => 'Contract code',
|
175 |
+
'field_username' => 'User name',
|
176 |
+
'field_password' => 'Password',
|
177 |
+
'field_email' => 'E-mail',
|
178 |
+
'desc_email' => 'The e-mail address at which you will be informed about any errors that occur during invoice sending. As this module cannot know if it is called from an interactive administrator screen, it will not display any messages in the user interface. Therefore you have to fill in an e-mail address.',
|
179 |
+
|
180 |
+
'invoiceSettingsHeader' => 'Your invoice settings',
|
181 |
+
'option_empty' => 'Select one',
|
182 |
+
|
183 |
+
'field_digitalServices' => 'Do you sell digital services?',
|
184 |
+
'option_digitalServices_1' => 'Both digitals services and normal products.',
|
185 |
+
'option_digitalServices_2' => 'Only products that are subject to dutch VAT.',
|
186 |
+
'option_digitalServices_3' => 'Only digital services subject to the regulations concerning using foreign VAT rates.',
|
187 |
+
'desc_digitalServices' => 'Select whether your store offers digital services that are subject to foreign VAT for clients in other EU countries.
|
188 |
+
See <a href="http://www.belastingdienst.nl/wps/wcm/connect/bldcontentnl/belastingdienst/zakelijk/btw/zakendoen_met_het_buitenland/goederen_en_diensten_naar_andere_eu_landen/btw_berekenen_bij_diensten/wijziging_in_digitale_diensten_vanaf_2015/wijziging_in_digitale_diensten_vanaf_2015">Dutch tax office: services to other EU countries (in dutch)</a>.
|
189 |
+
Using this setting this plugin can better determine the invoice type; perform some validations; and extract exact VAT rates where they are calculated using rounded amounts.',
|
190 |
+
|
191 |
+
'field_vatFreeProducts' => 'Do you sell VAT free products or services?',
|
192 |
+
'option_vatFreeProducts_1' => 'Both VAT free and VAT liable products or services.',
|
193 |
+
'option_vatFreeProducts_2' => 'Only products or services that are VAT liable.',
|
194 |
+
'option_vatFreeProducts_3' => 'Only VAT free products or services.',
|
195 |
+
'desc_vatFreeProducts' => 'Select whether your store offers products or services that are VAT free, e.g. education.
|
196 |
+
Using this setting this plugin can better determine the invoice type and perform some validations.',
|
197 |
+
|
198 |
+
'field_invoiceNrSource' => 'Invoice number',
|
199 |
+
'option_invoiceNrSource_1' => 'Use the web shop invoice number. Note: if no invoice has been created for the order yet, the order number will be used!',
|
200 |
+
'option_invoiceNrSource_2' => 'Use the web shop order number as invoice number.',
|
201 |
+
'option_invoiceNrSource_3' => 'Have Acumulus create an invoice number.',
|
202 |
+
'desc_invoiceNrSource' => 'Select which number to use for the invoice in Acumulus.',
|
203 |
+
|
204 |
+
'field_dateToUse' => 'Invoice date',
|
205 |
+
'option_dateToUse_1' => 'Use the invoice date. Note: if no invoice has been created for the order yet, the order create date will be used!',
|
206 |
+
'option_dateToUse_2' => 'Use the order create date.',
|
207 |
+
'option_dateToUse_3' => 'Use the transfer date.',
|
208 |
+
'desc_dateToUse' => 'Select which date to use for the invoice in Acumulus.',
|
209 |
+
|
210 |
+
'field_salutation' => 'Salutations',
|
211 |
+
'desc_salutation' => 'Specify the salutations for the Acumulus invoice. You can use [#property] to get a property value out of the order or customer values, e.g. "Dear [#firstName],".',
|
212 |
+
|
213 |
+
'field_clientData' => 'Customer address data',
|
214 |
+
'option_sendCustomer' => 'Send consumer client records to Acumulus.',
|
215 |
+
'option_overwriteIfExists' => 'Overwrite existing address data.',
|
216 |
+
'desc_clientData' => 'Acumulus allows you to store client data.
|
217 |
+
This extension automatically sends client data to Acumulus.
|
218 |
+
If you don\'t want this, uncheck this option.
|
219 |
+
All consumer invoices will be booked on one and the same fictitious client.
|
220 |
+
You should uncheck the second option if you edit customer address data manually in Acumulus.
|
221 |
+
If you unchecked the first option, the second option only applies to business clients.',
|
222 |
+
|
223 |
+
'field_defaultCustomerType' => 'Create customers as',
|
224 |
+
|
225 |
+
'field_defaultAccountNumber' => 'Bank account number',
|
226 |
+
'desc_defaultAccountNumber' => 'Select the (bank) account number at which you want to receive all your order payments.',
|
227 |
+
|
228 |
+
'field_defaultCostCenter' => 'Cost center',
|
229 |
+
'desc_defaultCostCenter' => 'Select the cost center to assign your orders to.',
|
230 |
+
|
231 |
+
'field_defaultInvoiceTemplate' => 'Invoice template (due)',
|
232 |
+
'field_defaultInvoicePaidTemplate' => 'Invoice template (paid)',
|
233 |
+
'option_same_template' => 'Same template as for due',
|
234 |
+
'desc_defaultInvoiceTemplates' => 'Select the invoice templates to print your web shop orders with due respectively paid orders.',
|
235 |
+
|
236 |
+
'field_removeEmptyShipping' => 'Shipping costs',
|
237 |
+
'option_removeEmptyShipping' => 'Do not send free shipping or in store pick-up lines on invoices.',
|
238 |
+
'desc_removeEmptyShipping' => 'To allow Acumulus to print packing slips, this extension normally always sends a shipping line, even with free shipping or in store pickup.
|
239 |
+
If you don\'t want this, check this option.',
|
240 |
+
|
241 |
+
'field_triggerInvoiceSendEvent' => 'Send the invoice to Acumulus',
|
242 |
+
'option_triggerInvoiceSendEvent_0' => 'Do not send automatically.',
|
243 |
+
'option_triggerInvoiceSendEvent_1' => 'When an order reaches the state as defined below.',
|
244 |
+
'option_triggerInvoiceSendEvent_2' => 'When the invoice gets created for this order.',
|
245 |
+
'desc_triggerInvoiceSendEvent' => 'Select when to send the invoice to Acumulus. If you select "Do not send automatically" you will have to use the send batch form.',
|
246 |
+
|
247 |
+
'option_empty_triggerOrderStatus' => 'Do not send automatically',
|
248 |
+
'field_triggerOrderStatus' => 'Order state',
|
249 |
+
'desc_triggerOrderStatus' => 'Select the order states at which orders will be sent to Acumulus. If you select multiple states, an order will be sent as soon as one of the selected states is reached. An order will only be sent once. This extension only uses order data. so you may select any status. The invoice does not already have to be created.',
|
250 |
+
|
251 |
+
'emailAsPdfSettingsHeader' => 'PDF Invoice',
|
252 |
+
'desc_emailAsPdfInformation' => 'On sending the order details to Acumulus, Acumulus can send a PDF invoice to your customer. The mail will be sent to the clients\' email address.',
|
253 |
+
|
254 |
+
'field_emailAsPdf' => 'Enable the feature',
|
255 |
+
'option_emailAsPdf' => 'Have Acumulus send the invoice as PDF.',
|
256 |
+
'desc_emailAsPdf' => 'If you check this option, you can use the other options below to configure the emails to your preferences. However, to configure the text in them mail body, go to Acumulus to "Beheer - Factuur-sjablonen".',
|
257 |
+
|
258 |
+
'field_emailFrom' => 'Sender',
|
259 |
+
'desc_emailFrom' => 'The email address to use as sender. If you leave this empty, the email address of the Acumulus template will be used. We advice you to leave this empty.',
|
260 |
+
|
261 |
+
'field_emailBcc' => 'BCC',
|
262 |
+
'desc_emailBcc' => 'Additional email addresses to send the invoice to, e.g. the email address of your own administration department. If you leave this empty the invoice email will only be sent to your client.',
|
263 |
+
|
264 |
+
'field_subject' => 'Subject',
|
265 |
+
'desc_subject' => 'The subject line of the email. If you leave this empty "Invoice [invoice#] Order [order#]" will be used. You can use [#b] to place the order number in the subject and [#f] for the invoice number (from the webshop, not Acumulus).',
|
266 |
+
|
267 |
+
'versionInformationHeader' => 'Module information',
|
268 |
+
'desc_versionInformation' => 'Please mention this information with any support request.',
|
269 |
+
|
270 |
+
'field_debug' => 'Debug and support',
|
271 |
+
'option_debug_1' => 'Send messages to Acumulus and only receive a mail when there are errors or warnings.',
|
272 |
+
'option_debug_2' => 'Send messages to Acumulus and receive a mail with the results.',
|
273 |
+
'option_debug_3' => 'Do not send messages to Acumulus, but receive a mail with the message as would have been sent.',
|
274 |
+
'option_debug_4' => 'Send messages to Acumulus, but Acumulus will only check the input for errors and warnings, not store any changes.',
|
275 |
+
'desc_debug' => 'Select a debug mode. Choose for the 1st option unless otherwise instructed by support staff.',
|
276 |
+
|
277 |
+
'field_logLevel' => 'Log level',
|
278 |
+
'option_logLevel_0' => 'Don\'t log any message.',
|
279 |
+
'option_logLevel_1' => 'Only log error messages.',
|
280 |
+
'option_logLevel_2' => 'Log error messages and warnings.',
|
281 |
+
'option_logLevel_3' => 'Log error messages, warnings and notices.',
|
282 |
+
'option_logLevel_4' => 'Log error messages, warnings, notices and communication messages.',
|
283 |
+
'desc_logLevel' => 'Select a log level. Choose for the 1st or 2nd option unless otherwise instructed by support staff.',
|
284 |
+
|
285 |
+
);
|
286 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/ConfigInterface.php
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Shop;
|
3 |
+
|
4 |
+
/**
|
5 |
+
* Defines an interface to retrieve shop specific configuration settings.
|
6 |
+
*
|
7 |
+
* Configuration is stored in the host environment, normally a web shop.
|
8 |
+
* This interface abstracts from how a specific web shop does so.
|
9 |
+
*/
|
10 |
+
interface ConfigInterface
|
11 |
+
{
|
12 |
+
// Web shop configuration related constants.
|
13 |
+
const TriggerInvoiceSendEvent_None = 0;
|
14 |
+
const TriggerInvoiceSendEvent_OrderStatus = 1;
|
15 |
+
const TriggerInvoiceSendEvent_InvoiceCreate = 2;
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Returns the set of settings related to reacting to shop events.
|
19 |
+
*
|
20 |
+
* @return array
|
21 |
+
* A keyed array with the keys:
|
22 |
+
* - triggerInvoiceSendEvent
|
23 |
+
* - triggerOrderStatus
|
24 |
+
*/
|
25 |
+
public function getShopEventSettings();
|
26 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/ConfigStore.php
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Shop;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\Log;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Defines an interface to access the shop specific's config store.
|
8 |
+
*/
|
9 |
+
abstract class ConfigStore implements ConfigStoreInterface
|
10 |
+
{
|
11 |
+
/** @var string */
|
12 |
+
protected $shopName;
|
13 |
+
|
14 |
+
/**
|
15 |
+
* ConfigStore constructor.
|
16 |
+
*
|
17 |
+
* @param string $shopNamespace
|
18 |
+
*/
|
19 |
+
public function __construct($shopNamespace)
|
20 |
+
{
|
21 |
+
$pos = strrpos($shopNamespace, '\\');
|
22 |
+
$this->shopName = $pos !== false ? substr($shopNamespace, $pos + 1) : $shopNamespace;
|
23 |
+
}
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Performs common save tasks.
|
27 |
+
*
|
28 |
+
* @param array $values
|
29 |
+
*
|
30 |
+
* @return array
|
31 |
+
*/
|
32 |
+
protected function saveCommon(array $values)
|
33 |
+
{
|
34 |
+
$copy = $values;
|
35 |
+
if (!empty($copy['password'])) {
|
36 |
+
$copy['password'] = 'REMOVED FOR SECURITY';
|
37 |
+
}
|
38 |
+
Log::getInstance()->notice('ConfigStore::save(): saving %s', serialize($copy));
|
39 |
+
|
40 |
+
// Remove password if not sent along.
|
41 |
+
if (array_key_exists('password', $values) && empty($values['password'])) {
|
42 |
+
unset($values['password']);
|
43 |
+
}
|
44 |
+
return $values;
|
45 |
+
}
|
46 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/ConfigStoreInterface.php
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Shop;
|
3 |
+
|
4 |
+
/**
|
5 |
+
* Defines an interface to access the shop specific's config store.
|
6 |
+
*/
|
7 |
+
interface ConfigStoreInterface
|
8 |
+
{
|
9 |
+
/**
|
10 |
+
* Returns an array with shop specific environment settings.
|
11 |
+
*
|
12 |
+
* @return array
|
13 |
+
* An array with keys:
|
14 |
+
* - moduleVersion
|
15 |
+
* - shopName
|
16 |
+
* - shopVersion
|
17 |
+
*/
|
18 |
+
public function getShopEnvironment();
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Loads the configuration from the actual configuration provider.
|
22 |
+
*
|
23 |
+
* @param array $keys
|
24 |
+
* An array of keys that are expected to be loaded.
|
25 |
+
*
|
26 |
+
* @return array
|
27 |
+
* An array with the raw (not necessarily casted) configuration values keyed
|
28 |
+
* by their name. Note that these values will overwrite the default values,
|
29 |
+
* so no NULL values should be returned, though other "empty" values (false,
|
30 |
+
* 0) may be returned.
|
31 |
+
*/
|
32 |
+
public function load(array $keys);
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Stores the values to the actual configuration provider.
|
36 |
+
*
|
37 |
+
* @param array $values
|
38 |
+
* A keyed array that contains the values to store. Note that values may be
|
39 |
+
* "empty", eg 0 or false. Only NULL values may be ignored
|
40 |
+
*
|
41 |
+
* @return bool
|
42 |
+
* Success.
|
43 |
+
*/
|
44 |
+
public function save(array $values);
|
45 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/ConfirmUninstallForm.php
ADDED
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @file
|
4 |
+
* Contains ${NAMESPACE}${NAME}.
|
5 |
+
*/
|
6 |
+
|
7 |
+
namespace Siel\Acumulus\Shop;
|
8 |
+
|
9 |
+
use Siel\Acumulus\Helpers\Form;
|
10 |
+
use Siel\Acumulus\Helpers\TranslatorInterface;
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Class ConfirmUninstallForm
|
14 |
+
*/
|
15 |
+
class ConfirmUninstallForm extends Form
|
16 |
+
{
|
17 |
+
/**
|
18 |
+
* Constructor.
|
19 |
+
*
|
20 |
+
* @param \Siel\Acumulus\Helpers\TranslatorInterface $translator
|
21 |
+
*/
|
22 |
+
public function __construct(TranslatorInterface $translator)
|
23 |
+
{
|
24 |
+
parent::__construct($translator);
|
25 |
+
|
26 |
+
$translations = new ConfirmUninstallFormTranslations();
|
27 |
+
$this->translator->add($translations);
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Executes the form action on valid form submission.
|
32 |
+
*
|
33 |
+
* Override to implement the actual form handling, like saving values.
|
34 |
+
*
|
35 |
+
* @return bool
|
36 |
+
* Success.
|
37 |
+
*/
|
38 |
+
protected function execute()
|
39 |
+
{
|
40 |
+
// @todo: Implement execute() method.
|
41 |
+
}
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Internal version of getFields();
|
45 |
+
*
|
46 |
+
* Internal method, do not call directly.
|
47 |
+
* DO override this method not getFields().
|
48 |
+
*
|
49 |
+
* @return array[]
|
50 |
+
* The definition of the form.
|
51 |
+
*/
|
52 |
+
protected function getFieldDefinitions()
|
53 |
+
{
|
54 |
+
$fields = array();
|
55 |
+
|
56 |
+
// 1st fieldset: Confirm uninstall message.
|
57 |
+
$fields['uninstallHeader'] = array(
|
58 |
+
'type' => 'fieldset',
|
59 |
+
'legend' => $this->t('uninstallHeader'),
|
60 |
+
'fields' => array(
|
61 |
+
'uninstall_message' => array(
|
62 |
+
'type' => 'markup',
|
63 |
+
'value' => $this->t('desc_uninstall'),
|
64 |
+
),
|
65 |
+
),
|
66 |
+
);
|
67 |
+
|
68 |
+
return $fields;
|
69 |
+
}
|
70 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/ConfirmUninstallFormTranslations.php
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Shop;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\TranslationCollection;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Contains translations for the confirm uninstall form.
|
8 |
+
*/
|
9 |
+
class ConfirmUninstallFormTranslations extends TranslationCollection
|
10 |
+
{
|
11 |
+
protected $nl = array(
|
12 |
+
'uninstallHeader' => 'Bevestig verwijderen',
|
13 |
+
'desc_uninstall' => '<p>De module is uitgeschakeld. Maak een keuze of u ook alle data en instellingen wilt verwijderen of dat u deze (voorlopig) wilt bewaren.</p>',
|
14 |
+
);
|
15 |
+
|
16 |
+
protected $en = array(
|
17 |
+
'uninstallHeader' => 'Confirm uninstall',
|
18 |
+
'desc_uninstall' => '<p>The module has been disabled. Choose whether you also want to delete all data and settings or if you want to keep these for now.</p>',
|
19 |
+
);
|
20 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/InjectorInterface.php
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Shop;
|
3 |
+
|
4 |
+
/**
|
5 |
+
* InjectorInterface defines an interface to retrieve:
|
6 |
+
* - Instances of web shop specific overrides of the base classes and interfaces
|
7 |
+
* that are defined in the common package.
|
8 |
+
* - Singleton instances from other namespaces.
|
9 |
+
* - Instances that require some injection arguments in their constructor, that
|
10 |
+
* the implementing object can pass.
|
11 |
+
*/
|
12 |
+
interface InjectorInterface
|
13 |
+
{
|
14 |
+
/**
|
15 |
+
* @return \Siel\Acumulus\Helpers\TranslatorInterface
|
16 |
+
*/
|
17 |
+
public function getTranslator();
|
18 |
+
|
19 |
+
/**
|
20 |
+
* @return \Siel\Acumulus\Web\Service
|
21 |
+
*/
|
22 |
+
public function getService();
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Creates a wrapper object for a source object identified by the given
|
26 |
+
* parameters.
|
27 |
+
*
|
28 |
+
* @param string $invoiceSourceType
|
29 |
+
* The type of the invoice source to create.
|
30 |
+
* @param string|object|array $invoiceSourceOrId
|
31 |
+
* The invoice source itself or its id to create a Source wrapper for.
|
32 |
+
*
|
33 |
+
* @return \Siel\Acumulus\Invoice\Source
|
34 |
+
* A wrapper object around a shop specific invoice source object.
|
35 |
+
*/
|
36 |
+
public function getSource($invoiceSourceType, $invoiceSourceOrId);
|
37 |
+
|
38 |
+
/**
|
39 |
+
* @return \Siel\Acumulus\Invoice\Completor
|
40 |
+
*/
|
41 |
+
public function getCompletor();
|
42 |
+
|
43 |
+
/**
|
44 |
+
* @return \Siel\Acumulus\Invoice\Creator
|
45 |
+
*/
|
46 |
+
public function getCreator();
|
47 |
+
|
48 |
+
/**
|
49 |
+
* @return \Siel\Acumulus\Helpers\Mailer
|
50 |
+
*/
|
51 |
+
public function getMailer();
|
52 |
+
|
53 |
+
/**
|
54 |
+
* @return \Siel\Acumulus\Shop\ConfigStoreInterface
|
55 |
+
*/
|
56 |
+
public function getConfigStore();
|
57 |
+
|
58 |
+
/**
|
59 |
+
* @return \Siel\Acumulus\Shop\InvoiceManager
|
60 |
+
*/
|
61 |
+
public function getManager();
|
62 |
+
|
63 |
+
/**
|
64 |
+
* @return \Siel\Acumulus\Shop\AcumulusEntryModel
|
65 |
+
*/
|
66 |
+
public function getAcumulusEntryModel();
|
67 |
+
|
68 |
+
/**
|
69 |
+
* @param string $type
|
70 |
+
* The type of form requested.
|
71 |
+
*
|
72 |
+
* @return \Siel\Acumulus\Helpers\Form
|
73 |
+
*
|
74 |
+
* @todo: start using this in all plugins.
|
75 |
+
*/
|
76 |
+
public function getForm($type);
|
77 |
+
|
78 |
+
/**
|
79 |
+
* @return \Siel\Acumulus\Helpers\FormRenderer
|
80 |
+
*
|
81 |
+
* @todo: start using this in all plugins.
|
82 |
+
*/
|
83 |
+
public function getFormRenderer();
|
84 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/InvoiceManager.php
ADDED
@@ -0,0 +1,429 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Shop;
|
3 |
+
|
4 |
+
use DateTime;
|
5 |
+
use Siel\Acumulus\Invoice\Source;
|
6 |
+
use Siel\Acumulus\Web\ConfigInterface as WebConfigInterface;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Provides functionality to manage invoices.
|
10 |
+
*/
|
11 |
+
abstract class InvoiceManager
|
12 |
+
{
|
13 |
+
/** @var \Siel\Acumulus\Shop\Config */
|
14 |
+
protected $config;
|
15 |
+
|
16 |
+
/** @var \Siel\Acumulus\Invoice\Completor */
|
17 |
+
protected $completor;
|
18 |
+
|
19 |
+
/**
|
20 |
+
* @param Config $config
|
21 |
+
*/
|
22 |
+
public function __construct(Config $config)
|
23 |
+
{
|
24 |
+
$this->config = $config;
|
25 |
+
|
26 |
+
$translations = new BatchTranslations();
|
27 |
+
$config->getTranslator()->add($translations);
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Helper method to translate strings.
|
32 |
+
*
|
33 |
+
* @param string $key
|
34 |
+
* The key to get a translation for.
|
35 |
+
*
|
36 |
+
* @return string
|
37 |
+
* The translation for the given key or the key itself if no translation
|
38 |
+
* could be found.
|
39 |
+
*/
|
40 |
+
protected function t($key)
|
41 |
+
{
|
42 |
+
return $this->config->getTranslator()->get($key);
|
43 |
+
}
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Returns a list of invoice source types supported by this shop.
|
47 |
+
*
|
48 |
+
* The default implementation returns order and credit note. Override if the
|
49 |
+
* specific shop supports other types or does not support credit notes.
|
50 |
+
*
|
51 |
+
* @return string[]
|
52 |
+
* The list of supported invoice source types.
|
53 |
+
*/
|
54 |
+
public function getSupportedInvoiceSourceTypes()
|
55 |
+
{
|
56 |
+
return array(
|
57 |
+
Source::Order,
|
58 |
+
Source::CreditNote,
|
59 |
+
);
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Returns a list of existing invoice sources for the given id range.
|
64 |
+
*
|
65 |
+
* @param string $invoiceSourceType
|
66 |
+
* @param string $InvoiceSourceIdFrom
|
67 |
+
* @param string $InvoiceSourceIdTo
|
68 |
+
*
|
69 |
+
* @return \Siel\Acumulus\Invoice\Source[]
|
70 |
+
* An array of invoice sources of the given source type.
|
71 |
+
*/
|
72 |
+
abstract public function getInvoiceSourcesByIdRange($invoiceSourceType, $InvoiceSourceIdFrom, $InvoiceSourceIdTo);
|
73 |
+
|
74 |
+
/**
|
75 |
+
* Returns a list of existing invoice sources for the given reference range.
|
76 |
+
*
|
77 |
+
* Should be overridden when the reference is not the internal id.
|
78 |
+
*
|
79 |
+
* @param string $invoiceSourceType
|
80 |
+
* @param string $InvoiceSourceReferenceFrom
|
81 |
+
* @param string $InvoiceSourceReferenceTo
|
82 |
+
*
|
83 |
+
* @return \Siel\Acumulus\Invoice\Source[]
|
84 |
+
* An array of invoice sources of the given source type.
|
85 |
+
*/
|
86 |
+
public function getInvoiceSourcesByReferenceRange($invoiceSourceType, $InvoiceSourceReferenceFrom, $InvoiceSourceReferenceTo)
|
87 |
+
{
|
88 |
+
return $this->getInvoiceSourcesByIdRange($invoiceSourceType, $InvoiceSourceReferenceFrom, $InvoiceSourceReferenceTo);
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Returns a list of existing invoice sources for the given date range.
|
93 |
+
*
|
94 |
+
* @param string $invoiceSourceType
|
95 |
+
* @param DateTime $dateFrom
|
96 |
+
* @param DateTime $dateTo
|
97 |
+
*
|
98 |
+
* @return \Siel\Acumulus\Invoice\Source[]
|
99 |
+
* An array of invoice sources of the given source type.
|
100 |
+
*/
|
101 |
+
abstract public function getInvoiceSourcesByDateRange($invoiceSourceType, DateTime $dateFrom, DateTime $dateTo);
|
102 |
+
|
103 |
+
/**
|
104 |
+
* Creates a set of Invoice Sources given their ids or shop specific sources.
|
105 |
+
*
|
106 |
+
* @param string $invoiceSourceType
|
107 |
+
* @param array $idsOrSources
|
108 |
+
*
|
109 |
+
* @return \Siel\Acumulus\Invoice\Source[]
|
110 |
+
* A non keyed array with invoice Sources.
|
111 |
+
*/
|
112 |
+
public function getSourcesByIdsOrSources($invoiceSourceType, array $idsOrSources)
|
113 |
+
{
|
114 |
+
$results = array();
|
115 |
+
foreach ($idsOrSources as $sourceId) {
|
116 |
+
$results[] = $this->getSourceByIdOrSource($invoiceSourceType, $sourceId);
|
117 |
+
}
|
118 |
+
return $results;
|
119 |
+
}
|
120 |
+
|
121 |
+
/**
|
122 |
+
* Creates a source given its type and id.
|
123 |
+
*
|
124 |
+
* @param string $invoiceSourceType
|
125 |
+
* @param int|array|object $idOrSource
|
126 |
+
*
|
127 |
+
* @return \Siel\Acumulus\Invoice\Source
|
128 |
+
* An invoice Source.
|
129 |
+
*/
|
130 |
+
protected function getSourceByIdOrSource($invoiceSourceType, $idOrSource)
|
131 |
+
{
|
132 |
+
return $this->config->getSource($invoiceSourceType, $idOrSource);
|
133 |
+
}
|
134 |
+
|
135 |
+
/**
|
136 |
+
* Sends multiple invoices to Acumulus.
|
137 |
+
*
|
138 |
+
* @param \Siel\Acumulus\Invoice\Source[] $invoiceSources
|
139 |
+
* @param bool $forceSend
|
140 |
+
* @param string[] $log
|
141 |
+
*
|
142 |
+
* @return bool
|
143 |
+
* Success.
|
144 |
+
*/
|
145 |
+
public function sendMultiple(array $invoiceSources, $forceSend, array &$log)
|
146 |
+
{
|
147 |
+
$this->config->getTranslator()->add(new BatchTranslations());
|
148 |
+
$errorLogged = false;
|
149 |
+
$success = true;
|
150 |
+
$time_limit = ini_get('max_execution_time');
|
151 |
+
/** @var Source $invoiceSource */
|
152 |
+
foreach ($invoiceSources as $invoiceSource) {
|
153 |
+
// Try to keep the script running, but note that other systems involved,
|
154 |
+
// think the (Apache) web server, may have their own time-out.
|
155 |
+
// Use @ to prevent messages like "Warning: set_time_limit(): Cannot set
|
156 |
+
// max execution time limit due to system policy in ...".
|
157 |
+
if (!@set_time_limit($time_limit) && !$errorLogged) {
|
158 |
+
$this->config->getLog()->warning('InvoiceManager::sendMultiple(): could not set time limit.');
|
159 |
+
$errorLogged = true;
|
160 |
+
}
|
161 |
+
|
162 |
+
$status = $this->send($invoiceSource, $forceSend);
|
163 |
+
switch ($status) {
|
164 |
+
case WebConfigInterface::Status_Success:
|
165 |
+
$message = 'message_batch_send_1_success';
|
166 |
+
break;
|
167 |
+
case WebConfigInterface::Status_Errors:
|
168 |
+
case WebConfigInterface::Status_Exception:
|
169 |
+
$message = 'message_batch_send_1_errors';
|
170 |
+
$success = false;
|
171 |
+
break;
|
172 |
+
case WebConfigInterface::Status_Warnings:
|
173 |
+
$message = 'message_batch_send_1_warnings';
|
174 |
+
break;
|
175 |
+
case WebConfigInterface::Status_NotSent:
|
176 |
+
$message = 'message_batch_send_1_skipped';
|
177 |
+
break;
|
178 |
+
case WebConfigInterface::Status_SendingPrevented_InvoiceCreated:
|
179 |
+
$message = 'message_batch_send_1_prevented_invoiceCreated';
|
180 |
+
break;
|
181 |
+
case WebConfigInterface::Status_SendingPrevented_InvoiceCompleted:
|
182 |
+
$message = 'message_batch_send_1_prevented_invoiceCompleted';
|
183 |
+
break;
|
184 |
+
default:
|
185 |
+
$message = "Status unknown $status (sending invoice for %1\$s %2\$s)";
|
186 |
+
break;
|
187 |
+
}
|
188 |
+
$log[$invoiceSource->getId()] = sprintf($this->t($message), $this->t($invoiceSource->getType()), $invoiceSource->getReference());
|
189 |
+
}
|
190 |
+
return $success;
|
191 |
+
}
|
192 |
+
|
193 |
+
/**
|
194 |
+
* Processes an invoice source status change event.
|
195 |
+
*
|
196 |
+
* For now we don't look at the status for credit notes: they are always sent.
|
197 |
+
*
|
198 |
+
* @param \Siel\Acumulus\Invoice\Source $invoiceSource
|
199 |
+
* The source whose status changed.
|
200 |
+
*
|
201 |
+
* @return int
|
202 |
+
* Status, one of the WebConfigInterface::Status_ constants.
|
203 |
+
*/
|
204 |
+
public function sourceStatusChange(Source $invoiceSource)
|
205 |
+
{
|
206 |
+
$status = $invoiceSource->getStatus();
|
207 |
+
$statusString = $status === null ? 'null' : (string) $status;
|
208 |
+
$this->config->getLog()->notice('InvoiceManager::sourceStatusChange(%s %d, %s)', $invoiceSource->getType(), $invoiceSource->getId(), $statusString);
|
209 |
+
$result = WebConfigInterface::Status_NotSent;
|
210 |
+
$shopEventSettings = $this->config->getShopEventSettings();
|
211 |
+
if ($invoiceSource->getType() === Source::CreditNote
|
212 |
+
|| ($shopEventSettings['triggerInvoiceSendEvent'] == Config::TriggerInvoiceSendEvent_OrderStatus
|
213 |
+
&& in_array($status, $shopEventSettings['triggerOrderStatus']))
|
214 |
+
) {
|
215 |
+
$result = $this->send($invoiceSource, false);
|
216 |
+
} else {
|
217 |
+
$this->config->getLog()->notice('InvoiceManager::sourceStatusChange(%s %d, %s): not sending triggerEvent = %d, triggerOrderStatus = [%s]',
|
218 |
+
$invoiceSource->getType(),
|
219 |
+
$invoiceSource->getId(),
|
220 |
+
$statusString,
|
221 |
+
$shopEventSettings['triggerInvoiceSendEvent'],
|
222 |
+
is_array($shopEventSettings['triggerOrderStatus']) ? implode(',', $shopEventSettings['triggerOrderStatus']) : 'no array'
|
223 |
+
);
|
224 |
+
}
|
225 |
+
return $result;
|
226 |
+
}
|
227 |
+
|
228 |
+
/**
|
229 |
+
* Processes an invoice create event.
|
230 |
+
*
|
231 |
+
* @param \Siel\Acumulus\Invoice\Source $invoiceSource
|
232 |
+
* The source for which a shop invoice was created.
|
233 |
+
*
|
234 |
+
* @return int
|
235 |
+
* Status
|
236 |
+
*/
|
237 |
+
public function invoiceCreate(Source $invoiceSource)
|
238 |
+
{
|
239 |
+
$this->config->getLog()->notice('InvoiceManager::invoiceCreate(%s %d)', $invoiceSource->getType(), $invoiceSource->getId());
|
240 |
+
$result = WebConfigInterface::Status_NotSent;
|
241 |
+
$shopEventSettings = $this->config->getShopEventSettings();
|
242 |
+
if ($shopEventSettings['triggerInvoiceSendEvent'] == Config::TriggerInvoiceSendEvent_InvoiceCreate) {
|
243 |
+
$result = $this->send($invoiceSource, false);
|
244 |
+
}
|
245 |
+
return $result;
|
246 |
+
}
|
247 |
+
|
248 |
+
/**
|
249 |
+
* Creates and sends an invoice to Acumulus for an order.
|
250 |
+
*
|
251 |
+
* @param \Siel\Acumulus\Invoice\Source $invoiceSource
|
252 |
+
* The source object (order, credit note) for which the invoice was created.
|
253 |
+
* @param bool $forceSend
|
254 |
+
* force sending the invoice even if an invoice has already been sent for
|
255 |
+
* the given order.
|
256 |
+
*
|
257 |
+
* @return int
|
258 |
+
* Status.
|
259 |
+
*/
|
260 |
+
public function send(Source $invoiceSource, $forceSend = false)
|
261 |
+
{
|
262 |
+
$result = WebConfigInterface::Status_NotSent;
|
263 |
+
if ($forceSend || !$this->config->getAcumulusEntryModel()->getByInvoiceSource($invoiceSource)) {
|
264 |
+
$invoice = $this->config->getCreator()->create($invoiceSource);
|
265 |
+
|
266 |
+
$this->triggerInvoiceCreated($invoice, $invoiceSource);
|
267 |
+
|
268 |
+
if ($invoice !== null) {
|
269 |
+
$localMessages = array();
|
270 |
+
$invoice = $this->config->getCompletor()->complete($invoice, $invoiceSource, $localMessages);
|
271 |
+
|
272 |
+
$this->triggerInvoiceCompleted($invoice, $invoiceSource);
|
273 |
+
|
274 |
+
if ($invoice !== null) {
|
275 |
+
$service = $this->config->getService();
|
276 |
+
$result = $service->invoiceAdd($invoice);
|
277 |
+
$result = $service->mergeLocalMessages($result, $localMessages);
|
278 |
+
|
279 |
+
$this->triggerInvoiceSent($invoice, $invoiceSource, $result);
|
280 |
+
|
281 |
+
// Check if an entryid was created and store entry id and token.
|
282 |
+
if (!empty($result['invoice']['entryid'])) {
|
283 |
+
$this->config->getAcumulusEntryModel()->save($invoiceSource, $result['invoice']['entryid'], $result['invoice']['token']);
|
284 |
+
}
|
285 |
+
|
286 |
+
// Send a mail if there are messages.
|
287 |
+
$messages = $service->resultToMessages($result);
|
288 |
+
if (!empty($messages)) {
|
289 |
+
$this->config->getLog()->notice('InvoiceManager::send(%s %d, %s) result: %s',
|
290 |
+
$invoiceSource->getType(), $invoiceSource->getId(), $forceSend ? 'true' : 'false', $service->messagesToText($messages));
|
291 |
+
$this->mailInvoiceAddResult($result, $messages, $invoiceSource);
|
292 |
+
}
|
293 |
+
|
294 |
+
$result = $result['status'];
|
295 |
+
} else {
|
296 |
+
$result = WebConfigInterface::Status_SendingPrevented_InvoiceCompleted;
|
297 |
+
$this->config->getLog()->notice('InvoiceManager::send(%s %d, %s): invoiceCompleted prevented sending',
|
298 |
+
$invoiceSource->getType(), $invoiceSource->getId(), $forceSend ? 'true' : 'false');
|
299 |
+
}
|
300 |
+
} else {
|
301 |
+
$result = WebConfigInterface::Status_SendingPrevented_InvoiceCreated;
|
302 |
+
$this->config->getLog()->notice('InvoiceManager::send(%s %d, %s): invoiceCreated prevented sending',
|
303 |
+
$invoiceSource->getType(), $invoiceSource->getId(), $forceSend ? 'true' : 'false');
|
304 |
+
}
|
305 |
+
} else {
|
306 |
+
$this->config->getLog()->notice('InvoiceManager::send(%s %d, %s): not sent',
|
307 |
+
$invoiceSource->getType(), $invoiceSource->getId(), $forceSend ? 'true' : 'false');
|
308 |
+
}
|
309 |
+
return $result;
|
310 |
+
}
|
311 |
+
|
312 |
+
/**
|
313 |
+
* Sends an email with the results of a sent invoice.
|
314 |
+
*
|
315 |
+
* The mail is sent to the shop administrator (emailonerror setting).
|
316 |
+
*
|
317 |
+
* @param array $result
|
318 |
+
* @param string[] $messages
|
319 |
+
* @param \Siel\Acumulus\Invoice\Source $invoiceSource
|
320 |
+
*
|
321 |
+
* @return bool
|
322 |
+
* Success.
|
323 |
+
*/
|
324 |
+
protected function mailInvoiceAddResult(array $result, array $messages, Source $invoiceSource)
|
325 |
+
{
|
326 |
+
return $this->config->getMailer()->sendInvoiceAddMailResult($result, $messages, $invoiceSource->getType(), $invoiceSource->getReference());
|
327 |
+
}
|
328 |
+
|
329 |
+
/**
|
330 |
+
* Triggers an event that an invoice for Acumulus has been created and is
|
331 |
+
* ready to be completed and sent.
|
332 |
+
*
|
333 |
+
* This allows to inject custom behavior to alter the invoice just before
|
334 |
+
* completing and sending.
|
335 |
+
*
|
336 |
+
* @param array $invoice
|
337 |
+
* The invoice that has been created.
|
338 |
+
* @param Source $invoiceSource
|
339 |
+
* The source object (order, credit note) for which the invoice was created.
|
340 |
+
*/
|
341 |
+
protected function triggerInvoiceCreated(array &$invoice, Source $invoiceSource)
|
342 |
+
{
|
343 |
+
// Default implementation: no event.
|
344 |
+
}
|
345 |
+
|
346 |
+
/**
|
347 |
+
* Triggers an event that an invoice for Acumulus has been created and
|
348 |
+
* completed and is ready to be sent.
|
349 |
+
*
|
350 |
+
* This allows to inject custom behavior to alter the invoice just before
|
351 |
+
* sending.
|
352 |
+
*
|
353 |
+
* @param array $invoice
|
354 |
+
* The invoice that has been created and completed.
|
355 |
+
* @param Source $invoiceSource
|
356 |
+
* The source object (order, credit note) for which the invoice was created.
|
357 |
+
*/
|
358 |
+
protected function triggerInvoiceCompleted(array &$invoice, Source $invoiceSource)
|
359 |
+
{
|
360 |
+
// Default implementation: no event.
|
361 |
+
}
|
362 |
+
|
363 |
+
/**
|
364 |
+
* Triggers an event after an invoice for Acumulus has been sent.
|
365 |
+
*
|
366 |
+
* This allows to inject custom behavior to react to invoice sending.
|
367 |
+
*
|
368 |
+
* @param array $invoice
|
369 |
+
* The invoice that has been sent.
|
370 |
+
* @param Source $invoiceSource
|
371 |
+
* The source object (order, credit note) for which the invoice was sent.
|
372 |
+
* @param array $result
|
373 |
+
* The result as sent back by Acumulus. This array contains the following
|
374 |
+
* keys:
|
375 |
+
* - invoice: array
|
376 |
+
* - invoicenumber: string
|
377 |
+
* - token: string
|
378 |
+
* - entryid: string
|
379 |
+
* - errors: array
|
380 |
+
* - error: array
|
381 |
+
* - code: string
|
382 |
+
* - codetag: string
|
383 |
+
* - message: string
|
384 |
+
* - counterrors: int
|
385 |
+
* - warnings: array
|
386 |
+
* - warning: array
|
387 |
+
* - code: string
|
388 |
+
* - codetag: string
|
389 |
+
* - message: string
|
390 |
+
* - countwarnings: int
|
391 |
+
*/
|
392 |
+
protected function triggerInvoiceSent(array $invoice, Source $invoiceSource, array $result)
|
393 |
+
{
|
394 |
+
// Default implementation: no event.
|
395 |
+
}
|
396 |
+
|
397 |
+
/**
|
398 |
+
* Returns the given DateTime in a format that the actual database layer
|
399 |
+
* accepts for comparison in a SELECT query.
|
400 |
+
*
|
401 |
+
* This default implementation returns the DateTime as a string in ISO format
|
402 |
+
* (yyyy-mm-dd hh:mm:ss).
|
403 |
+
*
|
404 |
+
* @param \DateTime $date
|
405 |
+
*
|
406 |
+
* @return int|string
|
407 |
+
*/
|
408 |
+
protected function getSqlDate(DateTime $date)
|
409 |
+
{
|
410 |
+
return $date->format('Y-m-d H:i:s');
|
411 |
+
}
|
412 |
+
|
413 |
+
/**
|
414 |
+
* Helper method to retrieve the values from 1 column of a query result.
|
415 |
+
*
|
416 |
+
* @param array $dbResults
|
417 |
+
* @param string $key
|
418 |
+
*
|
419 |
+
* @return int[]
|
420 |
+
*/
|
421 |
+
protected function getCol(array $dbResults, $key)
|
422 |
+
{
|
423 |
+
$results = array();
|
424 |
+
foreach ($dbResults as $dbResult) {
|
425 |
+
$results[] = (int) $dbResult[$key];
|
426 |
+
}
|
427 |
+
return $results;
|
428 |
+
}
|
429 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Shop/ModuleTranslations.php
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Shop;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\TranslationCollection;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Contains module related translations, like module name, buttons, requirement
|
8 |
+
* messages, and save/cancel/failure messages.
|
9 |
+
*/
|
10 |
+
class ModuleTranslations extends TranslationCollection
|
11 |
+
{
|
12 |
+
protected $nl = array(
|
13 |
+
// Linking into the shop's extension system, standard buttons and messages
|
14 |
+
'extensions' => 'Extensies',
|
15 |
+
'modules' => 'Modules',
|
16 |
+
'page_title' => 'Acumulus instellingen',
|
17 |
+
'module_name' => 'Acumulus',
|
18 |
+
'module_description' => 'Verstuurt uw facturen automatisch naar Acumulus',
|
19 |
+
'text_home' => 'Home',
|
20 |
+
'button_settings' => 'Instellingen',
|
21 |
+
'button_save' => 'Opslaan',
|
22 |
+
'button_back' => 'Terug naar overzicht',
|
23 |
+
'button_confirm_uninstall' => 'Ja, verwijder data en instellingen',
|
24 |
+
'button_cancel_uninstall' => 'Nee, alleen uitschakelen, bewaar data en instellingen',
|
25 |
+
'button_cancel' => 'Annuleren',
|
26 |
+
'button_send' => 'Verzenden',
|
27 |
+
'message_config_saved' => 'De instellingen zijn opgeslagen.',
|
28 |
+
'message_uninstall' => 'Wilt u de configuratie-instellingen verwijderen?',
|
29 |
+
|
30 |
+
// Requirements
|
31 |
+
'message_error_req_curl' => 'Voor het gebruik van deze module dient de CURL PHP extensie actief te zijn op uw server.',
|
32 |
+
'message_error_req_xml' => 'Voor het gebruik van deze module met het output format XML, dient de SimpleXML PHP extensie actief te zijn op uw server.',
|
33 |
+
'message_error_req_dom' => 'Voor het gebruik van deze module dient de DOM PHP extensie actief te zijn op uw server.',
|
34 |
+
|
35 |
+
);
|
36 |
+
|
37 |
+
protected $en = array(
|
38 |
+
'extensions' => 'Extensions',
|
39 |
+
'modules' => 'Modules',
|
40 |
+
'page_title' => 'Acumulus settings',
|
41 |
+
'module_name' => 'Acumulus',
|
42 |
+
'module_description' => 'Automatically sends your invoices to Acumulus',
|
43 |
+
'text_home' => 'Home',
|
44 |
+
'button_settings' => 'Settings',
|
45 |
+
'button_save' => 'Save',
|
46 |
+
'button_back' => 'Back to list',
|
47 |
+
'button_confirm_uninstall' => 'Yes, uninstall data and settings',
|
48 |
+
'button_cancel_uninstall' => 'No, disable only, keep data and settings',
|
49 |
+
'button_cancel' => 'Cancel',
|
50 |
+
'button_send' => 'Send',
|
51 |
+
'message_config_saved' => 'The settings are saved.',
|
52 |
+
'message_uninstall' => 'Are you sure that you want to delete the configuration settings?',
|
53 |
+
|
54 |
+
// Requirements
|
55 |
+
'message_error_req_curl' => 'The CURL PHP extension needs to be activated on your server for this module to work.',
|
56 |
+
'message_error_req_xml' => 'The SimpleXML extension needs to be activated on your server for this module to be able to work with the XML format.',
|
57 |
+
'message_error_req_dom' => 'The DOM PHP extension needs to be activated on your server for this module to work.',
|
58 |
+
|
59 |
+
);
|
60 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Web/Communicator.php
ADDED
@@ -0,0 +1,487 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Web;
|
3 |
+
|
4 |
+
use DOMDocument;
|
5 |
+
use DOMElement;
|
6 |
+
use Exception;
|
7 |
+
use LibXMLError;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Communication implements the communication with the Acumulus WebAPI.
|
11 |
+
*
|
12 |
+
* It offers:
|
13 |
+
* - Conversion between array and XML.
|
14 |
+
* - Conversion from Json to array.
|
15 |
+
* - (https) Communication with the Acumulus webservice using the curl library.
|
16 |
+
* - Good error handling during communication.
|
17 |
+
*/
|
18 |
+
class Communicator
|
19 |
+
{
|
20 |
+
|
21 |
+
/** @var \Siel\Acumulus\Web\ConfigInterface */
|
22 |
+
protected $config;
|
23 |
+
|
24 |
+
/** @var array */
|
25 |
+
protected $warnings;
|
26 |
+
|
27 |
+
/** @var array */
|
28 |
+
protected $errors;
|
29 |
+
|
30 |
+
public function __construct(ConfigInterface $config)
|
31 |
+
{
|
32 |
+
$this->config = $config;
|
33 |
+
}
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Checks and, if necessary, corrects the status.
|
37 |
+
*
|
38 |
+
* If local errors or warnings were added, the status may be incorrectly
|
39 |
+
* indicating success. This method checks for this and corrects the status.
|
40 |
+
*
|
41 |
+
* @param array $response
|
42 |
+
* A response structure with, at least, fields 'errors', 'warnings' and
|
43 |
+
* 'status'.
|
44 |
+
*/
|
45 |
+
public function checkStatus(array &$response)
|
46 |
+
{
|
47 |
+
// - Check if status is consistent (local errors and warnings should alter
|
48 |
+
// the status as well.
|
49 |
+
if (!empty($response['errors'])) {
|
50 |
+
if ($response['status'] != ConfigInterface::Status_Exception) {
|
51 |
+
$response['status'] = ConfigInterface::Status_Errors;
|
52 |
+
}
|
53 |
+
} else if (!empty($response['warnings'])) {
|
54 |
+
if ($response['status'] != ConfigInterface::Status_Exception && $response['status'] != ConfigInterface::Status_Errors) {
|
55 |
+
$response['status'] = ConfigInterface::Status_Warnings;
|
56 |
+
}
|
57 |
+
}
|
58 |
+
}
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Sends a message to the given API function and returns the results.
|
62 |
+
*
|
63 |
+
* For debugging purposes the return array also includes a key 'trace',
|
64 |
+
* containing an array with 2 keys, request and response, with the actual
|
65 |
+
* strings as were sent.
|
66 |
+
*
|
67 |
+
* @param string $apiFunction
|
68 |
+
* The API function to invoke.
|
69 |
+
* @param array $message
|
70 |
+
* The values to submit.
|
71 |
+
*
|
72 |
+
* @return array
|
73 |
+
* An array with the results including any warning and/or error messages.
|
74 |
+
*/
|
75 |
+
public function callApiFunction($apiFunction, array $message)
|
76 |
+
{
|
77 |
+
// Reset warnings and errors.
|
78 |
+
$this->warnings = array();
|
79 |
+
$this->errors = array();
|
80 |
+
|
81 |
+
try {
|
82 |
+
// Compose URI.
|
83 |
+
$uri = $this->config->getBaseUri() . '/' . $this->config->getApiVersion() . '/' . $apiFunction . '.php';
|
84 |
+
|
85 |
+
// Complete message with values common to all API calls:
|
86 |
+
// - contract part
|
87 |
+
// - format part
|
88 |
+
// - environment part
|
89 |
+
$env = $this->config->getEnvironment();
|
90 |
+
$message = array_merge(array(
|
91 |
+
'contract' => $this->config->getCredentials(),
|
92 |
+
'format' => $this->config->getOutputFormat(),
|
93 |
+
'testmode' => $this->config->getDebug() === ConfigInterface::Debug_TestMode ? ConfigInterface::TestMode_Test : ConfigInterface::TestMode_Normal,
|
94 |
+
'connector' => array(
|
95 |
+
'application' => "{$env['shopName']} {$env['shopVersion']}",
|
96 |
+
'webkoppel' => "Acumulus {$env['moduleVersion']}",
|
97 |
+
'development' => 'SIEL - Buro RaDer',
|
98 |
+
'remark' => "Library {$env['libraryVersion']} - PHP {$env['phpVersion']}",
|
99 |
+
'sourceuri' => 'https://www.siel.nl/',
|
100 |
+
),
|
101 |
+
), $message);
|
102 |
+
|
103 |
+
// Send message, receive response.
|
104 |
+
$response = $this->sendApiMessage($uri, $message);
|
105 |
+
} catch (Exception $e) {
|
106 |
+
$this->errors[] = array(
|
107 |
+
'code' => $e->getCode(),
|
108 |
+
'codetag' => "File: {$e->getFile()}, Line: {$e->getLine()}",
|
109 |
+
'message' => $e->getMessage(),
|
110 |
+
);
|
111 |
+
$response = array();
|
112 |
+
}
|
113 |
+
|
114 |
+
// Process response.
|
115 |
+
// - Simplify errors and warnings parts: remove indirection and count.
|
116 |
+
if (!empty($response['errors']['error'])) {
|
117 |
+
$response['errors'] = $response['errors']['error'];
|
118 |
+
// If there was exactly 1 error, it wasn't put in an array of errors.
|
119 |
+
if (!is_array(reset($response['errors']))) {
|
120 |
+
$response['errors'] = array($response['errors']);
|
121 |
+
}
|
122 |
+
} else if (!isset($response['errors'])) {
|
123 |
+
$response['errors'] = array();
|
124 |
+
} else {
|
125 |
+
unset($response['errors']['count_errors']);
|
126 |
+
}
|
127 |
+
|
128 |
+
if (!empty($response['warnings']['warning'])) {
|
129 |
+
$response['warnings'] = $response['warnings']['warning'];
|
130 |
+
// If there was exactly 1 warning, it wasn't put in an array of warnings.
|
131 |
+
if (!is_array(reset($response['warnings']))) {
|
132 |
+
$response['warnings'] = array($response['warnings']);
|
133 |
+
}
|
134 |
+
} else if (!isset($response['warnings'])) {
|
135 |
+
$response['warnings'] = array();
|
136 |
+
} else {
|
137 |
+
unset($response['warnings']['count_warnings']);
|
138 |
+
}
|
139 |
+
|
140 |
+
// - Add local errors and warnings.
|
141 |
+
if (!empty($this->errors)) {
|
142 |
+
// Internal error(s), return those as well.
|
143 |
+
$response['errors'] = array_merge($this->errors, $response['errors']);
|
144 |
+
}
|
145 |
+
if (!empty($this->warnings)) {
|
146 |
+
// Internal warning(s), return those as well.
|
147 |
+
$response['warnings'] = array_merge($this->warnings, $response['warnings']);
|
148 |
+
}
|
149 |
+
|
150 |
+
// - Add status if not set. if no status is present the call failed, so we
|
151 |
+
// set the status to 1.
|
152 |
+
if (!isset($response['status'])) {
|
153 |
+
$response['status'] = ConfigInterface::Status_Errors;
|
154 |
+
}
|
155 |
+
|
156 |
+
$this->checkStatus($response);
|
157 |
+
|
158 |
+
return $response;
|
159 |
+
}
|
160 |
+
|
161 |
+
/**
|
162 |
+
* Sends a message to the Acumulus API and returns the answer.
|
163 |
+
*
|
164 |
+
* Any errors during:
|
165 |
+
* - conversion of the message to xml,
|
166 |
+
* - communication with the Acumulus WebAPI service
|
167 |
+
* - converting the answer to an array
|
168 |
+
* are returned as an 'error' in the 'errors' part of the return value.
|
169 |
+
*
|
170 |
+
*
|
171 |
+
* @param string $uri
|
172 |
+
* The URI of the Acumulus WebAPI call to send the message to.
|
173 |
+
* @param array $message
|
174 |
+
* The message to send to the Acumulus WebAPI.
|
175 |
+
*
|
176 |
+
* @return array
|
177 |
+
* The response as specified on
|
178 |
+
* https://apidoc.sielsystems.nl/content/warning-error-and-status-response-section-most-api-calls.
|
179 |
+
*/
|
180 |
+
protected function sendApiMessage($uri, array $message)
|
181 |
+
{
|
182 |
+
$resultBase = array();
|
183 |
+
|
184 |
+
// Convert message to XML. XML requires 1 top level tag, so add one.
|
185 |
+
// The tagname is ignored by the Acumulus WebAPI.
|
186 |
+
$message = $this->convertToXml(array('myxml' => $message));
|
187 |
+
// Keep track of communication for debugging/logging at higher levels.
|
188 |
+
$resultBase['trace']['request'] = preg_replace('|<password>.*</password>|', '<password>REMOVED FOR SECURITY</password>', $message);
|
189 |
+
|
190 |
+
$response = $this->sendHttpPost($uri, array('xmlstring' => $message));
|
191 |
+
|
192 |
+
if ($response) {
|
193 |
+
$resultBase['trace']['response'] = $response;
|
194 |
+
$this->config->getLog()->debug('sendApiMessage(uri="%s", message="%s"), response="%s"',
|
195 |
+
$uri, $resultBase['trace']['request'], $resultBase['trace']['response']);
|
196 |
+
|
197 |
+
$result = false;
|
198 |
+
// When the API is gone we might receive an error message in an html page.
|
199 |
+
if ($this->isHtmlResponse($response)) {
|
200 |
+
$this->setHtmlReceivedError($response);
|
201 |
+
} else {
|
202 |
+
$alsoTryAsXml = false;
|
203 |
+
if ($this->config->getOutputFormat() === 'json') {
|
204 |
+
$result = json_decode($response, true);
|
205 |
+
if ($result === null) {
|
206 |
+
$this->setJsonError();
|
207 |
+
// Even if we pass <format>json</format> we might receive an XML
|
208 |
+
// response in case the XML was rejected before or during parsing.
|
209 |
+
$alsoTryAsXml = true;
|
210 |
+
}
|
211 |
+
}
|
212 |
+
if ($this->config->getOutputFormat() === 'xml' || $alsoTryAsXml) {
|
213 |
+
$result = $this->convertToArray($response);
|
214 |
+
}
|
215 |
+
}
|
216 |
+
|
217 |
+
if (is_array($result)) {
|
218 |
+
$resultBase += $result;
|
219 |
+
}
|
220 |
+
} else {
|
221 |
+
$this->config->getLog()->debug('sendApiMessage(uri="%s", message="%s"): failure',
|
222 |
+
$uri, $resultBase['trace']['request']);
|
223 |
+
}
|
224 |
+
|
225 |
+
return $resultBase;
|
226 |
+
}
|
227 |
+
|
228 |
+
/**
|
229 |
+
* @param string $uri
|
230 |
+
* The uri to send the HTTP request to.
|
231 |
+
* @param array|string $post
|
232 |
+
* An array of values to be placed in the POST body or an url-encoded string
|
233 |
+
* that contains all the POST values
|
234 |
+
*
|
235 |
+
* @return string|false
|
236 |
+
* The response body from the HTTP response or false in case of errors.
|
237 |
+
*/
|
238 |
+
protected function sendHttpPost($uri, $post)
|
239 |
+
{
|
240 |
+
$response = false;
|
241 |
+
|
242 |
+
// Open a curl connection.
|
243 |
+
$ch = curl_init();
|
244 |
+
if (!$ch) {
|
245 |
+
$this->setCurlError($ch, 'curl_init()');
|
246 |
+
return $response;
|
247 |
+
}
|
248 |
+
|
249 |
+
// Configure the curl connection.
|
250 |
+
$options = array(
|
251 |
+
CURLOPT_URL => $uri,
|
252 |
+
CURLOPT_RETURNTRANSFER => true,
|
253 |
+
CURLOPT_SSL_VERIFYPEER => false,
|
254 |
+
CURLOPT_POST => true,
|
255 |
+
CURLOPT_POSTFIELDS => $post,
|
256 |
+
//CURLOPT_PROXY => '127.0.0.1:8888', // Uncomment to debug with Fiddler.
|
257 |
+
);
|
258 |
+
if (!curl_setopt_array($ch, $options)) {
|
259 |
+
$this->setCurlError($ch, 'curl_setopt_array()');
|
260 |
+
return $response;
|
261 |
+
}
|
262 |
+
|
263 |
+
// Send and receive over the curl connection.
|
264 |
+
$response = curl_exec($ch);
|
265 |
+
if (!$response) {
|
266 |
+
$this->setCurlError($ch, 'curl_exec()');
|
267 |
+
} else {
|
268 |
+
// Close the connection (this operation cannot fail).
|
269 |
+
curl_close($ch);
|
270 |
+
}
|
271 |
+
|
272 |
+
return $response;
|
273 |
+
}
|
274 |
+
|
275 |
+
/**
|
276 |
+
* @param string $response
|
277 |
+
*
|
278 |
+
* @return bool
|
279 |
+
* True if the response is html, false otherwise.
|
280 |
+
*/
|
281 |
+
protected function isHtmlResponse($response)
|
282 |
+
{
|
283 |
+
return strtolower(substr($response, 0, strlen('<!doctype html'))) === '<!doctype html'
|
284 |
+
|| strtolower(substr($response, 0, strlen('<html'))) === '<html'
|
285 |
+
|| strtolower(substr($response, 0, strlen('<body'))) === '<body';
|
286 |
+
}
|
287 |
+
|
288 |
+
/**
|
289 |
+
* Converts an XML string to an array.
|
290 |
+
*
|
291 |
+
* @param string $xml
|
292 |
+
* A string containing XML.
|
293 |
+
*
|
294 |
+
* @return array|false
|
295 |
+
* An array representation of the XML string or false on errors.
|
296 |
+
*/
|
297 |
+
protected function convertToArray($xml)
|
298 |
+
{
|
299 |
+
// Convert the response to an array via a 3-way conversion:
|
300 |
+
// - create a simplexml object
|
301 |
+
// - convert that to json
|
302 |
+
// - convert json to array
|
303 |
+
libxml_use_internal_errors(true);
|
304 |
+
if (!($result = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA))) {
|
305 |
+
$this->setLibxmlErrors(libxml_get_errors());
|
306 |
+
return false;
|
307 |
+
}
|
308 |
+
|
309 |
+
if (!($result = json_encode($result))) {
|
310 |
+
$this->setJsonError();
|
311 |
+
return false;
|
312 |
+
}
|
313 |
+
if (($result = json_decode($result, true)) === null) {
|
314 |
+
$this->setJsonError();
|
315 |
+
return false;
|
316 |
+
}
|
317 |
+
|
318 |
+
return $result;
|
319 |
+
}
|
320 |
+
|
321 |
+
/**
|
322 |
+
* Converts a keyed, optionally multi-level, array to XML.
|
323 |
+
*
|
324 |
+
* Each key is converted to a tag, no attributes are used. Numeric sub-arrays
|
325 |
+
* are repeated using the same key.
|
326 |
+
*
|
327 |
+
* @param array $values
|
328 |
+
* The array to convert to XML.
|
329 |
+
*
|
330 |
+
* @return string
|
331 |
+
* The XML string
|
332 |
+
*/
|
333 |
+
protected function convertToXml(array $values)
|
334 |
+
{
|
335 |
+
$dom = new DOMDocument('1.0', 'utf-8');
|
336 |
+
$dom->xmlStandalone = true;
|
337 |
+
$dom->formatOutput = true;
|
338 |
+
|
339 |
+
$dom = $this->convertToDom($values, $dom);
|
340 |
+
$dom->normalizeDocument();
|
341 |
+
$result = $dom->saveXML();
|
342 |
+
|
343 |
+
return $result;
|
344 |
+
}
|
345 |
+
|
346 |
+
/**
|
347 |
+
* Recursively converts a value to a DOMDocument|DOMElement.
|
348 |
+
*
|
349 |
+
* @param mixed $values
|
350 |
+
* A keyed array, an numerically indexed array, or a scalar type.
|
351 |
+
* @param DOMDocument|DOMElement $element
|
352 |
+
* The element to append the values to.
|
353 |
+
*
|
354 |
+
* @return DOMDocument|DOMElement
|
355 |
+
*/
|
356 |
+
protected function convertToDom($values, $element)
|
357 |
+
{
|
358 |
+
/** @var DOMDocument $document */
|
359 |
+
static $document = null;
|
360 |
+
$isFirstElement = true;
|
361 |
+
|
362 |
+
if ($element instanceof DOMDocument) {
|
363 |
+
$document = $element;
|
364 |
+
}
|
365 |
+
if (is_array($values)) {
|
366 |
+
foreach ($values as $key => $value) {
|
367 |
+
if (is_int($key)) {
|
368 |
+
if ($isFirstElement) {
|
369 |
+
$node = $element;
|
370 |
+
$isFirstElement = false;
|
371 |
+
} else {
|
372 |
+
$node = $document->createElement($element->tagName);
|
373 |
+
$element->parentNode->appendChild($node);
|
374 |
+
}
|
375 |
+
} else {
|
376 |
+
$node = $document->createElement($key);
|
377 |
+
$element->appendChild($node);
|
378 |
+
}
|
379 |
+
$this->convertToDom($value, $node);
|
380 |
+
}
|
381 |
+
} else {
|
382 |
+
$element->appendChild($document->createTextNode(is_bool($values) ? ($values ? 'true' : 'false') : $values));
|
383 |
+
}
|
384 |
+
|
385 |
+
return $element;
|
386 |
+
}
|
387 |
+
|
388 |
+
/**
|
389 |
+
* Adds a curl error message to the result.
|
390 |
+
*
|
391 |
+
* @param resource|bool $ch
|
392 |
+
* @param string $function
|
393 |
+
*/
|
394 |
+
protected function setCurlError($ch, $function)
|
395 |
+
{
|
396 |
+
$env = $this->config->getEnvironment();
|
397 |
+
$this->errors[] = array(
|
398 |
+
'code' => $ch ? curl_errno($ch) : 0,
|
399 |
+
'codetag' => "$function (Curl: {$env['curlVersion']})",
|
400 |
+
'message' => $ch ? curl_error($ch) : '',
|
401 |
+
);
|
402 |
+
curl_close($ch);
|
403 |
+
}
|
404 |
+
|
405 |
+
/**
|
406 |
+
* Adds a libxml error messages to the result.
|
407 |
+
*
|
408 |
+
* @param LibXMLError[] $errors
|
409 |
+
*/
|
410 |
+
protected function setLibxmlErrors(array $errors)
|
411 |
+
{
|
412 |
+
foreach ($errors as $error) {
|
413 |
+
$message = array(
|
414 |
+
'code' => $error->code,
|
415 |
+
'codetag' => "Line: {$error->line}, Column: {$error->column}",
|
416 |
+
'message' => trim($error->message),
|
417 |
+
);
|
418 |
+
if ($error->level === LIBXML_ERR_WARNING) {
|
419 |
+
$this->warnings[] = $message;
|
420 |
+
} else {
|
421 |
+
$this->errors[] = $message;
|
422 |
+
}
|
423 |
+
}
|
424 |
+
}
|
425 |
+
|
426 |
+
/**
|
427 |
+
* Adds a json error message to the result.
|
428 |
+
*/
|
429 |
+
protected function setJsonError()
|
430 |
+
{
|
431 |
+
$code = json_last_error();
|
432 |
+
switch ($code) {
|
433 |
+
case JSON_ERROR_NONE:
|
434 |
+
$message = 'No error';
|
435 |
+
break;
|
436 |
+
case JSON_ERROR_DEPTH:
|
437 |
+
$message = 'Maximum stack depth exceeded';
|
438 |
+
break;
|
439 |
+
case JSON_ERROR_STATE_MISMATCH:
|
440 |
+
$message = 'Underflow or the modes mismatch';
|
441 |
+
break;
|
442 |
+
case JSON_ERROR_CTRL_CHAR:
|
443 |
+
$message = 'Unexpected control character found';
|
444 |
+
break;
|
445 |
+
case JSON_ERROR_SYNTAX:
|
446 |
+
$message = 'Syntax error, malformed JSON';
|
447 |
+
break;
|
448 |
+
case JSON_ERROR_UTF8:
|
449 |
+
$message = 'Malformed UTF-8 characters, possibly incorrectly encoded';
|
450 |
+
break;
|
451 |
+
default:
|
452 |
+
$message = 'Unknown error';
|
453 |
+
break;
|
454 |
+
}
|
455 |
+
$env = $this->config->getEnvironment();
|
456 |
+
$this->errors[] = array(
|
457 |
+
'code' => $code,
|
458 |
+
'codetag' => "(json: {$env['jsonVersion']})",
|
459 |
+
'message' => $message,
|
460 |
+
);
|
461 |
+
}
|
462 |
+
|
463 |
+
/**
|
464 |
+
* Adds an error message to the result.
|
465 |
+
*
|
466 |
+
* @param string $response
|
467 |
+
* String containing an html document.
|
468 |
+
*/
|
469 |
+
protected function setHtmlReceivedError($response)
|
470 |
+
{
|
471 |
+
libxml_use_internal_errors(true);
|
472 |
+
$doc = new DOMDocument('1.0', 'utf-8');
|
473 |
+
$doc->loadHTML($response);
|
474 |
+
$body = $doc->getElementsByTagName('body');
|
475 |
+
if ($body->length > 0) {
|
476 |
+
$body = $body->item(0)->textContent;
|
477 |
+
} else {
|
478 |
+
$body = '';
|
479 |
+
}
|
480 |
+
$this->errors[] = array(
|
481 |
+
'code' => 'HTML response received',
|
482 |
+
'codetag' => '',
|
483 |
+
'message' => $body,
|
484 |
+
);
|
485 |
+
}
|
486 |
+
|
487 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Web/CommunicatorLocal.php
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Web;
|
3 |
+
|
4 |
+
/**
|
5 |
+
* CommunicationLocal is a class derived from Communication that can be used for
|
6 |
+
* testing purposes. It does not actually send the message to Acumulus and fakes
|
7 |
+
* a response.
|
8 |
+
*/
|
9 |
+
class CommunicatorLocal extends Communicator
|
10 |
+
{
|
11 |
+
/**
|
12 |
+
* @inheritdoc
|
13 |
+
*/
|
14 |
+
protected function sendHttpPost($uri, $post)
|
15 |
+
{
|
16 |
+
if ($this->config->getOutputFormat() === 'json') {
|
17 |
+
$response = '{
|
18 |
+
"errors": {
|
19 |
+
"count_errors": "0"
|
20 |
+
},
|
21 |
+
"warnings": {
|
22 |
+
"warning": [ {
|
23 |
+
"code": "599",
|
24 |
+
"codetag": "LOCAL",
|
25 |
+
"message": "Warning - The message has not been sent. The communication layer operates in local debug mode."
|
26 |
+
} ],
|
27 |
+
"count_warnings": "1"
|
28 |
+
},
|
29 |
+
"status": "0"
|
30 |
+
}';
|
31 |
+
} else {
|
32 |
+
/** @noinspection HtmlUnknownTag */
|
33 |
+
$response = '<myxml>
|
34 |
+
<errors>
|
35 |
+
<count_errors>0</count_errors>
|
36 |
+
</errors>
|
37 |
+
<warnings>
|
38 |
+
<warning>
|
39 |
+
<code>599</code>
|
40 |
+
<codetag>LOCAL</codetag>
|
41 |
+
<message>Warning - The message has not been sent. The communication layer operates in local debug mode.</message>
|
42 |
+
</warning>
|
43 |
+
</warnings>
|
44 |
+
</myxml>';
|
45 |
+
}
|
46 |
+
return str_replace(array("\r", "\n", "\t"), '', $response);
|
47 |
+
}
|
48 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Web/ConfigInterface.php
ADDED
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Web;
|
3 |
+
|
4 |
+
/**
|
5 |
+
* CommunicationConfigInterface defines an interface to store and retrieve
|
6 |
+
* communication specific configuration values.
|
7 |
+
*
|
8 |
+
* Configuration is stored in the host environment (normally a web shop), this
|
9 |
+
* interface abstracts from how a specific web shop does so.
|
10 |
+
*/
|
11 |
+
interface ConfigInterface
|
12 |
+
{
|
13 |
+
const libraryVersion = '4.3.0';
|
14 |
+
|
15 |
+
// Web service configuration related constants.
|
16 |
+
const Status_NotSent = -1;
|
17 |
+
const Status_Success = 0;
|
18 |
+
const Status_Errors = 1;
|
19 |
+
const Status_Warnings = 2;
|
20 |
+
const Status_Exception = 3;
|
21 |
+
const Status_SendingPrevented_InvoiceCreated = 4;
|
22 |
+
const Status_SendingPrevented_InvoiceCompleted = 5;
|
23 |
+
|
24 |
+
const Debug_None = 1;
|
25 |
+
const Debug_SendAndLog = 2;
|
26 |
+
const Debug_TestMode = 4;
|
27 |
+
const Debug_StayLocal = 3;
|
28 |
+
|
29 |
+
// Web service API constants.
|
30 |
+
const TestMode_Normal = 0;
|
31 |
+
const TestMode_Test = 1;
|
32 |
+
|
33 |
+
// Web service related defaults.
|
34 |
+
const baseUri = 'https://api.sielsystems.nl/acumulus';
|
35 |
+
//const baseUri = 'https://ng1.sielsystems.nl';
|
36 |
+
const apiVersion = 'stable';
|
37 |
+
const outputFormat = 'json';
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Returns the URI of the Acumulus API to connect with.
|
41 |
+
*
|
42 |
+
* This method returns the base URI, without version indicator and API call.
|
43 |
+
*
|
44 |
+
* @return string
|
45 |
+
* The URI of the Acumulus API.
|
46 |
+
*/
|
47 |
+
public function getBaseUri();
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Returns the version of the Acumulus API to use.
|
51 |
+
*
|
52 |
+
* A version number may be part of the URI, so this value implicitly also
|
53 |
+
* defines the API version to communicate with.
|
54 |
+
*
|
55 |
+
* @return string
|
56 |
+
* The version of the Acumulus API to use.
|
57 |
+
*/
|
58 |
+
public function getApiVersion();
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Indicates the debug mode of the web services communicator.
|
62 |
+
*
|
63 |
+
* @return int
|
64 |
+
* One of the ConfigInterface::Debug_... constants.
|
65 |
+
*/
|
66 |
+
public function getDebug();
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Returns the current log level for log messages from this module.
|
70 |
+
*
|
71 |
+
* @return int
|
72 |
+
* One of the Log::... constants.
|
73 |
+
*/
|
74 |
+
public function getLogLevel();
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Returns the format the output from the Acumulus API should be in.
|
78 |
+
*
|
79 |
+
* @return string
|
80 |
+
* xml or json.
|
81 |
+
*/
|
82 |
+
public function getOutputFormat();
|
83 |
+
|
84 |
+
/**
|
85 |
+
* Returns the contract credentials to authenticate with the Acumulus API.
|
86 |
+
*
|
87 |
+
* @return array
|
88 |
+
* A keyed array with the keys:
|
89 |
+
* - contractcode
|
90 |
+
* - username
|
91 |
+
* - password
|
92 |
+
* - emailonerror
|
93 |
+
* - emailonwarning
|
94 |
+
*/
|
95 |
+
public function getCredentials();
|
96 |
+
|
97 |
+
/**
|
98 |
+
* Returns information about the environment of this library.
|
99 |
+
*
|
100 |
+
* @return array
|
101 |
+
* A keyed array with information about the environment of this library:
|
102 |
+
* - libraryVersion
|
103 |
+
* - moduleVersion
|
104 |
+
* - shopName
|
105 |
+
* - shopVersion
|
106 |
+
* - phpVersion
|
107 |
+
* - os
|
108 |
+
* - curlVersion
|
109 |
+
* - jsonVersion
|
110 |
+
*/
|
111 |
+
public function getEnvironment();
|
112 |
+
|
113 |
+
/**
|
114 |
+
* @return \Siel\Acumulus\Helpers\Log
|
115 |
+
*/
|
116 |
+
public function getLog();
|
117 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Web/Service.php
ADDED
@@ -0,0 +1,412 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Web;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\TranslatorInterface;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Provides an easy interface towards the different API calls of the Acumulus
|
8 |
+
* web API.
|
9 |
+
*
|
10 |
+
* This class simplifies the communication so that the different web shop
|
11 |
+
* specific interfaces can be more rapidly developed.
|
12 |
+
*
|
13 |
+
* More info:
|
14 |
+
* - https://apidoc.sielsystems.nl/
|
15 |
+
* - http://www.siel.nl/webkoppelingen/
|
16 |
+
*
|
17 |
+
* The Web API call wrappers return their information as a keyed array, which is
|
18 |
+
* a simplified version of the call specific response structure and the exit
|
19 |
+
* structure as described on
|
20 |
+
* https://apidoc.sielsystems.nl/content/warning-error-and-status-response-section-most-api-calls.
|
21 |
+
*
|
22 |
+
* The general part is represented by the following keys in the result:
|
23 |
+
* - status: int; 0 = success; 1 = Failed, Errors found; 2 = Success with
|
24 |
+
* warnings; 3 = Exception, Please contact Acumulus technical support.
|
25 |
+
* - errors: an array of errors, an error is an array with the following keys:
|
26 |
+
* - code: int, see https://apidoc.sielsystems.nl/content/exit-and-warning-codes
|
27 |
+
* - codetag: string, a special code tag. Use this as a reference when
|
28 |
+
* communicating with Acumulus technical support.
|
29 |
+
* - message: string, a message describing the warning or error.
|
30 |
+
* - warnings: an array of warning arrays, these have the same keys as an error.
|
31 |
+
*/
|
32 |
+
class Service
|
33 |
+
{
|
34 |
+
/** @var \Siel\Acumulus\Web\ConfigInterface */
|
35 |
+
protected $config;
|
36 |
+
|
37 |
+
/** @var \Siel\Acumulus\Helpers\TranslatorInterface */
|
38 |
+
protected $translator;
|
39 |
+
|
40 |
+
/** @var \Siel\Acumulus\Web\Communicator */
|
41 |
+
protected $communicator;
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Constructor.
|
45 |
+
*
|
46 |
+
* @param \Siel\Acumulus\Web\ConfigInterface $config
|
47 |
+
* @param \Siel\Acumulus\Helpers\TranslatorInterface $translator
|
48 |
+
*/
|
49 |
+
public function __construct(ConfigInterface $config, TranslatorInterface $translator)
|
50 |
+
{
|
51 |
+
$this->config = $config;
|
52 |
+
$this->communicator = null;
|
53 |
+
|
54 |
+
$this->translator = $translator;
|
55 |
+
$webServiceTranslations = new Translations();
|
56 |
+
$this->translator->add($webServiceTranslations);
|
57 |
+
}
|
58 |
+
|
59 |
+
/**
|
60 |
+
* Helper method to translate strings.
|
61 |
+
*
|
62 |
+
* @param string $key
|
63 |
+
* The key to get a translation for.
|
64 |
+
*
|
65 |
+
* @return string
|
66 |
+
* The translation for the given key or the key itself if no translation
|
67 |
+
* could be found.
|
68 |
+
*/
|
69 |
+
protected function t($key)
|
70 |
+
{
|
71 |
+
return $this->translator->get($key);
|
72 |
+
}
|
73 |
+
|
74 |
+
/**
|
75 |
+
* Lazy loads the communicator.
|
76 |
+
*
|
77 |
+
* @return Communicator
|
78 |
+
*/
|
79 |
+
protected function getCommunicator()
|
80 |
+
{
|
81 |
+
if ($this->communicator === null) {
|
82 |
+
if ($this->config->getDebug() == ConfigInterface::Debug_StayLocal) {
|
83 |
+
$this->communicator = new CommunicatorLocal($this->config);
|
84 |
+
} else {
|
85 |
+
$this->communicator = new Communicator($this->config);
|
86 |
+
}
|
87 |
+
}
|
88 |
+
return $this->communicator;
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* If the result contains any errors or warnings, a list of verbose messages
|
93 |
+
* is returned.
|
94 |
+
*
|
95 |
+
* @param array $result
|
96 |
+
* A keyed array that contains the results, but also any messages in the
|
97 |
+
* keys 'errors and 'warnings'.
|
98 |
+
* @param bool $addTraceMessages
|
99 |
+
* $result may contain the actual request and response as sent in the key
|
100 |
+
* 'trace'. If this parameter is true the trace should be added to the
|
101 |
+
* messages to be returned as well.
|
102 |
+
*
|
103 |
+
* @return string[]
|
104 |
+
* An array with textual messages that can be used to inform the user.
|
105 |
+
*/
|
106 |
+
public function resultToMessages(array $result, $addTraceMessages = true)
|
107 |
+
{
|
108 |
+
$messages = array();
|
109 |
+
foreach ($result['errors'] as $error) {
|
110 |
+
$message = "{$error['code']}: ";
|
111 |
+
$message .= $this->t($error['message']);
|
112 |
+
if ($error['codetag']) {
|
113 |
+
$message .= " ({$error['codetag']})";
|
114 |
+
}
|
115 |
+
$messages[] = $this->t('message_error') . ' ' . $message;
|
116 |
+
}
|
117 |
+
foreach ($result['warnings'] as $warning) {
|
118 |
+
$message = "{$warning['code']}: ";
|
119 |
+
$message .= $this->t($warning['message']);
|
120 |
+
if ($warning['codetag']) {
|
121 |
+
$message .= " ({$warning['codetag']})";
|
122 |
+
}
|
123 |
+
$messages[] = $this->t('message_warning') . ' ' . $message;
|
124 |
+
}
|
125 |
+
|
126 |
+
if ($addTraceMessages && (!empty($messages) || $this->config->getDebug() != ConfigInterface::Debug_None)) {
|
127 |
+
if (isset($result['trace'])) {
|
128 |
+
$messages[] = $this->t('message_info_for_user');
|
129 |
+
if (isset($result['trace']['request'])) {
|
130 |
+
$messages[] = $this->t('message_sent') . ":\n" . $result['trace']['request'];
|
131 |
+
}
|
132 |
+
if (isset($result['trace']['response'])) {
|
133 |
+
$messages[] = $this->t('message_received') . ":\n" . $result['trace']['response'];
|
134 |
+
}
|
135 |
+
}
|
136 |
+
}
|
137 |
+
|
138 |
+
return $messages;
|
139 |
+
}
|
140 |
+
|
141 |
+
/**
|
142 |
+
* Converts an array of messages to a string that can be used in a text mail.
|
143 |
+
*
|
144 |
+
* @param string[] $messages
|
145 |
+
*
|
146 |
+
* @return string
|
147 |
+
*/
|
148 |
+
public function messagesToText(array $messages)
|
149 |
+
{
|
150 |
+
return '* ' . implode("\n\n* ", $messages) . "\n\n";
|
151 |
+
}
|
152 |
+
|
153 |
+
/**
|
154 |
+
* Converts an array of messages to a string that can be used in an html mail.
|
155 |
+
*
|
156 |
+
* @param string[] $messages
|
157 |
+
*
|
158 |
+
* @return string
|
159 |
+
*/
|
160 |
+
public function messagesToHtml(array $messages)
|
161 |
+
{
|
162 |
+
$messages_html = array();
|
163 |
+
foreach ($messages as $message) {
|
164 |
+
$messages_html[] = nl2br(htmlspecialchars($message, ENT_NOQUOTES));
|
165 |
+
}
|
166 |
+
return '<ul><li>' . implode("</li><li>", $messages_html) . '</li></ul>';
|
167 |
+
}
|
168 |
+
|
169 |
+
/**
|
170 |
+
* @param int $status
|
171 |
+
*
|
172 |
+
* @return string
|
173 |
+
*/
|
174 |
+
public function getStatusText($status)
|
175 |
+
{
|
176 |
+
switch ($status) {
|
177 |
+
case ConfigInterface::Status_Success:
|
178 |
+
return $this->t('message_response_0');
|
179 |
+
case ConfigInterface::Status_Errors:
|
180 |
+
return $this->t('message_response_1');
|
181 |
+
case ConfigInterface::Status_Warnings:
|
182 |
+
return $this->t('message_response_2');
|
183 |
+
case ConfigInterface::Status_Exception:
|
184 |
+
return $this->t('message_response_3');
|
185 |
+
default:
|
186 |
+
return $this->t('message_response_x') . $status;
|
187 |
+
}
|
188 |
+
}
|
189 |
+
|
190 |
+
/**
|
191 |
+
* Retrieves a list of accounts.
|
192 |
+
*
|
193 |
+
* @return array
|
194 |
+
* Besides the general response structure, the actual result of this call is
|
195 |
+
* returned under the key 'accounts' and consists of an array of 'accounts',
|
196 |
+
* each 'account' being a keyed array with keys:
|
197 |
+
* - accountid
|
198 |
+
* - accountnumber
|
199 |
+
* - accountdescription
|
200 |
+
*
|
201 |
+
* See https://apidoc.sielsystems.nl/content/picklist-accounts-bankrekeningen.
|
202 |
+
*/
|
203 |
+
public function getPicklistAccounts()
|
204 |
+
{
|
205 |
+
return $this->getPicklist('account');
|
206 |
+
}
|
207 |
+
|
208 |
+
/**
|
209 |
+
* Retrieves a list of contact types.
|
210 |
+
*
|
211 |
+
* @return array
|
212 |
+
* Besides the general response structure, the actual result of this call is
|
213 |
+
* returned under the key 'contacttypes' and consists of an array of
|
214 |
+
* 'contacttypes', each 'contacttype' being a keyed array with keys:
|
215 |
+
* - contacttypeid
|
216 |
+
* - contacttypename
|
217 |
+
*
|
218 |
+
* See https://apidoc.sielsystems.nl/content/picklist-contacttypes-contactsoorten.
|
219 |
+
*/
|
220 |
+
public function getPicklistContactTypes()
|
221 |
+
{
|
222 |
+
return $this->getPicklist('contacttype');
|
223 |
+
}
|
224 |
+
|
225 |
+
/**
|
226 |
+
* Retrieves a list of cost centers.
|
227 |
+
*
|
228 |
+
* @return array
|
229 |
+
* Besides the general response structure, the actual result of this call is
|
230 |
+
* returned under the key 'costcenters' and consists of an array of
|
231 |
+
* 'costcenters', each 'costcenter' being a keyed array with keys:
|
232 |
+
* - costcenterid
|
233 |
+
* - costcentername
|
234 |
+
*
|
235 |
+
* See https://apidoc.sielsystems.nl/content/picklist-costcenters-kostenplaatsen.
|
236 |
+
*/
|
237 |
+
public function getPicklistCostCenters()
|
238 |
+
{
|
239 |
+
return $this->getPicklist('costcenter');
|
240 |
+
}
|
241 |
+
|
242 |
+
/**
|
243 |
+
* Retrieves a list of cost headings.
|
244 |
+
*
|
245 |
+
* @return array
|
246 |
+
* Besides the general response structure, the actual result of this call is
|
247 |
+
* returned under the key 'costheadings' and consists of an array of
|
248 |
+
* 'costheadings', each 'costheading' being a keyed array with keys:
|
249 |
+
* - costheadingid
|
250 |
+
* - costheadingname
|
251 |
+
*
|
252 |
+
* See https://apidoc.sielsystems.nl/content/picklist-costheadings-kostensoorten.
|
253 |
+
*/
|
254 |
+
public function getPicklistCostHeadings()
|
255 |
+
{
|
256 |
+
return $this->getPicklist('costheading');
|
257 |
+
}
|
258 |
+
|
259 |
+
/**
|
260 |
+
* Retrieves a list of cost types.
|
261 |
+
*
|
262 |
+
* @return array
|
263 |
+
* Besides the general response structure, the actual result of this call is
|
264 |
+
* returned under the key 'invoicetemplates' and consists of an array of
|
265 |
+
* 'invoicetemplates', each 'invoicetemplate' being a keyed array with keys:
|
266 |
+
* - invoicetemplateid
|
267 |
+
* - invoicetemplatename
|
268 |
+
*
|
269 |
+
* See https://apidoc.sielsystems.nl/content/picklist-invoice-templates-factuursjablonen.
|
270 |
+
*/
|
271 |
+
public function getPicklistInvoiceTemplates()
|
272 |
+
{
|
273 |
+
return $this->getPicklist('invoicetemplate');
|
274 |
+
}
|
275 |
+
|
276 |
+
/**
|
277 |
+
* Retrieves a list of VAT types.
|
278 |
+
*
|
279 |
+
* @return array
|
280 |
+
* Besides the general response structure, the actual result of this call is
|
281 |
+
* returned under the key 'vattypes' and consists of an array of 'vattypes',
|
282 |
+
* each 'vattype' being a keyed array with keys:
|
283 |
+
* - 'vattypeid'
|
284 |
+
* - 'vattypename'
|
285 |
+
*
|
286 |
+
* See https://apidoc.sielsystems.nl/content/picklist-vattypes-btw-groepen.
|
287 |
+
*/
|
288 |
+
public function getPicklistVatTypes()
|
289 |
+
{
|
290 |
+
return $this->getPicklist('vattype');
|
291 |
+
}
|
292 |
+
|
293 |
+
/**
|
294 |
+
* A helper method to retrieve a given picklist.
|
295 |
+
*
|
296 |
+
* The Acumulus API for picklists is so well standardized, that it is possible
|
297 |
+
* to use 1 general picklist retrieval function that can process all picklist
|
298 |
+
* types.
|
299 |
+
*
|
300 |
+
* @param string $picklist
|
301 |
+
* The picklist to retrieve, specify in singular form: account, contacttype,
|
302 |
+
* costcenter, etc.
|
303 |
+
*
|
304 |
+
* @return array
|
305 |
+
* Besides the general response structure, the actual result of this call is
|
306 |
+
* returned under the key $picklist in plural format (with an 's' attached)
|
307 |
+
* and consists of an array of keyed arrays, each keyed array being 1 result
|
308 |
+
* of the requested picklist.
|
309 |
+
*/
|
310 |
+
protected function getPicklist($picklist)
|
311 |
+
{
|
312 |
+
$plural = $picklist . 's';
|
313 |
+
$response = $this->getCommunicator()->callApiFunction("picklists/picklist_$plural", array());
|
314 |
+
// Simplify result: remove indirection.
|
315 |
+
if (!empty($response[$plural][$picklist])) {
|
316 |
+
$response[$plural] = $response[$plural][$picklist];
|
317 |
+
// If there was only 1 result, it wasn't put in an array.
|
318 |
+
if (!is_array(reset($response[$plural]))) {
|
319 |
+
$response[$plural] = array($response[$plural]);
|
320 |
+
}
|
321 |
+
} else {
|
322 |
+
$response[$plural] = array();
|
323 |
+
}
|
324 |
+
return $response;
|
325 |
+
}
|
326 |
+
|
327 |
+
/**
|
328 |
+
* Retrieves a list of VAT rates for the given country at the given date.
|
329 |
+
*
|
330 |
+
* @param string $countryCode
|
331 |
+
* Country code of the country to retrieve the VAT info for.
|
332 |
+
* @param string $date
|
333 |
+
* ISO date string (yyyy-mm-dd) for the date to retrieve the VAT info for.
|
334 |
+
*
|
335 |
+
* @return array
|
336 |
+
* Besides the general response structure, the actual result of this call is
|
337 |
+
* returned under the key 'vatinfo' and consists of an array of "vatinfo's",
|
338 |
+
* each 'vatinfo' being a keyed array with keys:
|
339 |
+
* - vattype
|
340 |
+
* - vatrate
|
341 |
+
*
|
342 |
+
* See https://apidoc.sielsystems.nl/content/lookup-vatinfo-btw-informatie.
|
343 |
+
*/
|
344 |
+
public function getVatInfo($countryCode, $date = '')
|
345 |
+
{
|
346 |
+
if (empty($date)) {
|
347 |
+
$date = date('Y-m-d');
|
348 |
+
}
|
349 |
+
$message = array(
|
350 |
+
'vatdate' => $date,
|
351 |
+
'vatcountry' => $countryCode,
|
352 |
+
);
|
353 |
+
$response = $this->getCommunicator()->callApiFunction("lookups/lookup_vatinfo", $message);
|
354 |
+
// Simplify result: remove indirection.
|
355 |
+
if (!empty($response['vatinfo']['vat'])) {
|
356 |
+
$response['vatinfo'] = $response['vatinfo']['vat'];
|
357 |
+
// If there was only 1 result, it wasn't put in an array.
|
358 |
+
if (!is_array(reset($response['vatinfo']))) {
|
359 |
+
$response['vatinfo'] = array($response['vatinfo']);
|
360 |
+
}
|
361 |
+
} else {
|
362 |
+
$response['vatinfo'] = array();
|
363 |
+
}
|
364 |
+
return $response;
|
365 |
+
}
|
366 |
+
|
367 |
+
/**
|
368 |
+
* Sends an invoice to Acumulus.
|
369 |
+
*
|
370 |
+
* @param array $invoice
|
371 |
+
* The invoice to send.
|
372 |
+
*
|
373 |
+
* @return array
|
374 |
+
* Besides the general response structure, the actual result of this call is
|
375 |
+
* returned under the following key:
|
376 |
+
* - invoice: an array of information about the created invoice, being an
|
377 |
+
* array with keys:
|
378 |
+
* - invoicenumber
|
379 |
+
* - token
|
380 |
+
* - entryid
|
381 |
+
* If the key invoice is present, it indicates success.
|
382 |
+
*
|
383 |
+
* See https://apidoc.sielsystems.nl/content/invoice-add.
|
384 |
+
* See https://apidoc.sielsystems.nl/content/warning-error-and-status-response-section-most-api-calls
|
385 |
+
* for more information on the contents of the returned array.
|
386 |
+
*/
|
387 |
+
public function invoiceAdd(array $invoice)
|
388 |
+
{
|
389 |
+
return $this->getCommunicator()->callApiFunction("invoices/invoice_add", $invoice);
|
390 |
+
}
|
391 |
+
|
392 |
+
/**
|
393 |
+
* Merges any local messages into the result structure and adapts the status
|
394 |
+
* to correctly reflect local warnings and errors as well.
|
395 |
+
*
|
396 |
+
* @param array $result
|
397 |
+
* @param array $localMessages
|
398 |
+
*
|
399 |
+
* @return array
|
400 |
+
*/
|
401 |
+
public function mergeLocalMessages(array $result, array $localMessages)
|
402 |
+
{
|
403 |
+
if (!empty($localMessages['errors'])) {
|
404 |
+
$result['errors'] = array_merge($result['errors'], $localMessages['errors']);
|
405 |
+
}
|
406 |
+
if (!empty($localMessages['warnings'])) {
|
407 |
+
$result['warnings'] = array_merge($result['warnings'], $localMessages['warnings']);
|
408 |
+
}
|
409 |
+
$this->getCommunicator()->checkStatus($result);
|
410 |
+
return $result;
|
411 |
+
}
|
412 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/Web/Translations.php
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\Web;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\TranslationCollection;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Contains translations for classes in the \Siel\Acumulus\Web namespace.
|
8 |
+
*/
|
9 |
+
class Translations extends TranslationCollection
|
10 |
+
{
|
11 |
+
protected $nl = array(
|
12 |
+
'message_error' => 'Fout',
|
13 |
+
'message_warning' => 'Waarschuwing',
|
14 |
+
'message_info_for_user' => 'De informatie hieronder wordt alleen getoond om eventuele support te vergemakkelijken. U kunt deze informatie negeren.',
|
15 |
+
'message_sent' => 'Verzonden bericht',
|
16 |
+
'message_received' => 'Ontvangen bericht',
|
17 |
+
'message_response_0' => 'Succes. Zonder waarschuwingen',
|
18 |
+
'message_response_1' => 'Mislukt. Fouten gevonden',
|
19 |
+
'message_response_2' => 'Succes. Met waarschuwingen',
|
20 |
+
'message_response_3' => 'Fout. Neem contact op met Acumulus',
|
21 |
+
'message_response_x' => 'Onbekende status code',
|
22 |
+
);
|
23 |
+
|
24 |
+
protected $en = array(
|
25 |
+
'message_error' => 'Error',
|
26 |
+
'message_warning' => 'Warning',
|
27 |
+
'message_info_for_user' => 'The information below is only shown to facilitate support. You may ignore these messages.',
|
28 |
+
'message_sent' => 'Message sent',
|
29 |
+
'message_received' => 'Message received',
|
30 |
+
'message_response_0' => 'Success. Without warnings',
|
31 |
+
'message_response_1' => 'Failed. Errors found',
|
32 |
+
'message_response_2' => 'Success. With any warnings',
|
33 |
+
'message_response_3' => 'Exception. Please contact Acumulus technical support',
|
34 |
+
'message_response_x' => 'Unknown status code',
|
35 |
+
);
|
36 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Helpers/FormMapper.php
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\WooCommerce\Helpers;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\Form;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* FormMapper maps an Acumulus form definition to a WooCommerce form definition.
|
8 |
+
*/
|
9 |
+
class FormMapper
|
10 |
+
{
|
11 |
+
const required = '<span class="required">*</span>';
|
12 |
+
|
13 |
+
/** @var string */
|
14 |
+
protected $page;
|
15 |
+
|
16 |
+
/** @var FormRenderer */
|
17 |
+
protected $formRenderer;
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Maps a set of field definitions.
|
21 |
+
*
|
22 |
+
* @param Form $form
|
23 |
+
* @param string $page
|
24 |
+
*
|
25 |
+
* @return \Siel\Acumulus\WooCommerce\Helpers\FormRenderer
|
26 |
+
*/
|
27 |
+
public function map(Form $form, $page)
|
28 |
+
{
|
29 |
+
$this->formRenderer = new FormRenderer();
|
30 |
+
$this->page = $page;
|
31 |
+
$form->addValues();
|
32 |
+
$this->fields($form->getFields(), '');
|
33 |
+
return $this->formRenderer;
|
34 |
+
}
|
35 |
+
|
36 |
+
/**
|
37 |
+
* Maps a set of field definitions.
|
38 |
+
*
|
39 |
+
* @param array[] $fields
|
40 |
+
* @param string $section
|
41 |
+
* The page section to add the fields to.
|
42 |
+
*/
|
43 |
+
protected function fields(array $fields, $section)
|
44 |
+
{
|
45 |
+
foreach ($fields as $id => $field) {
|
46 |
+
$field['id'] = $id;
|
47 |
+
if (!isset($field['name'])) {
|
48 |
+
$field['name'] = $id;
|
49 |
+
}
|
50 |
+
if (!isset($field['attributes'])) {
|
51 |
+
$field['attributes'] = array();
|
52 |
+
}
|
53 |
+
if (!isset($field['label'])) {
|
54 |
+
$field['label'] = '';
|
55 |
+
}
|
56 |
+
$this->field($field, $section);
|
57 |
+
}
|
58 |
+
}
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Maps a single field definition.
|
62 |
+
*
|
63 |
+
* @param array $field
|
64 |
+
* Field(set) definition.
|
65 |
+
* @param string $section
|
66 |
+
* The section this item (if it is a field) should be added to.
|
67 |
+
*/
|
68 |
+
protected function field(array $field, $section)
|
69 |
+
{
|
70 |
+
if ($field['type'] === 'fieldset') {
|
71 |
+
$renderer = $this->formRenderer;
|
72 |
+
add_settings_section($field['id'], $field['legend'], function () use ($renderer, $field) {
|
73 |
+
$renderer->field($field);
|
74 |
+
}, $this->page);
|
75 |
+
$this->fields($field['fields'], $field['id']);
|
76 |
+
} else {
|
77 |
+
$required = !empty($field['attributes']['required']) ? static::required : '';
|
78 |
+
add_settings_field($field['id'], $field['label'] . $required, array($this->formRenderer, 'field'), $this->page, $section, $field);
|
79 |
+
}
|
80 |
+
}
|
81 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Helpers/FormRenderer.php
ADDED
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\WooCommerce\Helpers;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\Form;
|
5 |
+
use Siel\Acumulus\Helpers\FormRenderer as BaseFormRenderer;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class FormRenderer renders a form in the WordPress settings pages standard.
|
9 |
+
*/
|
10 |
+
class FormRenderer extends BaseFormRenderer
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* Constructor.
|
14 |
+
*/
|
15 |
+
public function __construct()
|
16 |
+
{
|
17 |
+
$this->radioWrapperTag = 'ul';
|
18 |
+
$this->radioWrapperClass = '';
|
19 |
+
$this->radio1WrapperTag = 'li';
|
20 |
+
$this->radio1WrapperClass = '';
|
21 |
+
$this->checkboxWrapperTag = 'ul';
|
22 |
+
$this->checkboxWrapperClass = '';
|
23 |
+
$this->checkbox1WrapperTag = 'li';
|
24 |
+
$this->checkbox1WrapperClass = '';
|
25 |
+
}
|
26 |
+
|
27 |
+
/**
|
28 |
+
* @inheritdoc
|
29 |
+
*
|
30 |
+
* This override does only set the form, as we need it later on to retrieve
|
31 |
+
* additional information about field sets.
|
32 |
+
*/
|
33 |
+
public function render(Form $form)
|
34 |
+
{
|
35 |
+
$this->form = $form;
|
36 |
+
return '';
|
37 |
+
}
|
38 |
+
|
39 |
+
/**
|
40 |
+
* @inheritdoc
|
41 |
+
*
|
42 |
+
* This override makes access public and echo's the output besides returning
|
43 |
+
* it as WordPress is in field by field outputting mode when this method gets
|
44 |
+
* called.
|
45 |
+
*/
|
46 |
+
public function field(array $field)
|
47 |
+
{
|
48 |
+
$output = parent::field($field);
|
49 |
+
echo $output;
|
50 |
+
return $output;
|
51 |
+
}
|
52 |
+
|
53 |
+
/**
|
54 |
+
* @inheritdoc
|
55 |
+
*
|
56 |
+
* This override only renders the description as WordPress already renders the
|
57 |
+
* fieldset title and the fields.
|
58 |
+
*/
|
59 |
+
protected function renderFieldset(array $field)
|
60 |
+
{
|
61 |
+
$output = '';
|
62 |
+
if (!empty($field['description'])) {
|
63 |
+
$output .= $this->renderDescription($field['description']);
|
64 |
+
}
|
65 |
+
return $output;
|
66 |
+
}
|
67 |
+
|
68 |
+
/**
|
69 |
+
* {@inheritdoc}
|
70 |
+
*
|
71 |
+
* This override:
|
72 |
+
* - Skips the rendering of the wrapper and label as WordPress does that
|
73 |
+
* itself.
|
74 |
+
* - Echo's the output as WordPress is in outputting mode here.
|
75 |
+
*/
|
76 |
+
protected function renderField(array $field)
|
77 |
+
{
|
78 |
+
$type = $field['type'];
|
79 |
+
$id = $field['id'];
|
80 |
+
$name = $field['name'];
|
81 |
+
$value = isset($field['value']) ? $field['value'] : '';
|
82 |
+
$attributes = $field['attributes'];
|
83 |
+
$description = isset($field['description']) ? $field['description'] : '';
|
84 |
+
$options = isset($field['options']) ? $field['options'] : array();
|
85 |
+
|
86 |
+
$output = '';
|
87 |
+
|
88 |
+
$output .= $this->renderElement($type, $id, $name, $value, $attributes, $options);
|
89 |
+
if ($type !== 'hidden') {
|
90 |
+
$output .= $this->renderDescription($description);
|
91 |
+
}
|
92 |
+
return $output;
|
93 |
+
}
|
94 |
+
|
95 |
+
/**
|
96 |
+
* Recursively searches a field by its id.
|
97 |
+
*
|
98 |
+
* @param array[] $fields
|
99 |
+
* @param string $id
|
100 |
+
*
|
101 |
+
* @return array|null
|
102 |
+
*/
|
103 |
+
protected function getFieldById($fields, $id)
|
104 |
+
{
|
105 |
+
if (array_key_exists($id, $fields)) {
|
106 |
+
return $fields[$id];
|
107 |
+
}
|
108 |
+
foreach ($fields as $id => $field) {
|
109 |
+
if (isset($field['fields'])) {
|
110 |
+
$result = $this->getFieldById($field['fields'], $id);
|
111 |
+
if ($result !== null) {
|
112 |
+
return $result;
|
113 |
+
}
|
114 |
+
}
|
115 |
+
}
|
116 |
+
return null;
|
117 |
+
}
|
118 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Helpers/Log.php
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\WooCommerce\Helpers;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\Log as BaseLog;
|
5 |
+
use WC_Logger;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Extends the base log class to log any library logging to the WP log.
|
9 |
+
*/
|
10 |
+
class Log extends BaseLog
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* {@inheritdoc}
|
14 |
+
*
|
15 |
+
* This override checks for WP_DEBUG first.
|
16 |
+
*/
|
17 |
+
protected function write($message, $severity)
|
18 |
+
{
|
19 |
+
$logger = new WC_Logger();
|
20 |
+
$message = sprintf('%s - %s', $this->getSeverityString($severity), $message);
|
21 |
+
$logger->add('acumulus', $message);
|
22 |
+
}
|
23 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Helpers/Mailer.php
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\WooCommerce\Helpers;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\Mailer as BaseMailer;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Extends the base mailer class to send a mail using the WP mail features.
|
8 |
+
*/
|
9 |
+
class Mailer extends BaseMailer
|
10 |
+
{
|
11 |
+
/**
|
12 |
+
* {@inheritdoc}
|
13 |
+
*/
|
14 |
+
public function sendInvoiceAddMailResult(array $result, array $messages, $invoiceSourceType, $invoiceSourceReference)
|
15 |
+
{
|
16 |
+
$to = $this->getToAddress();
|
17 |
+
|
18 |
+
$subject = $this->getSubject($result);
|
19 |
+
|
20 |
+
$fromEmail = get_bloginfo('admin_email');
|
21 |
+
$fromName = get_bloginfo('name');
|
22 |
+
$headers = array(
|
23 |
+
"from: $fromName <$fromEmail>",
|
24 |
+
'Content-Type: text/html; charset=UTF-8',
|
25 |
+
);
|
26 |
+
|
27 |
+
$body = $this->getBody($result, $messages, $invoiceSourceType, $invoiceSourceReference);
|
28 |
+
$html = $body['html'];
|
29 |
+
|
30 |
+
return wp_mail($to, $subject, $html, $headers);
|
31 |
+
}
|
32 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Invoice/Creator.php
ADDED
@@ -0,0 +1,486 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\WooCommerce\Invoice;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Helpers\Number;
|
5 |
+
use Siel\Acumulus\Invoice\ConfigInterface;
|
6 |
+
use Siel\Acumulus\Invoice\Creator as BaseCreator;
|
7 |
+
use WC_Abstract_Order;
|
8 |
+
use WC_Coupon;
|
9 |
+
use WC_Order;
|
10 |
+
use WC_Product;
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Allows to create arrays in the Acumulus invoice structure from a WordPress
|
14 |
+
* order or order refund.
|
15 |
+
*/
|
16 |
+
class Creator extends BaseCreator
|
17 |
+
{
|
18 |
+
// More specifically typed property.
|
19 |
+
/** @var Source */
|
20 |
+
protected $invoiceSource;
|
21 |
+
|
22 |
+
/** @var WC_Abstract_Order The order or refund that is sent to Acumulus. */
|
23 |
+
protected $shopSource;
|
24 |
+
|
25 |
+
/** @var WC_Order The order self or the order that got refunded. */
|
26 |
+
protected $order;
|
27 |
+
|
28 |
+
/** @var bool Whether the order has (non empty) item lines. */
|
29 |
+
protected $hasItemLines;
|
30 |
+
|
31 |
+
/**
|
32 |
+
* {@inheritdoc}
|
33 |
+
*
|
34 |
+
* This override also initializes WooCommerce specific properties related to
|
35 |
+
* the source.
|
36 |
+
*/
|
37 |
+
protected function setInvoiceSource($invoiceSource)
|
38 |
+
{
|
39 |
+
parent::setInvoiceSource($invoiceSource);
|
40 |
+
switch ($this->invoiceSource->getType()) {
|
41 |
+
case Source::Order:
|
42 |
+
$this->shopSource = $this->invoiceSource->getSource();
|
43 |
+
$this->order = $this->shopSource;
|
44 |
+
break;
|
45 |
+
case Source::CreditNote:
|
46 |
+
$this->shopSource = $this->invoiceSource->getSource();
|
47 |
+
$this->order = new WC_Order($this->shopSource->post->post_parent);
|
48 |
+
break;
|
49 |
+
}
|
50 |
+
}
|
51 |
+
|
52 |
+
/**
|
53 |
+
* {@inheritdoc}
|
54 |
+
*/
|
55 |
+
protected function getCustomer()
|
56 |
+
{
|
57 |
+
$result = array();
|
58 |
+
|
59 |
+
$order = $this->order;
|
60 |
+
|
61 |
+
$this->addIfNotEmpty($result, 'contactyourid', $order->customer_user);
|
62 |
+
$this->addEmpty($result, 'companyname1', $order->billing_company);
|
63 |
+
$result['fullname'] = $order->billing_first_name . ' ' . $order->billing_last_name;
|
64 |
+
$this->addEmpty($result, 'address1', $order->billing_address_1);
|
65 |
+
$this->addEmpty($result, 'address2', $order->billing_address_2);
|
66 |
+
$this->addEmpty($result, 'postalcode', $order->billing_postcode);
|
67 |
+
$this->addEmpty($result, 'city', $order->billing_city);
|
68 |
+
if (isset($order->billing_country)) {
|
69 |
+
$result['countrycode'] = $order->billing_country;
|
70 |
+
}
|
71 |
+
// The EU VAT Number plugin allows customers to indicate their VAT number as
|
72 |
+
// to apply for the reversed VAT scheme. The vat number is stored under the
|
73 |
+
// '_vat_number' meta key, though older versions did so under the
|
74 |
+
// 'VAT Number' key.
|
75 |
+
// See http://docs.woothemes.com/document/eu-vat-number-2/
|
76 |
+
$this->addIfNotEmpty($result, 'vatnumber', get_post_meta($order->id, 'VAT Number', true));
|
77 |
+
$this->addIfNotEmpty($result, 'vatnumber', get_post_meta($order->id, 'vat_number', true));
|
78 |
+
$this->addIfNotEmpty($result, 'telephone', $order->billing_phone);
|
79 |
+
$result['email'] = $order->billing_email;
|
80 |
+
|
81 |
+
return $result;
|
82 |
+
}
|
83 |
+
|
84 |
+
/**
|
85 |
+
* {@inheritdoc}
|
86 |
+
*
|
87 |
+
* For refunds, this override also searches in the order that gets refunded.
|
88 |
+
*/
|
89 |
+
protected function searchProperty($property)
|
90 |
+
{
|
91 |
+
$value = parent::searchProperty($property);
|
92 |
+
if (empty($value) && $this->invoiceSource->getType() === Source::CreditNote) {
|
93 |
+
// Also try the order that gets refunded.
|
94 |
+
$value = $this->getProperty($property, $this->order);
|
95 |
+
}
|
96 |
+
return $value;
|
97 |
+
}
|
98 |
+
|
99 |
+
/**
|
100 |
+
* {@inheritdoc}
|
101 |
+
*/
|
102 |
+
protected function getInvoiceNumber($invoiceNumberSource)
|
103 |
+
{
|
104 |
+
return $this->invoiceSource->getReference();
|
105 |
+
}
|
106 |
+
|
107 |
+
/**
|
108 |
+
* Returns the date to use as invoice date for the order.
|
109 |
+
*
|
110 |
+
* param int $dateToUse
|
111 |
+
* \Siel\Acumulus\Invoice\ConfigInterface\InvoiceDate_InvoiceCreate or
|
112 |
+
* \Siel\Acumulus\Invoice\ConfigInterface\InvoiceDate_OrderCreate
|
113 |
+
*
|
114 |
+
* @return string
|
115 |
+
* Date to send to Acumulus as the invoice date: yyyy-mm-dd.
|
116 |
+
*/
|
117 |
+
protected function getInvoiceDateOrder(/*$dateToUse*/)
|
118 |
+
{
|
119 |
+
// createdAt returns yyyy-mm-dd hh:mm:ss, take date part.
|
120 |
+
return substr($this->shopSource->order_date, 0, strlen('2000-01-01'));
|
121 |
+
}
|
122 |
+
|
123 |
+
/**
|
124 |
+
* Returns the date to use as invoice date for the order refund.
|
125 |
+
*
|
126 |
+
* param int $dateToUse
|
127 |
+
* \Siel\Acumulus\Invoice\ConfigInterface\InvoiceDate_InvoiceCreate or
|
128 |
+
* \Siel\Acumulus\Invoice\ConfigInterface\InvoiceDate_OrderCreate
|
129 |
+
*
|
130 |
+
* @return string
|
131 |
+
* Date to send to Acumulus as the invoice date: yyyy-mm-dd.
|
132 |
+
*/
|
133 |
+
protected function getInvoiceDateCreditNote(/*$dateToUse*/)
|
134 |
+
{
|
135 |
+
return substr($this->shopSource->post->post_date, 0, strlen('2000-01-01'));
|
136 |
+
}
|
137 |
+
|
138 |
+
/**
|
139 |
+
* Returns whether the order has been paid or not.
|
140 |
+
*
|
141 |
+
* @return int
|
142 |
+
* \Siel\Acumulus\Invoice\ConfigInterface::PaymentStatus_Paid or
|
143 |
+
* \Siel\Acumulus\Invoice\ConfigInterface::PaymentStatus_Due
|
144 |
+
*/
|
145 |
+
protected function getPaymentStateOrder()
|
146 |
+
{
|
147 |
+
return $this->shopSource->needs_payment() ? ConfigInterface::PaymentStatus_Due : ConfigInterface::PaymentStatus_Paid;
|
148 |
+
}
|
149 |
+
|
150 |
+
/**
|
151 |
+
* Returns whether the order refund has been paid or not.
|
152 |
+
*
|
153 |
+
* For now we assume that a refund is paid back on creation.
|
154 |
+
*
|
155 |
+
* @return int
|
156 |
+
* \Siel\Acumulus\Invoice\ConfigInterface::PaymentStatus_Paid or
|
157 |
+
* \Siel\Acumulus\Invoice\ConfigInterface::PaymentStatus_Due
|
158 |
+
*/
|
159 |
+
protected function getPaymentStateCreditNote()
|
160 |
+
{
|
161 |
+
return ConfigInterface::PaymentStatus_Paid;
|
162 |
+
}
|
163 |
+
|
164 |
+
/**
|
165 |
+
* Returns the payment date of the order.
|
166 |
+
*
|
167 |
+
* @return string
|
168 |
+
* The payment date of the order (yyyy-mm-dd).
|
169 |
+
*/
|
170 |
+
protected function getPaymentDateOrder()
|
171 |
+
{
|
172 |
+
return substr($this->shopSource->paid_date, 0, strlen('2000-01-01'));
|
173 |
+
}
|
174 |
+
|
175 |
+
/**
|
176 |
+
* Returns the payment date of the order refund.
|
177 |
+
*
|
178 |
+
* We take the last modified date as pay date.
|
179 |
+
*
|
180 |
+
* @return string
|
181 |
+
* The payment date of the order refund (yyyy-mm-dd).
|
182 |
+
*/
|
183 |
+
protected function getPaymentDateCreditNote()
|
184 |
+
{
|
185 |
+
return substr($this->shopSource->post->post_modified, 0, strlen('2000-01-01'));
|
186 |
+
}
|
187 |
+
|
188 |
+
/**
|
189 |
+
* {@inheritdoc}
|
190 |
+
*
|
191 |
+
* This override provides the values meta-invoice-amountinc and
|
192 |
+
* meta-invoice-vatamount.
|
193 |
+
*/
|
194 |
+
protected function getInvoiceTotals()
|
195 |
+
{
|
196 |
+
return array(
|
197 |
+
'meta-invoice-amountinc' => $this->shopSource->get_total(),
|
198 |
+
'meta-invoice-vatamount' => $this->shopSource->get_total_tax(),
|
199 |
+
);
|
200 |
+
}
|
201 |
+
|
202 |
+
/**
|
203 |
+
* {@inheritdoc}
|
204 |
+
*/
|
205 |
+
protected function getItemLines()
|
206 |
+
{
|
207 |
+
$result = array();
|
208 |
+
$lines = $this->shopSource->get_items(apply_filters('woocommerce_admin_order_item_types', 'line_item'));
|
209 |
+
foreach ($lines as $order_item_id => $line) {
|
210 |
+
$line['order_item_id'] = $order_item_id;
|
211 |
+
$itemLine = $this->getItemLine($line);
|
212 |
+
if ($itemLine) {
|
213 |
+
$result[] = $itemLine;
|
214 |
+
}
|
215 |
+
}
|
216 |
+
|
217 |
+
$this->hasItemLines = count($result) > 0;
|
218 |
+
return $result;
|
219 |
+
}
|
220 |
+
|
221 |
+
/**
|
222 |
+
* Returns 1 item line.
|
223 |
+
*
|
224 |
+
* @param array $item
|
225 |
+
*
|
226 |
+
* @return array
|
227 |
+
* May be empty if the line should not be sent (e.g. qty = 0 on a refund).
|
228 |
+
*/
|
229 |
+
protected function getItemLine(array $item)
|
230 |
+
{
|
231 |
+
$result = array();
|
232 |
+
|
233 |
+
// Qty = 0 can happen on refunds: products that are not returned are still
|
234 |
+
// listed but have qty = 0.
|
235 |
+
if (Number::isZero($item['qty'])) {
|
236 |
+
return $result;
|
237 |
+
}
|
238 |
+
|
239 |
+
// get_item_total() returns cost per item after discount and ex vat (2nd
|
240 |
+
// param).
|
241 |
+
$productPriceEx = $this->shopSource->get_item_total($item, false, false);
|
242 |
+
$productPriceInc = $this->shopSource->get_item_total($item, true, false);
|
243 |
+
// get_item_tax returns tax per item after discount.
|
244 |
+
$productVat = $this->shopSource->get_item_tax($item, false);
|
245 |
+
|
246 |
+
$result['product'] = $item['name'];
|
247 |
+
$isVariation = !empty($item['variation_id']);
|
248 |
+
$product = $this->shopSource->get_product_from_item($item);
|
249 |
+
// $product can be NULL if the product has been deleted.
|
250 |
+
if ($product instanceof WC_Product) {
|
251 |
+
$this->addIfNotEmpty($result, 'itemnumber', $product->get_sku());
|
252 |
+
if ($isVariation) {
|
253 |
+
$variation = $this->getVariantDescription($item, $product);
|
254 |
+
$result['product'] .= " ($variation)";
|
255 |
+
}
|
256 |
+
}
|
257 |
+
|
258 |
+
// WooCommerce does not support the margin scheme. So in a standard install
|
259 |
+
// this method will always return false. But if this method happens to
|
260 |
+
// return true anyway (customisation, hook), the costprice tag will trigger
|
261 |
+
// vattype = 5 for Acumulus.
|
262 |
+
if ($this->allowMarginScheme() && !empty($item['cost_price'])) {
|
263 |
+
// Margin scheme:
|
264 |
+
// - Do not put VAT on invoice: send price incl VAT as unitprice.
|
265 |
+
// - But still send the VAT rate to Acumulus.
|
266 |
+
// Costprice > 0 is the trigger for Acumulus to use the margin scheme.
|
267 |
+
$result += array(
|
268 |
+
'unitprice' => $productPriceInc,
|
269 |
+
'costprice' => $item['cost_price'],
|
270 |
+
);
|
271 |
+
} else {
|
272 |
+
$result += array(
|
273 |
+
'unitprice' => $productPriceEx,
|
274 |
+
'unitpriceinc' => $productPriceInc,
|
275 |
+
'vatamount' => $productVat,
|
276 |
+
);
|
277 |
+
}
|
278 |
+
|
279 |
+
$result['quantity'] = $item['qty'];
|
280 |
+
// Precision: one of the prices is entered by the administrator and thus can
|
281 |
+
// be considered exact. The computed one is not rounded, so we can assume a
|
282 |
+
// very high precision for all values here.
|
283 |
+
$result += $this->getVatRangeTags($productVat, $productPriceEx, 0.001, 0.001);
|
284 |
+
|
285 |
+
return $result;
|
286 |
+
}
|
287 |
+
|
288 |
+
/**
|
289 |
+
* Returns a description for this variant.
|
290 |
+
*
|
291 |
+
* @param array $item
|
292 |
+
* @param \WC_Product $product
|
293 |
+
*
|
294 |
+
* @return string
|
295 |
+
* The description for this variant, a string in the form:
|
296 |
+
* (variant: value, variant: value, ...)
|
297 |
+
*/
|
298 |
+
protected function getVariantDescription(array $item, \WC_Product $product)
|
299 |
+
{
|
300 |
+
$result = array();
|
301 |
+
|
302 |
+
if ($metadata = $this->shopSource->has_meta($item['order_item_id'])) {
|
303 |
+
// Define hidden core fields.
|
304 |
+
$hiddenOrderItemMeta = apply_filters('woocommerce_hidden_order_itemmeta', array(
|
305 |
+
'_qty',
|
306 |
+
'_tax_class',
|
307 |
+
'_product_id',
|
308 |
+
'_variation_id',
|
309 |
+
'_line_subtotal',
|
310 |
+
'_line_subtotal_tax',
|
311 |
+
'_line_total',
|
312 |
+
'_line_tax',
|
313 |
+
));
|
314 |
+
foreach ($metadata as $meta) {
|
315 |
+
// Skip hidden core fields and serialized data (also hidden core fields).
|
316 |
+
if (in_array($meta['meta_key'], $hiddenOrderItemMeta) || is_serialized($meta['meta_value'])) {
|
317 |
+
continue;
|
318 |
+
}
|
319 |
+
|
320 |
+
// Get attribute data.
|
321 |
+
if (taxonomy_exists(wc_sanitize_taxonomy_name($meta['meta_key']))) {
|
322 |
+
$term = get_term_by('slug', $meta['meta_value'], wc_sanitize_taxonomy_name($meta['meta_key']));
|
323 |
+
$meta['meta_key'] = wc_attribute_label(wc_sanitize_taxonomy_name($meta['meta_key']));
|
324 |
+
$meta['meta_value'] = isset($term->name) ? $term->name : $meta['meta_value'];
|
325 |
+
} else {
|
326 |
+
$meta['meta_key'] = apply_filters('woocommerce_attribute_label', wc_attribute_label($meta['meta_key'], $product), $meta['meta_key']);
|
327 |
+
}
|
328 |
+
|
329 |
+
$result[] = $meta['meta_key'] . ': ' . rawurldecode($meta['meta_value']);
|
330 |
+
}
|
331 |
+
}
|
332 |
+
|
333 |
+
return implode(', ', $result);
|
334 |
+
}
|
335 |
+
|
336 |
+
/**
|
337 |
+
* @inheritdoc
|
338 |
+
*
|
339 |
+
* WooCommerce has general fee lines, so we have to override this method to
|
340 |
+
* add these general fees (type unknown to us)
|
341 |
+
*/
|
342 |
+
protected function getFeeLines()
|
343 |
+
{
|
344 |
+
$result = parent::getFeeLines();
|
345 |
+
|
346 |
+
// So far, all amounts found on refunds are negative, so we probably don't
|
347 |
+
// need to correct the sign on these lines either: but this has not been
|
348 |
+
// tested yet!.
|
349 |
+
foreach ($this->shopSource->get_fees() as $feeLine) {
|
350 |
+
$line = $this->getFeeLine($feeLine);
|
351 |
+
$line['meta-line-type'] = static::LineType_Other;
|
352 |
+
$result[] = $line;
|
353 |
+
}
|
354 |
+
return $result;
|
355 |
+
}
|
356 |
+
|
357 |
+
/**
|
358 |
+
* @param array $line
|
359 |
+
*
|
360 |
+
* @return array
|
361 |
+
*/
|
362 |
+
protected function getFeeLine(array $line)
|
363 |
+
{
|
364 |
+
$feeEx = $line['line_total'];
|
365 |
+
$feeVat = $line['line_tax'];
|
366 |
+
|
367 |
+
$result = array(
|
368 |
+
'product' => $this->t($line['name']),
|
369 |
+
'unitprice' => $feeEx,
|
370 |
+
'quantity' => 1,
|
371 |
+
'vatamount' => $feeVat,
|
372 |
+
) + $this->getVatRangeTags($feeVat, $feeEx);
|
373 |
+
|
374 |
+
return $result;
|
375 |
+
}
|
376 |
+
|
377 |
+
/**
|
378 |
+
* {@inheritdoc}
|
379 |
+
*/
|
380 |
+
protected function getShippingLine()
|
381 |
+
{
|
382 |
+
// Precision: shipping costs are entered ex VAT, so that may be rounded to
|
383 |
+
// the cent by the administrator. The computed costs inc VAT is rounded to
|
384 |
+
// the cent as well, so both are to be considered precise to the cent.
|
385 |
+
$shippingEx = $this->shopSource->get_total_shipping();
|
386 |
+
$shippingVat = $this->shopSource->get_shipping_tax();
|
387 |
+
if (!Number::isZero($shippingEx)) {
|
388 |
+
$description = $this->t('shipping_costs');
|
389 |
+
} else {
|
390 |
+
// We do not need to indicate that free shipping is not refunded on an
|
391 |
+
// order refund.
|
392 |
+
if ($this->invoiceSource->getType() == Source::CreditNote) {
|
393 |
+
return array();
|
394 |
+
}
|
395 |
+
$description = $this->t('free_shipping');
|
396 |
+
}
|
397 |
+
|
398 |
+
$result = array(
|
399 |
+
'product' => $description,
|
400 |
+
'unitprice' => $shippingEx,
|
401 |
+
'quantity' => 1,
|
402 |
+
'vatamount' => $shippingVat,
|
403 |
+
) + $this->getVatRangeTags($shippingVat, $shippingEx);
|
404 |
+
|
405 |
+
return $result;
|
406 |
+
}
|
407 |
+
|
408 |
+
/**
|
409 |
+
* {@inheritdoc}
|
410 |
+
*/
|
411 |
+
protected function getDiscountLines()
|
412 |
+
{
|
413 |
+
$result = array();
|
414 |
+
|
415 |
+
// For refunds without any articles (probably just a manual refund) we don't
|
416 |
+
// need to know what discounts were applied on the original order. So skip
|
417 |
+
// get_used_coupons() on refunds without articles.
|
418 |
+
if ($this->invoiceSource->getType() !== Source::CreditNote || $this->hasItemLines) {
|
419 |
+
// Add a line for all coupons applied. Coupons are only stored on the order,
|
420 |
+
// not on refunds, so use the order property.
|
421 |
+
$usedCoupons = $this->order->get_used_coupons();
|
422 |
+
foreach ($usedCoupons as $code) {
|
423 |
+
$coupon = new WC_Coupon($code);
|
424 |
+
$result[] = $this->getDiscountLine($coupon);
|
425 |
+
}
|
426 |
+
}
|
427 |
+
return $result;
|
428 |
+
}
|
429 |
+
|
430 |
+
/**
|
431 |
+
* Returns 1 order discount line for 1 coupon usage.
|
432 |
+
*
|
433 |
+
* In woocommerce, discounts are implemented with coupons. Multiple coupons
|
434 |
+
* can be used per order. Coupons can:
|
435 |
+
* - have a fixed amount or a percentage.
|
436 |
+
* - be applied to the whole cart or only be used for a set of products.
|
437 |
+
*
|
438 |
+
* Hooray:
|
439 |
+
* As of WooCommerce 2.3, coupons can no longer be set as "apply after tax":
|
440 |
+
* https://woocommerce.wordpress.com/2014/12/12/upcoming-coupon-changes-in-woocommerce-2-3/
|
441 |
+
* WC_Coupon::apply_before_tax() now always returns true (and thus might be
|
442 |
+
* deprecated and removed in the future): do no longer use.
|
443 |
+
*
|
444 |
+
* @param WC_Coupon $coupon
|
445 |
+
*
|
446 |
+
* @return array
|
447 |
+
*/
|
448 |
+
protected function getDiscountLine(WC_Coupon $coupon)
|
449 |
+
{
|
450 |
+
// Get a description for the value of this coupon.
|
451 |
+
// Entered discount amounts follow the wc_prices_include_tax() setting. Use
|
452 |
+
// that info in the description.
|
453 |
+
$description = sprintf('%s %s: ', $this->t('discount_code'), $coupon->code);
|
454 |
+
if (in_array($coupon->discount_type, array('fixed_product', 'fixed_cart'))) {
|
455 |
+
$amount = $this->getSign() * $coupon->coupon_amount;
|
456 |
+
if (!Number::isZero($amount)) {
|
457 |
+
$description .= sprintf('€%.2f (%s)', $amount, wc_prices_include_tax() ? $this->t('inc_vat') : $this->t('ex_vat'));
|
458 |
+
}
|
459 |
+
if ($coupon->enable_free_shipping()) {
|
460 |
+
if (!Number::isZero($amount)) {
|
461 |
+
$description .= ' + ';
|
462 |
+
}
|
463 |
+
$description .= $this->t('free_shipping');
|
464 |
+
}
|
465 |
+
} else {
|
466 |
+
$description .= $coupon->coupon_amount . '%';
|
467 |
+
if ($coupon->enable_free_shipping()) {
|
468 |
+
$description .= ' + ' . $this->t('free_shipping');
|
469 |
+
}
|
470 |
+
}
|
471 |
+
|
472 |
+
// Discounts are already applied, add a descriptive line with 0 amount.
|
473 |
+
// The VAT rate to categorize this line under should be determined by the
|
474 |
+
// completor.
|
475 |
+
return array(
|
476 |
+
'itemnumber' => $coupon->code,
|
477 |
+
'product' => $description,
|
478 |
+
'unitprice' => 0,
|
479 |
+
'unitpriceinc' => 0,
|
480 |
+
'quantity' => 1,
|
481 |
+
'vatrate' => null,
|
482 |
+
'vatamount' => 0,
|
483 |
+
'meta-vatrate-source' => static::VatRateSource_Completor,
|
484 |
+
);
|
485 |
+
}
|
486 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Invoice/Source.php
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\WooCommerce\Invoice;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Invoice\Source as BaseSource;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Wraps a WooCommerce order in an invoice source object.
|
8 |
+
*
|
9 |
+
* Since WC 2.2.0 multiple order types can be defined, @see
|
10 |
+
* wc_register_order_type() and wc_get_order_types(). WooCommerce itself defines
|
11 |
+
* 'shop_order' and 'shop_order_refund'. The base class for all these types of
|
12 |
+
* orders is WC_Abstract_Order
|
13 |
+
*/
|
14 |
+
class Source extends BaseSource
|
15 |
+
{
|
16 |
+
// More specifically typed properties.
|
17 |
+
/** @var \WC_Abstract_Order */
|
18 |
+
protected $source;
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Loads an Order or refund source for the set id.
|
22 |
+
*/
|
23 |
+
protected function setSource()
|
24 |
+
{
|
25 |
+
$this->source = WC()->order_factory->get_order($this->id);
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Sets the id based on the loaded Order or Order refund.
|
30 |
+
*/
|
31 |
+
protected function setId()
|
32 |
+
{
|
33 |
+
$this->id = $this->source->id;
|
34 |
+
}
|
35 |
+
|
36 |
+
/**
|
37 |
+
* {@inheritdoc}
|
38 |
+
*/
|
39 |
+
public function getReference()
|
40 |
+
{
|
41 |
+
// Method get_order_number() is used for when other plugins are installed
|
42 |
+
// that add an order number that differs from the ID. Known plugins that do
|
43 |
+
// so: woocommerce-sequential-order-numbers(-pro) and
|
44 |
+
// wc-sequential-order-numbers.
|
45 |
+
return $this->source->get_order_number();
|
46 |
+
}
|
47 |
+
|
48 |
+
/**
|
49 |
+
* {@inheritDoc}
|
50 |
+
*
|
51 |
+
* @return string
|
52 |
+
*/
|
53 |
+
public function getStatus()
|
54 |
+
{
|
55 |
+
return $this->source->get_status();
|
56 |
+
}
|
57 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Shop/AcumulusEntryModel.php
ADDED
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\WooCommerce\Shop;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Shop\AcumulusEntryModel as BaseAcumulusEntryModel;
|
5 |
+
use Siel\Acumulus\WooCommerce\Helpers\Log;
|
6 |
+
use Siel\Acumulus\WooCommerce\Invoice\Source;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Implements the WooCommerce/WordPress specific acumulus entry model class.
|
10 |
+
*
|
11 |
+
* In WordPress this data is stored as metadata. As such, the "records" returned
|
12 |
+
* here are an array of all metadata values, thus not filtered by Acumulus keys.
|
13 |
+
*/
|
14 |
+
class AcumulusEntryModel extends BaseAcumulusEntryModel
|
15 |
+
{
|
16 |
+
const KEY_ENTRY_ID = '_acumulus_entry_id';
|
17 |
+
const KEY_TOKEN = '_acumulus_token';
|
18 |
+
// Note this meta key is not actually stored as the post_type can give us the
|
19 |
+
// same information.
|
20 |
+
const KEY_TYPE = '_acumulus_type';
|
21 |
+
const KEY_CREATED = '_acumulus_created';
|
22 |
+
const KEY_UPDATED = '_acumulus_updated';
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Helper method that converts a WP/WC post type to a source type constant.
|
26 |
+
*
|
27 |
+
* @param string $shopType
|
28 |
+
*
|
29 |
+
* @return string
|
30 |
+
*/
|
31 |
+
protected function shopTypeToSourceType($shopType)
|
32 |
+
{
|
33 |
+
switch ($shopType) {
|
34 |
+
case 'shop_order':
|
35 |
+
return Source::Order;
|
36 |
+
case 'shop_order_refund':
|
37 |
+
return Source::CreditNote;
|
38 |
+
default:
|
39 |
+
Log::getInstance()->error('InvoiceManager::shopTypeToSourceType(%s): unknown', $shopType);
|
40 |
+
return '';
|
41 |
+
}
|
42 |
+
}
|
43 |
+
|
44 |
+
/**
|
45 |
+
* {@inheritdoc}
|
46 |
+
*/
|
47 |
+
public function getByEntryId($entryId)
|
48 |
+
{
|
49 |
+
$result = false;
|
50 |
+
$metaQuery = array(
|
51 |
+
'posts_per_page' => 1,
|
52 |
+
'meta_key' => static::KEY_ENTRY_ID,
|
53 |
+
'meta_value' => $entryId,
|
54 |
+
'meta_compare' => '=',
|
55 |
+
);
|
56 |
+
$posts = query_posts($metaQuery);
|
57 |
+
if (!empty($posts)) {
|
58 |
+
$post = reset($posts);
|
59 |
+
$result = get_post_meta($post->id);
|
60 |
+
$result[static::KEY_TYPE] = $this->shopTypeToSourceType($post->post_type);
|
61 |
+
}
|
62 |
+
return $result !== false ? $result : null;
|
63 |
+
}
|
64 |
+
|
65 |
+
/**
|
66 |
+
* {@inheritdoc}
|
67 |
+
*/
|
68 |
+
public function getByInvoiceSourceId($invoiceSourceType, $invoiceSourceId)
|
69 |
+
{
|
70 |
+
$result = null;
|
71 |
+
$post = get_post($invoiceSourceId);
|
72 |
+
if (!empty($post->post_type) && $this->shopTypeToSourceType($post->post_type) === $invoiceSourceType) {
|
73 |
+
$result = get_post_meta($invoiceSourceId);
|
74 |
+
if (isset($result[static::KEY_ENTRY_ID])) {
|
75 |
+
// Acumulus meta data found: add invoice type as that is not stored in
|
76 |
+
// the meta data.
|
77 |
+
$result[static::KEY_TYPE] = $invoiceSourceType;
|
78 |
+
} else {
|
79 |
+
$result = null;
|
80 |
+
}
|
81 |
+
}
|
82 |
+
return $result;
|
83 |
+
}
|
84 |
+
|
85 |
+
/**
|
86 |
+
* {@inheritdoc}
|
87 |
+
*
|
88 |
+
* This override uses the WordPress meta data API to store the acumulus entry
|
89 |
+
* data with the order.
|
90 |
+
*/
|
91 |
+
public function save($invoiceSource, $entryId, $token)
|
92 |
+
{
|
93 |
+
$now = $this->sqlNow();
|
94 |
+
$orderId = $invoiceSource->getId();
|
95 |
+
add_post_meta($orderId, static::KEY_CREATED, $now, true);
|
96 |
+
//$exists = add_post_meta($orderId, '_acumulus_created', $now, true) === FALSE;
|
97 |
+
return update_post_meta($orderId, static::KEY_ENTRY_ID, $entryId) !== false
|
98 |
+
&& update_post_meta($orderId, static::KEY_TOKEN, $token) !== false
|
99 |
+
&& update_post_meta($orderId, static::KEY_UPDATED, $now) !== false;
|
100 |
+
}
|
101 |
+
|
102 |
+
/**
|
103 |
+
* {@inheritdoc}
|
104 |
+
*/
|
105 |
+
protected function insert($invoiceSource, $entryId, $token, $created)
|
106 |
+
{
|
107 |
+
throw new \BadMethodCallException(__METHOD__ . ' not implemented');
|
108 |
+
}
|
109 |
+
|
110 |
+
/**
|
111 |
+
* {@inheritdoc}
|
112 |
+
*/
|
113 |
+
protected function update($record, $entryId, $token, $updated)
|
114 |
+
{
|
115 |
+
throw new \BadMethodCallException(__METHOD__ . ' not implemented');
|
116 |
+
}
|
117 |
+
|
118 |
+
/**
|
119 |
+
* {@inheritdoc}
|
120 |
+
*/
|
121 |
+
protected function sqlNow()
|
122 |
+
{
|
123 |
+
return current_time('timestamp', true);
|
124 |
+
}
|
125 |
+
|
126 |
+
/**
|
127 |
+
* {@inheritdoc}
|
128 |
+
*
|
129 |
+
* We use the WordPress metadata API which is readily available, so nothing
|
130 |
+
* has to be done here.
|
131 |
+
*/
|
132 |
+
public function install()
|
133 |
+
{
|
134 |
+
return true;
|
135 |
+
}
|
136 |
+
|
137 |
+
/**
|
138 |
+
* {@inheritdoc}
|
139 |
+
*
|
140 |
+
* We use the WordPress metadata API which is readily available, so nothing
|
141 |
+
* has to be done here.
|
142 |
+
*/
|
143 |
+
public function uninstall()
|
144 |
+
{
|
145 |
+
// We do not delete all Acumulus metadata, not even via a confirmation page.
|
146 |
+
// If we would want to do so, we can use this code:
|
147 |
+
// delete_post_meta_by_key('_acumulus_entry_id'); // for other keys as well.
|
148 |
+
return true;
|
149 |
+
}
|
150 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Shop/ConfigForm.php
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\WooCommerce\Shop;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Invoice\ConfigInterface as InvoiceConfigInterface;
|
5 |
+
use Siel\Acumulus\Shop\ConfigForm as BaseConfigForm;
|
6 |
+
use Siel\Acumulus\Shop\ConfigInterface;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Class ConfigForm processes and builds the settings form page for the
|
10 |
+
* WooCommerce Acumulus module.
|
11 |
+
*/
|
12 |
+
class ConfigForm extends BaseConfigForm
|
13 |
+
{
|
14 |
+
/**
|
15 |
+
* {@inheritdoc}
|
16 |
+
*/
|
17 |
+
protected function getShopOrderStatuses()
|
18 |
+
{
|
19 |
+
$result = array();
|
20 |
+
|
21 |
+
$orderStatuses = wc_get_order_statuses();
|
22 |
+
foreach ($orderStatuses as $key => $label) {
|
23 |
+
$result[substr($key, strlen('wc-'))] = $label;
|
24 |
+
}
|
25 |
+
|
26 |
+
return $result;
|
27 |
+
}
|
28 |
+
|
29 |
+
/**
|
30 |
+
* {@inheritdoc}
|
31 |
+
*
|
32 |
+
* This override removes the 'Use invoice #' option as WC does not have
|
33 |
+
* separate invoices.
|
34 |
+
*/
|
35 |
+
protected function getInvoiceNrSourceOptions()
|
36 |
+
{
|
37 |
+
$result = parent::getInvoiceNrSourceOptions();
|
38 |
+
unset($result[InvoiceConfigInterface::InvoiceNrSource_ShopInvoice]);
|
39 |
+
return $result;
|
40 |
+
}
|
41 |
+
|
42 |
+
/**
|
43 |
+
* {@inheritdoc}
|
44 |
+
*
|
45 |
+
* This override removes the 'Use invoice date' option as WC does not have
|
46 |
+
* separate invoices.
|
47 |
+
*/
|
48 |
+
protected function getDateToUseOptions()
|
49 |
+
{
|
50 |
+
$result = parent::getDateToUseOptions();
|
51 |
+
unset($result[InvoiceConfigInterface::InvoiceDate_InvoiceCreate]);
|
52 |
+
return $result;
|
53 |
+
}
|
54 |
+
|
55 |
+
/**
|
56 |
+
* {@inheritdoc}
|
57 |
+
*
|
58 |
+
* This override removes the 'Use invoice sent' option as WC does not have
|
59 |
+
* separate invoices, let alone an event on sending it.
|
60 |
+
*/
|
61 |
+
protected function getTriggerInvoiceSendEventOptions()
|
62 |
+
{
|
63 |
+
$result = parent::getTriggerInvoiceSendEventOptions();
|
64 |
+
unset($result[ConfigInterface::TriggerInvoiceSendEvent_InvoiceCreate]);
|
65 |
+
return $result;
|
66 |
+
}
|
67 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Shop/ConfigStore.php
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\WooCommerce\Shop;
|
3 |
+
|
4 |
+
use Siel\Acumulus\Shop\ConfigStore as BaseConfigStore;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Implements the connection to the WordPress config component.
|
8 |
+
*/
|
9 |
+
class ConfigStore extends BaSeConfigStore
|
10 |
+
{
|
11 |
+
/**
|
12 |
+
* {@inheritdoc}
|
13 |
+
*/
|
14 |
+
public function getShopEnvironment()
|
15 |
+
{
|
16 |
+
global $wp_version, $woocommerce;
|
17 |
+
$environment = array(
|
18 |
+
// Lazy load is no longer needed (as in L3) as this method will only be
|
19 |
+
// called when the config gets actually queried.
|
20 |
+
'moduleVersion' => \Acumulus::create()->getVersionNumber(),
|
21 |
+
'shopName' => $this->shopName,
|
22 |
+
'shopVersion' => (isset($woocommerce) ? $woocommerce->version : 'unknown') . ' (WordPress: ' . $wp_version . ')',
|
23 |
+
);
|
24 |
+
return $environment;
|
25 |
+
}
|
26 |
+
|
27 |
+
/**
|
28 |
+
* {@inheritdoc}
|
29 |
+
*/
|
30 |
+
public function load(array $keys)
|
31 |
+
{
|
32 |
+
$result = array();
|
33 |
+
// Load the values from the web shop specific configuration.
|
34 |
+
$configurationValues = get_option('acumulus');
|
35 |
+
if (is_array($configurationValues)) {
|
36 |
+
foreach ($keys as $key) {
|
37 |
+
// Do not overwrite defaults if no value is set.
|
38 |
+
if (isset($configurationValues[$key])) {
|
39 |
+
$result[$key] = $configurationValues[$key];
|
40 |
+
}
|
41 |
+
}
|
42 |
+
}
|
43 |
+
return $result;
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* {@inheritdoc}
|
48 |
+
*/
|
49 |
+
public function save(array $values)
|
50 |
+
{
|
51 |
+
$values = $this->saveCommon($values);
|
52 |
+
|
53 |
+
$configurationValues = array();
|
54 |
+
foreach ($values as $key => $value) {
|
55 |
+
if ($value !== null) {
|
56 |
+
$configurationValues[$key] = $value;
|
57 |
+
}
|
58 |
+
}
|
59 |
+
return update_option('acumulus', $configurationValues);
|
60 |
+
}
|
61 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/Acumulus/WooCommerce/Shop/InvoiceManager.php
ADDED
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace Siel\Acumulus\WooCommerce\Shop;
|
3 |
+
|
4 |
+
use DateTime;
|
5 |
+
use Siel\Acumulus\Invoice\Source as BaseSource;
|
6 |
+
use Siel\Acumulus\Shop\InvoiceManager as BaseInvoiceManager;
|
7 |
+
use Siel\Acumulus\WooCommerce\Invoice\Source;
|
8 |
+
use WP_Query;
|
9 |
+
|
10 |
+
class InvoiceManager extends BaseInvoiceManager
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* Helper method that converts our source type constants to a WP/WC post type.
|
14 |
+
*
|
15 |
+
* @param string $invoiceSourceType
|
16 |
+
*
|
17 |
+
* @return string
|
18 |
+
*/
|
19 |
+
protected function sourceTypeToShopType($invoiceSourceType)
|
20 |
+
{
|
21 |
+
switch ($invoiceSourceType) {
|
22 |
+
case Source::Order:
|
23 |
+
return 'shop_order';
|
24 |
+
case Source::CreditNote:
|
25 |
+
return 'shop_order_refund';
|
26 |
+
default:
|
27 |
+
$this->config->getLog()->error('InvoiceManager::sourceTypeToShopType(%s): unknown', $invoiceSourceType);
|
28 |
+
return '';
|
29 |
+
}
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* {@inheritdoc}
|
34 |
+
*/
|
35 |
+
public function getInvoiceSourcesByIdRange($invoiceSourceType, $InvoiceSourceIdFrom, $InvoiceSourceIdTo)
|
36 |
+
{
|
37 |
+
// We use our own query here as defining a range of pots ids based on a
|
38 |
+
// between does not seem to be possible with the query syntax.
|
39 |
+
global $wpdb;
|
40 |
+
$key = 'ID';
|
41 |
+
$invoiceSourceIds = $wpdb->get_col($wpdb->prepare("SELECT `$key` FROM `{$wpdb->posts}` WHERE `$key` BETWEEN %u AND %u AND `post_type` = %s",
|
42 |
+
$InvoiceSourceIdFrom, $InvoiceSourceIdTo, $this->sourceTypeToShopType($invoiceSourceType)));
|
43 |
+
return $this->getSourcesByIdsOrSources($invoiceSourceType, $invoiceSourceIds);
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* {@inheritdoc}
|
48 |
+
*
|
49 |
+
* We support:
|
50 |
+
* - "WooCommerce Sequential Order Numbers (Pro)", see
|
51 |
+
* https://wordpress.org/plugins/woocommerce-sequential-order-numbers/ and
|
52 |
+
* http://docs.woothemes.com/document/sequential-order-numbers/.
|
53 |
+
* - "WC Sequential Order Numbers", see
|
54 |
+
* https://wordpress.org/plugins/wc-sequential-order-numbers/ and
|
55 |
+
* http://plugins.dualcube.com/product/wc-sequential-order-numbers/.
|
56 |
+
*
|
57 |
+
* If you know of other plugins, please let us know.
|
58 |
+
*/
|
59 |
+
public function getInvoiceSourcesByReferenceRange($invoiceSourceType, $InvoiceSourceReferenceFrom, $InvoiceSourceReferenceTo)
|
60 |
+
{
|
61 |
+
// To be able to define the query we need to know under which meta key the
|
62 |
+
// order number/reference is stored.
|
63 |
+
// - WooCommerce Sequential Order Numbers (Pro) uses the key order_number.
|
64 |
+
// - WC Sequential Order Numbers uses the keys order_number_formatted and
|
65 |
+
// order_number.
|
66 |
+
// Both only work with orders, not refunds.
|
67 |
+
if ((is_plugin_active('woocommerce-sequential-order-numbers/woocommerce-sequential-order-numbers.php') || is_plugin_active('woocommerce-sequential-order-numbers-pro/woocommerce-sequential-order-numbers-pro.php')) && $invoiceSourceType === Source::Order) {
|
68 |
+
// Search for the order by the order number as assigned by the plugin.
|
69 |
+
$args = array(
|
70 |
+
'meta_query' => array(
|
71 |
+
array(
|
72 |
+
'key' => '_order_number',
|
73 |
+
'value' => array($InvoiceSourceReferenceFrom, $InvoiceSourceReferenceTo),
|
74 |
+
'compare' => 'BETWEEN',
|
75 |
+
'type' => 'NUMERIC',
|
76 |
+
),
|
77 |
+
),
|
78 |
+
);
|
79 |
+
return $this->query2Sources($args, $invoiceSourceType);
|
80 |
+
} else if (is_plugin_active('wc-sequential-order-numbers/Sequential_Order_Numbers.php') && $invoiceSourceType === Source::Order) {
|
81 |
+
// This plugin has not been tested yet. It will probably give problems as
|
82 |
+
// on installing it does not add the used meta keys retrospectively. So
|
83 |
+
// I think this will only work for orders added after installation of this
|
84 |
+
// plugin. But then, that is a bit the nature of the idea of sequential
|
85 |
+
// order numbers: it should be used as of the beginning...
|
86 |
+
$args = array(
|
87 |
+
'meta_query' => array(
|
88 |
+
array(
|
89 |
+
'key' => '_order_number_formatted',
|
90 |
+
'value' => array($InvoiceSourceReferenceFrom, $InvoiceSourceReferenceTo),
|
91 |
+
'compare' => 'BETWEEN',
|
92 |
+
),
|
93 |
+
),
|
94 |
+
);
|
95 |
+
return $this->query2Sources($args, $invoiceSourceType);
|
96 |
+
}
|
97 |
+
return parent::getInvoiceSourcesByReferenceRange($invoiceSourceType, $InvoiceSourceReferenceFrom, $InvoiceSourceReferenceTo);
|
98 |
+
}
|
99 |
+
|
100 |
+
/**
|
101 |
+
* {@inheritdoc}
|
102 |
+
*/
|
103 |
+
public function getInvoiceSourcesByDateRange($invoiceSourceType, DateTime $dateFrom, DateTime $dateTo)
|
104 |
+
{
|
105 |
+
$args = array(
|
106 |
+
'date_query' => array(
|
107 |
+
array(
|
108 |
+
'column' => 'post_modified',
|
109 |
+
'after' => array(
|
110 |
+
'year' => $dateFrom->format('Y'),
|
111 |
+
'month' => $dateFrom->format('m'),
|
112 |
+
'day' => $dateFrom->format('d'),
|
113 |
+
),
|
114 |
+
'before' => array(
|
115 |
+
'year' => $dateTo->format('Y'),
|
116 |
+
'month' => $dateTo->format('m'),
|
117 |
+
'day' => $dateTo->format('d'),
|
118 |
+
),
|
119 |
+
'inclusive' => true,
|
120 |
+
),
|
121 |
+
),
|
122 |
+
);
|
123 |
+
return $this->query2Sources($args, $invoiceSourceType);
|
124 |
+
}
|
125 |
+
|
126 |
+
/**
|
127 |
+
* {@inheritdoc}
|
128 |
+
*
|
129 |
+
* This WooCommerce override applies the 'acumulus_invoice_created' filter.
|
130 |
+
*/
|
131 |
+
protected function triggerInvoiceCreated(array &$invoice, BaseSource $invoiceSource)
|
132 |
+
{
|
133 |
+
$invoice = apply_filters('acumulus_invoice_created', $invoice, $invoiceSource);
|
134 |
+
}
|
135 |
+
|
136 |
+
/**
|
137 |
+
* {@inheritdoc}
|
138 |
+
*
|
139 |
+
* This WooCommerce override applies the 'acumulus_invoice_completed' filter.
|
140 |
+
*/
|
141 |
+
protected function triggerInvoiceCompleted(array &$invoice, BaseSource $invoiceSource)
|
142 |
+
{
|
143 |
+
$invoice = apply_filters('acumulus_invoice_completed', $invoice, $invoiceSource);
|
144 |
+
}
|
145 |
+
|
146 |
+
/**
|
147 |
+
* {@inheritdoc}
|
148 |
+
*
|
149 |
+
* This WooCommerce override executes the 'acumulus_invoice_sent' action.
|
150 |
+
*/
|
151 |
+
protected function triggerInvoiceSent(array $invoice, BaseSource $invoiceSource, array $result)
|
152 |
+
{
|
153 |
+
do_action('acumulus_invoice_sent', $invoice, $invoiceSource, $result);
|
154 |
+
}
|
155 |
+
|
156 |
+
/**
|
157 |
+
* Helper method to get a list of Source given a set of query arguments.
|
158 |
+
*
|
159 |
+
* @param array $args
|
160 |
+
* @param string $invoiceSourceType
|
161 |
+
* @param bool $sort
|
162 |
+
*
|
163 |
+
* @return Source[]
|
164 |
+
*/
|
165 |
+
protected function query2Sources(array $args, $invoiceSourceType, $sort = true)
|
166 |
+
{
|
167 |
+
// Add default arguments.
|
168 |
+
$args = $args + array(
|
169 |
+
'fields' => 'ids',
|
170 |
+
'numberposts' => -1,
|
171 |
+
'post_type' => $this->sourceTypeToShopType($invoiceSourceType),
|
172 |
+
'post_status' => array_keys(wc_get_order_statuses()),
|
173 |
+
);
|
174 |
+
$query = new WP_Query($args);
|
175 |
+
$ids = $query->get_posts();
|
176 |
+
if ($sort) {
|
177 |
+
sort($ids);
|
178 |
+
}
|
179 |
+
return $this->getSourcesByIdsOrSources($invoiceSourceType, $ids);
|
180 |
+
}
|
181 |
+
}
|
app/code/community/Siel/Acumulus/libraries/Siel/leesmij.txt
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Acumulus web service API library
|
2 |
+
===============================
|
3 |
+
|
4 |
+
@author: Buro Rader (http://www.burorader.com/).
|
5 |
+
@support: TODO. zie het Acumulus forum.
|
6 |
+
|
7 |
+
Inleiding
|
8 |
+
---------
|
9 |
+
Deze Acumulus web service API library is bedoeld om het ontwikkelen van client
|
10 |
+
side code die communiceert met de Acumulus API te vereenvoudigen. De library is
|
11 |
+
in eerste instantie ontwikkelt voor gebruik door de koppelingen voor de webshop
|
12 |
+
software van Magento, PrestaShop, OpenCart, VirtueMart en WooCommerce, maar is
|
13 |
+
dusdanig opgezet dat ook andere koppelingen er hun voordeel mee kunnen doen.
|
14 |
+
|
15 |
+
Dit is dus een cross-CMS/webshop library. Daarom kan de code in deze library
|
16 |
+
niet voldoen aan specifieke richtlijnen voor een specifieke webshop of CMS. Denk
|
17 |
+
hierbij aan:
|
18 |
+
- Coding standards.
|
19 |
+
- Organisatie en naamgeving.
|
20 |
+
- Vertaling door gebruikmaking van webshop specifieke vertaalfunctionaliteit.
|
21 |
+
- Webshop specifieke "Best practises".
|
22 |
+
|
23 |
+
Licentie
|
24 |
+
--------
|
25 |
+
Als deze library u apart ter beschikking is gesteld, is dit onder de GNU GENERAL
|
26 |
+
PUBLIC LICENSE versie 3. De Engelstalige en enige officiële tekst hiervan kunt u
|
27 |
+
vinden op http://www.gnu.org/licenses/gpl.html. Een niet bindende
|
28 |
+
Nederlandstalige vertaling hiervan vindt u op
|
29 |
+
http://bartbeuving.files.wordpress.com/2008/07/gpl-v3-nl-101.pdf. Beide teksten
|
30 |
+
zijn ook meegeleverd met deze module.
|
31 |
+
|
32 |
+
Als deze library u als onderdeel van een koppeling ter beschikking is gesteld,
|
33 |
+
geldt de licentie van de koppeling ook voor deze library.
|
34 |
+
|
35 |
+
Ontwikkeling
|
36 |
+
------------
|
37 |
+
Deze library is nog (volop) in ontwikkeling en zal:
|
38 |
+
- waar nodig aangepast worden om met nog meer webshops om te kunnen gaan
|
39 |
+
- uitgebreid worden met nieuwe wensen.
|
40 |
+
- waar mogelijk en zinvol nog verder geabstraheerd worden.
|
41 |
+
|
42 |
+
Backwards compatibility is daarbij een zorg maar geen absolute vereiste. Omdat
|
43 |
+
de webshopkoppelingen met library uitgeleverd worden is dit voor gebruikers geen
|
44 |
+
probleem. Voor ontwikkelaars die zelfstandig met deze library aan de slag zijn
|
45 |
+
gegaan kan dit wel voor problemen zorgen.
|
46 |
+
|
47 |
+
todo's:
|
48 |
+
- Class "webshop capabilities" die een aantal methods die een webshop-specifieke
|
49 |
+
lijst terugkeren centraliseert (is er een credit note, is er een invoice)
|
50 |
+
- Class "Shop invoice" (wrapper zoals "Invoice Source").
|
51 |
+
- Margin scheme zal niet samen gaan met gebruik van getVatRangeTags(). Refactor
|
52 |
+
dit.
|
53 |
+
- Versturen credit notes via opties op config formulier regelen: (wel/niet
|
54 |
+
automatisch + moment van versturen),
|
app/code/community/Siel/Acumulus/libraries/Siel/psr4.php
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @file This file registers an autoloader for the Siel namespace library.
|
4 |
+
*
|
5 |
+
* As not all web shops support auto-loading based on namespaces or have other
|
6 |
+
* glitches, eg. expecting lower cased file names, we define our own autoloader.
|
7 |
+
* If the module cannot use the autoloader of the web shop, this file should be
|
8 |
+
* loaded during bootstrapping of the module.
|
9 |
+
*
|
10 |
+
* Thanks to https://gist.github.com/mageekguy/8300961
|
11 |
+
*/
|
12 |
+
namespace Siel;
|
13 |
+
|
14 |
+
// Prepend this autoloader: it will not throw, nor warn, while the shop specific
|
15 |
+
// autoloader might do so.
|
16 |
+
spl_autoload_register(
|
17 |
+
function ($class) {
|
18 |
+
if (strpos($class, __NAMESPACE__ . '\\') === 0) {
|
19 |
+
$fileName = __DIR__ . str_replace('\\', DIRECTORY_SEPARATOR, substr($class, strlen(__NAMESPACE__))) . '.php';
|
20 |
+
// Checking if the file exists prevent warnings in OpenCart1 where
|
21 |
+
// using just @include(...) did not help prevent them.
|
22 |
+
if (is_readable($fileName)) {
|
23 |
+
include($fileName);
|
24 |
+
}
|
25 |
+
}
|
26 |
+
},
|
27 |
+
false,
|
28 |
+
true);
|
app/code/community/Siel/Acumulus/license.txt
ADDED
@@ -0,0 +1,674 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
GNU GENERAL PUBLIC LICENSE
|
2 |
+
Version 3, 29 June 2007
|
3 |
+
|
4 |
+
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
5 |
+
Everyone is permitted to copy and distribute verbatim copies
|
6 |
+
of this license document, but changing it is not allowed.
|
7 |
+
|
8 |
+
Preamble
|
9 |
+
|
10 |
+
The GNU General Public License is a free, copyleft license for
|
11 |
+
software and other kinds of works.
|
12 |
+
|
13 |
+
The licenses for most software and other practical works are designed
|
14 |
+
to take away your freedom to share and change the works. By contrast,
|
15 |
+
the GNU General Public License is intended to guarantee your freedom to
|
16 |
+
share and change all versions of a program--to make sure it remains free
|
17 |
+
software for all its users. We, the Free Software Foundation, use the
|
18 |
+
GNU General Public License for most of our software; it applies also to
|
19 |
+
any other work released this way by its authors. You can apply it to
|
20 |
+
your programs, too.
|
21 |
+
|
22 |
+
When we speak of free software, we are referring to freedom, not
|
23 |
+
price. Our General Public Licenses are designed to make sure that you
|
24 |
+
have the freedom to distribute copies of free software (and charge for
|
25 |
+
them if you wish), that you receive source code or can get it if you
|
26 |
+
want it, that you can change the software or use pieces of it in new
|
27 |
+
free programs, and that you know you can do these things.
|
28 |
+
|
29 |
+
To protect your rights, we need to prevent others from denying you
|
30 |
+
these rights or asking you to surrender the rights. Therefore, you have
|
31 |
+
certain responsibilities if you distribute copies of the software, or if
|
32 |
+
you modify it: responsibilities to respect the freedom of others.
|
33 |
+
|
34 |
+
For example, if you distribute copies of such a program, whether
|
35 |
+
gratis or for a fee, you must pass on to the recipients the same
|
36 |
+
freedoms that you received. You must make sure that they, too, receive
|
37 |
+
or can get the source code. And you must show them these terms so they
|
38 |
+
know their rights.
|
39 |
+
|
40 |
+
Developers that use the GNU GPL protect your rights with two steps:
|
41 |
+
(1) assert copyright on the software, and (2) offer you this License
|
42 |
+
giving you legal permission to copy, distribute and/or modify it.
|
43 |
+
|
44 |
+
For the developers' and authors' protection, the GPL clearly explains
|
45 |
+
that there is no warranty for this free software. For both users' and
|
46 |
+
authors' sake, the GPL requires that modified versions be marked as
|
47 |
+
changed, so that their problems will not be attributed erroneously to
|
48 |
+
authors of previous versions.
|
49 |
+
|
50 |
+
Some devices are designed to deny users access to install or run
|
51 |
+
modified versions of the software inside them, although the manufacturer
|
52 |
+
can do so. This is fundamentally incompatible with the aim of
|
53 |
+
protecting users' freedom to change the software. The systematic
|
54 |
+
pattern of such abuse occurs in the area of products for individuals to
|
55 |
+
use, which is precisely where it is most unacceptable. Therefore, we
|
56 |
+
have designed this version of the GPL to prohibit the practice for those
|
57 |
+
products. If such problems arise substantially in other domains, we
|
58 |
+
stand ready to extend this provision to those domains in future versions
|
59 |
+
of the GPL, as needed to protect the freedom of users.
|
60 |
+
|
61 |
+
Finally, every program is threatened constantly by software patents.
|
62 |
+
States should not allow patents to restrict development and use of
|
63 |
+
software on general-purpose computers, but in those that do, we wish to
|
64 |
+
avoid the special danger that patents applied to a free program could
|
65 |
+
make it effectively proprietary. To prevent this, the GPL assures that
|
66 |
+
patents cannot be used to render the program non-free.
|
67 |
+
|
68 |
+
The precise terms and conditions for copying, distribution and
|
69 |
+
modification follow.
|
70 |
+
|
71 |
+
TERMS AND CONDITIONS
|
72 |
+
|
73 |
+
0. Definitions.
|
74 |
+
|
75 |
+
"This License" refers to version 3 of the GNU General Public License.
|
76 |
+
|
77 |
+
"Copyright" also means copyright-like laws that apply to other kinds of
|
78 |
+
works, such as semiconductor masks.
|
79 |
+
|
80 |
+
"The Program" refers to any copyrightable work licensed under this
|
81 |
+
License. Each licensee is addressed as "you". "Licensees" and
|
82 |
+
"recipients" may be individuals or organizations.
|
83 |
+
|
84 |
+
To "modify" a work means to copy from or adapt all or part of the work
|
85 |
+
in a fashion requiring copyright permission, other than the making of an
|
86 |
+
exact copy. The resulting work is called a "modified version" of the
|
87 |
+
earlier work or a work "based on" the earlier work.
|
88 |
+
|
89 |
+
A "covered work" means either the unmodified Program or a work based
|
90 |
+
on the Program.
|
91 |
+
|
92 |
+
To "propagate" a work means to do anything with it that, without
|
93 |
+
permission, would make you directly or secondarily liable for
|
94 |
+
infringement under applicable copyright law, except executing it on a
|
95 |
+
computer or modifying a private copy. Propagation includes copying,
|
96 |
+
distribution (with or without modification), making available to the
|
97 |
+
public, and in some countries other activities as well.
|
98 |
+
|
99 |
+
To "convey" a work means any kind of propagation that enables other
|
100 |
+
parties to make or receive copies. Mere interaction with a user through
|
101 |
+
a computer network, with no transfer of a copy, is not conveying.
|
102 |
+
|
103 |
+
An interactive user interface displays "Appropriate Legal Notices"
|
104 |
+
to the extent that it includes a convenient and prominently visible
|
105 |
+
feature that (1) displays an appropriate copyright notice, and (2)
|
106 |
+
tells the user that there is no warranty for the work (except to the
|
107 |
+
extent that warranties are provided), that licensees may convey the
|
108 |
+
work under this License, and how to view a copy of this License. If
|
109 |
+
the interface presents a list of user commands or options, such as a
|
110 |
+
menu, a prominent item in the list meets this criterion.
|
111 |
+
|
112 |
+
1. Source Code.
|
113 |
+
|
114 |
+
The "source code" for a work means the preferred form of the work
|
115 |
+
for making modifications to it. "Object code" means any non-source
|
116 |
+
form of a work.
|
117 |
+
|
118 |
+
A "Standard Interface" means an interface that either is an official
|
119 |
+
standard defined by a recognized standards body, or, in the case of
|
120 |
+
interfaces specified for a particular programming language, one that
|
121 |
+
is widely used among developers working in that language.
|
122 |
+
|
123 |
+
The "System Libraries" of an executable work include anything, other
|
124 |
+
than the work as a whole, that (a) is included in the normal form of
|
125 |
+
packaging a Major Component, but which is not part of that Major
|
126 |
+
Component, and (b) serves only to enable use of the work with that
|
127 |
+
Major Component, or to implement a Standard Interface for which an
|
128 |
+
implementation is available to the public in source code form. A
|
129 |
+
"Major Component", in this context, means a major essential component
|
130 |
+
(kernel, window system, and so on) of the specific operating system
|
131 |
+
(if any) on which the executable work runs, or a compiler used to
|
132 |
+
produce the work, or an object code interpreter used to run it.
|
133 |
+
|
134 |
+
The "Corresponding Source" for a work in object code form means all
|
135 |
+
the source code needed to generate, install, and (for an executable
|
136 |
+
work) run the object code and to modify the work, including scripts to
|
137 |
+
control those activities. However, it does not include the work's
|
138 |
+
System Libraries, or general-purpose tools or generally available free
|
139 |
+
programs which are used unmodified in performing those activities but
|
140 |
+
which are not part of the work. For example, Corresponding Source
|
141 |
+
includes interface definition files associated with source files for
|
142 |
+
the work, and the source code for shared libraries and dynamically
|
143 |
+
linked subprograms that the work is specifically designed to require,
|
144 |
+
such as by intimate data communication or control flow between those
|
145 |
+
subprograms and other parts of the work.
|
146 |
+
|
147 |
+
The Corresponding Source need not include anything that users
|
148 |
+
can regenerate automatically from other parts of the Corresponding
|
149 |
+
Source.
|
150 |
+
|
151 |
+
The Corresponding Source for a work in source code form is that
|
152 |
+
same work.
|
153 |
+
|
154 |
+
2. Basic Permissions.
|
155 |
+
|
156 |
+
All rights granted under this License are granted for the term of
|
157 |
+
copyright on the Program, and are irrevocable provided the stated
|
158 |
+
conditions are met. This License explicitly affirms your unlimited
|
159 |
+
permission to run the unmodified Program. The output from running a
|
160 |
+
covered work is covered by this License only if the output, given its
|
161 |
+
content, constitutes a covered work. This License acknowledges your
|
162 |
+
rights of fair use or other equivalent, as provided by copyright law.
|
163 |
+
|
164 |
+
You may make, run and propagate covered works that you do not
|
165 |
+
convey, without conditions so long as your license otherwise remains
|
166 |
+
in force. You may convey covered works to others for the sole purpose
|
167 |
+
of having them make modifications exclusively for you, or provide you
|
168 |
+
with facilities for running those works, provided that you comply with
|
169 |
+
the terms of this License in conveying all material for which you do
|
170 |
+
not control copyright. Those thus making or running the covered works
|
171 |
+
for you must do so exclusively on your behalf, under your direction
|
172 |
+
and control, on terms that prohibit them from making any copies of
|
173 |
+
your copyrighted material outside their relationship with you.
|
174 |
+
|
175 |
+
Conveying under any other circumstances is permitted solely under
|
176 |
+
the conditions stated below. Sublicensing is not allowed; section 10
|
177 |
+
makes it unnecessary.
|
178 |
+
|
179 |
+
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
180 |
+
|
181 |
+
No covered work shall be deemed part of an effective technological
|
182 |
+
measure under any applicable law fulfilling obligations under article
|
183 |
+
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
184 |
+
similar laws prohibiting or restricting circumvention of such
|
185 |
+
measures.
|
186 |
+
|
187 |
+
When you convey a covered work, you waive any legal power to forbid
|
188 |
+
circumvention of technological measures to the extent such circumvention
|
189 |
+
is effected by exercising rights under this License with respect to
|
190 |
+
the covered work, and you disclaim any intention to limit operation or
|
191 |
+
modification of the work as a means of enforcing, against the work's
|
192 |
+
users, your or third parties' legal rights to forbid circumvention of
|
193 |
+
technological measures.
|
194 |
+
|
195 |
+
4. Conveying Verbatim Copies.
|
196 |
+
|
197 |
+
You may convey verbatim copies of the Program's source code as you
|
198 |
+
receive it, in any medium, provided that you conspicuously and
|
199 |
+
appropriately publish on each copy an appropriate copyright notice;
|
200 |
+
keep intact all notices stating that this License and any
|
201 |
+
non-permissive terms added in accord with section 7 apply to the code;
|
202 |
+
keep intact all notices of the absence of any warranty; and give all
|
203 |
+
recipients a copy of this License along with the Program.
|
204 |
+
|
205 |
+
You may charge any price or no price for each copy that you convey,
|
206 |
+
and you may offer support or warranty protection for a fee.
|
207 |
+
|
208 |
+
5. Conveying Modified Source Versions.
|
209 |
+
|
210 |
+
You may convey a work based on the Program, or the modifications to
|
211 |
+
produce it from the Program, in the form of source code under the
|
212 |
+
terms of section 4, provided that you also meet all of these conditions:
|
213 |
+
|
214 |
+
a) The work must carry prominent notices stating that you modified
|
215 |
+
it, and giving a relevant date.
|
216 |
+
|
217 |
+
b) The work must carry prominent notices stating that it is
|
218 |
+
released under this License and any conditions added under section
|
219 |
+
7. This requirement modifies the requirement in section 4 to
|
220 |
+
"keep intact all notices".
|
221 |
+
|
222 |
+
c) You must license the entire work, as a whole, under this
|
223 |
+
License to anyone who comes into possession of a copy. This
|
224 |
+
License will therefore apply, along with any applicable section 7
|
225 |
+
additional terms, to the whole of the work, and all its parts,
|
226 |
+
regardless of how they are packaged. This License gives no
|
227 |
+
permission to license the work in any other way, but it does not
|
228 |
+
invalidate such permission if you have separately received it.
|
229 |
+
|
230 |
+
d) If the work has interactive user interfaces, each must display
|
231 |
+
Appropriate Legal Notices; however, if the Program has interactive
|
232 |
+
interfaces that do not display Appropriate Legal Notices, your
|
233 |
+
work need not make them do so.
|
234 |
+
|
235 |
+
A compilation of a covered work with other separate and independent
|
236 |
+
works, which are not by their nature extensions of the covered work,
|
237 |
+
and which are not combined with it such as to form a larger program,
|
238 |
+
in or on a volume of a storage or distribution medium, is called an
|
239 |
+
"aggregate" if the compilation and its resulting copyright are not
|
240 |
+
used to limit the access or legal rights of the compilation's users
|
241 |
+
beyond what the individual works permit. Inclusion of a covered work
|
242 |
+
in an aggregate does not cause this License to apply to the other
|
243 |
+
parts of the aggregate.
|
244 |
+
|
245 |
+
6. Conveying Non-Source Forms.
|
246 |
+
|
247 |
+
You may convey a covered work in object code form under the terms
|
248 |
+
of sections 4 and 5, provided that you also convey the
|
249 |
+
machine-readable Corresponding Source under the terms of this License,
|
250 |
+
in one of these ways:
|
251 |
+
|
252 |
+
a) Convey the object code in, or embodied in, a physical product
|
253 |
+
(including a physical distribution medium), accompanied by the
|
254 |
+
Corresponding Source fixed on a durable physical medium
|
255 |
+
customarily used for software interchange.
|
256 |
+
|
257 |
+
b) Convey the object code in, or embodied in, a physical product
|
258 |
+
(including a physical distribution medium), accompanied by a
|
259 |
+
written offer, valid for at least three years and valid for as
|
260 |
+
long as you offer spare parts or customer support for that product
|
261 |
+
model, to give anyone who possesses the object code either (1) a
|
262 |
+
copy of the Corresponding Source for all the software in the
|
263 |
+
product that is covered by this License, on a durable physical
|
264 |
+
medium customarily used for software interchange, for a price no
|
265 |
+
more than your reasonable cost of physically performing this
|
266 |
+
conveying of source, or (2) access to copy the
|
267 |
+
Corresponding Source from a network server at no charge.
|
268 |
+
|
269 |
+
c) Convey individual copies of the object code with a copy of the
|
270 |
+
written offer to provide the Corresponding Source. This
|
271 |
+
alternative is allowed only occasionally and noncommercially, and
|
272 |
+
only if you received the object code with such an offer, in accord
|
273 |
+
with subsection 6b.
|
274 |
+
|
275 |
+
d) Convey the object code by offering access from a designated
|
276 |
+
place (gratis or for a charge), and offer equivalent access to the
|
277 |
+
Corresponding Source in the same way through the same place at no
|
278 |
+
further charge. You need not require recipients to copy the
|
279 |
+
Corresponding Source along with the object code. If the place to
|
280 |
+
copy the object code is a network server, the Corresponding Source
|
281 |
+
may be on a different server (operated by you or a third party)
|
282 |
+
that supports equivalent copying facilities, provided you maintain
|
283 |
+
clear directions next to the object code saying where to find the
|
284 |
+
Corresponding Source. Regardless of what server hosts the
|
285 |
+
Corresponding Source, you remain obligated to ensure that it is
|
286 |
+
available for as long as needed to satisfy these requirements.
|
287 |
+
|
288 |
+
e) Convey the object code using peer-to-peer transmission, provided
|
289 |
+
you inform other peers where the object code and Corresponding
|
290 |
+
Source of the work are being offered to the general public at no
|
291 |
+
charge under subsection 6d.
|
292 |
+
|
293 |
+
A separable portion of the object code, whose source code is excluded
|
294 |
+
from the Corresponding Source as a System Library, need not be
|
295 |
+
included in conveying the object code work.
|
296 |
+
|
297 |
+
A "User Product" is either (1) a "consumer product", which means any
|
298 |
+
tangible personal property which is normally used for personal, family,
|
299 |
+
or household purposes, or (2) anything designed or sold for incorporation
|
300 |
+
into a dwelling. In determining whether a product is a consumer product,
|
301 |
+
doubtful cases shall be resolved in favor of coverage. For a particular
|
302 |
+
product received by a particular user, "normally used" refers to a
|
303 |
+
typical or common use of that class of product, regardless of the status
|
304 |
+
of the particular user or of the way in which the particular user
|
305 |
+
actually uses, or expects or is expected to use, the product. A product
|
306 |
+
is a consumer product regardless of whether the product has substantial
|
307 |
+
commercial, industrial or non-consumer uses, unless such uses represent
|
308 |
+
the only significant mode of use of the product.
|
309 |
+
|
310 |
+
"Installation Information" for a User Product means any methods,
|
311 |
+
procedures, authorization keys, or other information required to install
|
312 |
+
and execute modified versions of a covered work in that User Product from
|
313 |
+
a modified version of its Corresponding Source. The information must
|
314 |
+
suffice to ensure that the continued functioning of the modified object
|
315 |
+
code is in no case prevented or interfered with solely because
|
316 |
+
modification has been made.
|
317 |
+
|
318 |
+
If you convey an object code work under this section in, or with, or
|
319 |
+
specifically for use in, a User Product, and the conveying occurs as
|
320 |
+
part of a transaction in which the right of possession and use of the
|
321 |
+
User Product is transferred to the recipient in perpetuity or for a
|
322 |
+
fixed term (regardless of how the transaction is characterized), the
|
323 |
+
Corresponding Source conveyed under this section must be accompanied
|
324 |
+
by the Installation Information. But this requirement does not apply
|
325 |
+
if neither you nor any third party retains the ability to install
|
326 |
+
modified object code on the User Product (for example, the work has
|
327 |
+
been installed in ROM).
|
328 |
+
|
329 |
+
The requirement to provide Installation Information does not include a
|
330 |
+
requirement to continue to provide support service, warranty, or updates
|
331 |
+
for a work that has been modified or installed by the recipient, or for
|
332 |
+
the User Product in which it has been modified or installed. Access to a
|
333 |
+
network may be denied when the modification itself materially and
|
334 |
+
adversely affects the operation of the network or violates the rules and
|
335 |
+
protocols for communication across the network.
|
336 |
+
|
337 |
+
Corresponding Source conveyed, and Installation Information provided,
|
338 |
+
in accord with this section must be in a format that is publicly
|
339 |
+
documented (and with an implementation available to the public in
|
340 |
+
source code form), and must require no special password or key for
|
341 |
+
unpacking, reading or copying.
|
342 |
+
|
343 |
+
7. Additional Terms.
|
344 |
+
|
345 |
+
"Additional permissions" are terms that supplement the terms of this
|
346 |
+
License by making exceptions from one or more of its conditions.
|
347 |
+
Additional permissions that are applicable to the entire Program shall
|
348 |
+
be treated as though they were included in this License, to the extent
|
349 |
+
that they are valid under applicable law. If additional permissions
|
350 |
+
apply only to part of the Program, that part may be used separately
|
351 |
+
under those permissions, but the entire Program remains governed by
|
352 |
+
this License without regard to the additional permissions.
|
353 |
+
|
354 |
+
When you convey a copy of a covered work, you may at your option
|
355 |
+
remove any additional permissions from that copy, or from any part of
|
356 |
+
it. (Additional permissions may be written to require their own
|
357 |
+
removal in certain cases when you modify the work.) You may place
|
358 |
+
additional permissions on material, added by you to a covered work,
|
359 |
+
for which you have or can give appropriate copyright permission.
|
360 |
+
|
361 |
+
Notwithstanding any other provision of this License, for material you
|
362 |
+
add to a covered work, you may (if authorized by the copyright holders of
|
363 |
+
that material) supplement the terms of this License with terms:
|
364 |
+
|
365 |
+
a) Disclaiming warranty or limiting liability differently from the
|
366 |
+
terms of sections 15 and 16 of this License; or
|
367 |
+
|
368 |
+
b) Requiring preservation of specified reasonable legal notices or
|
369 |
+
author attributions in that material or in the Appropriate Legal
|
370 |
+
Notices displayed by works containing it; or
|
371 |
+
|
372 |
+
c) Prohibiting misrepresentation of the origin of that material, or
|
373 |
+
requiring that modified versions of such material be marked in
|
374 |
+
reasonable ways as different from the original version; or
|
375 |
+
|
376 |
+
d) Limiting the use for publicity purposes of names of licensors or
|
377 |
+
authors of the material; or
|
378 |
+
|
379 |
+
e) Declining to grant rights under trademark law for use of some
|
380 |
+
trade names, trademarks, or service marks; or
|
381 |
+
|
382 |
+
f) Requiring indemnification of licensors and authors of that
|
383 |
+
material by anyone who conveys the material (or modified versions of
|
384 |
+
it) with contractual assumptions of liability to the recipient, for
|
385 |
+
any liability that these contractual assumptions directly impose on
|
386 |
+
those licensors and authors.
|
387 |
+
|
388 |
+
All other non-permissive additional terms are considered "further
|
389 |
+
restrictions" within the meaning of section 10. If the Program as you
|
390 |
+
received it, or any part of it, contains a notice stating that it is
|
391 |
+
governed by this License along with a term that is a further
|
392 |
+
restriction, you may remove that term. If a license document contains
|
393 |
+
a further restriction but permits relicensing or conveying under this
|
394 |
+
License, you may add to a covered work material governed by the terms
|
395 |
+
of that license document, provided that the further restriction does
|
396 |
+
not survive such relicensing or conveying.
|
397 |
+
|
398 |
+
If you add terms to a covered work in accord with this section, you
|
399 |
+
must place, in the relevant source files, a statement of the
|
400 |
+
additional terms that apply to those files, or a notice indicating
|
401 |
+
where to find the applicable terms.
|
402 |
+
|
403 |
+
Additional terms, permissive or non-permissive, may be stated in the
|
404 |
+
form of a separately written license, or stated as exceptions;
|
405 |
+
the above requirements apply either way.
|
406 |
+
|
407 |
+
8. Termination.
|
408 |
+
|
409 |
+
You may not propagate or modify a covered work except as expressly
|
410 |
+
provided under this License. Any attempt otherwise to propagate or
|
411 |
+
modify it is void, and will automatically terminate your rights under
|
412 |
+
this License (including any patent licenses granted under the third
|
413 |
+
paragraph of section 11).
|
414 |
+
|
415 |
+
However, if you cease all violation of this License, then your
|
416 |
+
license from a particular copyright holder is reinstated (a)
|
417 |
+
provisionally, unless and until the copyright holder explicitly and
|
418 |
+
finally terminates your license, and (b) permanently, if the copyright
|
419 |
+
holder fails to notify you of the violation by some reasonable means
|
420 |
+
prior to 60 days after the cessation.
|
421 |
+
|
422 |
+
Moreover, your license from a particular copyright holder is
|
423 |
+
reinstated permanently if the copyright holder notifies you of the
|
424 |
+
violation by some reasonable means, this is the first time you have
|
425 |
+
received notice of violation of this License (for any work) from that
|
426 |
+
copyright holder, and you cure the violation prior to 30 days after
|
427 |
+
your receipt of the notice.
|
428 |
+
|
429 |
+
Termination of your rights under this section does not terminate the
|
430 |
+
licenses of parties who have received copies or rights from you under
|
431 |
+
this License. If your rights have been terminated and not permanently
|
432 |
+
reinstated, you do not qualify to receive new licenses for the same
|
433 |
+
material under section 10.
|
434 |
+
|
435 |
+
9. Acceptance Not Required for Having Copies.
|
436 |
+
|
437 |
+
You are not required to accept this License in order to receive or
|
438 |
+
run a copy of the Program. Ancillary propagation of a covered work
|
439 |
+
occurring solely as a consequence of using peer-to-peer transmission
|
440 |
+
to receive a copy likewise does not require acceptance. However,
|
441 |
+
nothing other than this License grants you permission to propagate or
|
442 |
+
modify any covered work. These actions infringe copyright if you do
|
443 |
+
not accept this License. Therefore, by modifying or propagating a
|
444 |
+
covered work, you indicate your acceptance of this License to do so.
|
445 |
+
|
446 |
+
10. Automatic Licensing of Downstream Recipients.
|
447 |
+
|
448 |
+
Each time you convey a covered work, the recipient automatically
|
449 |
+
receives a license from the original licensors, to run, modify and
|
450 |
+
propagate that work, subject to this License. You are not responsible
|
451 |
+
for enforcing compliance by third parties with this License.
|
452 |
+
|
453 |
+
An "entity transaction" is a transaction transferring control of an
|
454 |
+
organization, or substantially all assets of one, or subdividing an
|
455 |
+
organization, or merging organizations. If propagation of a covered
|
456 |
+
work results from an entity transaction, each party to that
|
457 |
+
transaction who receives a copy of the work also receives whatever
|
458 |
+
licenses to the work the party's predecessor in interest had or could
|
459 |
+
give under the previous paragraph, plus a right to possession of the
|
460 |
+
Corresponding Source of the work from the predecessor in interest, if
|
461 |
+
the predecessor has it or can get it with reasonable efforts.
|
462 |
+
|
463 |
+
You may not impose any further restrictions on the exercise of the
|
464 |
+
rights granted or affirmed under this License. For example, you may
|
465 |
+
not impose a license fee, royalty, or other charge for exercise of
|
466 |
+
rights granted under this License, and you may not initiate litigation
|
467 |
+
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
468 |
+
any patent claim is infringed by making, using, selling, offering for
|
469 |
+
sale, or importing the Program or any portion of it.
|
470 |
+
|
471 |
+
11. Patents.
|
472 |
+
|
473 |
+
A "contributor" is a copyright holder who authorizes use under this
|
474 |
+
License of the Program or a work on which the Program is based. The
|
475 |
+
work thus licensed is called the contributor's "contributor version".
|
476 |
+
|
477 |
+
A contributor's "essential patent claims" are all patent claims
|
478 |
+
owned or controlled by the contributor, whether already acquired or
|
479 |
+
hereafter acquired, that would be infringed by some manner, permitted
|
480 |
+
by this License, of making, using, or selling its contributor version,
|
481 |
+
but do not include claims that would be infringed only as a
|
482 |
+
consequence of further modification of the contributor version. For
|
483 |
+
purposes of this definition, "control" includes the right to grant
|
484 |
+
patent sublicenses in a manner consistent with the requirements of
|
485 |
+
this License.
|
486 |
+
|
487 |
+
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
488 |
+
patent license under the contributor's essential patent claims, to
|
489 |
+
make, use, sell, offer for sale, import and otherwise run, modify and
|
490 |
+
propagate the contents of its contributor version.
|
491 |
+
|
492 |
+
In the following three paragraphs, a "patent license" is any express
|
493 |
+
agreement or commitment, however denominated, not to enforce a patent
|
494 |
+
(such as an express permission to practice a patent or covenant not to
|
495 |
+
sue for patent infringement). To "grant" such a patent license to a
|
496 |
+
party means to make such an agreement or commitment not to enforce a
|
497 |
+
patent against the party.
|
498 |
+
|
499 |
+
If you convey a covered work, knowingly relying on a patent license,
|
500 |
+
and the Corresponding Source of the work is not available for anyone
|
501 |
+
to copy, free of charge and under the terms of this License, through a
|
502 |
+
publicly available network server or other readily accessible means,
|
503 |
+
then you must either (1) cause the Corresponding Source to be so
|
504 |
+
available, or (2) arrange to deprive yourself of the benefit of the
|
505 |
+
patent license for this particular work, or (3) arrange, in a manner
|
506 |
+
consistent with the requirements of this License, to extend the patent
|
507 |
+
license to downstream recipients. "Knowingly relying" means you have
|
508 |
+
actual knowledge that, but for the patent license, your conveying the
|
509 |
+
covered work in a country, or your recipient's use of the covered work
|
510 |
+
in a country, would infringe one or more identifiable patents in that
|
511 |
+
country that you have reason to believe are valid.
|
512 |
+
|
513 |
+
If, pursuant to or in connection with a single transaction or
|
514 |
+
arrangement, you convey, or propagate by procuring conveyance of, a
|
515 |
+
covered work, and grant a patent license to some of the parties
|
516 |
+
receiving the covered work authorizing them to use, propagate, modify
|
517 |
+
or convey a specific copy of the covered work, then the patent license
|
518 |
+
you grant is automatically extended to all recipients of the covered
|
519 |
+
work and works based on it.
|
520 |
+
|
521 |
+
A patent license is "discriminatory" if it does not include within
|
522 |
+
the scope of its coverage, prohibits the exercise of, or is
|
523 |
+
conditioned on the non-exercise of one or more of the rights that are
|
524 |
+
specifically granted under this License. You may not convey a covered
|
525 |
+
work if you are a party to an arrangement with a third party that is
|
526 |
+
in the business of distributing software, under which you make payment
|
527 |
+
to the third party based on the extent of your activity of conveying
|
528 |
+
the work, and under which the third party grants, to any of the
|
529 |
+
parties who would receive the covered work from you, a discriminatory
|
530 |
+
patent license (a) in connection with copies of the covered work
|
531 |
+
conveyed by you (or copies made from those copies), or (b) primarily
|
532 |
+
for and in connection with specific products or compilations that
|
533 |
+
contain the covered work, unless you entered into that arrangement,
|
534 |
+
or that patent license was granted, prior to 28 March 2007.
|
535 |
+
|
536 |
+
Nothing in this License shall be construed as excluding or limiting
|
537 |
+
any implied license or other defenses to infringement that may
|
538 |
+
otherwise be available to you under applicable patent law.
|
539 |
+
|
540 |
+
12. No Surrender of Others' Freedom.
|
541 |
+
|
542 |
+
If conditions are imposed on you (whether by court order, agreement or
|
543 |
+
otherwise) that contradict the conditions of this License, they do not
|
544 |
+
excuse you from the conditions of this License. If you cannot convey a
|
545 |
+
covered work so as to satisfy simultaneously your obligations under this
|
546 |
+
License and any other pertinent obligations, then as a consequence you may
|
547 |
+
not convey it at all. For example, if you agree to terms that obligate you
|
548 |
+
to collect a royalty for further conveying from those to whom you convey
|
549 |
+
the Program, the only way you could satisfy both those terms and this
|
550 |
+
License would be to refrain entirely from conveying the Program.
|
551 |
+
|
552 |
+
13. Use with the GNU Affero General Public License.
|
553 |
+
|
554 |
+
Notwithstanding any other provision of this License, you have
|
555 |
+
permission to link or combine any covered work with a work licensed
|
556 |
+
under version 3 of the GNU Affero General Public License into a single
|
557 |
+
combined work, and to convey the resulting work. The terms of this
|
558 |
+
License will continue to apply to the part which is the covered work,
|
559 |
+
but the special requirements of the GNU Affero General Public License,
|
560 |
+
section 13, concerning interaction through a network will apply to the
|
561 |
+
combination as such.
|
562 |
+
|
563 |
+
14. Revised Versions of this License.
|
564 |
+
|
565 |
+
The Free Software Foundation may publish revised and/or new versions of
|
566 |
+
the GNU General Public License from time to time. Such new versions will
|
567 |
+
be similar in spirit to the present version, but may differ in detail to
|
568 |
+
address new problems or concerns.
|
569 |
+
|
570 |
+
Each version is given a distinguishing version number. If the
|
571 |
+
Program specifies that a certain numbered version of the GNU General
|
572 |
+
Public License "or any later version" applies to it, you have the
|
573 |
+
option of following the terms and conditions either of that numbered
|
574 |
+
version or of any later version published by the Free Software
|
575 |
+
Foundation. If the Program does not specify a version number of the
|
576 |
+
GNU General Public License, you may choose any version ever published
|
577 |
+
by the Free Software Foundation.
|
578 |
+
|
579 |
+
If the Program specifies that a proxy can decide which future
|
580 |
+
versions of the GNU General Public License can be used, that proxy's
|
581 |
+
public statement of acceptance of a version permanently authorizes you
|
582 |
+
to choose that version for the Program.
|
583 |
+
|
584 |
+
Later license versions may give you additional or different
|
585 |
+
permissions. However, no additional obligations are imposed on any
|
586 |
+
author or copyright holder as a result of your choosing to follow a
|
587 |
+
later version.
|
588 |
+
|
589 |
+
15. Disclaimer of Warranty.
|
590 |
+
|
591 |
+
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
592 |
+
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
593 |
+
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
594 |
+
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
595 |
+
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
596 |
+
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
597 |
+
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
598 |
+
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
599 |
+
|
600 |
+
16. Limitation of Liability.
|
601 |
+
|
602 |
+
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
603 |
+
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
604 |
+
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
605 |
+
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
606 |
+
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
607 |
+
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
608 |
+
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
609 |
+
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
610 |
+
SUCH DAMAGES.
|
611 |
+
|
612 |
+
17. Interpretation of Sections 15 and 16.
|
613 |
+
|
614 |
+
If the disclaimer of warranty and limitation of liability provided
|
615 |
+
above cannot be given local legal effect according to their terms,
|
616 |
+
reviewing courts shall apply local law that most closely approximates
|
617 |
+
an absolute waiver of all civil liability in connection with the
|
618 |
+
Program, unless a warranty or assumption of liability accompanies a
|
619 |
+
copy of the Program in return for a fee.
|
620 |
+
|
621 |
+
END OF TERMS AND CONDITIONS
|
622 |
+
|
623 |
+
How to Apply These Terms to Your New Programs
|
624 |
+
|
625 |
+
If you develop a new program, and you want it to be of the greatest
|
626 |
+
possible use to the public, the best way to achieve this is to make it
|
627 |
+
free software which everyone can redistribute and change under these terms.
|
628 |
+
|
629 |
+
To do so, attach the following notices to the program. It is safest
|
630 |
+
to attach them to the start of each source file to most effectively
|
631 |
+
state the exclusion of warranty; and each file should have at least
|
632 |
+
the "copyright" line and a pointer to where the full notice is found.
|
633 |
+
|
634 |
+
<one line to give the program's name and a brief idea of what it does.>
|
635 |
+
Copyright (C) <year> <name of author>
|
636 |
+
|
637 |
+
This program is free software: you can redistribute it and/or modify
|
638 |
+
it under the terms of the GNU General Public License as published by
|
639 |
+
the Free Software Foundation, either version 3 of the License, or
|
640 |
+
(at your option) any later version.
|
641 |
+
|
642 |
+
This program is distributed in the hope that it will be useful,
|
643 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
644 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
645 |
+
GNU General Public License for more details.
|
646 |
+
|
647 |
+
You should have received a copy of the GNU General Public License
|
648 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
649 |
+
|
650 |
+
Also add information on how to contact you by electronic and paper mail.
|
651 |
+
|
652 |
+
If the program does terminal interaction, make it output a short
|
653 |
+
notice like this when it starts in an interactive mode:
|
654 |
+
|
655 |
+
<program> Copyright (C) <year> <name of author>
|
656 |
+
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
657 |
+
This is free software, and you are welcome to redistribute it
|
658 |
+
under certain conditions; type `show c' for details.
|
659 |
+
|
660 |
+
The hypothetical commands `show w' and `show c' should show the appropriate
|
661 |
+
parts of the General Public License. Of course, your program's commands
|
662 |
+
might be different; for a GUI interface, you would use an "about box".
|
663 |
+
|
664 |
+
You should also get your employer (if you work as a programmer) or school,
|
665 |
+
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
666 |
+
For more information on this, and how to apply and follow the GNU GPL, see
|
667 |
+
<http://www.gnu.org/licenses/>.
|
668 |
+
|
669 |
+
The GNU General Public License does not permit incorporating your program
|
670 |
+
into proprietary programs. If your program is a subroutine library, you
|
671 |
+
may consider it more useful to permit linking proprietary applications with
|
672 |
+
the library. If this is what you want to do, use the GNU Lesser General
|
673 |
+
Public License instead of this License. But first, please read
|
674 |
+
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
app/code/community/Siel/Acumulus/licentie-nl.pdf
ADDED
Binary file
|
app/code/community/Siel/Acumulus/sql/acumulus_setup/install-4.0.0.php
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/** @var $this Siel_Acumulus_Model_Resource_Setup */
|
4 |
+
$installer = $this;
|
5 |
+
$installer->startSetup();
|
6 |
+
$table = $installer->getTableDefinition();
|
7 |
+
$installer->getConnection()->createTable($table);
|
8 |
+
$installer->endSetup();
|
app/code/community/Siel/Acumulus/sql/acumulus_setup/upgrade-3.4.4-4.0.0.php
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/** @var Mage_Core_Model_Resource $resource */
|
4 |
+
$resource = Mage::getSingleton('core/resource');
|
5 |
+
/** @var $this Siel_Acumulus_Model_Resource_Setup */
|
6 |
+
$installer = $this;
|
7 |
+
$installer->startSetup();
|
8 |
+
|
9 |
+
$connection = $installer->getConnection();
|
10 |
+
|
11 |
+
$tableName = $installer->getTable('acumulus/entry');
|
12 |
+
$oldTableName = $tableName . '_old';
|
13 |
+
|
14 |
+
// Rename current table.
|
15 |
+
$connection->renameTablesBatch(array(array('oldName' => $tableName, 'newName' => $oldTableName)));
|
16 |
+
|
17 |
+
// Create new table.
|
18 |
+
$table = $installer->getTableDefinition();
|
19 |
+
$connection->createTable($table);
|
20 |
+
|
21 |
+
// Copy data from old to new table.
|
22 |
+
// - Orders:
|
23 |
+
$insertOrders = <<<SQL
|
24 |
+
insert into $tableName
|
25 |
+
(entry_id, token, source_type, source_id, created, updated)
|
26 |
+
select entry_id, token, 'Order' as source_type, order_id as source_id, created, updated
|
27 |
+
from $oldTableName
|
28 |
+
where creditmemo_id is null;
|
29 |
+
SQL;
|
30 |
+
$connection->query($insertOrders);
|
31 |
+
|
32 |
+
// - Credit memos:
|
33 |
+
$insertCreditNotes = <<<SQL
|
34 |
+
insert into $tableName
|
35 |
+
(entry_id, token, source_type, source_id, created, updated)
|
36 |
+
select entry_id, token, 'CreditNote' as source_type, creditmemo_id as source_id, created, updated
|
37 |
+
from $oldTableName
|
38 |
+
where creditmemo_id is not null;
|
39 |
+
SQL;
|
40 |
+
$connection->query($insertCreditNotes);
|
41 |
+
|
42 |
+
// Delete old table.
|
43 |
+
$connection->dropTable($oldTableName);
|
44 |
+
|
45 |
+
$installer->endSetup();
|
app/etc/modules/Siel_Acumulus.xml
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0"?>
|
2 |
+
<config>
|
3 |
+
<modules>
|
4 |
+
<Siel_Acumulus>
|
5 |
+
<active>true</active>
|
6 |
+
<codePool>community</codePool>
|
7 |
+
</Siel_Acumulus>
|
8 |
+
</modules>
|
9 |
+
</config>
|
package.xml
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0"?>
|
2 |
+
<package>
|
3 |
+
<name>Siel_Acumulus</name>
|
4 |
+
<version>4.3.0</version>
|
5 |
+
<stability>stable</stability>
|
6 |
+
<license uri="http://www.gnu.org/licenses/gpl-3.0.en.html">GNU v3</license>
|
7 |
+
<channel>community</channel>
|
8 |
+
<extends/>
|
9 |
+
<summary>This addon connects your Magento store to the Dutch SIEL Acumulus online financial administration application.</summary>
|
10 |
+
<description>The Acumulus addon connects your Magento store to the Dutch SIEL Acumulus online financial administration application. It can add your invoices automatically or via a batch send form to your administration, saving you a lot of manual, error prone work.
|
11 |
+

|
12 |
+
So, the Acumulus addon assumes that:
|
13 |
+
<ul>
|
14 |
+
<li>You have an account with SIEL Acumulus (see https://www.siel.nl/acumulus/ or https://www.siel.nl/acumulus/koppelingen/webwinkels/Magento/).</li>
|
15 |
+
</ul>
|
16 |
+
If not, this addon is useless and will not do anything.
|
17 |
+

|
18 |
+
The Acumulus addon:
|
19 |
+
<ul>
|
20 |
+
<li>Reacts to order status changes via events.</li>
|
21 |
+
<li>Does have 2 admin screens: a settings and a batch send screen.</li>
|
22 |
+
<li>Does not in any way interfere with the front-end UI.</li>
|
23 |
+
</ul>
|
24 |
+
</description>
|
25 |
+
<notes>See changelog.txt</notes>
|
26 |
+
<authors><author><name>SIEL Acumulus</name><user>MAG002648740</user><email>erwin@burorader.com</email></author></authors>
|
27 |
+
<date>2016-03-09</date>
|
28 |
+
<time>11:54:17</time>
|
29 |
+
<contents><target name="magecommunity"><dir name="Siel"><dir name="Acumulus"><dir name="Block"><dir name="Adminhtml"><dir name="Form"><file name="Form.php" hash="c32631f5ff4d1ec72630a896a14cefe1"/></dir><file name="Form.php" hash="3447b44cb78547aa3ccaf90be8913c71"/></dir></dir><dir name="Helper"><file name="CompiledMagentoAutoLoader.php" hash="624856afa5ffe2b86918fddab1276ac7"/><file name="Data.php" hash="e282053f1509f5729b8313bff4afa04f"/></dir><dir name="Model"><file name="Entry.php" hash="ecbee1f8734af44b0af5ab1096a7610c"/><dir name="Order"><file name="Observer.php" hash="db7a0075c168e2305b5daa0ec586f12f"/></dir><dir name="Resource"><dir name="Entry"><file name="Collection.php" hash="9ce6644ce43795f616d64fde860da859"/></dir><file name="Entry.php" hash="2cfcf403ae511d0a6855a0c4d21cd494"/><file name="Setup.php" hash="0963aabe90a080b59f2493ff80e4545e"/></dir></dir><file name="changelog.txt" hash="e64229f676be66ebf14469f88fa94d42"/><dir name="controllers"><dir name="Adminhtml"><file name="AcumulusController.php" hash="6da0889cd922b109f3fc84bb2662f580"/></dir></dir><dir name="etc"><file name="adminhtml.xml" hash="fb59e7cfc09fc06e5fba3288f1b518d6"/><file name="config.xml" hash="53f5d4abf723209ac6131808bf435a05"/></dir><file name="events.txt" hash="63aae80ed8a7672a47f3b07742117afc"/><file name="leesmij.txt" hash="ac1c4c7f2877db37d171b1656c79b497"/><dir name="libraries"><dir name="Siel"><dir name="Acumulus"><dir name="Helpers"><file name="Countries.php" hash="1a628daefc8e80557952aa231e3fa668"/><file name="Form.php" hash="418e59a0db0844480a9cc41e9c757aa9"/><file name="FormRenderer.php" hash="70d9d54039220bf5f354a50037a653ee"/><file name="Log.php" hash="02c5333d527d27d719fc71085c84a2fa"/><file name="MailTranslations.php" hash="6e0a782bc1fc0f0b586eaaa16054b241"/><file name="Mailer.php" hash="56886e4151bfa6d8039dbd48d8af9504"/><file name="Number.php" hash="fac66430236c1e8418304e0f6b6e17f9"/><file name="Requirements.php" hash="d201fa16a5b9737f94eeca5408fb2240"/><file name="TranslationCollection.php" hash="53b41c859c88211beebbb2b07aeee244"/><file name="Translator.php" hash="5b6a88b816e8c2eb43e91f94a0205803"/><file name="TranslatorInterface.php" hash="80ef668eff7043dfa84ce7aadb662957"/></dir><dir name="Invoice"><file name="Completor.php" hash="555a6af4e4b1ec8adc5e0f47b9c15a7c"/><file name="CompletorInvoiceLines.php" hash="4874fb9642f0f335a417e594e2d00f2c"/><dir name="CompletorStrategy"><file name="ApplySameVatRate.php" hash="96c3686b9108e5235c850b6b3f9650c5"/><file name="Fail.php" hash="2305fca1967bb77f8e160999872c3fd2"/><file name="SplitKnownDiscountLine.php" hash="b5dae2d3fc008a462f20591bf976563d"/><file name="SplitLine.php" hash="690d0e402e11456472cadc94c9c973c1"/><file name="SplitNonMatchingLine.php" hash="b18b4673a0de5a51e0d50c2f9d7c946b"/><file name="TryAllVatRatePermutations.php" hash="9bcd8ac1b92c8e7980e4e298a9d966ae"/></dir><file name="CompletorStrategyBase.php" hash="6f8d30ce3c9f0f8a4aea412dcf3f0ee4"/><file name="CompletorStrategyLines.php" hash="8ccdc9cd8659b1a920789938f11d31fc"/><file name="ConfigInterface.php" hash="d2a123c1c3c6422f229a06af6b3c2c7e"/><file name="Creator.php" hash="253965039fa20fc233df96b4f20f6f41"/><file name="Source.php" hash="c391c921b901e5e73ce1fc281c66e2e7"/><file name="Translations.php" hash="9e15b2027260c4ba4be1a94958a43653"/></dir><dir name="Joomla"><dir name="Helpers"><file name="FormRenderer.php" hash="c16967c0ec8df7f58da4404bba717700"/><file name="Log.php" hash="e96e7c54ad8ef6c17d9f83540e3fbcef"/><file name="Mailer.php" hash="a89cf15036c8bac4c06beb6ed1b823dd"/></dir><dir name="HikaShop"><dir name="Invoice"><file name="Creator.php" hash="16b90dc15febc660a9c39c93ea0a78b5"/><file name="Source.php" hash="0dc47cf33c0797028844a61107ae7315"/></dir><dir name="Shop"><file name="ConfigForm.php" hash="fbf71cdcfdce070f1242cf196ceaf276"/><file name="InvoiceManager.php" hash="54dbeef00c8f5af79e8594ab6f3684f0"/></dir></dir><dir name="Shop"><file name="AcumulusEntryModel.php" hash="cdaa80c74132cd6b2513cf8ed7d86aba"/><file name="BatchForm.php" hash="908f7a909a6c7b4ac548fd8cadcae65c"/><file name="ConfigForm.php" hash="8c2d4a210dfc4ebdf0d5d10eafcf1e34"/><file name="ConfigStore.php" hash="78a755e624249e56f4ff8409524c51f4"/><file name="InvoiceManager.php" hash="976eab3a9248be0ab4e7c57bd2e3bc8c"/></dir><dir name="VirtueMart"><dir name="Invoice"><file name="Creator.php" hash="5f6c8dda2a65a453b5bb31d29564cfdd"/><file name="Source.php" hash="245cb6f0fee76f458e6fc5b46d78b260"/></dir><dir name="Shop"><file name="ConfigForm.php" hash="0f132abe45dbd3eedf3d8209796e8e18"/><file name="InvoiceManager.php" hash="6cbbcf81daf69e730f2971f7b6b40ad0"/></dir></dir></dir><dir name="Magento"><dir name="Helpers"><file name="FormMapper.php" hash="93b91014706253a6359534f2153c4d4f"/><file name="Log.php" hash="68869b7309c9b6c47baf55cbdac8f8ad"/><file name="Mailer.php" hash="edd028edf6493e83f7a524f3aeebe73f"/></dir><dir name="Invoice"><file name="Completor.php" hash="e89536c6267a92c82291d396f8fdc22e"/><file name="Creator.php" hash="b9480ac0b43e5d6b8489fbf82fd63c1e"/><file name="Source.php" hash="74e763d74e8200325f2dd48e0c643fa3"/></dir><dir name="Shop"><file name="AcumulusEntryModel.php" hash="f523ed9a0882c3bf059859db983efc18"/><file name="BatchForm.php" hash="fd3464cbbf7b9ec43616f7e56c4dcca8"/><file name="ConfigForm.php" hash="a8360d982c1782bdb02beabf28b95c60"/><file name="ConfigStore.php" hash="291955dd1b79c49c49a768d4a0e9f831"/><file name="InvoiceManager.php" hash="fffa04ae2f7e7837b830f8c7f58be348"/></dir></dir><dir name="OpenCart"><dir name="Helpers"><file name="Log.php" hash="a2f540ce3f0204a390af036b85d2acc6"/><file name="Mailer.php" hash="461938b3047e3eaf475c803cd833b42d"/><file name="OcHelper.php" hash="a0495d4b96b1fa5ea7ef978913344fad"/><file name="Registry.php" hash="f5a8dd23e676db98764dc655a78a4e2b"/></dir><dir name="Invoice"><file name="Creator.php" hash="ba901e1c41a574465f13ccda4ab6ae59"/><file name="Source.php" hash="480af34a70dfe6464c3b5b2db3f7ca7a"/></dir><dir name="OpenCart1"><dir name="Helpers"><file name="FormRenderer.php" hash="6f25870323a83c63680e1d93a7810d08"/><file name="OcHelper.php" hash="83cf3d748f464b604779a01e9a560757"/></dir><dir name="Shop"><file name="InvoiceManager.php" hash="54084e3864a8429e6091da4b4613dde9"/></dir></dir><dir name="OpenCart2"><dir name="Helpers"><file name="FormRenderer.php" hash="ca896df3d4a50a0106d6584a59688ae8"/><file name="OcHelper.php" hash="b247c71a0ea4ce11a8734a871b643ea0"/></dir><dir name="Shop"><file name="InvoiceManager.php" hash="a709321e4e27f6e543f9cb7cc1659cfd"/></dir></dir><dir name="Shop"><file name="AcumulusEntryModel.php" hash="c4c4af48b934400c5f66b953d1fa7f81"/><file name="BatchForm.php" hash="cec6d8648a979781f93ba4fe0ecc79ce"/><file name="ConfigForm.php" hash="e4d13008c8a90505fbb7672ce0787ef5"/><file name="ConfigStore.php" hash="ad7b986b89ae919246dccf5d9785945e"/><file name="InvoiceManager.php" hash="82552ce4cca460d0247e5a673d729067"/></dir></dir><dir name="PrestaShop"><dir name="Helpers"><file name="FormMapper.php" hash="223658340e4ca00f23447a97f38f1820"/><file name="Log.php" hash="ccf831f432ba4b04f444dccf2b59c4a8"/><file name="Mailer.php" hash="ed6ff4f7cca198f5e5f021846c297899"/><dir name="mails"><dir name="en"><file name="message.html" hash="d41d8cd98f00b204e9800998ecf8427e"/><file name="message.txt" hash="d41d8cd98f00b204e9800998ecf8427e"/></dir><dir name="nl"><file name="message.html" hash="d41d8cd98f00b204e9800998ecf8427e"/><file name="message.txt" hash="d41d8cd98f00b204e9800998ecf8427e"/></dir><file name=".htaccess" hash="dd31bb1af314c32c34de8e7aff34b429"/></dir></dir><dir name="Invoice"><file name="Creator.php" hash="909a5aad31aa79b8682bd1bbdbd8729e"/><file name="Source.php" hash="0dfec460efd64c7ec38921e883a35de7"/></dir><dir name="Shop"><file name="AcumulusEntryModel.php" hash="a82393e075ce69fe8abb0ffa04859d6b"/><file name="BatchForm.php" hash="a43bf0208d854f4c8bc14209c7ec2b02"/><file name="ConfigForm.php" hash="655381f1efee167c5c1ca48e1f3b78bb"/><file name="ConfigStore.php" hash="13bbe959be7f3b0be5d785238773e5a7"/><file name="InvoiceManager.php" hash="391ee86c5ac3493e99323e28cd106b3e"/></dir></dir><dir name="Shop"><file name="AcumulusEntryModel.php" hash="014fea20e7c734fb09ea18a65fcf1bd6"/><file name="BatchForm.php" hash="b6ad9775a7d259c295904b3ff1d24f0d"/><file name="BatchFormTranslations.php" hash="eb8743fee58e6eac1222acf455f558eb"/><file name="BatchTranslations.php" hash="b2de3631e360132c5d4588edb6cd3364"/><file name="Config.php" hash="cd7882bb87cb92354ce95b90a4c4b116"/><file name="ConfigForm.php" hash="934aea51f141aafb13c0424a30e84b04"/><file name="ConfigFormTranslations.php" hash="776eb4af036a7eb710b327c8342848c5"/><file name="ConfigInterface.php" hash="7b30cccd5b91638c68bc9a7dba2d145e"/><file name="ConfigStore.php" hash="564843ddf549fc61634d4d204b9097e9"/><file name="ConfigStoreInterface.php" hash="ce6236dd8c1946e5ffb297093d97cfee"/><file name="ConfirmUninstallForm.php" hash="d8ae9595709065611fa60d52b97ae2bc"/><file name="ConfirmUninstallFormTranslations.php" hash="4004dfe1556b16e762764eb428d6868e"/><file name="InjectorInterface.php" hash="9cd5c574245a42a3a673e27b3f8d7aff"/><file name="InvoiceManager.php" hash="c3d79c08df8808a3da4ffb6632405521"/><file name="ModuleTranslations.php" hash="716fcb217c79abd10f83b6ebfe792486"/></dir><dir name="Web"><file name="Communicator.php" hash="16554201ae92f77f5381688c31e86081"/><file name="CommunicatorLocal.php" hash="3533284d6cbfb7c3faa461a85b4a9950"/><file name="ConfigInterface.php" hash="0ab7e4a24453e76e7101349296ef6773"/><file name="Service.php" hash="7a90175d5e66cbf9af4f6acf89854669"/><file name="Translations.php" hash="62c8f3bca470df9f16ee5f4b74780b80"/></dir><dir name="WooCommerce"><dir name="Helpers"><file name="FormMapper.php" hash="cdcac8bd8d44ce98d79dc519d3cfc4df"/><file name="FormRenderer.php" hash="38e84aa367100f9f8b2a7aede99fcd48"/><file name="Log.php" hash="3e7ca3fa5058c2566864702d0f4e9ea5"/><file name="Mailer.php" hash="8cf8c987fbdcc4bef6fec18f3adc348d"/></dir><dir name="Invoice"><file name="Creator.php" hash="a8a100aaa00a350d979bcd40f7252a73"/><file name="Source.php" hash="fcb716ad46e89c1607c6d2cdf37ead80"/></dir><dir name="Shop"><file name="AcumulusEntryModel.php" hash="051668471829b3c9ad45968325687a1c"/><file name="ConfigForm.php" hash="d678232c11285f291102913580939d27"/><file name="ConfigStore.php" hash="ea8df8c02347c512ea816b1d440950c7"/><file name="InvoiceManager.php" hash="dd4062e568b1c8765f5ff5f736ed332f"/></dir></dir></dir><file name="leesmij.txt" hash="f88ca59b1a830d1982c775ced8c07abf"/><file name="psr4.php" hash="a2e43be4d375ec396fc574bf13c19458"/></dir></dir><file name="license.txt" hash="d32239bcb673463ab874e80d47fae504"/><file name="licentie-nl.pdf" hash="1af9588b31002745c526e46feb911c6d"/><dir name="sql"><dir name="acumulus_setup"><file name="install-4.0.0.php" hash="06c933104dbd0f0d7fd6370506c046f2"/><file name="upgrade-3.4.4-4.0.0.php" hash="7a0d1a018ba31770657554876982783d"/></dir></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Siel_Acumulus.xml" hash="8b4935569d0eae94852fa9f3af8ed166"/></dir></target></contents>
|
30 |
+
<compatible/>
|
31 |
+
<dependencies><required><php><min>5.3.0</min><max>5.7.0</max></php><extension><name>SimpleXML</name><min/><max/></extension><extension><name>curl</name><min/><max/></extension><extension><name>dom</name><min/><max/></extension></required></dependencies>
|
32 |
+
</package>
|