Overview
Comment:Add opening balance, year closing, year edit, year creation, year removal
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA1: fdf0caeabfe46a84023281ecf6b71bab93e0dd3b
User & Date: bohwaz on 2020-10-03 15:42:06
Other Links: branch diff | manifest | tags
Context
2020-10-03
19:13
Year selection works check-in: 5b771b881c user: bohwaz tags: dev
15:42
Add opening balance, year closing, year edit, year creation, year removal check-in: fdf0caeabf user: bohwaz tags: dev
01:18
Year editing now works check-in: d8d509cc95 user: bohwaz tags: dev
Changes

Modified src/include/lib/Garradin/Accounting/Years.php from [ba1246c34b] to [10f18aeacf].

20
21
22
23
24
25
26






27
28
29
30
31
32
33






















	}

	static public function listOpen()
	{
		$em = EntityManager::getInstance(Year::class);
		return $em->all('SELECT * FROM @TABLE WHERE closed = 0 ORDER BY end_date;');
	}







	static public function list()
	{
		$em = EntityManager::getInstance(Year::class);
		return $em->all('SELECT * FROM @TABLE ORDER BY end_date;');
	}
}





























>
>
>
>
>
>






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

	static public function listOpen()
	{
		$em = EntityManager::getInstance(Year::class);
		return $em->all('SELECT * FROM @TABLE WHERE closed = 0 ORDER BY end_date;');
	}

	static public function listClosed()
	{
		$em = EntityManager::getInstance(Year::class);
		return $em->all('SELECT * FROM @TABLE WHERE closed = 1 ORDER BY end_date;');
	}

	static public function list()
	{
		$em = EntityManager::getInstance(Year::class);
		return $em->all('SELECT * FROM @TABLE ORDER BY end_date;');
	}

	static public function getNewYearDates(): array
	{
		$last_year = EntityManager::findOne(Year::class, 'SELECT * FROM @TABLE ORDER BY end_date DESC LIMIT 1;');

		if ($last_year) {
			$diff = $last_year->start_date->diff($last_year->end_date);

			$start_date = clone $last_year->end_date;
			$start_date->modify('+1 day');

			$end_date = clone $start_date;
			$end_date->add($diff);
		}
		else {
			$start_date = new \DateTime;
			$end_date = clone $start_date;
			$end_date->modify('+1 year');
		}

		return [$start_date, $end_date];
	}
}

Modified src/include/lib/Garradin/Entities/Accounting/Account.php from [6cd53f78f7] to [780b8fe6ec].

42
43
44
45
46
47
48



49
50
51
52
53
54
55
56
57
58


59
60
61
62
63
64
65
	 */
	const TYPE_OUTSTANDING = 5;

	const TYPE_ANALYTICAL = 6;
	const TYPE_VOLUNTEERING = 7;
	const TYPE_THIRD_PARTY = 8;




	const TYPES_NAMES = [
		'',
		'Recettes',
		'Dépenses',
		'Banque',
		'Caisse',
		'Attente d\'encaissement',
		'Analytique',
		'Bénévolat',
		'Tiers',


	];

	protected $id;
	protected $id_chart;
	protected $code;
	protected $label;
	protected $description;







>
>
>










>
>







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
	 */
	const TYPE_OUTSTANDING = 5;

	const TYPE_ANALYTICAL = 6;
	const TYPE_VOLUNTEERING = 7;
	const TYPE_THIRD_PARTY = 8;

	const TYPE_OPENING = 9;
	const TYPE_CLOSING = 10;

	const TYPES_NAMES = [
		'',
		'Recettes',
		'Dépenses',
		'Banque',
		'Caisse',
		'Attente d\'encaissement',
		'Analytique',
		'Bénévolat',
		'Tiers',
		'Ouverture',
		'Clôture',
	];

	protected $id;
	protected $id_chart;
	protected $code;
	protected $label;
	protected $description;

Modified src/include/lib/Garradin/Entities/Accounting/Line.php from [33bab57818] to [74fccc0311].

47
48
49
50
51
52
53



54
55
56
57
58













59
60
61
62
63
64
65
66
67
68
			if (!preg_match('/^(\d+)(?:[,.](\d{1,2}))?$/', $value, $match)) {
				throw new ValidationException('Le format du montant est invalide. Format accepté, exemple : 142,02');
			}

			$value = $match[1] . str_pad((int)@$match[2], 2, '0', STR_PAD_RIGHT);
			$value = (int) $value;
		}




		$value = parent::filterUserValue($key, $value);

		return $value;
	}














	public function selfCheck(): void
	{
		parent::selfCheck();
		$this->assert($this->credit || $this->debit, 'Aucun montant au débit ou au crédit.');
		$this->assert(($this->credit * $this->debit) === 0 && ($this->credit + $this->debit) > 0, 'Ligne non équilibrée : crédit ou débit doit valoir zéro.');
		$this->assert($this->id_transaction, 'Aucun mouvement n\'a été indiqué pour cette ligne.');
		$this->assert($this->reconciled === 0 || $this->reconciled === 1);
	}
}







>
>
>





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




|





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
			if (!preg_match('/^(\d+)(?:[,.](\d{1,2}))?$/', $value, $match)) {
				throw new ValidationException('Le format du montant est invalide. Format accepté, exemple : 142,02');
			}

			$value = $match[1] . str_pad((int)@$match[2], 2, '0', STR_PAD_RIGHT);
			$value = (int) $value;
		}
		elseif ($key == 'id_analytical' && $value == 0) {
			$value = null;
		}

		$value = parent::filterUserValue($key, $value);

		return $value;
	}

	public function importForm(array $source = null)
	{
		if (null === $source) {
			$source = $_POST;
		}

		if (empty($source['id_analytical'])) {
			unset($source['id_analytical']);
		}

		return parent::importForm($source);
	}

	public function selfCheck(): void
	{
		parent::selfCheck();
		$this->assert($this->credit || $this->debit, 'Aucun montant au débit ou au crédit');
		$this->assert(($this->credit * $this->debit) === 0 && ($this->credit + $this->debit) > 0, 'Ligne non équilibrée : crédit ou débit doit valoir zéro.');
		$this->assert($this->id_transaction, 'Aucun mouvement n\'a été indiqué pour cette ligne.');
		$this->assert($this->reconciled === 0 || $this->reconciled === 1);
	}
}

