Overview
Comment:Allow to move transactions past the closing date to another year
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk | stable
Files: files | file ages | folders
SHA1: addcc82d4afa23901ae7df7596548c17f4a8bb93
User & Date: bohwaz on 2020-12-12 23:24:02
Other Links: manifest | tags
Context
2020-12-13
14:18
Put 890 and 891 in the closing balance or the balance won't be balanced check-in: b744edc852 user: bohwaz tags: trunk, stable, 1.0.0-rc14
2020-12-12
23:24
Allow to move transactions past the closing date to another year check-in: addcc82d4a user: bohwaz tags: trunk, stable
13:36
Display chart name in years list check-in: cabdc28bd5 user: bohwaz tags: trunk, stable
Changes

Modified src/include/lib/Garradin/Accounting/Years.php from [c98819983d] to [d5bc306cae].

26
27
28
29
30
31
32






33
34
35
36
37
38
39

	static public function listOpen()
	{
		$db = EntityManager::getInstance(Year::class)->DB();
		return $db->get('SELECT *, (SELECT COUNT(*) FROM acc_transactions WHERE id_year = acc_years.id) AS nb_transactions
			FROM acc_years WHERE closed = 0 ORDER BY end_date;');
	}







	static public function listAssoc()
	{
		return DB::getInstance()->getAssoc('SELECT id, label FROM acc_years ORDER BY end_date;');
	}

	static public function listClosed()







>
>
>
>
>
>







26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

	static public function listOpen()
	{
		$db = EntityManager::getInstance(Year::class)->DB();
		return $db->get('SELECT *, (SELECT COUNT(*) FROM acc_transactions WHERE id_year = acc_years.id) AS nb_transactions
			FROM acc_years WHERE closed = 0 ORDER BY end_date;');
	}

	static public function listOpenAssocExcept(int $id)
	{
		$db = EntityManager::getInstance(Year::class)->DB();
		return $db->getAssoc('SELECT id, label FROM acc_years WHERE closed = 0 AND id != ? ORDER BY end_date;', $id);
	}

	static public function listAssoc()
	{
		return DB::getInstance()->getAssoc('SELECT id, label FROM acc_years ORDER BY end_date;');
	}

	static public function listClosed()

Modified src/include/lib/Garradin/Entities/Accounting/Year.php from [efb670bde0] to [3f994637f5].

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
		$db = DB::getInstance();

		$this->assert($this->id_chart !== null);

		if ($this->exists()) {
			$this->assert(
				!$db->test(Transaction::TABLE, 'id_year = ? AND date < ?', $this->id(), $this->start_date->format('Y-m-d')),
				'Des mouvements de cet exercice ont une date antérieure à la date de début de l\'exercice.'
			);

			$this->assert(
				!$db->test(Transaction::TABLE, 'id_year = ? AND date > ?', $this->id(), $this->end_date->format('Y-m-d')),
				'Des mouvements de cet exercice ont une date postérieure à la date de fin de l\'exercice.'
			);
		}
	}

	public function close(int $user_id): void
	{
		if ($this->closed) {
			throw new \LogicException('Cet exercice est déjà clôturé');
		}

		$this->set('closed', 1);
		$this->save();
	}



















	public function delete(): bool
	{
		// Manual delete of transactions, as there is a voluntary safeguard in SQL: no cascade
		DB::getInstance()->preparedQuery('DELETE FROM acc_transactions WHERE id_year = ?;', $this->id());

		// Clean up files







|




|













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







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
		$db = DB::getInstance();

		$this->assert($this->id_chart !== null);

		if ($this->exists()) {
			$this->assert(
				!$db->test(Transaction::TABLE, 'id_year = ? AND date < ?', $this->id(), $this->start_date->format('Y-m-d')),
				'Des écritures de cet exercice ont une date antérieure à la date de début de l\'exercice.'
			);

			$this->assert(
				!$db->test(Transaction::TABLE, 'id_year = ? AND date > ?', $this->id(), $this->end_date->format('Y-m-d')),
				'Des écritures de cet exercice ont une date postérieure à la date de fin de l\'exercice.'
			);
		}
	}

	public function close(int $user_id): void
	{
		if ($this->closed) {
			throw new \LogicException('Cet exercice est déjà clôturé');
		}

		$this->set('closed', 1);
		$this->save();
	}

	/**
	 * Splits an accounting year between the current year and another one, at a given date
	 * Any transaction after the given date will be moved to the target year.
	 */
	public function split(\DateTime $date, Year $target): void
	{
		if ($this->closed) {
			throw new \LogicException('Cet exercice est déjà clôturé');
		}

		if ($target->closed) {
			throw new \LogicException('L\'exercice cible est déjà clôturé');
		}

		DB::getInstance()->preparedQuery('UPDATE acc_transactions SET id_year = ? WHERE id_year = ? AND date > ?;',
			$target->id(), $this->id(), $date->format('Y-m-d'));
	}

	public function delete(): bool
	{
		// Manual delete of transactions, as there is a voluntary safeguard in SQL: no cascade
		DB::getInstance()->preparedQuery('DELETE FROM acc_transactions WHERE id_year = ?;', $this->id());

		// Clean up files

Modified src/templates/acc/years/close.tpl from [a98ff863c3] to [5b77dd26f5].

14
15
16
17
18
19
20

21
22
23
24
25
26
27
28
29
30
31
32
33
			Il ne sera plus possible de modifier ou supprimer les écritures de l'exercice clôturé.
		</p>
		<dl>
			<dt>Début de l'exercice</dt>
			<dd>{$year.start_date|date_short}</dd>
			<dt>Fin de l'exercice</dt>
			<dd>{$year.end_date|date_short}</dd>

		</h3>
	</fieldset>

	<p class="help">Les soldes créditeurs ou débiteurs de chaque compte pourront être reportés automatiquement lors de l'ouverture de l'exercice suivant.</p>

	<p class="submit">
		{csrf_field key="acc_years_close_%d"|args:$year.id}
		{button type="submit" name="close" label="Clôturer" shape="lock" class="main"}
	</p>

</form>

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







>






|






14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
			Il ne sera plus possible de modifier ou supprimer les écritures de l'exercice clôturé.
		</p>
		<dl>
			<dt>Début de l'exercice</dt>
			<dd>{$year.start_date|date_short}</dd>
			<dt>Fin de l'exercice</dt>
			<dd>{$year.end_date|date_short}</dd>
			<dd class="help">Si la date de clôture ne convient pas, il est possible de <a href="edit.php?id={$year.id}">modifier l'exercice</a> préalablement à la clôture.</dd>
		</h3>
	</fieldset>

	<p class="help">Les soldes créditeurs ou débiteurs de chaque compte pourront être reportés automatiquement lors de l'ouverture de l'exercice suivant.</p>

	<p class="submit">
		{csrf_field key=$csrf_key}
		{button type="submit" name="close" label="Clôturer" shape="lock" class="main"}
	</p>

</form>

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

Modified src/templates/acc/years/edit.tpl from [6825ad64b8] to [4ca0a535a8].

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





13
14
15
16
17
18
19
20
21
22











23
{include file="admin/_head.tpl" title="Modifier un exercice" current="acc/years"}

{form_errors}

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

	<fieldset>
		<legend>Modifier un exercice</legend>
		<dl>
			{input type="text" label="Libellé" name="label" source=$year required=true}
			{input type="date" label="Début de l'exercice" name="start_date" source=$year required=true}
			{input type="date" label="Fin de l'exercice" name="end_date" source=$year required=true}





		</dl>
	</fieldset>

	<p class="submit">
		{csrf_field key="acc_years_edit_%s"|args:$year.id}
		{button type="submit" name="edit" label="Enregistrer" shape="right" class="main"}
	</p>

</form>












{include file="admin/_foot.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
{include file="admin/_head.tpl" title="Modifier un exercice" current="acc/years"}

{form_errors}

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

	<fieldset>
		<legend>Modifier un exercice</legend>
		<dl>
			{input type="text" label="Libellé" name="label" source=$year required=true}
			{input type="date" label="Début de l'exercice" name="start_date" source=$year required=true}
			{input type="date" label="Fin de l'exercice" name="end_date" source=$year required=true}
			{input type="checkbox" label="Déplacer les écritures postérieures dans un autre exercice" value=1 name="split"}
		</dl>
		<dl class="split_year">
			<dd class="help">En cochant cette case, toute écriture située <em>après</em> la date de fin indiquée ci-dessus sera déplacée dans l'exercice sélectionné ci-dessous.</dd>
			{input type="select" name="split_year" options=$split_years label="Nouvel exercice à utiliser" help="Les écritures situées après la date de fin seront transférées dans cet exercice"}
		</dl>
	</fieldset>

	<p class="submit">
		{csrf_field key="acc_years_edit_%s"|args:$year.id}
		{button type="submit" name="edit" label="Enregistrer" shape="right" class="main"}
	</p>

</form>

{literal}
<script type="text/javascript">
let split = $('#f_split_1');
g.toggle('.split_year', split.checked);

split.onchange = () => {
	g.toggle('.split_year', split.checked);
};
</script>
{/literal}

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

Modified src/www/admin/acc/years/close.php from [c2cd93158e] to [e25c1f315a].

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
	throw new UserException('Exercice inconnu.');
}

if ($year->closed) {
	throw new UserException('Impossible de modifier un exercice clôturé.');
}

$rules = [
	'end_date' => 'date_format:d/m/Y|required',
];

if (f('close') && $form->check('acc_years_close_' . $year->id()))
{
	try {

		$year->close($user->id);
		$year->save();
		$session->set('acc_year', null);

		Utils::redirect(ADMIN_URL . 'acc/years/');
	}
	catch (UserException $e)
	{
		$form->addError($e->getMessage());
	}
}

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

$tpl->display('acc/years/close.tpl');







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


13
14
15
16
17
18
19




20
21

22
23
24
25

26
27






28
29
30
	throw new UserException('Exercice inconnu.');
}

if ($year->closed) {
	throw new UserException('Impossible de modifier un exercice clôturé.');
}





$csrf_key = 'acc_years_close_' . $year->id();


$form->runIf('close', function () use ($year, $user, $session) {
	$year->close($user->id);
	$year->save();
	$session->set('acc_year', null);

}, $csrf_key, ADMIN_URL . 'acc/years/');







$tpl->assign(compact('year', 'csrf_key'));

$tpl->display('acc/years/close.tpl');

Modified src/www/admin/acc/years/edit.php from [43b8da34f3] to [1f8f1a8a07].

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
<?php
namespace Garradin;

use Garradin\Accounting\Years;


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

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

$year = Years::get((int)qg('id'));

if (!$year) {
	throw new UserException('Exercice inconnu.');
}

if ($year->closed) {
	throw new UserException('Impossible de modifier un exercice clôturé.');
}

if (f('edit') && $form->check('acc_years_edit_' . $year->id()))
{













	try {





		$year->importForm();
		$year->save();

		Utils::redirect(ADMIN_URL . 'acc/years/');


	}
	catch (UserException $e)
	{
		$form->addError($e->getMessage());
	}
}




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


$tpl->display('acc/years/edit.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
59
60
<?php
namespace Garradin;

use Garradin\Accounting\Years;
use Garradin\Entities\Accounting\Year;

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

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

$year = Years::get((int)qg('id'));

if (!$year) {
	throw new UserException('Exercice inconnu.');
}

if ($year->closed) {
	throw new UserException('Impossible de modifier un exercice clôturé.');
}

$csrf_key = 'acc_years_edit_' . $year->id();

$form->runIf('edit', function () use ($year) {
	if (f('split')) {
		$date = \DateTime::createFromFormat('!d/m/Y', f('end_date'));

		if (!$date) {
			throw new UserException('Date de séparation invalide');
		}

		$target = f('split_year');

		if ($target) {
			$target = Years::get($target);
		}
		else {
			$target = new Year;
			$new_start = (clone $date)->modify('+1 day');
	        $target->label = sprintf('Exercice %d', $date->format('Y'));
	        $target->start_date = $new_start;
	        $target->end_date = (clone $new_start)->modify('+1 year');
	        $target->id_chart = $year->id_chart;
	        $target->save();
		}

		if (!$target) {
			throw new UserException('Exercice de séparation invalide');
		}


		$year->split($date, $target);
	}

	$year->importForm();
	$year->save();
}, $csrf_key, ADMIN_URL . 'acc/years/');

$tpl->assign(compact('year', 'csrf_key'));
$tpl->assign('split_years', ['' => '-- Créer un nouvel exercice'] + Years::listOpenAssocExcept($year->id()));

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