Overview
Comment:Assign automatically a type to a transaction if it was not specified in the CSV file
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk | stable
Files: files | file ages | folders
SHA3-256: 5f33bded63dbe6a959ac707594eff37b066295db0db6cdf7c5327b79acd6d6e9
User & Date: bohwaz on 2022-09-02 01:17:19
Other Links: manifest | tags
Context
2022-09-02
01:29
Fix default date when paying a debt was not formatted properly, fix [cc92d787154af38440ecfcc93a8b68815f406699] check-in: b1e7b09974 user: bohwaz tags: trunk, stable
01:17
Assign automatically a type to a transaction if it was not specified in the CSV file check-in: 5f33bded63 user: bohwaz tags: trunk, stable
2022-08-28
14:24
Fix: don't mark as invalid links to the homepage check-in: f25e3a6716 user: bohwaz tags: trunk, stable
Changes

Modified src/include/lib/Garradin/Accounting/Import.php from [119c7ad9c6] to [f47afbfa9c].

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

namespace Garradin\Accounting;

use Garradin\Entities\Accounting\Line;
use Garradin\Entities\Accounting\Transaction;
use Garradin\Entities\Accounting\Year;
use Garradin\CSV_Custom;
use Garradin\DB;
use Garradin\UserException;

class Import
{


