Modified src/include/lib/Garradin/Entities/Accounting/Transaction.php from [3b1790ae44] to [633f3145e5].

5
6
7
8
9
10
11

12
13
14
15
16
17
18
...
226
227
228
229
230
231
232
233


234
235
236
237
238
239
240
...
250
251
252
253
254
255
256
257


258
259
260
261
262
263
264
265

















































266
267
268
269
270
271
272
use KD2\DB\EntityManager;
use Garradin\Entity;
use Garradin\Fichiers;
use Garradin\Accounting\Accounts;
use Garradin\ValidationException;
use Garradin\DB;
use Garradin\Config;


class Transaction extends Entity
{
	const TABLE = 'acc_transactions';

	protected $id;
	protected $label;
................................................................................
				'debit'         => '0',
				'id_account'    => $to,
				'id_analytical' => !empty($source['id_analytical']) ? $source['id_analytical'] : null,
			]);
			$this->add($line);
		}
		else {
			foreach ($source['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)->import($line);
................................................................................
		}

		$this->importForm();

		$this->_old_lines = $this->getLines();
		$this->_lines = [];

		foreach ($source['lines'] as $i => $line) {


			$line['id_account'] = @count($line['account']) ? key($line['account']) : null;

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

			$line = (new Line)->import($line);

















































			$this->add($line);
		}
	}

	public function year()
	{
		return EntityManager::findOneById(Year::class, $this->id_year);







>







 







|
>
>







 







|
>
>



<



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







5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
...
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
use KD2\DB\EntityManager;
use Garradin\Entity;
use Garradin\Fichiers;
use Garradin\Accounting\Accounts;
use Garradin\ValidationException;
use Garradin\DB;
use Garradin\Config;
use Garradin\Utils;

class Transaction extends Entity
{
	const TABLE = 'acc_transactions';

	protected $id;
	protected $label;
................................................................................
				'debit'         => '0',
				'id_account'    => $to,
				'id_analytical' => !empty($source['id_analytical']) ? $source['id_analytical'] : null,
			]);
			$this->add($line);
		}
		else {
			$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)->import($line);
................................................................................
		}

		$this->importForm();

		$this->_old_lines = $this->getLines();
		$this->_lines = [];

		$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->add($line);
		}
	}

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

		if (!isset($source['lines']) || !is_array($source['lines'])) {
			throw new ValidationException('Aucun contenu trouvé dans le formulaire.');
		}

		$this->label = 'Balance d\'ouverture';
		$this->date = $year->start_date;
		$this->id_year = $year->id();

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

		foreach ($lines as $line) {
			$line['id_account'] = @count($line['account']) ? key($line['account']) : null;
			$line = (new Line)->importForm($line);
			$this->add($line);

			$debit += $line->debit;
			$credit += $line->credit;
		}

		if ($debit != $credit) {
			// Add final balance line
			$line = new Line;

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

			$open_account = EntityManager::findOne(Account::class, 'SELECT * FROM @TABLE WHERE id_chart = ? AND type = ? LIMIT 1;', $year->id_chart, Account::TYPE_OPENING);

			if (!$open_account) {
				throw new ValidationException('Aucun compte favori de bilan d\'ouverture n\'existe dans le plan comptable');
			}

			$line->id_account = $open_account->id();

			$this->add($line);
		}
	}

	public function year()
	{
		return EntityManager::findOneById(Year::class, $this->id_year);

Modified src/include/lib/Garradin/Entities/Accounting/Year.php from [9d5fb802e0] to [b42e0dfc74].

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
87
88
89

90
91
92
93
94
95






96
97
98
99
100















































use KD2\DB\EntityManager;
use Garradin\Entity;
use Garradin\DB;
use Garradin\UserException;

class Year extends Entity
{
    const TABLE = 'acc_years';

    protected $id;
    protected $label;
    protected $start_date;
    protected $end_date;
    protected $closed = 0;
    protected $id_chart;

    protected $_types = [
        'id'         => 'int',
        'label'      => 'string',
        'start_date' => 'date',
        'end_date'   => 'date',
        'closed'     => 'int',
        'id_chart'   => 'int',
    ];

    protected $_form_rules = [
        'label'      => 'required|string|max:200',
        'start_date' => 'required|date_format:d/m/Y',
        'end_date'   => 'required|date_format:d/m/Y',
    ];

    public function selfCheck(): void
    {
        parent::selfCheck();
        $this->assert($this->start_date < $this->end_date, 'La date de fin doit être postérieure à la date de début');
        $this->assert($this->closed === 0 || $this->closed === 1);
        $this->assert($this->closed == 1 || !isset($this->_modified['closed']), 'Il est interdit de réouvrir un exercice clôturé');

        $db = DB::getInstance();

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

        // Vérifier qu'on ne crée pas 2 exercices qui se recoupent
        if ($this->exists()) {
            $this->assert(
                !$db->test(self::TABLE, 'id != :id AND ((start_date <= :start_date AND end_date >= :start_date) OR (start_date <= :end_date AND end_date >= :start_date))',
                    ['id' => $this->id(), 'start_date' => $this->start_date->format('Y-m-d'), 'end_date' => $this->end_date->format('Y-m-d')]),
                'La date de début ou de fin se recoupe avec un exercice existant.'
            );

            $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.'
            );
        }
        else {
            $this->assert(
                !$db->test(self::TABLE, '(start_date <= :start_date AND end_date >= :start_date) OR (start_date <= :end_date AND end_date >= :start_date)',
                    ['start_date' => $this->start_date->format('Y-m-d'), 'end_date' => $this->end_date->format('Y-m-d')]),
                'La date de début ou de fin se recoupe avec un exercice existant.'
            );
        }
    }

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

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

    public function delete(): bool
    {
        $db = DB::getInstance();

        // Ne pas supprimer un compte qui est utilisé !
        if ($db->test(Transaction::TABLE, $db->where('id_year', $this->id())))
        {

            throw new UserException('Cet exercice ne peut être supprimé car des mouvements y sont liés.');
        }

        return parent::delete();
    }







    public function chart()
    {
        return EntityManager::findOneById(Chart::class, $this->id_chart);
    }
}






















































|

|
|
|
|
|
|

|
|
|
|
|
|
|
|

|
|
|
|
|

|
|
|
|
|
|

|

|

|
|
|
|
|
|
|

|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|

|
|

