Overview
Comment:Implement duplicate transaction button
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk | stable
Files: files | file ages | folders
SHA3-256: 0cfe628c98e91df7c7fd1d83c73a95eb07c19b2ddab4f192f010d75dbed00db4
User & Date: bohwaz on 2021-10-01 00:46:53
Other Links: manifest | tags
Context
2021-10-01
00:49
Only shown "add payment" button when the service is linked to accounting check-in: 90c1d755e2 user: bohwaz tags: trunk, stable
00:46
Implement duplicate transaction button check-in: 0cfe628c98 user: bohwaz tags: trunk, stable
2021-09-30
23:42
Fix page reloads that were triggered when not needed, reverting page type to "page" instead of category check-in: 006cbd71fa user: bohwaz tags: trunk, stable
Changes

Modified src/include/lib/Garradin/Entities/Accounting/Transaction.php from [07be54e40e] to [32bd594806].

87
88
89
90
91
92
93


94
95
96
97
98
99
100
		'reference' => 'string|max:200',
		'date'      => 'required|date_format:d/m/Y',
	];

	protected $_lines;
	protected $_old_lines = [];



	/**
	 * @var Transaction
	 */
	protected $_related;

	static public function getTypeFromAccountType(int $account_type)
	{







>
>







87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
		'reference' => 'string|max:200',
		'date'      => 'required|date_format:d/m/Y',
	];

	protected $_lines;
	protected $_old_lines = [];

	protected $_accounts = [];

	/**
	 * @var Transaction
	 */
	protected $_related;

	static public function getTypeFromAccountType(int $account_type)
	{
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
				return self::TYPE_ADVANCED;
		}
	}

	/**
	 * @param  bool $restrict_year Set to TRUE to only return lines linked to the correct chart, or FALSE (deprecated/legacy) to return all lines even if they are linked to accounts in the wrong chart!
	 */
	public function getLinesWithAccounts(bool $restrict_year = true)
	{
		$restrict = $restrict_year ? 'AND a.id_chart = y.id_chart' : '';



		$sql = sprintf('SELECT
			l.*, a.label AS account_name, a.code AS account_code,
			b.label AS analytical_name
			FROM acc_transactions_lines l
			INNER JOIN acc_accounts a ON a.id = l.id_account %s
			INNER JOIN acc_transactions t ON t.id = l.id_transaction
			INNER JOIN acc_years y ON y.id = t.id_year
			LEFT JOIN acc_accounts b ON b.id = l.id_analytical
			WHERE l.id_transaction = ? ORDER BY l.id;', $restrict);







		$em = EntityManager::getInstance(Line::class);




		return $em->DB()->get($sql, $this->id);


	}





















	public function getLines($with_accounts = false)
	{
		if (null === $this->_lines && $this->exists()) {
			$em = EntityManager::getInstance(Line::class);
			$this->_lines = $em->all('SELECT * FROM @TABLE WHERE id_transaction = ? ORDER BY id;', $this->id);
		}
		elseif (null === $this->_lines) {
			$this->_lines = [];







|

|

>
>
|
|
|
|
|
|
|
|
|

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







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
				return self::TYPE_ADVANCED;
		}
	}

	/**
	 * @param  bool $restrict_year Set to TRUE to only return lines linked to the correct chart, or FALSE (deprecated/legacy) to return all lines even if they are linked to accounts in the wrong chart!
	 */
	public function getLinesWithAccounts(bool $restrict_year = true): array
	{
		$db = EntityManager::getInstance(Line::class)->DB();

		if (null === $this->_lines || $restrict_year === false) {
			$restrict = $restrict_year ? 'AND a.id_chart = y.id_chart' : '';
			$sql = sprintf('SELECT
				l.*, a.label AS account_name, a.code AS account_code,
				b.label AS analytical_name
				FROM acc_transactions_lines l
				INNER JOIN acc_accounts a ON a.id = l.id_account %s
				INNER JOIN acc_transactions t ON t.id = l.id_transaction
				INNER JOIN acc_years y ON y.id = t.id_year
				LEFT JOIN acc_accounts b ON b.id = l.id_analytical
				WHERE l.id_transaction = %d ORDER BY l.id;', $restrict, $this->id());

			return $db->get($sql);
		}
		else {
			// Merge data from accounts with lines
			$accounts = [];
			$lines_with_accounts = [];

			foreach ($this->getLines() as $line) {
				if (!array_key_exists($line->id_account, $this->_accounts)) {
					$accounts[] = $line->id_account;
				}

				if ($line->id_analytical && !array_key_exists($line->id_analytical, $this->_accounts)) {
					$accounts[] = $line->id_analytical;
				}
			}

			if (count($accounts)) {
				$sql = sprintf('SELECT id, label, code FROM acc_accounts WHERE %s;', $db->where('id', 'IN', $accounts));
				$this->_accounts += $db->getGrouped($sql);
			}

			foreach ($this->getLines() as $line) {
				$account = [
					'account_code' => $this->_accounts[$line->id_account]->code,
					'account_name' => $this->_accounts[$line->id_account]->label,
					'analytical_name' => $line->id_analytical ? $this->_accounts[$line->id_analytical]->label : null,
				];

				$lines_with_accounts[] = (object) ($line->asArray() + $account);
			}

			return $lines_with_accounts;
		}
	}

	public function getLines(): array
	{
		if (null === $this->_lines && $this->exists()) {
			$em = EntityManager::getInstance(Line::class);
			$this->_lines = $em->all('SELECT * FROM @TABLE WHERE id_transaction = ? ORDER BY id;', $this->id);
		}
		elseif (null === $this->_lines) {
			$this->_lines = [];
250
251
252
253
254
255
256









































257
258
259
260
261
262
263
		$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');
		}







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







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
337
338
339
340
		$type = $this->getTypesDetails()[$this->type];

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

	/**
	 * Creates a new Transaction entity (not saved) from an existing one,
	 * trying to adapt to a different chart if possible
	 * @param  int    $id
	 * @param  Year   $year Target year
	 * @return Transaction
	 */
	public function duplicate(Year $year): Transaction
	{
		$new = new Transaction;

		$copy = ['type', 'status', 'label', 'notes', 'reference', 'date'];

		foreach ($copy as $field) {
			$new->$field = $this->$field;
		}

		$copy = ['credit', 'debit', 'id_account', 'label', 'reference'];
		$lines = DB::getInstance()->get('SELECT
				l.credit, l.debit, l.label, l.reference, b.id AS id_account
			FROM acc_transactions_lines l
			INNER JOIN acc_accounts a ON a.id = l.id_account
			LEFT JOIN acc_accounts b ON b.code = a.code AND b.id_chart = ?
			WHERE l.id_transaction = ?;',
			$year->chart()->id,
			$this->id()
		);

		foreach ($lines as $l) {
			$line = new Line;
			foreach ($copy as $field) {
				$line->$field = $l->$field;
			}

			$new->addLine($line);
		}

		return $new;
	}


/*
	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');
		}
330
331
332
333
334
335
336

337

338
339
340
341
342
343
344
		{
			$line->id_transaction = $this->id();
			$line->save();
		}

		foreach ($this->_old_lines as $line)
		{

			$line->delete();

		}

		// Remove flag
		if (!$exists && $this->_related) {
			$this->_related->markPaid();
			$this->_related->save();
		}







>
|
>







407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
		{
			$line->id_transaction = $this->id();
			$line->save();
		}

		foreach ($this->_old_lines as $line)
		{
			if ($line->exists()) {
				$line->delete();
			}
		}

		// Remove flag
		if (!$exists && $this->_related) {
			$this->_related->markPaid();
			$this->_related->save();
		}

Modified src/templates/acc/transactions/details.tpl from [0098f837e0] to [b3d4a382f1].

1
2
3
4




5
6
7
8
9
10

11
12
13
14
15
16
17
{include file="admin/_head.tpl" title="Écriture n°%d"|args:$transaction.id current="acc"}

{if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN) && !$transaction->validated && !$tr_year->closed}
<nav class="tabs">




	<ul>
		<li><a href="edit.php?id={$transaction.id}">Modifier cette écriture</a></li>
		<li><a href="delete.php?id={$transaction.id}">Supprimer cette écriture</a></li>
	</ul>
</nav>
{/if}


{if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE) && $transaction.status & $transaction::STATUS_WAITING}
<div class="block alert">
	<form method="post" action="{$self_url}">
	{if $transaction.type == $transaction::TYPE_DEBT}
		<h3>Dette en attente</h3>
		{linkbutton shape="check" label="Enregistrer le règlement de cette dette" href="!acc/transactions/new.php?payoff_for=%d"|args:$transaction.id}


<

>
>
>
>




<

>







1
2

3
4
5
6
7
8
9
10
11

12
13
14
15
16
17
18
19
20
{include file="admin/_head.tpl" title="Écriture n°%d"|args:$transaction.id current="acc"}


<nav class="tabs">
{if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE)}
	<aside>{linkbutton href="new.php?copy=%d"|args:$transaction.id shape="plus" label="Dupliquer cette écriture"}</aside>
{/if}
{if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN) && !$transaction->validated && !$tr_year->closed}
	<ul>
		<li><a href="edit.php?id={$transaction.id}">Modifier cette écriture</a></li>
		<li><a href="delete.php?id={$transaction.id}">Supprimer cette écriture</a></li>
	</ul>

{/if}
</nav>

{if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE) && $transaction.status & $transaction::STATUS_WAITING}
<div class="block alert">
	<form method="post" action="{$self_url}">
	{if $transaction.type == $transaction::TYPE_DEBT}
		<h3>Dette en attente</h3>
		{linkbutton shape="check" label="Enregistrer le règlement de cette dette" href="!acc/transactions/new.php?payoff_for=%d"|args:$transaction.id}

Modified src/templates/acc/transactions/new.tpl from [49e96a9c33] to [91b454b44d].

64
65
66
67
68
69
70

71
72
73
74
75
76
77
78
				<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}
					{/foreach}
					</dl>
				{/if}
			</fieldset>
		{/foreach}
	{/if}








>
|







64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
				<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"}
						<?php $selected = $types_accounts[$key] ?? null; ?>
						{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=$selected}
					{/foreach}
					</dl>
				{/if}
			</fieldset>
		{/foreach}
	{/if}

Modified src/www/admin/acc/transactions/new.php from [d8191985b4] to [c93d48adea].

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\Entities\Accounting\Account;
use Garradin\Entities\Accounting\Transaction;
use Garradin\Entities\Files\File;

use Garradin\Accounting\Years;

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

$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE);

if (!CURRENT_YEAR_ID) {
	Utils::redirect(ADMIN_URL . 'acc/years/?msg=OPEN');
}

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

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



















$date = new \DateTime;

if ($session->get('acc_last_date')) {
	$date = \DateTime::createFromFormat('!d/m/Y', $session->get('acc_last_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
<?php
namespace Garradin;

use Garradin\Entities\Accounting\Account;
use Garradin\Entities\Accounting\Transaction;
use Garradin\Entities\Files\File;
use Garradin\Accounting\Transactions;
use Garradin\Accounting\Years;

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

$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE);

if (!CURRENT_YEAR_ID) {
	Utils::redirect(ADMIN_URL . 'acc/years/?msg=OPEN');
}

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

$transaction = new Transaction;
$lines = [[], []];
$amount = 0;
$payoff_for = qg('payoff_for') ?: f('payoff_for');
$types_accounts = null;

// Duplicate transaction
if (qg('copy')) {
	$old = Transactions::get((int)qg('copy'));
	$transaction = $old->duplicate($current_year);
	$lines = $transaction->getLinesWithAccounts();
	$payoff_for = null;
	$amount = $transaction->getLinesCreditSum();
	$types_accounts = $transaction->getTypesAccounts();
	$transaction->resetLines();

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

	unset($line);
}

$date = new \DateTime;

if ($session->get('acc_last_date')) {
	$date = \DateTime::createFromFormat('!d/m/Y', $session->get('acc_last_date'));
}

86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
		Utils::redirect(Utils::getSelfURI(false) . '?ok=' . $transaction->id());
	}
	catch (UserException $e) {
		$form->addError($e->getMessage());
	}
}

$tpl->assign(compact('transaction', 'payoff_for', 'amount', 'lines'));
$tpl->assign('payoff_targets', implode(':', [Account::TYPE_BANK, Account::TYPE_CASH, Account::TYPE_OUTSTANDING]));
$tpl->assign('ok', (int) qg('ok'));

$tpl->assign('types_details', Transaction::getTypesDetails());
$tpl->assign('chart_id', $chart->id());

$tpl->assign('analytical_accounts', ['' => '-- Aucun'] + $accounts->listAnalytical());
$tpl->display('acc/transactions/new.tpl');







|








105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
		Utils::redirect(Utils::getSelfURI(false) . '?ok=' . $transaction->id());
	}
	catch (UserException $e) {
		$form->addError($e->getMessage());
	}
}

$tpl->assign(compact('transaction', 'payoff_for', 'amount', 'lines', 'types_accounts'));
$tpl->assign('payoff_targets', implode(':', [Account::TYPE_BANK, Account::TYPE_CASH, Account::TYPE_OUTSTANDING]));
$tpl->assign('ok', (int) qg('ok'));

$tpl->assign('types_details', Transaction::getTypesDetails());
$tpl->assign('chart_id', $chart->id());

$tpl->assign('analytical_accounts', ['' => '-- Aucun'] + $accounts->listAnalytical());
$tpl->display('acc/transactions/new.tpl');