Extensions Paheko  Check-in [976de91258]

Overview
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: 976de91258030b0d7fbea7ed07a31b343e5daf68
User & Date: alinaar on 2023-05-26 11:46:15
Other Links: branch diff | manifest | tags
Context
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
Changes

Modified helloasso/admin/index.php from [da9f56c3de] to [f6484a8168].

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

37
38
	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);

	$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->display(PLUGIN_ROOT . '/templates/index.tpl');







|







>


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
26
27
28
<?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 = [];

$tpl->assign(compact('order', 'payments', 'items', 'payer_infos', 'found_user', 'mapped_user'));

$tpl->display(PLUGIN_ROOT . '/templates/order.tpl');







>











>







|


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






20




21


22
23
24

25
26
27
28
29
30
31
32
33
34
35
36
37

38
39
40
41
<?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);







$forms = EntityManager::getInstance(Form::class)->all('SELECT * FROM @TABLE WHERE id_credit_account IS NULL');




$tpl->assign('forms', $forms);


$tpl->assign('chart_id', Plugin\HelloAsso\HelloAsso::CHART_ID); // ToDo: make it dynamic

$form->runIf('form_submit', function() use ($ha) {

	$source = [];
	foreach ($_POST['credit'] as $id_form => $array)
	{
		$id_credit_account = array_keys($array)[0];
		$id_debit_account = array_keys($_POST['debit'][$id_form])[0];
		// ToDo: add a nice check
		if ($id_credit_account && $id_debit_account)
		{
			$source[$id_form]['credit'] = (int)$id_credit_account;
			$source[$id_form]['debit'] = (int)$id_debit_account;
		}
	}
	Forms::setAccounts($source);

	$ha->sync();
}, null, PLUGIN_ADMIN_URL . 'sync.php?ok=1');

$tpl->display(PLUGIN_ROOT . '/templates/sync.tpl');








>

>
>










>
>
>
>
>
>
|
>
>
>
>
|
>
>


|
>
|
|
|
|
|
|
|
|
|
|
|
|
|
>




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
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($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');







|







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
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($r->amount));
		}
	}

	public function listOrganizationItems(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
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
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($r->amount));
		}
	}

	public function getCheckout(string $organization, int $id): \stdClass
	{
		return $this->GET('v5/organizations/' . $organization . '/checkout-intents/' . (int)$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
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















<?php

namespace Garradin\Plugin\HelloAsso\Entities;

use Garradin\DB;
use Garradin\Entity;
use Garradin\ValidationException;

use DateTime;

class Form extends Entity
{
	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;
	
	protected ?int $id_credit_account;
	protected ?int $id_debit_account;

	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é',
	];
}



















|
|
<

<
<
|











<
<
<

















|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
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\Plugin\HelloAsso\Entities;

use Garradin\DB;
use Garradin\Entity;
use Garradin\ValidationException;

use DateTime;

class Item extends Entity
{
	const TABLE = 'plugin_helloasso_items';

	protected int $id;
	protected int $id_order;
	protected int $id_form;
	protected ?int $id_user;

	protected string $type;
	protected string $state;
	protected string $label;
	protected string $person;
	protected int $amount;

	protected ?string $custom_fields;
	protected string $raw_data;

	const TYPES = [
		'Donation'        => 'Don',
		'Payment'         => 'Paiement',
		'Registration'    => 'Inscription',




|
|
<

<
<
|







>





>







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
49















		'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
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
110
111
112
			}

			unset($list);

			$forms = API::getInstance()->listForms($o->organizationSlug);

			foreach ($forms as $form) {
				$data = $existing[$form->formSlug] ?? new Form;
				$data->org_name = $o->name;
				$data->org_slug = $o->organizationSlug;

				$data->name = strip_tags($form->privateTitle ?? $form->title);
				$data->type = $form->formType;
				$data->state = $form->state;
				$data->slug = $form->formSlug;

				$data->save();








			}
		}
	}
	
	static public function reset(): void
	{
		$sql = sprintf('DELETE FROM %s;', Form::TABLE);
		DB::getInstance()->exec($sql);
	}
	
	static function setAccounts(array $source): void
	{
		foreach ($source as $id_form => $accounts) {
			if (DB::getInstance()->exec(sprintf('UPDATE %s SET id_credit_account = %d, id_debit_account = %d WHERE id = %d;', Form::TABLE, $accounts['credit'], $accounts['debit'], (int)$id_form)) === false) {
				throw new \RuntimeException(sprintf('Cannot update %s plugin Forms\' accounting accounts.', HelloAsso::PROVIDER_LABEL));
			}
		}
	}
	
	/*static function hasAccounts(int $id): bool {
		return (bool)DB::getInstance()->firstColumn(sprintf('SELECT id FROM %s WHERE id = %d AND id_credit_account IS NOT NULL AND id_debit_account IS NOT NULL LIMIT 1;', Form::TABLE, $id));
	}*/
}







|
|
|

|
|
|
|

|
>
>
>
>
>
>
>
>