|
|
<
<
|
<
<
>
|
|

|
|

>
>
>
>
>
>
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
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
147
148
149
150
use KD2\DB\EntityManager;
use Garradin\Entity;
use Garradin\DB;
use Garradin\UserException;

class Year extends Entity
{
	const TABLE = 'acc_years';

	protected $id;
	protected $label;
	protected $start_date;
	protected $end_date;
	protected $closed = 0;
	protected $id_chart;

	protected $_types = [
		'id'         => 'int',
		'label'      => 'string',
		'start_date' => 'date',
		'end_date'   => 'date',
		'closed'     => 'int',
		'id_chart'   => 'int',
	];

	protected $_form_rules = [
		'label'      => 'required|string|max:200',
		'start_date' => 'required|date_format:d/m/Y',
		'end_date'   => 'required|date_format:d/m/Y',
	];

	public function selfCheck(): void
	{
		parent::selfCheck();
		$this->assert($this->start_date < $this->end_date, 'La date de fin doit être postérieure à la date de début');
		$this->assert($this->closed === 0 || $this->closed === 1);
		$this->assert($this->closed == 1 || !isset($this->_modified['closed']), 'Il est interdit de réouvrir un exercice clôturé');

		$db = DB::getInstance();

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

		// Vérifier qu'on ne crée pas 2 exercices qui se recoupent
		if ($this->exists()) {
			$this->assert(
				!$db->test(self::TABLE, 'id != :id AND ((start_date <= :start_date AND end_date >= :start_date) OR (start_date <= :end_date AND end_date >= :start_date))',
					['id' => $this->id(), 'start_date' => $this->start_date->format('Y-m-d'), 'end_date' => $this->end_date->format('Y-m-d')]),
				'La date de début ou de fin se recoupe avec un exercice existant.'
			);

			$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.'
			);
		}
		else {
			$this->assert(
				!$db->test(self::TABLE, '(start_date <= :start_date AND end_date >= :start_date) OR (start_date <= :end_date AND end_date >= :start_date)',
					['start_date' => $this->start_date->format('Y-m-d'), 'end_date' => $this->end_date->format('Y-m-d')]),
				'La date de début ou de fin se recoupe avec un exercice existant.'
			);
		}
	}

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

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

	public function delete(): bool
	{


		// Ne pas supprimer un compte qui est utilisé !


		if ($count = $this->countTransactions()) {
			throw new UserException(sprintf('Cet exercice ne peut être supprimé car %d écritures y sont liées.', $count));
		}

		return parent::delete();
	}

	public function countTransactions(): int
	{
		$db = DB::getInstance();
		return $db->count(Transaction::TABLE, $db->where('id_year', $this->id()));
	}

	public function chart()
	{
		return EntityManager::findOneById(Chart::class, $this->id_chart);
	}

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

		if (!isset($source['lines']) || !is_array($source['lines'])) {
			throw new UserException('Aucun contenu trouvé dans le formulaire.');
		}

		if (!isset($lines['account'], $lines['credit'], $lines['debit'])) {
			throw new UserException('Problème de contenu dans le formulaire.');
		}

		$transaction = new Transaction;
		$transaction->label = 'Balance d\'ouverture';
		$transaction->date = $this->date;
		$transaction->id_year = $this->id();

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

		foreach ($lines as $line) {
			$line['id_account'] = @count($line['account']) ? key($line['account']) : null;
			$line = (new Line)->importForm($line);
			$transaction->add($line);

			$debit += $line->debit;
			$credit += $line->credit;
		}

		$line = new Line;

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

		$line->id_account = EntityManager::findOne(Account::class, 'SELECT * FROM @TABLE WHERE id_chart = ? AND type = ? LIMIT 1;', $this->id_chart, Account::TYPE_OPENING);

		$transaction->add($line);

		return $transaction;
	}
}

Modified src/include/lib/Garradin/Utils.php from [5298184ce5] to [d1d3e6f4cb].

869
870
871
872
873
874
875
876
























            case 'unlock': return '🔓';
            case 'folder': return '🗀';
            case 'document': return '🗅';
            default:
                throw new \InvalidArgumentException('Unknown icon shape: ' . $shape);
        }
    }
}































|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
            case 'unlock': return '🔓';
            case 'folder': return '🗀';
            case 'document': return '🗅';
            default:
                throw new \InvalidArgumentException('Unknown icon shape: ' . $shape);
        }
    }

    static public function array_transpose(array $array): array
    {
        $out = [];
        $count = null;

        foreach ($array as $column => $rows) {
            if (null !== $count && count($rows) != $count) {
                throw new \LogicException('Array is inconsistent');
            }

            $count = count($rows);

            foreach ($rows as $k => $v) {
                if (!isset($out[$k])) {
                    $out[$k] = [];
                }

                $out[$k][$column] = $v;
            }
        }

        return $out;
    }
}

Modified src/templates/acc/transactions/_lines_form.tpl from [d3cc863597] to [2f81935c85].

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
		</tr>
	</thead>
	<tbody>
	{foreach from=$lines key="k" item="line"}
		<tr>
			<th>
				{if isset($lines_accounts)}
					{input type="list" target="%sacc/accounts/selector.php?target=all&fullname=1"|args:$admin_url name="lines[account][]" value=$lines_accounts[$k]}
				{else}
					{input type="list" target="%sacc/accounts/selector.php?target=all"|args:$admin_url name="lines[account][]"}
				{/if}
			</th>
			<td>{input type="money" name="lines[debit][]" value=$line.debit size=5}</td>
			<td>{input type="money" name="lines[credit][]" value=$line.credit size=5}</td>
			<td>{input type="text" name="lines[reference][]" value=$line.reference size=10}</td>
			<td>{input type="text" name="lines[label][]" value=$line.label}</td>
			{if count($analytical_accounts) > 0}
				<td>{input value=$line.id_analytical type="select" name="lines[id_analytical][]" options=$analytical_accounts}</td>
			{/if}
			<td>{button label="Enlever la ligne" shape="minus" name="remove_line"}</td>
		</tr>
	{/foreach}
	</tbody>
	<tfoot>
		<tr>
			<th>Total</th>
			<td>{input type="money" name="debit_total" readonly="readonly" tabindex="-1" }</td>
			<td>{input type="money" name="credit_total" readonly="readonly" tabindex="-1" }</td>
			<td colspan="3" id="lines_message"></td>
			<td>{button label="Ajouter une ligne" shape="plus"}</td>
		</tr>
	</tfoot>
