Overview
Comment:Use simple form for simple transactions
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev | 1.0.0-rc1
Files: files | file ages | folders
SHA1: 475f66a35e1ac0f1f8a1d301ff9f794e01e8b6e0
User & Date: bohwaz on 2020-11-26 18:55:58
Other Links: branch diff | manifest | tags
Context
2020-11-26
19:31
Fix change saved search check-in: 52cd841c7f user: bohwaz tags: dev, 1.0.0-rc1
18:55
Use simple form for simple transactions check-in: 475f66a35e user: bohwaz tags: dev, 1.0.0-rc1
18:55
Focus on select in year selector check-in: 8f44f4de63 user: bohwaz tags: dev
Changes

Modified src/include/lib/Garradin/Entities/Accounting/Transaction.php from [76b93019d2] to [d169fbe57f].

157
158
159
160
161
162
163









































164
165
166
167
168
169
170
				return $line;
			}
		}

		return null;
	}










































/*
	public function getHash()
	{
		if (!$this->id_year) {
			throw new \LogicException('Il n\'est pas possible de hasher un mouvement qui n\'est pas associé à un exercice');
		}








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







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
				return $line;
			}
		}

		return null;
	}

	public function getLinesCreditSum()
	{
		$sum = 0;

		foreach ($this->getLines() as $line) {
			$sum += $line->credit;
		}

		return $sum;
	}

	public function getTypesAccounts()
	{
		if ($this->type == self::TYPE_ADVANCED) {
			return [];
		}

		$debit = null;
		$credit = null;

		$lines = $this->getLinesWithAccounts();

		foreach ($lines as $line) {
			$account = [$line->id_account => sprintf('%s — %s', $line->account_code, $line->account_name)];

			if ($line->debit) {
				$debit = $account;
			}
			else {
				$credit = $account;
			}
		}

		$type = $this->getTypesDetails()[$this->type];

		return [
			$type->accounts[0]->position == 'credit' ? $credit : $debit,
			$type->accounts[1]->position == 'credit' ? $credit : $debit,
		];
	}

/*
	public function getHash()
	{
		if (!$this->id_year) {
			throw new \LogicException('Il n\'est pas possible de hasher un mouvement qui n\'est pas associé à un exercice');
		}

434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463

	public function importFromEditForm(?array $source = null): void
	{
		if (null === $source) {
			$source = $_POST;
		}

		$this->importForm();

		$this->resetLines();

		$lines = Utils::array_transpose($source['lines']);

		foreach ($lines as $i => $line) {
			$line['id_account'] = @count($line['account']) ? key($line['account']) : null;

			if (!$line['id_account']) {
				throw new ValidationException('Numéro de compte invalide sur la ligne ' . ($i+1));
			}

			$line = (new Line)->importForm($line);
			$this->addLine($line);
		}
	}

	public function importFromBalanceForm(Year $year, ?array $source = null): void
	{
		if (null === $source) {
			$source = $_POST;
		}







<


|
<
<
<
<
<
<
<
<
<
<
<
<







475
476
477
478
479
480
481

482
483
484












485
486
487
488
489
490
491

	public function importFromEditForm(?array $source = null): void
	{
		if (null === $source) {
			$source = $_POST;
		}



		$this->resetLines();
		$this->importFromNewForm($source);












	}

	public function importFromBalanceForm(Year $year, ?array $source = null): void
	{
		if (null === $source) {
			$source = $_POST;
		}
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
					],
				],
				'label' => self::TYPES_NAMES[self::TYPE_EXPENSE],
				'help' => null,
			],
			self::TYPE_TRANSFER => [
				'accounts' => [
						[
						'label' => 'De',
						'targets' => [Account::TYPE_BANK, Account::TYPE_CASH, Account::TYPE_OUTSTANDING],
						'position' => 'debit',
					],
					[
						'label' => 'Vers',
						'targets' => [Account::TYPE_BANK, Account::TYPE_CASH, Account::TYPE_OUTSTANDING],







|







622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
					],
				],
				'label' => self::TYPES_NAMES[self::TYPE_EXPENSE],
				'help' => null,
			],
			self::TYPE_TRANSFER => [
				'accounts' => [
					[
						'label' => 'De',
						'targets' => [Account::TYPE_BANK, Account::TYPE_CASH, Account::TYPE_OUTSTANDING],
						'position' => 'debit',
					],
					[
						'label' => 'Vers',
						'targets' => [Account::TYPE_BANK, Account::TYPE_CASH, Account::TYPE_OUTSTANDING],

Modified src/templates/acc/transactions/edit.tpl from [ae85799b01] to [5d5cdf1278].

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="admin/_head.tpl" title="Modification d'une écriture" current="acc/new" js=1}

<form method="post" action="{$self_url}" enctype="multipart/form-data" data-focus="1">
	{form_errors}

	<fieldset>



















		<legend>Informations</legend>
		<dl>
			{input source=$transaction type="text" name="label" label="Libellé général" required=1}
			{input source=$transaction type="date" name="date" label="Date" required=1}


		</dl>



	</fieldset>





	{* Saisie avancée *}
	<fieldset data-types="advanced">
		{include file="acc/transactions/_lines_form.tpl" chart_id=$chart.id}







	</fieldset>


	<fieldset>
		<legend>Détails</legend>



		<dl>
			{input default=$linked_users type="list" multiple=true name="users" label="Membres associés" target="membres/selector.php"}
			{input source=$transaction type="text" name="reference" label="Numéro de pièce comptable"}
			{input source=$transaction type="textarea" name="notes" label="Remarques" rows=4 cols=30}

			{input source=$transaction type="file" name="file" label="Ajouter un fichier joint"}





		</dl>
	</fieldset>

	<p class="submit">
		{csrf_field key="acc_edit_%d"|args:$transaction.id}
		{button type="submit" name="save" label="Enregistrer" shape="right" class="main"}
	</p>
|

|



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


<
|
>
>

>
>
>


>
>
>
>
|
<
|
>
>
>
>
>
>
>
|
>


|
>
>
>

|
<
|

|
>
>
>
>
>







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
{include file="admin/_head.tpl" title="Modification d'une écriture" current="acc/simple" js=1}

<form method="post" action="{$self_url}" enctype="multipart/form-data" data-focus="#f_date">
	{form_errors}

	<fieldset>
		<legend>Type d'écriture</legend>
		<dl>
		{foreach from=$types_details item="type"}
			<dd class="radio-btn">
				{input type="radio" name="type" value=$type.id source=$transaction label=null}
				<label for="f_type_{$type.id}">
					<div>
						<h3>{$type.label}</h3>
						{if !empty($type.help)}
							<p>{$type.help}</p>
						{/if}
					</div>
				</label>
			</dd>
		{/foreach}
		</dl>
	</fieldset>

	<fieldset>
		<legend>Informations</legend>
		<dl>

			{input type="date" name="date" label="Date" required=1 source=$transaction}
			{input type="text" name="label" label="Libellé" required=1 source=$transaction}
			{input type="text" name="reference" label="Numéro de pièce comptable" help="Numéro de facture, de note de frais, etc."}
		</dl>
		<dl data-types="all-but-advanced">
			{input type="money" name="amount" label="Montant" required=1 default=$amount}
		</dl>
	</fieldset>

	{foreach from=$types_details item="type"}
		<fieldset data-types="t{$type.id}">
			<legend>{$type.label}</legend>
			{if $type.id == $transaction::TYPE_ADVANCED}
				{* Saisie avancée *}

				{include file="acc/transactions/_lines_form.tpl" chart_id=$current_year.id_chart}
			{else}
				<dl>
				{foreach from=$type.accounts key="key" item="account"}
					{input type="list" target="acc/charts/accounts/selector.php?targets=%s&chart=%d"|args:$account.targets_string,$chart_id name="account_%d_%d"|args:$type.id,$key label=$account.label required=1 default=$types_accounts[$key]}
				{/foreach}
				</dl>
			{/if}
		</fieldset>
	{/foreach}

	<fieldset>
		<legend>Détails facultatifs</legend>
		<dl data-types="t{$transaction::TYPE_REVENUE} t{$transaction::TYPE_EXPENSE} t{$transaction::TYPE_TRANSFER}">
			{input type="text" name="payment_reference" label="Référence de paiement" help="Numéro de chèque, numéro de transaction CB, etc." source=$transaction}
		</dl>
		<dl>
			{input type="list" multiple=true name="users" label="Membres associés" target="membres/selector.php" default=$linked_users}

			{input type="textarea" name="notes" label="Remarques" rows=4 cols=30 source=$transaction}

			{input type="file" name="file" label="Ajouter un fichier joint"}
		</dl>
		<dl data-types="all-but-advanced">
			{if count($analytical_accounts) > 1}
				{input type="select" name="id_analytical" label="Projet (compte analytique)" options=$analytical_accounts}
			{/if}
		</dl>
	</fieldset>

	<p class="submit">
		{csrf_field key="acc_edit_%d"|args:$transaction.id}
		{button type="submit" name="save" label="Enregistrer" shape="right" class="main"}
	</p>

Modified src/templates/acc/transactions/new.tpl from [5b2936b517] to [68bb283d75].

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
	<p class="submit">
		{csrf_field key="acc_transaction_new"}
		{button type="submit" name="save" label="Enregistrer" shape="right" class="main"}
	</p>

</form>

{literal}
<script type="text/javascript" defer="defer" async="async">
function initForm() {
	// Hide type specific parts of the form
	function hideAllTypes() {
		g.toggle('[data-types]', false);
	}

	// Toggle parts of the form when a type is selected
	function selectType(v) {
		hideAllTypes();
		g.toggle('[data-types~=t' + v + ']', true);
		g.toggle('[data-types=all-but-advanced]', v != <?=$transaction::TYPE_ADVANCED?>);
		// Disable required form elements, or the form won't be able to be submitted
		$('[data-types=all-but-advanced] input[required]').forEach((e) => {
			e.disabled = v == 'advanced' ? true : false;
		});

	}

	var radios = $('fieldset input[type=radio][name=type]');

	radios.forEach((e) => {
		e.onchange = () => {
			selectType(e.value);
		};
	});

	hideAllTypes();

	// In case of a pre-filled form: show the correct part of the form
	var current = document.querySelector('input[name=type]:checked');
	if (current) {
		selectType(current.value);
	}
}

initForm();

g.script('scripts/accounting.js', () => { initTransactionForm(); });
</script>
{/literal}

{include file="admin/_foot.tpl"}







<

<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|




96
97
98
99
100
101
102

103





104









105





















106
107
108
109
110
	<p class="submit">
		{csrf_field key="acc_transaction_new"}
		{button type="submit" name="save" label="Enregistrer" shape="right" class="main"}
	</p>

</form>


<script type="text/javascript" defer="defer" async="async">





let is_new = {if $payoff_for}false{else}true{/if};









{literal}





















g.script('scripts/accounting.js', () => { initTransactionForm(is_new); });
</script>
{/literal}

{include file="admin/_foot.tpl"}

Modified src/www/admin/acc/transactions/edit.php from [6970179df1] to [4ea18fe075].

1
2
3
4

5
6
7
8
9
10
11
<?php

namespace Garradin;


use Garradin\Accounting\Transactions;
use Garradin\Accounting\Years;

require_once __DIR__ . '/../_inc.php';

$session->requireAccess('compta', Membres::DROIT_ADMIN);





>







1
2
3
4
5
6
7
8
9
10
11
12
<?php

namespace Garradin;

use Garradin\Entities\Accounting\Transaction;
use Garradin\Accounting\Transactions;
use Garradin\Accounting\Years;

require_once __DIR__ . '/../_inc.php';

$session->requireAccess('compta', Membres::DROIT_ADMIN);

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

$rules = [
	'lines' => 'array|required',
];

if (f('save') && $form->check('acc_edit_' . $transaction->id(), $rules)) {
	try {
		$_POST['type'] = 'advanced';

		$transaction->importFromEditForm();
		$transaction->save();

		// Append file
		if (!empty($_FILES['file']['name'])) {
			$file = Fichiers::upload($_FILES['file']);
			$file->linkTo(Fichiers::LIEN_COMPTA, $transaction->id());







<
<







33
34
35
36
37
38
39


40
41
42
43
44
45
46

$rules = [
	'lines' => 'array|required',
];

if (f('save') && $form->check('acc_edit_' . $transaction->id(), $rules)) {
	try {


		$transaction->importFromEditForm();
		$transaction->save();

		// Append file
		if (!empty($_FILES['file']['name'])) {
			$file = Fichiers::upload($_FILES['file']);
			$file->linkTo(Fichiers::LIEN_COMPTA, $transaction->id());
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
		Utils::redirect(ADMIN_URL . 'acc/transactions/details.php?id=' . $transaction->id());
	}
	catch (UserException $e) {
		$form->addError($e->getMessage());
	}
}

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


if (!empty($_POST['lines']) && is_array($_POST['lines'])) {
	$lines = Utils::array_transpose($_POST['lines']);

	foreach ($lines as &$line) {

		$line['credit'] = Utils::moneyToInteger($line['credit']);
		$line['debit'] = Utils::moneyToInteger($line['debit']);
	}
}
else {
	$lines = $transaction->getLinesWithAccounts();

	foreach ($lines as $k => &$line) {
		$line->account = [$line->id_account => sprintf('%s — %s', $line->account_code, $line->account_name)];
	}
}










$tpl->assign('lines', $lines);
$tpl->assign('analytical_accounts', ['' => '-- Aucun'] + $accounts->listAnalytical());
$tpl->assign('linked_users', $transaction->listLinkedUsersAssoc());

$tpl->display('acc/transactions/edit.tpl');







|
>





>
|
|










>
>
>
>
>
>
>
>
>
|




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
		Utils::redirect(ADMIN_URL . 'acc/transactions/details.php?id=' . $transaction->id());
	}
	catch (UserException $e) {
		$form->addError($e->getMessage());
	}
}

$types_accounts = [];
$lines = [];

if (!empty($_POST['lines']) && is_array($_POST['lines'])) {
	$lines = Utils::array_transpose($_POST['lines']);

	foreach ($lines as &$line) {
		$line = (object) $line;
		$line->credit = Utils::moneyToInteger($line->credit);
		$line->debit = Utils::moneyToInteger($line->debit);
	}
}
else {
	$lines = $transaction->getLinesWithAccounts();

	foreach ($lines as $k => &$line) {
		$line->account = [$line->id_account => sprintf('%s — %s', $line->account_code, $line->account_name)];
	}
}

if ($transaction->type != Transaction::TYPE_ADVANCED) {
	$types_accounts = $transaction->getTypesAccounts();
}

$amount = $transaction->getLinesCreditSum();

$tpl->assign(compact('transaction', 'lines', 'types_accounts', 'amount'));

$tpl->assign('types_details', Transaction::getTypesDetails());
$tpl->assign('chart_id', $chart->id());
$tpl->assign('analytical_accounts', ['' => '-- Aucun'] + $accounts->listAnalytical());
$tpl->assign('linked_users', $transaction->listLinkedUsersAssoc());

$tpl->display('acc/transactions/edit.tpl');

Modified src/www/admin/acc/transactions/new.php from [ccff134992] to [e1558cb4be].

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
	Utils::redirect(ADMIN_URL . 'acc/years/?msg=OPEN');
}

$chart = $current_year->chart();
$accounts = $chart->accounts();

$transaction = new Transaction;
$transaction->type = Transaction::TYPE_REVENUE;
$lines = [[], []];
$amount = 0;
$payoff_for = qg('payoff_for') ?: f('payoff_for');

// Quick pay-off for debts and credits, directly from a debt/credit details page
if ($id = $payoff_for) {
	$payoff_for = $transaction->payOffFrom($id);







|







13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
	Utils::redirect(ADMIN_URL . 'acc/years/?msg=OPEN');
}

$chart = $current_year->chart();
$accounts = $chart->accounts();

$transaction = new Transaction;
$transaction->type = -1;
$lines = [[], []];
$amount = 0;
$payoff_for = qg('payoff_for') ?: f('payoff_for');

// Quick pay-off for debts and credits, directly from a debt/credit details page
if ($id = $payoff_for) {
	$payoff_for = $transaction->payOffFrom($id);

Modified src/www/admin/static/scripts/accounting.js from [59dcf240d0] to [0e2e45cbde].

1
2
3
4
5
6
7
8
function initTransactionForm() {
	// Advanced transaction: line management
	var lines = $('.transaction-lines tbody tr');

	function initLine(row) {
		var removeBtn = row.querySelector('button[name="remove_line"]');
		removeBtn.onclick = () => {
			var count = $('.transaction-lines tbody tr').length;
|







1
2
3
4
5
6
7
8
function initTransactionForm(is_new) {
	// Advanced transaction: line management
	var lines = $('.transaction-lines tbody tr');

	function initLine(row) {
		var removeBtn = row.querySelector('button[name="remove_line"]');
		removeBtn.onclick = () => {
			var count = $('.transaction-lines tbody tr').length;
103
104
105
106
107
108
109
110














































			return false;
		};
		line.parentNode.appendChild(n);
		initLine(n);
	};

	updateTotals();
}





















































|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
147
148
149
150
151
152
153
154
155
156
			return false;
		};
		line.parentNode.appendChild(n);
		initLine(n);
	};

	updateTotals();

	// Hide type specific parts of the form
	function hideAllTypes() {
		g.toggle('[data-types]', false);
	}

	// Toggle parts of the form when a type is selected
	function selectType(v) {
		hideAllTypes();
		g.toggle('[data-types~=t' + v + ']', true);
		g.toggle('[data-types=all-but-advanced]', v != 0);
		// Disable required form elements, or the form won't be able to be submitted
		$('[data-types=all-but-advanced] input[required]').forEach((e) => {
			e.disabled = v == 'advanced' ? true : false;
		});

	}

	var radios = $('fieldset input[type=radio][name=type]');

	radios.forEach((e) => {
		e.onchange = () => {
			document.querySelectorAll('fieldset').forEach((e, k) => {
				if (!is_new || k == 0 || e.dataset.types) return;
				g.toggle(e, true);
				g.toggle('p.submit', true);
			});
			selectType(e.value);
		};
	});

	hideAllTypes();

	// In case of a pre-filled form: show the correct part of the form
	var current = document.querySelector('input[name=type]:checked');
	if (current) {
		selectType(current.value);
	}

	if (is_new) {
		document.querySelectorAll('fieldset').forEach((e, k) => {
			if (k == 0) return;
			g.toggle(e, false);
			g.toggle('p.submit', false);
		});
	}
}