|
<
<
<
<
<
<
<
<
<
<
<
<
<
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
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): \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))) {







|







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
197
198
199
200
201
202
203
204
		];
		$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.');
		}

		$checkout = API::getInstance()->getCheckout($payment->organization, (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));
		}








>
>




















|







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
93
94
95
96
97
98
99
100
101
102
103
104
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
			}
		});

		$list->orderBy('id', true);
		return $list;
	}

	static public function sync(string $org_slug): 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);
			}

			if (HelloAsso::isTrial()) {
				break;
			}
		}
	}

	static protected function syncItem(\stdClass $data): void
	{
		$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', $data->name ?? Forms::getName($entity->id_form));
		$entity->set('custom_fields', count($data->fields) ? json_encode($data->fields) : null);




		// FIXME: handle options










		$entity->save();



	}

	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;







|













|








|

















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
>

>
>
|
>
|
>
>
>
>
>
>
>
>
|
>
>
>







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
166











































































167
168
169
170
171
172
			foreach ($data->customFields as $field) {
				$data->fields[$field->name] = $field->answer;
			}
		}

		return $data;
	}
	











































































	static public function reset(): void
	{
		$sql = sprintf('DELETE FROM %s;', Item::TABLE);
		DB::getInstance()->exec($sql);
	}
}







|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>






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
39
40

41
42
43
44
45
46
47
<?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',
			],
			'id_transaction' => [
				'label' => 'Écriture'

			],
			'label' => [
				'label' => 'Libellé'
			],
			'date' => [
				'label' => 'Date',
			],











>














>
>





>
>
>
>
>
>
>
>
>
>
>
>
>
>








|
|
>







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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181

	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()) {
			// If accounting is enabled, we record the payment only if credit and debit accounts are set
			if (!$accounting || ($accounting && $form->id_credit_account && $form->id_debit_account)) {
				$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;
				$accounts = $accounting ? [$form->id_credit_account, $form->id_debit_account] : null;
				$payment = Payments::createPayment(Payment::UNIQUE_TYPE, Payment::BANK_CARD_METHOD, self::STATUSES[$data->state], HelloAsso::PROVIDER_NAME, $accounts, $author_id, $author_name, $data->id, $label, $data->amount, $data, self::TRANSACTION_NOTE);
			}
		}
		else
		{
			if ($accounting && !$payment->id_transaction) { // Happens when the user decided to switch on the accounting while sync had already be done without accounting
				$transaction = Payments::createTransaction($payment, [$form->id_credit_account, $form->id_debit_account], self::TRANSACTION_NOTE);
				$payment->set('id_transaction', (int)$transaction->id);
			}
			$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);







>





<
<
|
|
|
|
<
|
<



<
<
<
<







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
11
12
13
14
15
16
17
18
19
20
-- 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,
	id_credit_account INTEGER NULL REFERENCES acc_accounts (id),
	id_debit_account INTEGER NULL REFERENCES acc_accounts (id)
);

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,










|
<
<







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
69
70
71
72
73
74
75
76
);

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
);


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,







>




>
>
>
>
>
>
>
>
>
>
>
>
>





>
>
>
>
>
>
>
>
>
>
>
>
>











>














|







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
7

8
9
10
11

12
13
14
15
16
17
18

{include file="common/dynamic_list_head.tpl"}

	{foreach from=$list->iterate() item="row"}

		<tr>
			<th class="num"><a href="order.php?id={$row.id_order}">{$row.id}</a></th>

			<td class="money">{$row.amount|money_currency|raw}</td>
			<td>{$row.type}</td>
			<td>{$row.label}</td>
			<td>{$row.person}</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>






|
>




>







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


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
{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">{if $row.id_transaction}{link href="!acc/transactions/details.php?id=%d"|args:$row.id_transaction label="#%d"|args:$row.id_transaction}{/if}</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.reference shape="help" label="Détails"}{/if}
			</td>
		</tr>
	{/foreach}

	</tbody>
</table>





>
>
|
>
>

















|






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
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_payment 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"}







|













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
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

{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 && $forms}
				{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 && $forms}

	<form method="POST" action="{$self_url}">

		<p class="alert block">Les types de recette et comptes d'encaissement doivent être renseignés pour formulaires suivants :</p>
		{foreach from=$forms item='form'}
			<fieldset>
				<legend>{$form.name}</legend>








					<dl>
						{input type="list" target="!acc/charts/accounts/selector.php?targets=%s&chart=%d"|args:'6':$chart_id name="credit[%d]"|args:$form.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="debit[%d]"|args:$form.id label="Compte d'encaissement" required=1}
					</dl>
			</fieldset>
		{/foreach}

		{button type="submit" name="form_submit" label="Enregistrer et lancer la synchronisation" shape="right" class="main"}
	</form>

{/if}

{include file="_foot.tpl"}







|








|
>

>
|
|
|
|
>
>
>
>
>
>
>
>

|
|

|
|
>
|

>



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()} &gt; {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"}