</table>







|











|













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
		</tr>
	</thead>
	<tbody>
	{foreach from=$lines key="k" item="line"}
		<tr>
			<th>
				{if isset($lines_accounts)}
					{input type="list" target="%sacc/accounts/selector.php?target=all"|args:$admin_url name="lines[account][]" value=$lines_accounts[$k]}
				{else}
					{input type="list" target="%sacc/accounts/selector.php?target=all"|args:$admin_url name="lines[account][]"}
				{/if}
			</th>
			<td>{input type="money" name="lines[debit][]" value=$line.debit size=5}</td>
			<td>{input type="money" name="lines[credit][]" value=$line.credit size=5}</td>
			<td>{input type="text" name="lines[reference][]" value=$line.reference size=10}</td>
			<td>{input type="text" name="lines[label][]" value=$line.label}</td>
			{if count($analytical_accounts) > 0}
				<td>{input value=$line.id_analytical type="select" name="lines[id_analytical][]" options=$analytical_accounts}</td>
			{/if}
			<td>{button label="Enlever la ligne" shape="minus" min="2" name="remove_line"}</td>
		</tr>
	{/foreach}
	</tbody>
	<tfoot>
		<tr>
			<th>Total</th>
			<td>{input type="money" name="debit_total" readonly="readonly" tabindex="-1" }</td>
			<td>{input type="money" name="credit_total" readonly="readonly" tabindex="-1" }</td>
			<td colspan="3" id="lines_message"></td>
			<td>{button label="Ajouter une ligne" shape="plus"}</td>
		</tr>
	</tfoot>
</table>

Modified src/templates/acc/transactions/delete.tpl from [5306fcce0b] to [978010906f].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{include file="admin/_head.tpl" title="Supprimer l'écriture n°%d"|args:$transaction.id current="acc"}

{form_errors}

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

    <fieldset>
        <legend>Supprimer cette écriture ?</legend>
        <h3 class="warning">
            Êtes-vous sûr de vouloir supprimer l'opération n°{$transaction.id}
            «&nbsp;{$transaction.label}&nbsp;» du {$transaction.date|date_fr:'d/m/Y'} ?
        </h3>
    </fieldset>

    <p class="submit">
        {csrf_field key="acc_delete_%d"|args:$transaction.id}
        <input type="submit" name="delete" value="Supprimer &rarr;" />
    </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
{include file="admin/_head.tpl" title="Supprimer l'écriture n°%d"|args:$transaction.id current="acc"}

{form_errors}

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

	<fieldset>
		<legend>Supprimer cette écriture ?</legend>
		<h3 class="warning">
			Êtes-vous sûr de vouloir supprimer l'opération n°{$transaction.id}
			«&nbsp;{$transaction.label}&nbsp;» du {$transaction.date|date_fr:'d/m/Y'} ?
		</h3>
	</fieldset>

	<p class="submit">
		{csrf_field key="acc_delete_%d"|args:$transaction.id}
		<input type="submit" name="delete" value="Supprimer &rarr;" />
	</p>

</form>

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

Modified src/templates/acc/transactions/new.tpl from [c01a249a64] to [046bad1dcf].