	/**
	 * Imports a CSV file of transactions in a year
	 * @param  string     $type    Type of CSV format
	 * @param  Year       $year    Target year where transactions should be updated or created
	 * @param  CSV_Custom $csv     CSV object
	 * @param  int        $user_id Current user ID, the one running the import
	 * @param  array      $options array of options
	 * @return ?array
	 */
	static public function import(string $type, Year $year, CSV_Custom $csv, int $user_id, array $options = []): ?array
	{
		$options_default = [
			'ignore_ids'      => false,
			'dry_run'         => false,
			'return_report'   => false,
		];

		$o = (object) array_merge($options_default, $options);



		if ($type != Export::GROUPED && $type != Export::SIMPLE && $type != Export::FEC) {
			throw new \InvalidArgumentException('Invalid type value');
		}

		if ($year->closed) {
			throw new \InvalidArgumentException('Closed year');













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


















>
>







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

namespace Garradin\Accounting;

use Garradin\Entities\Accounting\Line;
use Garradin\Entities\Accounting\Transaction;
use Garradin\Entities\Accounting\Year;
use Garradin\CSV_Custom;
use Garradin\DB;
use Garradin\UserException;

class Import
{
	static protected function saveImportedTransaction(Transaction $transaction, bool $dry_run = false, array &$report = null): void
	{
		if ($transaction->countLines() > 2) {
			$transaction->type = Transaction::TYPE_ADVANCED;
		}
		// Try to magically find out what kind of transaction this is
		elseif (!isset($transaction->type)) {
			$transaction->type = $transaction->findTypeFromAccounts();
		}

		if (!$dry_run) {
			if ($transaction->isModified()) {
				$transaction->save();
			}
		}
		else {
			$transaction->selfCheck();
		}

		if (null !== $report) {
			if (!$transaction->isModified()) {
				$target = 'unchanged';
			}
			elseif ($transaction->exists()) {
				$target = 'modified';
			}
			else {
				$target = 'created';
			}

			$report[$target][] = $transaction->asJournalArray();
		}
	}

	/**
	 * Imports a CSV file of transactions in a year
	 * @param  string     $type    Type of CSV format
	 * @param  Year       $year    Target year where transactions should be updated or created
	 * @param  CSV_Custom $csv     CSV object
	 * @param  int        $user_id Current user ID, the one running the import
	 * @param  array      $options array of options
	 * @return ?array
	 */
	static public function import(string $type, Year $year, CSV_Custom $csv, int $user_id, array $options = []): ?array
	{
		$options_default = [
			'ignore_ids'      => false,
			'dry_run'         => false,
			'return_report'   => false,
		];

		$o = (object) array_merge($options_default, $options);

		$dry_run = $o->dry_run;

		if ($type != Export::GROUPED && $type != Export::SIMPLE && $type != Export::FEC) {
			throw new \InvalidArgumentException('Invalid type value');
		}

		if ($year->closed) {
			throw new \InvalidArgumentException('Closed year');
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
		if ($o->return_report) {
			$report = ['created' => [], 'modified' => [], 'unchanged' => []];
		}
		else {
			$report = null;
		}

		$save_transaction = function (Transaction &$transaction) use ($o, &$report) {
			if (!is_null($report)) {
				if (!$transaction->isModified()) {
					$target = 'unchanged';
				}
				elseif ($transaction->exists()) {
					$target = 'modified';
				}
				else {
					$target = 'created';
				}

				$report[$target][] = $transaction->asJournalArray();
			}

			if ($transaction->countLines() > 2) {
				$transaction->type = Transaction::TYPE_ADVANCED;
			}

			if (!$o->dry_run) {
				if ($transaction->isModified()) {
					$transaction->save();
				}
			}
			else {
				$transaction->selfcheck();
			}

			$transaction = null;
		};

		$l = 1;

		try {
			$current_id = null;

			foreach ($csv->iterate() as $l => $row) {
				$row = (object) $row;







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







84
85
86
87
88
89
90































91
92
93
94
95
96
97
		if ($o->return_report) {
			$report = ['created' => [], 'modified' => [], 'unchanged' => []];
		}
		else {
			$report = null;
		}
































		$l = 1;

		try {
			$current_id = null;

			foreach ($csv->iterate() as $l => $row) {
				$row = (object) $row;
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
						&& empty($row->date)
						&& empty($row->notes)
						&& empty($row->reference)
					);

					// New transaction, save previous one
					if (null !== $transaction && $has_transaction) {

						$save_transaction($transaction);
					}

					if (!$has_transaction && null === $transaction) {
						throw new UserException('cette ligne n\'est reliée à aucune écriture');
					}
				}
				else {
					if (!empty($row->id) && $row->id != $current_id) {
						if (null !== $transaction) {

							$save_transaction($transaction);
						}

						$current_id = (int) $row->id;
					}

					if (empty($row->type)) {
						$row->type = Transaction::TYPES_NAMES[Transaction::TYPE_ADVANCED];
					}
				}

				// Find or create transaction
				if (null === $transaction) {
					if (!empty($row->id) && !$o->ignore_ids) {
						$transaction = Transactions::get((int)$row->id);








>
|









>
|




<
<
<
<







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
						&& empty($row->date)
						&& empty($row->notes)
						&& empty($row->reference)
					);

					// New transaction, save previous one
					if (null !== $transaction && $has_transaction) {
						self::saveImportedTransaction($transaction, $dry_run, $report);
						$transaction = null;
					}

					if (!$has_transaction && null === $transaction) {
						throw new UserException('cette ligne n\'est reliée à aucune écriture');
					}
				}
				else {
					if (!empty($row->id) && $row->id != $current_id) {
						if (null !== $transaction) {
							self::saveImportedTransaction($transaction, $dry_run, $report);
							$transaction = null;
						}

						$current_id = (int) $row->id;
					}




				}

				// Find or create transaction
				if (null === $transaction) {
					if (!empty($row->id) && !$o->ignore_ids) {
						$transaction = Transactions::get((int)$row->id);

147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
					}
					else {
						$transaction = new Transaction;
						$transaction->id_creator = $user_id;
						$transaction->id_year = $year->id();
					}

					if (!isset($types[$row->type])) {
						throw new UserException(sprintf('le type "%s" est inconnu', $row->type));
					}

					// FEC does not define type, so don't change it
					if (isset($row->type)) {
						$transaction->type = $types[$row->type];
					}







|







150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
					}
					else {
						$transaction = new Transaction;
						$transaction->id_creator = $user_id;
						$transaction->id_year = $year->id();
					}

					if (isset($row->type) && !isset($types[$row->type])) {
						throw new UserException(sprintf('le type "%s" est inconnu', $row->type));
					}

					// FEC does not define type, so don't change it
					if (isset($row->type)) {
						$transaction->type = $types[$row->type];
					}
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
						'debit'      => $row->amount,
						'id_account' => $debit_account,
					]);

					$transaction->addLine($l1);
					$transaction->addLine($l2);

					$save_transaction($transaction);
					$transaction = null;
				}
				else {
					$id_account = $accounts->getIdFromCode($row->account);

					if (!$id_account) {
						throw new UserException(sprintf('le compte "%s" n\'existe pas dans le plan comptable', $row->account));







|







226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
						'debit'      => $row->amount,
						'id_account' => $debit_account,
					]);

					$transaction->addLine($l1);
					$transaction->addLine($l2);

					self::saveImportedTransaction($transaction, $dry_run, $report);
					$transaction = null;
				}
				else {
					$id_account = $accounts->getIdFromCode($row->account);

					if (!$id_account) {
						throw new UserException(sprintf('le compte "%s" n\'existe pas dans le plan comptable', $row->account));
249
250
251
252
253
254
255

256
257
258
259
260
261
262
263
					$line = new Line;
					$line->importForm($data);
					$transaction->addLine($line);
				}
			}

			if (null !== $transaction) {

				$save_transaction($transaction);
			}
		}
		catch (UserException $e) {
			$db->rollback();
			$e->setMessage(sprintf('Erreur sur la ligne %d : %s', $l - 1, $e->getMessage()));

			if (null !== $transaction) {







>
|







252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
					$line = new Line;
					$line->importForm($data);
					$transaction->addLine($line);
				}
			}

			if (null !== $transaction) {
				self::saveImportedTransaction($transaction, $dry_run, $report);
				$transaction = null;
			}
		}
		catch (UserException $e) {
			$db->rollback();
			$e->setMessage(sprintf('Erreur sur la ligne %d : %s', $l - 1, $e->getMessage()));

			if (null !== $transaction) {

Modified src/include/lib/Garradin/Entities/Accounting/Transaction.php from [2ec4d6f85e] to [f348b1e49b].

104
105
106
107
108
109
110



















111
112
113
114
115
116
117
			case Account::TYPE_CASH:
			case Account::TYPE_OUTSTANDING:
				return self::TYPE_TRANSFER;
			default:
				return self::TYPE_ADVANCED;
		}
	}




















	public function getLinesWithAccounts(): array
	{
		$db = EntityManager::getInstance(Line::class)->DB();

		// Merge data from accounts with lines
		$accounts = [];







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







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
			case Account::TYPE_CASH:
			case Account::TYPE_OUTSTANDING:
				return self::TYPE_TRANSFER;
			default:
				return self::TYPE_ADVANCED;
		}
	}

