Comment: | HelloAsso: Accounting transactions implemented for all items and options synchronization regardless of form type (donation, payment, membership...) |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | dev |
Files: | files | file ages | folders |
SHA1: |
976de91258030b0d7fbea7ed07a31b34 |
User & Date: | alinaar on 2023-05-26 11:46:15 |
Other Links: | branch diff | manifest | tags |
2023-05-26
| ||
12:11 | HelloAsso: Payment sync history fixed and payment sync improved check-in: cdc82ba9a6 user: alinaar tags: dev | |
11:46 | HelloAsso: Accounting transactions implemented for all items and options synchronization regardless of form type (donation, payment, membership...) check-in: 976de91258 user: alinaar tags: dev | |
2023-05-22
| ||
22:05 | Merge check-in: 1fdf8c0816 user: alinaar tags: dev | |
Modified helloasso/admin/index.php from [da9f56c3de] to [f6484a8168].
︙ | ︙ | |||
22 23 24 25 26 27 28 | Utils::redirect(PLUGIN_ADMIN_URL . 'sync.php'); } $checkout = null; $form->runIf('generate_checkout', function () use ($ha, $checkout, $tpl) { // ToDo: add a nice check $user = EM::findOneById(User::class, (int)(array_keys($_POST['user'])[0])); | | > | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | Utils::redirect(PLUGIN_ADMIN_URL . 'sync.php'); } $checkout = null; $form->runIf('generate_checkout', function () use ($ha, $checkout, $tpl) { // ToDo: add a nice check $user = EM::findOneById(User::class, (int)(array_keys($_POST['user'])[0])); $checkout = $ha->createCheckout($_POST['org_slug'], $_POST['label'], (int)($_POST['amount'] * 100), $user, [ array_keys($_POST['credit'])[0], array_keys($_POST['debit'])[0] ]); $tpl->assign('checkout', $checkout); }); $tpl->assign('list', Forms::list()); $tpl->assign('restricted', $ha::isTrial()); $tpl->assign('orgOptions', [ $ha->plugin()->getConfig()->default_organization => $ha->plugin()->getConfig()->default_organization ]); $tpl->assign('chart_id', Plugin\HelloAsso\HelloAsso::CHART_ID); // ToDo: make it dynamic $tpl->display(PLUGIN_ROOT . '/templates/index.tpl'); |
Modified helloasso/admin/order.php from [8aaa01f89f] to [7ed6bfb7c3].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <?php namespace Garradin; use Garradin\Plugin\HelloAsso\Orders; use Garradin\Plugin\HelloAsso\Payments; use Garradin\Plugin\HelloAsso\Items; require __DIR__ . '/_inc.php'; $order = Orders::get((int)qg('id')); if (!$order) { throw new UserException('Commande inconnue'); } $payments = Payments::list($order); $items = Items::list($order); $payer_infos = $order->getPayerInfos(); //$found_user = $ha->findUserForPayment($order->payer); //$mapped_user = $ha->getMappedUser($order->payer); $found_user = $mapped_user = []; | > > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <?php namespace Garradin; use Garradin\Plugin\HelloAsso\Orders; use Garradin\Plugin\HelloAsso\Payments; use Garradin\Plugin\HelloAsso\Items; use Garradin\Plugin\HelloAsso\Options; require __DIR__ . '/_inc.php'; $order = Orders::get((int)qg('id')); if (!$order) { throw new UserException('Commande inconnue'); } $payments = Payments::list($order); $items = Items::list($order); $options = Options::list($order); $payer_infos = $order->getPayerInfos(); //$found_user = $ha->findUserForPayment($order->payer); //$mapped_user = $ha->getMappedUser($order->payer); $found_user = $mapped_user = []; $tpl->assign(compact('order', 'payments', 'items', 'options', 'payer_infos', 'found_user', 'mapped_user')); $tpl->display(PLUGIN_ROOT . '/templates/order.tpl'); |
Modified helloasso/admin/sync.php from [0adcdf6db4] to [0b9c6be55e].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <?php namespace Garradin; require __DIR__ . '/_inc.php'; use KD2\DB\EntityManager; use Garradin\Plugin\HelloAsso\Entities\Form; use Garradin\Plugin\HelloAsso\Forms; $csrf_key = 'sync'; $form->runIf('sync', function() use ($ha) { $ha->sync(); }, $csrf_key, PLUGIN_ADMIN_URL . 'sync.php?ok=1'); $tpl->assign('last_sync', $ha->getLastSync()); $tpl->assign('csrf_key', $csrf_key); | > > > > > > > > > | > > > > | > > | > | | | | | | | | | | | | | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | <?php namespace Garradin; require __DIR__ . '/_inc.php'; use KD2\DB\EntityManager; use Garradin\Plugin\HelloAsso\Entities\Form; use Garradin\Plugin\HelloAsso\Entities\Item; use Garradin\Plugin\HelloAsso\Forms; use Garradin\Plugin\HelloAsso\Entities\Chargeable; use Garradin\Plugin\HelloAsso\Chargeables; $csrf_key = 'sync'; $form->runIf('sync', function() use ($ha) { $ha->sync(); }, $csrf_key, PLUGIN_ADMIN_URL . 'sync.php?ok=1'); $tpl->assign('last_sync', $ha->getLastSync()); $tpl->assign('csrf_key', $csrf_key); $chargeables = Chargeables::allPlusExtraFields( sprintf(' SELECT c.*, f.name AS _form_name, i.label AS _item_name FROM @TABLE c LEFT JOIN %s f ON (f.id = c.id_form) LEFT JOIN %s i ON (i.id = c.id_item) WHERE c.id_credit_account IS NULL ', Form::TABLE, Item::TABLE), ['_form_name', '_item_name'] ); $tpl->assign('chargeables', $chargeables); $tpl->assign('chargeableTypes', Chargeable::TYPES); $tpl->assign('chart_id', Plugin\HelloAsso\HelloAsso::CHART_ID); // ToDo: make it dynamic $form->runIf('accounts_submit', function() use ($ha) { if (array_key_exists('chargeable_credit', $_POST)) { $source = []; foreach ($_POST['chargeable_credit'] as $id_item => $array) { $id_credit_account = array_keys($array)[0]; $id_debit_account = array_keys($_POST['chargeable_debit'][$id_item])[0]; // ToDo: add a nice check if ($id_credit_account && $id_debit_account) { $source[$id_item]['credit'] = (int)$id_credit_account; $source[$id_item]['debit'] = (int)$id_debit_account; } } Chargeables::setAccounts($source); } $ha->sync(); }, null, PLUGIN_ADMIN_URL . 'sync.php?ok=1'); $tpl->display(PLUGIN_ROOT . '/templates/sync.tpl'); |
Modified helloasso/lib/API.php from [4bd9fbded5] to [9076f8fdd9].
︙ | ︙ | |||
232 233 234 235 236 237 238 | $this->assert(isset($result->pagination->totalCount)); if (count($result->data)) { $r = $result->data[0]; $this->assert(isset($r->date)); $this->assert(strtotime($r->date)); $this->assert(isset($r->id)); | | | 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | $this->assert(isset($result->pagination->totalCount)); if (count($result->data)) { $r = $result->data[0]; $this->assert(isset($r->date)); $this->assert(strtotime($r->date)); $this->assert(isset($r->id)); $this->assert(!isset($r->amount->total) || ctype_digit((string)$r->amount->total)); // This can be empty if it's free } } public function listOrganizationPayments(string $organization, array $params): \stdClass { if (!preg_match('/^[a-z0-9_-]+$/', $organization)) { throw new \RuntimeException('Invalid organization slug'); |
︙ | ︙ | |||
263 264 265 266 267 268 269 | $this->assert(isset($r->date)); $this->assert(strtotime($r->date)); $this->assert(isset($r->order->id)); $this->assert(isset($r->payer)); $this->assert(isset($r->state)); $this->assert(isset($r->id)); $this->assert(isset($r->paymentReceiptUrl)); | | | 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 | $this->assert(isset($r->date)); $this->assert(strtotime($r->date)); $this->assert(isset($r->order->id)); $this->assert(isset($r->payer)); $this->assert(isset($r->state)); $this->assert(isset($r->id)); $this->assert(isset($r->paymentReceiptUrl)); $this->assert(isset($r->amount) && ctype_digit((string)$r->amount)); } } public function listOrganizationItems(string $organization, array $params = []): \stdClass { if (!preg_match('/^[a-z0-9_-]+$/', $organization)) { throw new \RuntimeException('Invalid organization slug'); |
︙ | ︙ | |||
295 296 297 298 299 300 301 | if (count($result->data)) { $r = $result->data[0]; $this->assert(isset($r->order->id)); $this->assert(isset($r->payer)); $this->assert(isset($r->state)); $this->assert(isset($r->type)); $this->assert(isset($r->id)); | | | 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 | if (count($result->data)) { $r = $result->data[0]; $this->assert(isset($r->order->id)); $this->assert(isset($r->payer)); $this->assert(isset($r->state)); $this->assert(isset($r->type)); $this->assert(isset($r->id)); $this->assert(isset($r->amount) && ctype_digit((string)$r->amount)); // int between -128 and 255 is interpreted as the ASCII value of a single character } } public function getCheckout(string $organization, int $id): \stdClass { return $this->GET('v5/organizations/' . $organization . '/checkout-intents/' . (int)$id); } |
︙ | ︙ |
Added helloasso/lib/ChargeableInterface.php version [c5d759431e].
> > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 | <?php namespace Garradin\Plugin\HelloAsso; interface ChargeableInterface { public function getItemId(): ?int; public function getLabel(): string; public function getAmount(): ?int; } |
Added helloasso/lib/Chargeables.php version [448b067c86].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | <?php namespace Garradin\Plugin\HelloAsso; use Garradin\Plugin\HelloAsso\Entities\Chargeable; use Garradin\Plugin\HelloAsso\ChargeableInterface; use Garradin\Plugin\HelloAsso\HelloAsso; use KD2\DB\EntityManager as EM; use Garradin\DB; class Chargeables { static public function get(int $id_form, int $type, string $label, ?int $amount): ?Chargeable { if (!array_key_exists($type, Chargeable::TYPES)) { throw new \RuntimeException('Invalid Chargeable type: %s. Allowed types are: %s.', $type, implode(', ', array_keys(Chargeable::TYPES))); } $amount_filter = (null === $amount ? 'amount IS NULL' : 'amount = :amount'); $params = [ $id_form, $type, $label, $amount ]; if (null === $amount) { array_pop($params); } return EM::findOne(Chargeable::class, 'SELECT * FROM @TABLE WHERE id_form = :id_form AND type = :type AND label = :label AND ' . $amount_filter, ...$params); } static public function allPlusExtraFields(string $query, array $extra_fields, ...$params): array { $res = self::iterateWithExtraFields($query, $extra_fields, ...$params); $out = []; foreach ($res as $row) { $out[] = $row; } return $out; } static public function iterateWithExtraFields(string $query, ?array $extra_fields, ...$params): iterable { $db = DB::getInstance(); $query = str_replace('@TABLE', Chargeable::TABLE, $query); $res = $db->preparedQuery($query, $params); while ($row = $res->fetchArray(\SQLITE3_ASSOC)) { $data = $row; foreach ($extra_fields as $field) if (array_key_exists($field, $row)) unset($data[$field]); $obj = new Chargeable(); $obj->exists(true); $obj->load($data); if ($extra_fields) { foreach ($extra_fields as $field) { if (!array_key_exists($field, $row)) { throw new \LogicException(sprintf('Specified extra field "%s" not provided to the query.', $field)); } $obj->{'set'.ucfirst(substr($field, 1))}($row[$field]); } } yield $obj; } $res->finalize(); } static public function createChargeable(int $id_form, ChargeableInterface $entity, int $type): Chargeable { $chargeable = new Chargeable(); $chargeable->set('type', $type); $chargeable->set('id_form', $id_form); $chargeable->set('id_item', $entity->getItemId()); $chargeable->set('label', $entity->getLabel()); $chargeable->set('amount', ($type === Chargeable::ONLY_ONE_ITEM_FORM_TYPE ? null : $entity->getAmount())); $chargeable->save(); return $chargeable; } static public function setAccounts(array $source): void { foreach ($source as $id => $accounts) { if (DB::getInstance()->exec(sprintf('UPDATE %s SET id_credit_account = %d, id_debit_account = %d WHERE id = %d;', Chargeable::TABLE, $accounts['credit'], $accounts['debit'], (int)$id)) === false) { throw new \RuntimeException(sprintf('Cannot update %s plugin Items\' accounting accounts.', HelloAsso::PROVIDER_LABEL)); } } } } |
Added helloasso/lib/Entities/Chargeable.php version [cd61ecfb35].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | <?php namespace Garradin\Plugin\HelloAsso\Entities; use Garradin\Entity; class Chargeable extends Entity { const TABLE = 'plugin_helloasso_chargeables'; const ITEM_TYPE = 1; const OPTION_TYPE = 2; const ONLY_ONE_ITEM_FORM_TYPE = 3; const CHECKOUT_TYPE = 4; const TYPES = [ self::ITEM_TYPE => 'Item', self::OPTION_TYPE => 'Option', self::ONLY_ONE_ITEM_FORM_TYPE => 'Don/Vente', self::CHECKOUT_TYPE => 'Checkout' ]; const TYPE_FROM_FORM = [ 'Donation' => self::ONLY_ONE_ITEM_FORM_TYPE, 'PaymentForm' => self::ONLY_ONE_ITEM_FORM_TYPE, 'Payment' => self::ITEM_TYPE, 'Membership' => self::ITEM_TYPE, 'Checkout' => self::CHECKOUT_TYPE, 'Shop' => self::ITEM_TYPE ]; protected int $id; protected int $id_form; protected ?int $id_item; // ONLY_ONE_ITEM_FORM_TYPE forms/payments have always only one item so we do not care about its value protected ?int $id_credit_account; protected ?int $id_debit_account; protected int $type; protected string $label; protected ?int $amount; protected ?string $_form_name = null; protected ?string $_item_name = null; public function setForm_name(string $name): void { $this->_form_name = $name; } public function getForm_name(): string { return $this->_form_name; } public function setItem_name(?string $name): void { $this->_item_name = $name; } public function getItem_name(): ?string { return $this->_item_name; } public function selfCheck(): void { parent::selfCheck(); if (!array_key_exists($this->type, Chargeable::TYPES)) { throw new \RuntimeException('Invalid Chargeable type: %s. Allowed types are: %s.', $type, implode(', ', array_keys(Chargeable::TYPES))); } } } |
Modified helloasso/lib/Entities/Form.php from [0eb3256395] to [5af3819487].
1 2 3 4 | <?php namespace Garradin\Plugin\HelloAsso\Entities; | | | < < < | < < < | > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | <?php namespace Garradin\Plugin\HelloAsso\Entities; use Garradin\Entity; use Garradin\Plugin\HelloAsso\ChargeableInterface; class Form extends Entity implements ChargeableInterface { const TABLE = 'plugin_helloasso_forms'; protected int $id; protected string $org_slug; protected string $org_name; protected string $name; protected string $state; protected string $type; protected string $slug; const TYPES = [ 'CrowdFunding' => 'Crowdfunding', 'Membership' => 'Adhésion', 'Event' => 'Billetteries', 'Donation' => 'Dons', 'PaymentForm' => 'Ventes', 'Checkout' => 'Encaissement', 'Shop' => 'Boutique', ]; const STATES = [ 'Draft' => 'brouillon', 'Public' => 'public', 'Private' => 'privé', 'Disabled' => 'désactivé', ]; public function getItemId(): ?int { return null; } public function getLabel(): string { return $this->name; } public function getAmount(): ?int { return null; } } |
Modified helloasso/lib/Entities/Item.php from [30bfe264e8] to [c17f6f487f].
1 2 3 4 | <?php namespace Garradin\Plugin\HelloAsso\Entities; | | | < < < | > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?php namespace Garradin\Plugin\HelloAsso\Entities; use Garradin\Entity; use Garradin\Plugin\HelloAsso\ChargeableInterface; class Item extends Entity implements ChargeableInterface { const TABLE = 'plugin_helloasso_items'; protected int $id; protected int $id_order; protected int $id_form; protected ?int $id_user; protected ?int $id_transaction; protected string $type; protected string $state; protected string $label; protected string $person; protected int $amount; protected int $has_options; protected ?string $custom_fields; protected string $raw_data; const TYPES = [ 'Donation' => 'Don', 'Payment' => 'Paiement', 'Registration' => 'Inscription', |
︙ | ︙ | |||
42 43 44 45 46 47 48 | 'Registered' => 'Enregistré', 'Deleted' => 'Supprimé', 'Refunded' => 'Remboursé', 'Unknown' => 'Inconnu', 'Canceled' => 'Annulé', 'Contested' => 'Contesté', ]; | | > > > > > > > > > > > > > > > | 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | 'Registered' => 'Enregistré', 'Deleted' => 'Supprimé', 'Refunded' => 'Remboursé', 'Unknown' => 'Inconnu', 'Canceled' => 'Annulé', 'Contested' => 'Contesté', ]; public function getItemId(): ?int { return $this->id; } public function getLabel(): string { return $this->label; } public function getAmount(): ?int { return $this->amount; } } |
Added helloasso/lib/Entities/Option.php version [36ff74212e].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <?php namespace Garradin\Plugin\HelloAsso\Entities; use Garradin\Entity; use Garradin\Plugin\HelloAsso\ChargeableInterface; class Option extends Entity implements ChargeableInterface { const TABLE = 'plugin_helloasso_item_options'; protected int $id; protected int $id_item; protected int $id_order; // Redundant but needed by DynamicList since it does not handle JOIN statement protected ?int $id_transaction; protected string $label; protected int $amount; protected ?string $custom_fields; protected string $raw_data; public function getItemId(): ?int { return $this->id_item; } public function getLabel(): string { return $this->label; } public function getAmount(): ?int { return $this->amount; } } |
Modified helloasso/lib/Forms.php from [6fe265e788] to [5788032b75].
1 2 3 4 5 6 7 8 9 10 11 12 13 | <?php namespace Garradin\Plugin\HelloAsso; use Garradin\Plugin\HelloAsso\Entities\Form; use Garradin\Plugin\HelloAsso\API; use Garradin\DB; use KD2\DB\EntityManager as EM; class Forms { | > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?php namespace Garradin\Plugin\HelloAsso; use Garradin\Plugin\HelloAsso\Entities\Form; use Garradin\Plugin\HelloAsso\Entities\Chargeable; use Garradin\Plugin\HelloAsso\API; use Garradin\Plugin\HelloAsso\Chargeables; use Garradin\DB; use KD2\DB\EntityManager as EM; class Forms { |
︙ | ︙ | |||
73 74 75 76 77 78 79 | } unset($list); $forms = API::getInstance()->listForms($o->organizationSlug); foreach ($forms as $form) { | | | | | | | | | > > > > > > > > | < < < < < < < < < < < < < | 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | } unset($list); $forms = API::getInstance()->listForms($o->organizationSlug); foreach ($forms as $form) { $entity = $existing[$form->formSlug] ?? new Form; $entity->org_name = $o->name; $entity->org_slug = $o->organizationSlug; $entity->name = strip_tags($form->privateTitle ?? $form->title); $entity->type = $form->formType; $entity->state = $form->state; $entity->slug = $form->formSlug; $entity->save(); if ($form->formType === 'Donation' || $form->formType === 'PaymentForm') { $chargeable = Chargeables::get($entity->id, Chargeable::ONLY_ONE_ITEM_FORM_TYPE, $entity->name, null); if (null === $chargeable) { $chargeable = Chargeables::createChargeable($entity->id, $entity, Chargeable::ONLY_ONE_ITEM_FORM_TYPE); } } } } } static public function reset(): void { $sql = sprintf('DELETE FROM %s;', Form::TABLE); DB::getInstance()->exec($sql); } } |
Modified helloasso/lib/HelloAsso.php from [6cbd7fae5e] to [e44ef9f89e].
︙ | ︙ | |||
143 144 145 146 147 148 149 | return empty($this->config->oauth) ? false : true; } public function getConfig(): ?\stdClass { return $this->config; } | | | 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | return empty($this->config->oauth) ? false : true; } public function getConfig(): ?\stdClass { return $this->config; } public function createCheckout(string $organization, string $label, int $amount, User $user, ?array $accounts = null): \stdClass { $label .= ' - ' . $user->nom . ' - ' . self::PROVIDER_LABEL; // Resume user failed attemp if ($payment = EntityManager::findOne(Payment::class, 'SELECT * FROM @TABLE WHERE id_author = :id_user AND label = :label AND status = :status AND method = :method AND type = :type AND date >= datetime(\'now\', :expiration)', (int)$user->id, $label, Payment::AWAITING_STATUS, Payment::BANK_CARD_METHOD, Payment::UNIQUE_TYPE, '-' . self::PAYMENT_EXPIRATION)) { // Resume current checkout if (isset($payment->extra_data->checkout) && !(new \DateTime($payment->extra_data->checkout->date) < new \DateTime('now -' . self::CHECKOUT_LINK_EXPIRATION))) { |
︙ | ︙ | |||
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | ]; $checkout = API::getInstance()->createCheckout($organization, $payment->amount, $label, $payment->id, $this->plugin->url() . self::REDIRECTION_FILE, $metadata); $checkout->date = (new \DateTime())->format('Y-m-d H:i:s'); $checkout->csrf = $csrf; $payment->setExtraData('checkout', $checkout); $payment->set('reference', $checkout->id); $payment->setExtraData('organization', $organization); $payment->addLog(sprintf(self::CHECKOUT_CREATION_LOG_LABEL, (int)$checkout->id)); $payment->save(); return $checkout; } static public function checkPaymentStatus(Payment $payment): void { if ($payment->status !== Payment::AWAITING_STATUS) { return ; } if ($payment->provider != self::PROVIDER_NAME) { throw new \LogicException(sprintf('This is not a %s payment!', self::PROVIDER_LABEL)); } if (empty($payment->reference)) { throw new \LogicException('This payment does not have a reference.'); } | > > | | 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | ]; $checkout = API::getInstance()->createCheckout($organization, $payment->amount, $label, $payment->id, $this->plugin->url() . self::REDIRECTION_FILE, $metadata); $checkout->date = (new \DateTime())->format('Y-m-d H:i:s'); $checkout->csrf = $csrf; $payment->setExtraData('checkout', $checkout); $payment->set('reference', $checkout->id); $payment->setExtraData('organization', $organization); $payment->setExtraData('id_credit_account', $accounts ? $accounts[0] : null); $payment->setExtraData('id_debit_account', $accounts ? $accounts[1] : null); $payment->addLog(sprintf(self::CHECKOUT_CREATION_LOG_LABEL, (int)$checkout->id)); $payment->save(); return $checkout; } static public function checkPaymentStatus(Payment $payment): void { if ($payment->status !== Payment::AWAITING_STATUS) { return ; } if ($payment->provider != self::PROVIDER_NAME) { throw new \LogicException(sprintf('This is not a %s payment!', self::PROVIDER_LABEL)); } if (empty($payment->reference)) { throw new \LogicException('This payment does not have a reference.'); } $checkout = API::getInstance()->getCheckout($payment->org_slug, (int)$payment->reference); file_put_contents(self::LOG_FILE, sprintf("\n\n==== %s - Fetch: %s ====\n\n%s\n", date('d/m/Y H:i:s'), $payment->reference, json_encode($checkout, JSON_PRETTY_PRINT)), FILE_APPEND); if ($checkout->metadata->payment_id != $payment->id) { throw new \LogicException(sprintf('Payment ref. "%s" does not match payment ID "%s" (metadata payment_id = %s)', $payment->reference, $payment->id, $checkout->metadata->payment_id)); } |
︙ | ︙ |
Modified helloasso/lib/Items.php from [a8b3b3ffbe] to [0ec5e5cfd0].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | <?php namespace Garradin\Plugin\HelloAsso; use Garradin\Plugin\HelloAsso\Entities\Form; use Garradin\Plugin\HelloAsso\Entities\Item; use Garradin\Plugin\HelloAsso\Entities\Order; use Garradin\Plugin\HelloAsso\Entities\Payment; use Garradin\Plugin\HelloAsso\API; use Garradin\DB; use Garradin\DynamicList; use Garradin\Utils; use KD2\DB\EntityManager as EM; class Items { static public function get(int $id): ?Item { return EM::findOneById(Item::class, $id); } static public function list($for): DynamicList { $columns = [ 'id' => [ 'label' => 'Référence', ], 'amount' => [ 'label' => 'Montant', ], 'type' => [ 'label' => 'Type', ], 'label' => [ 'label' => 'Objet', ], 'person' => [ 'label' => 'Personne', ], 'custom_fields' => [ 'label' => 'Champs', ], 'state' => [ 'label' => 'Statut', ], 'id_order' => [], | > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | <?php namespace Garradin\Plugin\HelloAsso; use Garradin\Plugin\HelloAsso\Entities\Form; use Garradin\Plugin\HelloAsso\Entities\Item; use Garradin\Plugin\HelloAsso\Entities\Chargeable; use Garradin\Plugin\HelloAsso\Entities\Option; use Garradin\Plugin\HelloAsso\Entities\Order; use Garradin\Plugin\HelloAsso\Entities\Payment; use Garradin\Plugin\HelloAsso\API; use Garradin\Plugin\HelloAsso\HelloAsso; use Garradin\Plugin\HelloAsso\ChargeableInterface; use Garradin\DB; use Garradin\DynamicList; use Garradin\Utils; use Garradin\Entities\Accounting\Transaction; use Garradin\Accounting\Years; use Garradin\Plugin\HelloAsso\Payments; use KD2\DB\EntityManager as EM; class Items { const TRANSACTION_PREFIX = 'Item'; const TRANSACTION_NOTE = 'Générée automatiquement par l\'extension ' . HelloAsso::PROVIDER_LABEL . '.'; const DONATION_LABEL = 'Don'; const CHECKOUT_LABEL = 'Paiement orphelin - commande #%d (%s)'; static public function get(int $id): ?Item { return EM::findOneById(Item::class, $id); } static public function list($for): DynamicList { $columns = [ 'id' => [ 'label' => 'Référence', ], 'id_transaction' => [ 'label' => 'Écriture' ], 'amount' => [ 'label' => 'Montant', ], 'type' => [ 'label' => 'Type', ], 'label' => [ 'label' => 'Objet', ], 'person' => [ 'label' => 'Personne', ], 'options' => [ 'label' => 'Options', 'select' => "(CASE WHEN has_options THEN 'oui' ELSE '-' END)" ], // sprintf("(SELECT (CASE WHEN COUNT(id) > 0 THEN 'oui' ELSE '-' END) FROM %s o WHERE o.id_item = %s.id)", Option::TABLE, Item::TABLE) 'custom_fields' => [ 'label' => 'Champs', ], 'state' => [ 'label' => 'Statut', ], 'id_order' => [], |
︙ | ︙ | |||
86 87 88 89 90 91 92 | } }); $list->orderBy('id', true); return $list; } | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | > > > | > | > > > > > > > > | > > > | 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | } }); $list->orderBy('id', true); return $list; } static public function sync(string $org_slug, bool $accounting = true): void { $params = [ 'pageSize' => HelloAsso::getPageSize(), ]; $page_count = 1; for ($i = 1; $i <= $page_count; $i++) { $params['pageIndex'] = $i; $result = API::getInstance()->listOrganizationItems($org_slug, $params); $page_count = $result->pagination->totalPages; foreach ($result->data as $order) { self::syncItem($order, $accounting); } if (HelloAsso::isTrial()) { break; } } } static protected function syncItem(\stdClass $data, bool $accounting): Item { $entity = self::get($data->id) ?? new Item; $entity->set('raw_data', json_encode($data)); $data = self::transform($data); if (!$entity->exists()) { $entity->set('id', $data->id); $entity->set('id_order', $data->order_id); $entity->set('id_form', Forms::getId($data->org_slug, $data->form_slug)); } $entity->set('amount', $data->amount); $entity->set('state', $data->state); $entity->set('type', $data->type); $entity->set('person', $data->user_name ?? $data->payer_name); $entity->set('label', self::generateLabel($data, (int)$entity->id_form)); $entity->set('custom_fields', count($data->fields) ? json_encode($data->fields) : null); $entity->set('has_options', (int)isset($data->options)); $entity->save(); $optionEntities = []; if (isset($data->options)) { foreach ($data->options as $option) { $optionEntities[] = self::syncOption($option, $data, $entity->id, $accounting); } } // Creating a transaction only if payment is unique and already done (not pending) and accounts sets if ($accounting && !$entity->id_transaction && (count($data->payments) === 1 && $data->payments[0]->state === Payments::AUTHORIZED_STATUS)) { if ($entity->amount) { if ($data->order->formType !== 'Checkout') { // All cases except Checkout self::accountChargeable((int)$entity->id_form, $entity, Chargeable::TYPE_FROM_FORM[$data->order->formType], (int)$data->payments[0]->id, new \DateTime($data->payments[0]->date)); } else { if (!$payment = Payments::get((int)$data->payments[0]->id)) { throw new \RuntimeException(sprintf('Payment #%d matching checkout item #%d not found.', $data->payments[0]->id, $entity->id)); } if (isset($payment->id_credit_account) && $payment->id_credit_account && $payment->id_debit_account) {// This feature will be available once the ChekoutIntent callback is fixed $transaction = self::createTransaction($entity, [$payment->id_credit_account, $payment->id_debit_account], (int)$data->payments[0]->id, $payment->date); $payment->set('id_transaction', $transaction->id); } elseif (self::accountChargeable((int)$entity->id_form, $entity, Chargeable::TYPE_FROM_FORM[$data->order->formType], (int)$data->payments[0]->id, new \DateTime($data->payments[0]->date))) { $payment->set('id_transaction', $entity->id_transaction); } $payment->save(); } } if (isset($data->options)) { foreach ($optionEntities as $option) { self::accountChargeable((int)$entity->id_form, $option, Chargeable::OPTION_TYPE, (int)$data->payments[0]->id, new \DateTime($data->payments[0]->date)); } } } return $entity; } static protected function syncOption(\stdClass $data, \stdClass $full_data, int $id_item, bool $accounting): Option { $option = EM::findOne(Option::class, 'SELECT * FROM @TABLE WHERE id_item = :id_item AND label = :name AND amount = :amount', $id_item, $data->name, $data->amount) ?? new Option; $option->set('raw_data', json_encode($data)); $data = self::transformOption($data); if (!$option->exists()) { $option->set('id_item', (int)$full_data->id); $option->set('id_order', (int)$full_data->order_id); } $option->set('amount', $data->amount); $option->set('label', $data->name ?? Forms::getName($option->id_form)); $option->set('custom_fields', count($data->fields) ? json_encode($data->fields) : null); $option->save(); return $option; } static protected function accountChargeable(int $id_form, ChargeableInterface $entity, int $type, int $id_payment, \DateTime $date): bool { $amount = ($type === Chargeable::ONLY_ONE_ITEM_FORM_TYPE ? null : $entity->getAmount()); $chargeable = Chargeables::get($id_form, $type, $entity->getLabel(), $amount); if (null === $chargeable) { $chargeable = Chargeables::createChargeable($id_form, $entity, $type); } elseif ($chargeable->id_credit_account && $chargeable->id_debit_account) { $transaction = self::createTransaction($entity, [(int)$chargeable->id_credit_account, (int)$chargeable->id_debit_account], $id_payment, $date); $entity->id_transaction = (int)$transaction->id; $entity->save(); return true; } return false; } static protected function transform(\stdClass $data): \stdClass { $data->id = (int) $data->id; $data->order_id = (int) $data->order->id; $data->payer_name = isset($data->payer) ? Payment::getPayerName($data->payer) : null; |
︙ | ︙ | |||
159 160 161 162 163 164 165 | foreach ($data->customFields as $field) { $data->fields[$field->name] = $field->answer; } } return $data; } | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 | foreach ($data->customFields as $field) { $data->fields[$field->name] = $field->answer; } } return $data; } static protected function transformOption(\stdClass $data): \stdClass { $data->fields = []; if (!empty($data->user)) { $data->fields = Payment::getPayerInfos($data->user); } if (!empty($data->customFields)) { foreach ($data->customFields as $field) { $data->fields[$field->name] = $field->answer; } } return $data; } static protected function createTransaction(ChargeableInterface $entity, array $accounts, int $id_payment, \DateTime $date): Transaction { if (!$id_year = Years::getOpenYearIdMatchingDate($date)) { throw new \RuntimeException(sprintf('No opened accounting year matching the item date "%s"!', $date->format('Y-m-d'))); } // ToDo: check accounts validity (right number for the Transaction type) $transaction = new Transaction(); $transaction->type = Transaction::TYPE_REVENUE; $transaction->reference = (string)Payments::getId($id_payment); $source = [ 'status' => Transaction::STATUS_PAID, 'label' => self::TRANSACTION_PREFIX . ' - ' . $entity->getLabel(), 'notes' => self::TRANSACTION_NOTE, 'payment_reference' => $id_payment, 'date' => \KD2\DB\Date::createFromInterface($date), 'id_year' => (int)$id_year, 'amount' => $entity->getAmount() / 100, 'simple' => [ Transaction::TYPE_REVENUE => [ 'credit' => [ (int)$accounts[0] => null ], 'debit' => [ (int)$accounts[1] => null ] ]] // , 'id_user'/'id_creator' => ... ]; $transaction->importForm($source); if (!$transaction->save()) { throw new \RuntimeException(sprintf('Cannot record item/option transaction. Item/option ID: %d.', $entity->id)); } return $transaction; } static protected function generateLabel(\stdClass $data, int $id_form): string { if ($data->order->formType === 'Checkout') { $payment = Payments::getByOrderId((int)$data->order->id); if (null === $payment) { throw new \RuntimeException('No payment matching retreived checkout item #%d (order #%d).', $data->id, $data->order->id); } return sprintf(self::CHECKOUT_LABEL, $payment->id, $payment->label); } elseif (!isset($data->name)) { if ($data->type === 'Donation' && $data->order->formType !== 'Donation') { return self::DONATION_LABEL; } else { return Forms::getName($id_form); } } else { return $data->name; } } static public function reset(): void { $sql = sprintf('DELETE FROM %s;', Item::TABLE); DB::getInstance()->exec($sql); } } |
Added helloasso/lib/Options.php version [ffcd8420f5].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | <?php namespace Garradin\Plugin\HelloAsso; use Garradin\Plugin\HelloAsso\Entities\Option; use Garradin\DynamicList; class Options { static public function list($for): DynamicList { $columns = [ 'id' => [], 'id_transaction' => [ 'label' => 'Écriture' ], 'amount' => [ 'label' => 'Montant', ], 'label' => [ 'label' => 'Objet', ], 'custom_fields' => [ 'label' => 'Champs', ] ]; $tables = Option::TABLE; $list = new DynamicList($columns, $tables); $conditions = sprintf('id_order = %d', $for->id); $list->setConditions($conditions); $list->setTitle(sprintf('Commande - %d - Items', $for->id)); $list->setModifier(function ($row) { if (isset($row->custom_fields)) { $row->custom_fields = json_decode($row->custom_fields, true); } }); $list->setExportCallback(function (&$row) { $row->amount = $row->amount ? Utils::money_format($row->amount, '.', '', false) : null; // Serialize custom fields as a text field if (isset($row->custom_fields)) { $row->custom_fields = implode("\n", array_map(function ($v, $k) { return "$k: $v"; }, $row->custom_fields, array_keys($row->custom_fields))); } }); $list->orderBy('id', true); return $list; } } |
Modified helloasso/lib/Payments.php from [1a7d54b265] to [d04a2f7802].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | <?php namespace Garradin\Plugin\HelloAsso; use Garradin\Plugin\HelloAsso\Entities\Form; use Garradin\Plugin\HelloAsso\Entities\Order; use Garradin\Plugin\HelloAsso\Entities as HA; use Garradin\Plugin\HelloAsso\API; use Garradin\Payments\Payments as Paheko_Payments; use Garradin\Entities\Payments\Payment; use Garradin\Entities\Users\User; use Garradin\DB; use Garradin\DynamicList; use Garradin\Utils; use KD2\DB\EntityManager as EM; class Payments extends Paheko_Payments { const AUTHORIZED_STATUS = 'Authorized'; const STATUSES = [ self::AUTHORIZED_STATUS => Payment::VALIDATED_STATUS ]; // ToDo: complete the list from HA\Payment class const UPDATE_MESSAGE = 'Mise à jour du paiement'; const TRANSACTION_NOTE = 'Générée automatiquement par l\'extension ' . HelloAsso::PROVIDER_LABEL . '.'; static public function get(int $id): ?Payment { return EM::findOne(Payment::class, 'SELECT * FROM @TABLE WHERE provider = :provider AND json_extract(extra_data, \'$.id\') = :id', HelloAsso::PROVIDER_NAME, $id); } static public function list($for): DynamicList { $columns = [ 'id' => [], 'reference' => [ 'label' => 'Référence', ], | > > > > > > > > > > > > > > > > > | | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | <?php namespace Garradin\Plugin\HelloAsso; use Garradin\Plugin\HelloAsso\Entities\Form; use Garradin\Plugin\HelloAsso\Entities\Order; use Garradin\Plugin\HelloAsso\Entities as HA; use Garradin\Plugin\HelloAsso\API; use Garradin\Payments\Payments as Paheko_Payments; use Garradin\Entities\Payments\Payment; use Garradin\Entities\Accounting\Transaction; use Garradin\Entities\Users\User; use Garradin\DB; use Garradin\DynamicList; use Garradin\Utils; use KD2\DB\EntityManager as EM; class Payments extends Paheko_Payments { const AUTHORIZED_STATUS = 'Authorized'; const STATUSES = [ self::AUTHORIZED_STATUS => Payment::VALIDATED_STATUS ]; // ToDo: complete the list from HA\Payment class const UPDATE_MESSAGE = 'Mise à jour du paiement'; const TRANSACTION_NOTE = 'Générée automatiquement par l\'extension ' . HelloAsso::PROVIDER_LABEL . '.'; static protected ?array $payment_ids = null; static public function get(int $id): ?Payment { return EM::findOne(Payment::class, 'SELECT * FROM @TABLE WHERE provider = :provider AND json_extract(extra_data, \'$.id\') = :id', HelloAsso::PROVIDER_NAME, $id); } static public function getId(int $reference): ?int { if (!isset(self::$payment_ids)) { self::$payment_ids = DB::getInstance()->getAssoc(sprintf('SELECT reference, id FROM %s;', Payment::TABLE)); } return self::$payment_ids[$reference] ?? null; } static public function getByOrderId(int $id_order): ?Payment { return EM::findOne(Payment::class, 'SELECT * FROM @TABLE WHERE provider = :provider AND json_extract(extra_data, \'$.id_order\') = :id_order', HelloAsso::PROVIDER_NAME, $id_order); } static public function list($for): DynamicList { $columns = [ 'id' => [], 'reference' => [ 'label' => 'Référence', ], 'transactions' => [ 'label' => 'Écritures', 'select' => sprintf('(SELECT GROUP_CONCAT(id, \';\') FROM %s t WHERE t.reference = %s.id)', Transaction::TABLE, Payment::TABLE) ], 'label' => [ 'label' => 'Libellé' ], 'date' => [ 'label' => 'Date', ], |
︙ | ︙ | |||
87 88 89 90 91 92 93 94 95 96 97 98 99 100 | } $list->setModifier(function ($row) { $row->state = HA\Payment::STATES[$row->state] ?? 'Inconnu'; if ($row->id_author) { $row->author = EM::findOneById(User::class, (int)$row->id_author); } }); $list->setExportCallback(function (&$row) { $row->amount = $row->amount ? Utils::money_format($row->amount, '.', '', false) : null; }); $list->orderBy('date', true); | > > > | 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | } $list->setModifier(function ($row) { $row->state = HA\Payment::STATES[$row->state] ?? 'Inconnu'; if ($row->id_author) { $row->author = EM::findOneById(User::class, (int)$row->id_author); } if (isset($row->transactions)) { $row->transactions = explode(';', $row->transactions); } }); $list->setExportCallback(function (&$row) { $row->amount = $row->amount ? Utils::money_format($row->amount, '.', '', false) : null; }); $list->orderBy('date', true); |
︙ | ︙ | |||
147 148 149 150 151 152 153 154 155 156 157 158 | static protected function syncPayment(\stdClass $raw_data, bool $accounting): void { $payment = self::get($raw_data->id) ?? new Payment; $data = self::formatData($raw_data); $data->raw_data = &$raw_data; $data->id_form = Forms::getId($data->org_slug, $data->form_slug); if (!$form = EM::findOneById(Form::class, $data->id_form)) { throw new \RuntimeException(sprintf('Form not found! Form ID: %d.', $data->id_form)); } if (!$payment->exists()) { | > < < | | | | < | < < < < < | 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | static protected function syncPayment(\stdClass $raw_data, bool $accounting): void { $payment = self::get($raw_data->id) ?? new Payment; $data = self::formatData($raw_data); $data->raw_data = &$raw_data; $data->id_form = Forms::getId($data->org_slug, $data->form_slug); if (!$form = EM::findOneById(Form::class, $data->id_form)) { throw new \RuntimeException(sprintf('Form not found! Form ID: %d.', $data->id_form)); } if (!$payment->exists()) { $id = DB::getInstance()->firstColumn(sprintf('SELECT id FROM %s WHERE email = \'%s\' LIMIT 1;', User::TABLE, $data->payer->email)); $author_id = $id ?? null; $author_name = $data->payer->lastName . ' ' . $data->payer->firstName; $label = ($data->order ? $data->order->formName . ' - ' : '') . $data->payer_name . ' - ' . HelloAsso::PROVIDER_NAME . ' #' . $data->id; $payment = Payments::createPayment(Payment::UNIQUE_TYPE, Payment::BANK_CARD_METHOD, self::STATUSES[$data->state], HelloAsso::PROVIDER_NAME, null, $author_id, $author_name, $data->id, $label, $data->amount, $data, self::TRANSACTION_NOTE); } else { $payment->set('amount', $data->amount); $payment->set('status', self::STATUSES[$data->state]); $payment->set('history', $data->date->format('Y-m-d H:i:s') . ' - '. self::UPDATE_MESSAGE . "\n" . $payment->history); $payment->setExtraData('date', $data->date); $payment->setExtraData('transfer_date', $data->transfer_date); $payment->setExtraData('person', $data->payer_name); |
︙ | ︙ |
Modified helloasso/schema.sql from [2ed01f6ce9] to [f16757826b].
1 2 3 4 5 6 7 8 9 10 | -- Cache list of forms CREATE TABLE IF NOT EXISTS plugin_helloasso_forms ( id INTEGER PRIMARY KEY, org_name TEXT NOT NULL, org_slug TEXT NOT NULL, name TEXT NOT NULL, slug TEXT NOT NULL, type TEXT NOT NULL, | | < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | -- Cache list of forms CREATE TABLE IF NOT EXISTS plugin_helloasso_forms ( id INTEGER PRIMARY KEY, org_name TEXT NOT NULL, org_slug TEXT NOT NULL, name TEXT NOT NULL, slug TEXT NOT NULL, type TEXT NOT NULL, state TEXT NOT NULL ); CREATE UNIQUE INDEX IF NOT EXISTS plugin_helloasso_forms_key ON plugin_helloasso_forms(org_slug, slug); CREATE TABLE IF NOT EXISTS plugin_helloasso_orders ( id INTEGER PRIMARY KEY NOT NULL, id_form INTEGER NOT NULL REFERENCES plugin_helloasso_forms(id) ON DELETE CASCADE, |
︙ | ︙ | |||
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | ); CREATE TABLE IF NOT EXISTS plugin_helloasso_items ( id INTEGER PRIMARY KEY NOT NULL, id_form INTEGER NOT NULL REFERENCES plugin_helloasso_forms(id) ON DELETE CASCADE, id_order INTEGER NOT NULL REFERENCES plugin_helloasso_orders(id) ON DELETE CASCADE, id_user INTEGER NULL REFERENCES users(id) ON DELETE SET NULL, type TEXT NOT NULL, state TEXT NOT NULL, person TEXT NOT NULL, label TEXT NOT NULL, amount INTEGER NOT NULL, raw_data TEXT NOT NULL, custom_fields TEXT NULL ); /* CREATE TABLE IF NOT EXISTS plugin_helloasso_options ( id INTEGER PRIMARY KEY NOT NULL, id_order INTEGER NOT NULL REFERENCES plugin_helloasso_orders(id) ON DELETE CASCADE, hash TEXT NOT NULL, label TEXT NOT NULL, amount INTEGER NOT NULL, raw_data TEXT NOT NULL ); */ CREATE TABLE IF NOT EXISTS plugin_helloasso_payments ( id INTEGER PRIMARY KEY NOT NULL, id_form INTEGER NOT NULL REFERENCES plugin_helloasso_forms(id) ON DELETE CASCADE, id_order INTEGER NOT NULL REFERENCES plugin_helloasso_orders(id) ON DELETE CASCADE, id_user INTEGER NULL REFERENCES users(id) ON DELETE SET NULL, id_transaction INTEGER NULL REFERENCES acc_transactions(id) ON DELETE SET NULL, amount INTEGER NOT NULL, state TEXT NOT NULL, transfer_date TEXT NULL, person TEXT NULL, date TEXT NOT NULL, receipt_url TEXT NULL, raw_data TEXT NOT NULL ); | > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | ); CREATE TABLE IF NOT EXISTS plugin_helloasso_items ( id INTEGER PRIMARY KEY NOT NULL, id_form INTEGER NOT NULL REFERENCES plugin_helloasso_forms(id) ON DELETE CASCADE, id_order INTEGER NOT NULL REFERENCES plugin_helloasso_orders(id) ON DELETE CASCADE, id_user INTEGER NULL REFERENCES users(id) ON DELETE SET NULL, id_transaction INTEGER NULL REFERENCES acc_transactions(id) ON DELETE SET NULL, type TEXT NOT NULL, state TEXT NOT NULL, person TEXT NOT NULL, label TEXT NOT NULL, amount INTEGER NOT NULL, has_options INTEGER NOT NULL, raw_data TEXT NOT NULL, custom_fields TEXT NULL ); CREATE TABLE IF NOT EXISTS plugin_helloasso_item_options ( id INTEGER PRIMARY KEY NOT NULL, id_item INTEGER NOT NULL REFERENCES plugin_helloasso_items(id) ON DELETE CASCADE, -- Redundant but needed by DynamicList since it does not handle JOIN statement id_order INTEGER NOT NULL REFERENCES plugin_helloasso_items(id) ON DELETE CASCADE, id_transaction INTEGER NULL REFERENCES acc_transactions(id) ON DELETE SET NULL, label TEXT NOT NULL, amount INTEGER NOT NULL, raw_data TEXT NOT NULL, custom_fields TEXT NULL ); CREATE TABLE IF NOT EXISTS plugin_helloasso_chargeables ( id INTEGER PRIMARY KEY NOT NULL, id_form INTEGER NOT NULL REFERENCES plugin_helloasso_forms(id) ON DELETE CASCADE, id_item INTEGER NULL REFERENCES plugin_helloasso_items(id) ON DELETE CASCADE, id_credit_account INTEGER NULL REFERENCES acc_accounts (id) ON DELETE SET NULL, id_debit_account INTEGER NULL REFERENCES acc_accounts (id) ON DELETE SET NULL, type INTEGER NOT NULL, label TEXT NOT NULL, amount INTEGER NULL ); CREATE UNIQUE INDEX IF NOT EXISTS plugin_helloasso_chargeables_key ON plugin_helloasso_chargeables(id_form, id_item, type, label, amount); /* CREATE TABLE IF NOT EXISTS plugin_helloasso_options ( id INTEGER PRIMARY KEY NOT NULL, id_order INTEGER NOT NULL REFERENCES plugin_helloasso_orders(id) ON DELETE CASCADE, hash TEXT NOT NULL, label TEXT NOT NULL, amount INTEGER NOT NULL, raw_data TEXT NOT NULL ); */ /* Replaced by the new Paheko native "payment" table CREATE TABLE IF NOT EXISTS plugin_helloasso_payments ( id INTEGER PRIMARY KEY NOT NULL, id_form INTEGER NOT NULL REFERENCES plugin_helloasso_forms(id) ON DELETE CASCADE, id_order INTEGER NOT NULL REFERENCES plugin_helloasso_orders(id) ON DELETE CASCADE, id_user INTEGER NULL REFERENCES users(id) ON DELETE SET NULL, id_transaction INTEGER NULL REFERENCES acc_transactions(id) ON DELETE SET NULL, amount INTEGER NOT NULL, state TEXT NOT NULL, transfer_date TEXT NULL, person TEXT NULL, date TEXT NOT NULL, receipt_url TEXT NULL, raw_data TEXT NOT NULL ); */ CREATE TABLE IF NOT EXISTS plugin_helloasso_targets ( -- List of forms that should create users or subscriptions id INTEGER PRIMARY KEY NOT NULL, id_form INTEGER NOT NULL REFERENCES plugin_helloasso_forms(id) ON DELETE CASCADE, label TEXT NOT NULL, |
︙ | ︙ |
Modified helloasso/templates/_items_list.tpl from [3872c63bad] to [c8cdff8228].
1 2 3 4 5 6 | {include file="common/dynamic_list_head.tpl"} {foreach from=$list->iterate() item="row"} <tr> | | > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | {include file="common/dynamic_list_head.tpl"} {foreach from=$list->iterate() item="row"} <tr> <td class="num"><a href="order.php?id={$row.id_order}">{$row.id}</a></td> <td class="num"><a href="{$admin_url}acc/transactions/details.php?id={$row.id_transaction}">{$row.id_transaction}</a></td> <td class="money">{$row.amount|money_currency|raw}</td> <td>{$row.type}</td> <td>{$row.label}</td> <td>{$row.person}</td> <td>{$row.options|escape|nl2br}</td> {if property_exists($row, 'custom_fields')} <td> {if $row.custom_fields} <table> {foreach from=$row.custom_fields item="value" key="name"} <tr> <td>{$name}</td> |
︙ | ︙ |
Added helloasso/templates/_options_list.tpl version [44e0637b73].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | {include file="common/dynamic_list_head.tpl"} {foreach from=$list->iterate() item="row"} <tr> <td class="num"><a href="{$admin_url}acc/transactions/details.php?id={$row.id_transaction}">{$row.id_transaction}</a></td> <td class="money">{$row.amount|money_currency|raw}</td> <td>{$row.label}</td> <td>{$row.options|escape|nl2br}</td> {if property_exists($row, 'custom_fields')} <td> {if $row.custom_fields} <table> {foreach from=$row.custom_fields item="value" key="name"} <tr> <td>{$name}</td> <th>{$value}</th> </tr> {/foreach} </table> {/if} </td> {/if} </tr> {/foreach} </tbody> </table> |
Modified helloasso/templates/_payments_list.tpl from [69a84a866b] to [d99bdac14c].
1 2 3 4 5 | {include file="common/dynamic_list_head.tpl"} {foreach from=$list->iterate() item="row"} <tr> <td class="num"><a href="payment.php?id={$row.id}">{$row.reference}</a></td> | > > | > > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | {include file="common/dynamic_list_head.tpl"} {foreach from=$list->iterate() item="row"} <tr> <td class="num"><a href="payment.php?id={$row.id}">{$row.reference}</a></td> <td class="num"> {foreach from=$row.transactions item="id_transaction"} {link href="!acc/transactions/details.php?id=%d"|args:$id_transaction label="#%d"|args:$id_transaction} {/foreach} </td> <td>{$row.label}</td> <td>{$row.date|date}</td> <td class="money">{$row.amount|money_currency|raw}</td> <td> {if $row.id_author && $row.author} <a href="{$admin_url}users/details.php?id={$row.author.id|intval}">{$row.author.nom}</a> {else} {$row.author_name} {/if} </td> <td>{$row.state}</td> <td>{$row.transfert_date|date}</td> <td class="num"><a href="{$plugin_admin_url}order.php?id={$row.id_order}">{$row.id_order}</a></td> <td class="actions"> {if $row.receipt_url} {linkbutton href=$row.receipt_url target="_blank" shape="print" label="Attestation de paiement"} {/if} {if $details}{linkbutton href="payment.php?id=%s"|args:$row.id shape="help" label="Détails"}{/if} </td> </tr> {/foreach} </tbody> </table> |
Modified helloasso/templates/index.tpl from [8edf5d4ec2] to [1cb5bd784c].
︙ | ︙ | |||
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | <legend>Paiement</legend> <dl> {input type="select" name="org_slug" label="Association" options=$orgOptions required=true} {input type="text" name="label" label="Libellé" required=true} {input type="money" name="amount" label="Montant" required=true} {input type="list" name="user" label="Membre" target="!users/selector.php" can_delete="true" required=true} </dl> </fieldset> {**** ToDo: add csrf token ****} {button type="submit" name="generate_checkout" label="Créer" class="main"} </form> {/if} {include file="_foot.tpl"} | > > > > | 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | <legend>Paiement</legend> <dl> {input type="select" name="org_slug" label="Association" options=$orgOptions required=true} {input type="text" name="label" label="Libellé" required=true} {input type="money" name="amount" label="Montant" required=true} {input type="list" name="user" label="Membre" target="!users/selector.php" can_delete="true" required=true} </dl> <dl> {input type="list" target="!acc/charts/accounts/selector.php?targets=%s&chart=%d"|args:'6':$chart_id name="credit" label="Type de recette" required=1} {input type="list" target="!acc/charts/accounts/selector.php?targets=%s&chart=%d"|args:'1:2:3':$chart_id name="debit" label="Compte d'encaissement" required=1} </dl> </fieldset> {**** ToDo: add csrf token ****} {button type="submit" name="generate_checkout" label="Créer" class="main"} </form> {/if} {include file="_foot.tpl"} |
Modified helloasso/templates/order.tpl from [6c97c895c5] to [8e14549a34].
︙ | ︙ | |||
17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <dd>{if $order.status}Payée{else}Paiement incomplet{/if}</dd> </dl> <h2 class="ruler">Éléments de la commande</h2> {include file="%s/templates/_items_list.tpl"|args:$plugin_root list=$items details=false} <h2 class="ruler">Paiements</h2> {include file="%s/templates/_payments_list.tpl"|args:$plugin_root list=$payments details=false} <h2 class="ruler">Personne ayant effectué le paiement</h2> <dl class="describe"> | > > > > | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <dd>{if $order.status}Payée{else}Paiement incomplet{/if}</dd> </dl> <h2 class="ruler">Éléments de la commande</h2> {include file="%s/templates/_items_list.tpl"|args:$plugin_root list=$items details=false} <h2 class="ruler">Options de la commande</h2> {include file="%s/templates/_options_list.tpl"|args:$plugin_root list=$options details=false} <h2 class="ruler">Paiements</h2> {include file="%s/templates/_payments_list.tpl"|args:$plugin_root list=$payments details=false} <h2 class="ruler">Personne ayant effectué le paiement</h2> <dl class="describe"> |
︙ | ︙ |
Modified helloasso/templates/orders.tpl from [15cdde214c] to [f58069017b].
︙ | ︙ | |||
17 18 19 20 21 22 23 | {else} {$row.person} {/if} </td> <td>{$row.status}</td> <td class="num"><a href="{$plugin_admin_url}payment.php?ref={$row.id_payment}">{$row.id_payment}</a></td> <td class="actions"> | | | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | {else} {$row.person} {/if} </td> <td>{$row.status}</td> <td class="num"><a href="{$plugin_admin_url}payment.php?ref={$row.id_payment}">{$row.id_payment}</a></td> <td class="actions"> {linkbutton href="order.php?id=%s"|args:$row.id shape="help" label="Détails"} </td> </tr> {/foreach} </tbody> </table> {* Not yet supported {pagination url=$list->paginationURL() page=$list.page bypage=$list.per_page total=$list->count()} *} {include file="_foot.tpl"} |
Modified helloasso/templates/sync.tpl from [797338f514] to [c4eb71cdd0].
︙ | ︙ | |||
18 19 20 21 22 23 24 | {if !$last_sync && $last_sync > (new \DateTime('1 hour ago'))} <p class="alert block">Il n'est pas possible d'effectuer plus d'une synchronisation manuelle par heure.</p> {else} <form method="post" action="{$self_url}"> <p class="submit"> {csrf_field key=$csrf_key} | | | > > | | | | > > > > > > > > | | | | > | > | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | {if !$last_sync && $last_sync > (new \DateTime('1 hour ago'))} <p class="alert block">Il n'est pas possible d'effectuer plus d'une synchronisation manuelle par heure.</p> {else} <form method="post" action="{$self_url}"> <p class="submit"> {csrf_field key=$csrf_key} {if $plugin->config->accounting && $chargeables} {button type="submit" name="sync" value=1 label="Synchroniser les anciennes données uniquement"} {else} {button type="submit" name="sync" value=1 label="Synchroniser les données" shape="right" class="main"} {/if} </p> </form> {/if} {if $plugin->config->accounting} {if $chargeables} <form method="POST" action="{$self_url}"> {if $chargeables} <p class="alert block">Les types de recette et comptes d'encaissement doivent être renseignés pour articles suivants :</p> {foreach from=$chargeables item='chargeable'} <fieldset> <legend> {if $chargeable.type === Plugin\HelloAsso\Entities\Chargeable::ONLY_ONE_ITEM_FORM_TYPE} {$chargeable->getForm_name()} {elseif $chargeable.type === Plugin\HelloAsso\Entities\Chargeable::CHECKOUT_TYPE} {$chargeable->label} {else} {$chargeable->getForm_name()} > {if $chargeable.type === Plugin\HelloAsso\Entities\Chargeable::OPTION_TYPE}"{$chargeable->getItem_name()}" option {/if}"{$chargeable.label}" {$chargeable.amount|escape|money_currency} {/if} </legend> <dl> {input type="list" target="!acc/charts/accounts/selector.php?targets=%s&chart=%d"|args:'6':$chart_id name="chargeable_credit[%d]"|args:$chargeable.id label="Type de recette" required=1} {input type="list" target="!acc/charts/accounts/selector.php?targets=%s&chart=%d"|args:'1:2:3':$chart_id name="chargeable_debit[%d]"|args:$chargeable.id label="Compte d'encaissement" required=1} </dl> </fieldset> {/foreach} {/if} {button type="submit" name="accounts_submit" label="Enregistrer et lancer la synchronisation" shape="right" class="main"} </form> {/if} {/if} {include file="_foot.tpl"} |