1
2
3
4
5
6
7
8
9
10
..
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{include file="admin/_head.tpl" title="Saisie d'une écriture" current="acc/new" js=1}

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

	{if $ok}
		<p class="confirm">
			L'opération numéro <a href="details.php?id={$ok}">{$ok}</a> a été ajoutée.
			(<a href="details.php?id={$ok}">Voir l'opération</a>)
		</p>
................................................................................
			{input type="radio" name="type" value="advanced" label="Saisie avancée" help="Choisir les comptes du plan comptable, ventiler une écriture sur plusieurs comptes, etc."}
		</dl>
	</fieldset>

	<fieldset data-types="transfer">
		<legend>Virement</legend>
		<dl>
			{input type="list" target="%sacc/accounts/selector.php?target=common"|args:$admin_url name="from" label="De" required=1}
			{input type="list" target="%sacc/accounts/selector.php?target=common"|args:$admin_url name="to" label="Vers" required=1}
		</dl>
	</fieldset>

	<fieldset data-types="revenue">
		<legend>Recette</legend>
		<dl>
			{input type="list" target="%sacc/accounts/selector.php?target=revenue"|args:$admin_url name="revenue_from" label="Type de recette" required=1}


|







 







|
|







1
2
3
4
5
6
7
8
9
10
..
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{include file="admin/_head.tpl" title="Saisie d'une écriture" current="acc/new" js=1}

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

	{if $ok}
		<p class="confirm">
			L'opération numéro <a href="details.php?id={$ok}">{$ok}</a> a été ajoutée.
			(<a href="details.php?id={$ok}">Voir l'opération</a>)
		</p>
................................................................................
			{input type="radio" name="type" value="advanced" label="Saisie avancée" help="Choisir les comptes du plan comptable, ventiler une écriture sur plusieurs comptes, etc."}
		</dl>
	</fieldset>

	<fieldset data-types="transfer">
		<legend>Virement</legend>
		<dl>
			{input type="list" target="%sacc/accounts/selector.php?target=common"|args:$admin_url name="transfer_from" label="De" required=1}
			{input type="list" target="%sacc/accounts/selector.php?target=common"|args:$admin_url name="transfer_to" label="Vers" required=1}
		</dl>
	</fieldset>

	<fieldset data-types="revenue">
		<legend>Recette</legend>
		<dl>
			{input type="list" target="%sacc/accounts/selector.php?target=revenue"|args:$admin_url name="revenue_from" label="Type de recette" required=1}

Added src/templates/acc/years/balance.tpl version [dc337a7397].











































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
{include file="admin/_head.tpl" title="Balance d'ouverture" current="acc/years" js=1}

{form_errors}

{if $year->countTransactions()}
<p class="alert">
	<strong>Attention&nbsp;!</strong>
	Cet exercice a déjà des écritures, peut-être avez-vous déjà renseigné la balance d'ouverture&nbsp;?
</p>
{/if}

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

	<fieldset>
		<legend>Exercice&nbsp;: «&nbsp;{$year.label}&nbsp;» du {$year.start_date|date_fr:'d/m/Y'} au {$year.end_date|date_fr:'d/m/Y'}</legend>
		<p class="help">
			Renseigner ici les soldes d'ouverture (débiteur ou créditeur) des comptes.
		</p>

		{if null === $previous_year}
		<dl>
			<dt><label for="f_from_year">Reprendre les soldes de fermeture de l'exercice suivant</label></dt>
			<dd>
				<select id="f_from_year" name="from_year">
					<option value="">-- Aucun</option>
					{foreach from=$years item="year"}
					<option value="{$year.id}">{$year.label} — {$year.start_date|date_fr:'d/m/Y'} au {$year.end_date|date_fr:'d/m/Y'}</option>
					{/foreach}
				</select>
			</dd>
		</dl>
		{else}
		<table class="list transaction-lines">
			<thead>
				<tr>
					<th>Compte</th>
					<td>Débit</td>
					<td>Crédit</td>
					<td></td>
				</tr>
			</thead>
			<tbody>
			{foreach from=$lines key="k" item="line"}
				<tr>
					<th>
						{input type="list" target="%sacc/accounts/selector.php?target=all"|args:$admin_url name="lines[account][]" value=$lines_accounts[$k]}
					</th>
					<td>{input type="money" name="lines[debit][]" value=$line.debit size=5}</td>
					<td>{input type="money" name="lines[credit][]" value=$line.credit size=5}</td>
					<td>{button label="Enlever la ligne" shape="minus" min="1" name="remove_line"}</td>
				</tr>
			{/foreach}
			</tbody>
			<tfoot>
				<tr>
					<th>Total</th>
					<td>{input type="money" name="debit_total" readonly="readonly" tabindex="-1" }</td>
					<td>{input type="money" name="credit_total" readonly="readonly" tabindex="-1" }</td>
					<td>{button label="Ajouter une ligne" shape="plus"}</td>
				</tr>
			</tfoot>
		</table>
		{/if}
	</fieldset>

	<p class="submit">
		{if null === $previous_year}
			<input type="submit" name="next" value="Continuer &rarr;" />
		{else}
			{csrf_field key="acc_years_balance_%s"|args:$year.id}
			<input type="hidden" name="from_year" value="{$previous_year}" />
			<input type="submit" name="save" value="Sauvegarder &rarr;" />

			{literal}
			<script type="text/javascript" defer="defer" async="async">
			g.script('scripts/accounting.js', () => { initTransactionForm(); });
			</script>
			{/literal}
		{/if}
	</p>

</form>


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

Modified src/templates/acc/years/close.tpl from [6f4abfc5df] to [782159b3de].

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
{include file="admin/_head.tpl" title="Clôturer un exercice" current="compta/exercices" js=1}

{form_errors}

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

    <fieldset>
        <legend>Clôturer un exercice</legend>
        <h3 class="warning">
            Êtes-vous sûr de vouloir clôturer l'exercice «&nbsp;{$exercice.libelle}&nbsp;» ?
        </h3>
        <p class="warning">
            Un exercice clôturé ne peut plus être rouvert ou modifié&nbsp;!<br />
            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>{$exercice.debut|date_fr:'d/m/Y'}</dd>
            <dt><label for="f_fin">Fin de l'exercice</label></dt>
            <dd class="help">Si des opérations existent après cette date, elles seront automatiquement
                attribuées à un nouvel exercice.</dd>
            <dd><input type="date" name="fin" id="f_fin" value="{form_field name=fin default=$exercice.fin|date_fr:'Y-m-d'}" size="10" /></dd>
            <dt>
                <input type="checkbox" name="reports" {form_field name=reports default="1" checked=true} id="f_reports" /> <label for="f_reports">Exécuter automatiquement les reports à nouveau</label>
            </dt>
            <dd class="help">Les soldes créditeurs et débiteurs de chaque compte seront reportés 
                automatiquement dans le nouvel exercice. Si vous ne cochez pas la case, vous devrez faire les reports à nouveau vous-même.</dd>
        </h3>
    </fieldset>



    <p class="submit">
        {csrf_field key="compta_cloturer_exercice_%s"|args:$exercice.id}

        <input type="submit" name="close" value="Clôturer &rarr;" />
    </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
{include file="admin/_head.tpl" title="Clôturer un exercice" current="acc/years"}

{form_errors}

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

	<fieldset>
		<legend>Clôturer un exercice</legend>
		<h3 class="warning">
			Êtes-vous sûr de vouloir clôturer l'exercice «&nbsp;{$year.label}&nbsp;» ?
		</h3>
		<p class="alert">
			Un exercice clôturé ne peut plus être rouvert ou modifié&nbsp;!<br />
			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_fr:'d/m/Y'}</dd>
			<dt>Fin de l'exercice</dt>
			<dd>{$year.end_date|date_fr:'d/m/Y'}</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}
		<input type="submit" name="close" value="Clôturer &rarr;" />
	</p>

</form>

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

Modified src/templates/acc/years/delete.tpl from [604797e2eb] to [ba80abf622].

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
{include file="admin/_head.tpl" title="Supprimer un exercice" current="compta/exercices"}

{form_errors}

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

    <fieldset>
        <legend>Supprimer un exercice</legend>
        <h3 class="warning">
            Êtes-vous sûr de vouloir supprimer l'exercice «&nbsp;{$exercice.libelle}&nbsp;»
            du {$exercice.debut|date_fr:'d/m/Y'} au {$exercice.fin|date_fr:'d/m/Y'} ?
        </h3>
        <p class="help">
            Attention, l'exercice ne pourra pas être supprimé si des opérations y sont
            toujours affectées.
        </p>
    </fieldset>

    <p class="submit">
        {csrf_field key="compta_supprimer_exercice_%s"|args:$exercice.id}

        <input type="submit" name="delete" value="Supprimer &rarr;" />
    </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
{include file="admin/_head.tpl" title="Supprimer un exercice" current="acc/years"}

{form_errors}

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

	<fieldset>
		<legend>Supprimer un exercice</legend>
		<h3 class="warning">
			Êtes-vous sûr de vouloir supprimer l'exercice «&nbsp;{$year.label}&nbsp;» du {$year.start_date|date_fr:'d/m/Y'} au {$year.end_date|date_fr:'d/m/Y'} ?

		</h3>
		<p class="help">
			Attention, l'exercice ne pourra pas être supprimé si des opérations y sont toujours affectées.

		</p>
	</fieldset>

	<p class="submit">

		{csrf_field key="acc_years_delete_%s"|args:$year.id}
		<input type="submit" name="delete" value="Supprimer &rarr;" />
	</p>