	public function findTypeFromAccounts(): int
	{
		if (count($this->getLines()) != 2) {
			return self::TYPE_ADVANCED;
		}

		foreach ($this->getLinesWithAccounts() as $line) {
			if ($line->account_position == Account::REVENUE && $line->credit) {
				return self::TYPE_REVENUE;
			}
			elseif ($line->account_position == Account::EXPENSE && $line->debit) {
				return self::TYPE_EXPENSE;
			}
		}

		// Did not find a expense/revenue account: fall back to advanced
		return self::TYPE_ADVANCED;
	}

	public function getLinesWithAccounts(): array
	{
		$db = EntityManager::getInstance(Line::class)->DB();

		// Merge data from accounts with lines
		$accounts = [];

Modified src/www/admin/acc/years/export.php from [ff77b91d6e] to [f39aed5a9b].

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
$types = [
	Export::FULL => [
		'label' => 'Complet (comptabilité d\'engagement)',
		'help' => '(Conseillé pour transfert vers un autre logiciel) Chaque ligne reprend toutes les informations de la ligne et de l\'écriture.',
	],
	Export::GROUPED => [
		'label' => 'Complet groupé',
		'help' => 'Idem, sauf que les lignes suivantes ne mentionnent pas les informations de l\'écriture.',
	],
	Export::SIMPLE => [
		'label' => 'Simplifié (comptabilité de trésorerie)',
		'help' => 'Les écritures avancées ne sont pas inclues dans cet export.',
	],
	Export::FEC => [
		'label' => 'FEC (Fichier des Écritures Comptables)',







|







34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
$types = [
	Export::FULL => [
		'label' => 'Complet (comptabilité d\'engagement)',
		'help' => '(Conseillé pour transfert vers un autre logiciel) Chaque ligne reprend toutes les informations de la ligne et de l\'écriture.',
	],
	Export::GROUPED => [
		'label' => 'Complet groupé',
		'help' => 'Les colonnes de l\'écriture ne sont pas répétées pour chaque ligne.',
	],
	Export::SIMPLE => [
		'label' => 'Simplifié (comptabilité de trésorerie)',
		'help' => 'Les écritures avancées ne sont pas inclues dans cet export.',
	],
	Export::FEC => [
		'label' => 'FEC (Fichier des Écritures Comptables)',