</form>

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

Modified src/templates/acc/years/index.tpl from [30203193f6] to [5080bc206e].

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="Exercices" current="acc/years"}

<nav class="tabs">
    <ul>
        <li class="current"><a href="{$self_url}">Exercices</a></li>
        <li><a href="{$admin_url}acc/years/new.php">Nouvel exercice</a></li>
    </ul>
</nav>

{if !empty($list)}
    <dl class="list">
    {foreach from=$list item="year"}
        <dt>{$year.label}</dt>
        <dd class="desc">
            {if $year.closed}Clôturé{else}En cours{/if}
            | Du {$year.start_date|date_fr:'d/m/Y'} au {$year.end_date|date_fr:'d/m/Y'}
        </dd>
        <dd class="desc">
            <a href="{$admin_url}acc/reports/journal.php?exercice={$year.id}">Journal général</a>
            | <a href="{$admin_url}acc/reports/grand_livre.php?exercice={$year.id}">Grand livre</a>
            | <a href="{$admin_url}acc/reports/compte_resultat.php?exercice={$year.id}">Compte de résultat</a>
            | <a href="{$admin_url}acc/reports/bilan.php?exercice={$year.id}">Bilan</a>
        </dd>
        {if $session->canAccess('compta', Membres::DROIT_ADMIN) && !$year.closed}
        <dd class="actions">

            {linkbutton label="Modifier" shape="edit" href="acc/years/edit.php?id=%d"|args:$year.id}
            {linkbutton label="Clôturer" shape="lock" href="acc/years/close.php?id=%d"|args:$year.id}
            {linkbutton label="Supprimer" shape="delete" href="acc/years/delete.php?id=%d"|args:$year.id}
        </dd>
        {/if}
    {/foreach}
    </dl>
{else}
    <p class="alert">
        Il n'y a pas d'exercice en cours.
    </p>
{/if}

{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
40
{include file="admin/_head.tpl" title="Exercices" current="acc/years"}

<nav class="tabs">
	<ul>
		<li class="current"><a href="{$self_url}">Exercices</a></li>
		<li><a href="{$admin_url}acc/years/new.php">Nouvel exercice</a></li>
	</ul>
</nav>

{if !empty($list)}
	<dl class="list">
	{foreach from=$list item="year"}
		<dt>{$year.label}</dt>
		<dd class="desc">
			{if $year.closed}Clôturé{else}En cours{/if}
			| Du {$year.start_date|date_fr:'d/m/Y'} au {$year.end_date|date_fr:'d/m/Y'}
		</dd>
		<dd class="desc">
			<a href="{$admin_url}acc/reports/journal.php?exercice={$year.id}">Journal général</a>
			| <a href="{$admin_url}acc/reports/grand_livre.php?exercice={$year.id}">Grand livre</a>
			| <a href="{$admin_url}acc/reports/compte_resultat.php?exercice={$year.id}">Compte de résultat</a>
			| <a href="{$admin_url}acc/reports/bilan.php?exercice={$year.id}">Bilan</a>
		</dd>
		{if $session->canAccess('compta', Membres::DROIT_ADMIN) && !$year.closed}
		<dd class="actions">
			{linkbutton label="Balance d'ouverture" shape="reset" href="acc/years/balance.php?id=%d"|args:$year.id}
			{linkbutton label="Modifier" shape="edit" href="acc/years/edit.php?id=%d"|args:$year.id}
			{linkbutton label="Clôturer" shape="lock" href="acc/years/close.php?id=%d"|args:$year.id}
			{linkbutton label="Supprimer" shape="delete" href="acc/years/delete.php?id=%d"|args:$year.id}
		</dd>
		{/if}
	{/foreach}
	</dl>
{else}
	<p class="alert">
		Il n'y a pas d'exercice en cours.
	</p>
{/if}

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

Modified src/templates/acc/years/new.tpl from [c84bd45486] to [21dceb0d83].

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
{include file="admin/_head.tpl" title="Commencer un exercice" current="compta/exercices" js=1}

{form_errors}

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

    <fieldset>
        <legend>Commencer un nouvel exercice</legend>
        <dl>
            <dt><label for="f_libelle">Libellé</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd><input type="text" name="libelle" id="f_libelle" value="{form_field name=libelle}" required="required" /></dd>
            <dt><label for="f_debut">Début de l'exercice</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd><input type="date" name="debut" id="f_debut" value="{form_field name=debut}" size="10" required="required" /></dd>
            <dt><label for="f_fin">Fin de l'exercice</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd><input type="date" name="fin" id="f_fin" value="{form_field name=fin}" size="10" required="required" /></dd>
        </dl>
    </fieldset>

    <p class="submit">
        {csrf_field key="compta_ajout_exercice"}
        <input type="submit" name="add" value="Enregistrer &rarr;" />
    </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
{include file="admin/_head.tpl" title="Commencer un exercice" current="acc/years" js=1}

{form_errors}

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

	<fieldset>
		<legend>Commencer un nouvel exercice</legend>
		<dl>
			{input type="select_groups" options=$charts name="id_chart" label="Plan comptable" required=true}
			{input type="text" name="label" label="Libellé" required=true}
			{input type="date" label="Début de l'exercice" name="start_date" required=true default=$start_date}
			{input type="date" label="Fin de l'exercice" name="end_date" required=true default=$end_date}


		</dl>
	</fieldset>

	<p class="submit">
		{csrf_field key="acc_years_new"}
		<input type="submit" name="new" value="Créer ce nouvel exercice &rarr;" />
	</p>

</form>

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

Modified src/www/admin/acc/transactions/delete.php from [b8196c91e9] to [34de837216].

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
require_once __DIR__ . '/../_inc.php';

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

$transaction = Transactions::get((int) qg('id'));

if (!$transaction) {
    throw new UserException('Cette écriture n\'existe pas');
}

if (f('delete'))
{
    if ($form->check('acc_delete_' . $transaction->id))
    {
        try
        {
            $transaction->delete();
            Utils::redirect(ADMIN_URL . 'acc/');
        }
        catch (UserException $e)
        {
            $form->addError($e->getMessage());
        }
    }
}

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

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







|


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





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
require_once __DIR__ . '/../_inc.php';

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

$transaction = Transactions::get((int) qg('id'));

if (!$transaction) {
	throw new UserException('Cette écriture n\'existe pas');
}



if (f('delete') && $form->check('acc_delete_' . $transaction->id))
{
	try
	{
		$transaction->delete();
		Utils::redirect(ADMIN_URL . 'acc/');
	}
	catch (UserException $e)
	{
		$form->addError($e->getMessage());
	}

}

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

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

Modified src/www/admin/acc/transactions/details.php from [30440cd5ef] to [6884309cde].

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use Garradin\Accounting\Transactions;

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

$transaction = Transactions::get((int) qg('id'));

if (!$transaction) {
    throw new UserException('Cette écriture n\'existe pas');
}

$tpl->assign('files', $transaction->listFiles());
$tpl->assign('transaction', $transaction);
$tpl->assign('tr_year', $transaction->year());
$tpl->assign('creator_name', $transaction->id_creator ? (new Membres)->getNom($transaction->id_creator) : null);

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

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







|










4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use Garradin\Accounting\Transactions;

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

$transaction = Transactions::get((int) qg('id'));

if (!$transaction) {
	throw new UserException('Cette écriture n\'existe pas');
}

$tpl->assign('files', $transaction->listFiles());
$tpl->assign('transaction', $transaction);
$tpl->assign('tr_year', $transaction->year());
$tpl->assign('creator_name', $transaction->id_creator ? (new Membres)->getNom($transaction->id_creator) : null);

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

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

Modified src/www/admin/acc/transactions/edit.php from [a41f081562] to [2d12d19c01].

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

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

if (f('save') && $form->check('acc_edit_' . $transaction->id(), $rules)) {
	try {
		$lines = f('lines');
		$max = count($lines['label']);

		if ($max != count($lines['debit'])
			|| $max != count($lines['credit'])
			|| $max != count($lines['reference'])
			|| $max != count($lines['account']))
		{
			throw new UserException('Erreur dans les lignes de l\'écriture');
		}

		$out = [];

		// Reorder the POST data as a proper array
		for ($i = 0; $i < $max; $i++) {
			$out[] = [
				'debit'      => $lines['debit'][$i],
				'credit'     => $lines['credit'][$i],
				'reference'  => $lines['reference'][$i],
				'label'      => $lines['label'][$i],
				'account'    => $lines['account'][$i],
				'id_analytical' => !empty($lines['id_analytical'][$i]) ? $lines['id_analytical'][$i] : null,
			];
		}

		$_POST['type'] = 'advanced';
		$_POST['lines'] = $lines = $out;

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

		// Append file
		if (!empty($_FILES['file']['name'])) {
			$file = Fichiers::upload($_FILES['file']);







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

<







19
20
21
22
23
24
25

























26

27
28
29
30
31
32
33

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

Modified src/www/admin/acc/transactions/new.php from [70fe30ac8b] to [bb27947203].

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
$accounts = $chart->accounts();

$transaction = new Transaction;
$lines = [[], []];

if (f('save') && $form->check('acc_transaction_new')) {
	try {
		// Advanced transaction: handle lines
		if (f('type') == 'advanced' && $lines = f('lines'))
		{
			$max = count($lines['label']);

			if ($max != count($lines['debit'])
				|| $max != count($lines['credit'])
				|| $max != count($lines['reference'])
				|| $max != count($lines['account']))
			{
				throw new UserException('Erreur dans les lignes de l\'écriture');
			}

			$out = [];

			// Reorder the POST data as a proper array
			for ($i = 0; $i < $max; $i++) {
				$out[] = [
					'debit'      => $lines['debit'][$i],
					'credit'     => $lines['credit'][$i],
					'reference'  => $lines['reference'][$i],
					'label'      => $lines['label'][$i],
					'account'    => $lines['account'][$i],
					'id_analytical' => !empty($lines['id_analytical'][$i]) ? $lines['id_analytical'][$i] : null,
				];
			}

			$_POST['lines'] = $lines = $out;
		}

		$transaction->id_year = $year->id();
		$transaction->importFromNewForm($chart->id());
		$transaction->id_creator = $session->getUser()->id;
		$transaction->save();

		// Append file
		if (!empty($_FILES['file']['name'])) {







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







11
12
13
14
15
16
17






























18
19
20
21
22
23
24
$accounts = $chart->accounts();

$transaction = new Transaction;
$lines = [[], []];

if (f('save') && $form->check('acc_transaction_new')) {
	try {






























		$transaction->id_year = $year->id();
		$transaction->importFromNewForm($chart->id());
		$transaction->id_creator = $session->getUser()->id;
		$transaction->save();

		// Append file
		if (!empty($_FILES['file']['name'])) {

Added src/www/admin/acc/years/balance.php version [2bc713be3a].

































































































































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

use Garradin\Entities\Accounting\Transaction;
use Garradin\Accounting\Reports;
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('save') && $form->check('acc_years_balance_' . $year->id()))
{
	try {
		$transaction = new Transaction;
		$transaction->importFromBalanceForm($year);
		$transaction->save();

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

$previous_year = null;
$lines = [[]];
$lines_accounts = [[]];
$years = Years::listClosed();

if (!count($years)) {
	$previous_year = 0;
}
elseif (null !== f('from_year')) {
	$previous_year = (int)f('from_year');
}

if ($previous_year) {
	$lines = Reports::getClosingSumsWithAccounts($previous_year);
	$lines_accounts = [];

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

$tpl->assign('lines', $lines);
$tpl->assign('lines_accounts', $lines_accounts);
$tpl->assign('years', $years);
$tpl->assign('previous_year', $previous_year);
$tpl->assign('year', $year);

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

Modified src/www/admin/acc/years/close.php from [27ad552537] to [b388dca412].

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

<?php
namespace Garradin;



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

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

$e = new Compta\Exercices;

$exercice = $e->get((int)qg('id'));

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

if (f('close'))


{
    $form->check('compta_cloturer_exercice_' . $exercice->id, [
        'fin'     => 'date|required',
        'reports' => 'boolean',

    ]);

    if (!$form->hasErrors())

    {
        try
        {
            $id = $e->close($exercice->id, f('fin'));
        

            if ($id && f('reports'))
            {
                $e->doReports($exercice->id, Utils::modifyDate(f('fin'), '+1 day'));
            }

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

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

$tpl->display('admin/compta/exercices/cloturer.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
<?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é.');
}



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


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

		$year->close();

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

Modified src/www/admin/acc/years/delete.php from [47c6a4fb3d] to [c73e896eab].

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_once __DIR__ . '/../_inc.php';

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

$e = new Compta\Exercices;

$exercice = $e->get((int)qg('id'), true);

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

if ($exercice->cloture && $exercice->nb_operations > 0)
{
    throw new UserException('Impossible de supprimer un exercice clôturé.');
}

if (f('delete'))
{
    if ($form->check('compta_supprimer_exercice_'.$exercice->id))
    {

        try
        {
            $id = $e->delete($exercice->id);

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

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

$tpl->display('admin/compta/exercices/supprimer.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
<?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 supprimer un exercice clôturé.');
}





if (f('delete') && $form->check('acc_years_delete_' . $year->id())) {
	try
	{
		$year->delete();

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


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


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

Modified src/www/admin/acc/years/new.php from [6ab96d643e] to [2a28b1fe7e].

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





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

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

$e = new Compta\Exercices;

if (f('add'))
{
    $form->check('compta_ajout_exercice', [
        'libelle' => 'required',
        'fin'     => 'required|date',
        'debut'   => 'required|date',
    ]);

    if (!$form->hasErrors())
    {
        try



        {
            $id = $e->add([
                'libelle' =>  f('libelle'),
                'debut'   =>  f('debut'),
                'fin'     =>  f('fin'),
            ]);

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





$tpl->display('admin/compta/exercices/ajouter.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
<?php
namespace Garradin;

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

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

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

if (f('new') && $form->check('acc_years_new')) {











	try {
		$year = new Year;
		$year->importForm();
		$year->save();







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

$new_dates = Years::getNewYearDates();
$tpl->assign('start_date', $new_dates[0]);
$tpl->assign('end_date', $new_dates[1]);
$tpl->assign('charts', (new Charts)->listByCountry());

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

Modified src/www/admin/static/scripts/accounting.js from [53bda5fbb6] to [0075f54b09].

1
2
3
4
5
6

7

8
9
10
11
12
13
14
15
16
17
..
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
function initTransactionForm() {
	// Advanced transaction: line management
	var lines = $('.transaction-lines tbody tr');

	function initLine(row) {
		row.querySelector('button[name="remove_line"]').onclick = () => {

			var count = $('.transaction-lines tbody tr').length;


			if (count <= 2) {
				alert("Il n'est pas possible d'avoir moins de deux lignes dans une écriture.");
				return false;
			}

			row.parentNode.removeChild(row);
			updateTotals();
		};

................................................................................
				debit += v;
			}
			else {
				credit += v;
			}
		});


		$('#lines_message').innerHTML = (debit === credit) ? '' : '<span class="alert">Écriture non équilibrée</span>';


		debit = debit ? debit + '' : '000';
		credit = credit ? credit + '' : '000';
		$('#f_debit_total').value = (debit.substr(0, debit.length-2) || '0') + ',' + debit.substr(-2);
		$('#f_credit_total').value = (credit.substr(0, credit.length-2) || '0') + ',' + credit.substr(-2);
	}

................................................................................
	// Add row button
	$('.transaction-lines tfoot button')[0].onclick = () => {
		var line = $('.transaction-lines tbody tr')[0];
		var n = line.cloneNode(true);
		n.querySelectorAll('input').forEach((e) => {
			e.value = '';
		});
		n.querySelector('.input-list .label').innerHTML = '';


		var b = n.querySelector('.input-list button');
		b.onclick = () => {
			g.current_list_input = b.parentNode;
			g.openFrameDialog(b.value);
			return false;
		};
		initLine(n);
		line.parentNode.appendChild(n);
	};

	updateTotals();
}





|
>

>

|
|







 







>
|
>







 







|
>
>












1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
..
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
103
104
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;
			var min = removeBtn.getAttribute('min');

			if (count <= min) {
				alert("Il n'est pas possible d'avoir moins de " + min + " lignes dans une écriture.");
				return false;
			}

			row.parentNode.removeChild(row);
			updateTotals();
		};

................................................................................
				debit += v;
			}
			else {
				credit += v;
			}
		});

		if (m = $('#lines_message')) {
			m.innerHTML = (debit === credit) ? '' : '<span class="alert">Écriture non équilibrée</span>';
		}

		debit = debit ? debit + '' : '000';
		credit = credit ? credit + '' : '000';
		$('#f_debit_total').value = (debit.substr(0, debit.length-2) || '0') + ',' + debit.substr(-2);
		$('#f_credit_total').value = (credit.substr(0, credit.length-2) || '0') + ',' + credit.substr(-2);
	}

................................................................................
	// Add row button
	$('.transaction-lines tfoot button')[0].onclick = () => {
		var line = $('.transaction-lines tbody tr')[0];
		var n = line.cloneNode(true);
		n.querySelectorAll('input').forEach((e) => {
			e.value = '';
		});
		if (l = n.querySelector('.input-list .label')) {
			l.parentNode.removeChild(l);
		}
		var b = n.querySelector('.input-list button');
		b.onclick = () => {
			g.current_list_input = b.parentNode;
			g.openFrameDialog(b.value);
			return false;
		};
		initLine(n);
		line.parentNode.appendChild(n);
	};

	updateTotals();
}

Modified src/www/admin/static/scripts/global.js from [ef372ef71b] to [2edf1c1a52].

232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
			var btn = document.createElement('button');
			btn.className = 'icn-btn';
			btn.type = 'button';
			btn.setAttribute('data-icon', '✘');
			btn.onclick = () => span.parentNode.removeChild(span);
			span.appendChild(btn);
		}
		else {
			i.removeChild(i.querySelector('span'));
		}

		i.appendChild(span);
		g.closeDialog();
		i.firstChild.focus();
	};








|
|







232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
			var btn = document.createElement('button');
			btn.className = 'icn-btn';
			btn.type = 'button';
			btn.setAttribute('data-icon', '✘');
			btn.onclick = () => span.parentNode.removeChild(span);
			span.appendChild(btn);
		}
		else if (old = i.querySelector('span')) {
			i.removeChild(old);
		}

		i.appendChild(span);
		g.closeDialog();
		i.firstChild.focus();
	};