Comment: | Merge with trunk |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | dev |
Files: | files | file ages | folders |
SHA3-256: |
3ca70608d95e21f3ed6daf06c8922b80 |
User & Date: | bohwaz on 2022-11-08 00:42:36 |
Other Links: | branch diff | manifest | tags |
2022-11-08
| ||
00:43 | Merge missing file check-in: a52d9558be user: bohwaz tags: dev | |
00:42 | Merge with trunk check-in: 3ca70608d9 user: bohwaz tags: dev | |
00:25 | Move migrations check-in: 443e5220c8 user: bohwaz tags: dev | |
2022-11-07
| ||
23:52 | Don't allow to change the country of a chart unless it's NULL check-in: 0d3c679467 user: bohwaz tags: trunk, stable, 1.2.1 | |
Modified src/VERSION from [988e9a1ff9] to [98db9e6a5b].
Added src/include/data/charts/ch_asso.csv version [e384d6a130].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 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 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | Numéro,Libellé,Description,Position,Favori 1,Actifs,,Actif, 10,Liquidités,,Actif, 100,Caisses,,Actif, 1000,Caisse,,Actif,Favori 1005,Caisse Euro,,Actif,Favori 1010,CCP,,Actif, 102,Banques,,Actif, 1020,Compte bancaire,,Actif,Favori 1030,Dépôts à court terme (< 3 mois),,Actif, 109,Comptes d’attente,,Actif, 1090,Dépôts en attente,,Actif,Favori 11,Titres cotés en bourse et détenus à court terme,,Actif, 1100,Titres,,Actif, 12,Débiteurs,,Actif, 120,Débiteurs résultant de la vente de biens et de prestations de services,,Actif, 1200,Débiteurs résultant de la vente de biens et de prestations de services,,Actif, 125,Autres débiteurs,,Actif, 1250,Autres débiteurs,,Actif, 13,Stocks,,Actif, 1300,Stocks divers,,Actif, 14,Actifs transitoires (comptes de régularisation d’actifs),,Actif, 1400,Impôts anticipés,,Actif, 1410,Produits à recevoir,,Actif, 1420,Charges payées d'avance,,Actif, 15,Immobilisations financières,,Actif, 1500,Cautions loyers,,Actif, 16,Participations,,Actif, 1600,Participations dans d’autres entités,,Actif, 17,Immobilisations corporelles,,Actif, 1700,Mobilier,,Actif, 1710,Informatique,,Actif, 18,Immobilisations incorporelles,,Actif, 1800,Licences informatiques,,Actif, 2,Passifs,,Passif, 20,Créanciers,,Passif, 2000,Fournisseurs,,Passif, 2050,Autres dettes à court terme,,Passif, 21,Dettes à court terme liées aux charges de personnel,,Passif, 2100,Créanciers caisse AVS,,Passif, 2110,Créanciers LAA,,Passif, 2120,Créanciers LPP,,Passif, 2130,Créanciers impôt source,,Passif, 2140,Créanciers assurance indemnités journalières maladie,,Passif, 2180,Salaires à payer,,Passif, 22,Dettes financières à court terme,,Passif, 2200,Emprunts bancaires à rembourser dans l'année,,Passif, 23,Passifs transitoires (comptes de régularisation de passifs),,Passif, 2310,Produits constatés d'avance,,Passif, 2320,Charges à payer,,Passif, 24,Dettes à long terme,,Passif, 2400,Emprunts bancaires à rembourser à plus d'une année,,Passif, 2410,Autres dettes à long terme,,Passif, 26,Provisions,,Passif, 28,Fonds affectés (Swiss GAAP RPC),À subdiviser par projet,Passif, 29,Fonds propres,,Passif, 2900,Capital initial versé (ou capital de fondation),,Passif, 2910,Réserve associative,,Passif, 2990,Résultats reportés (fonds libres),,Passif, 2999,Résultat de l'exercice,,Passif, 29991,Résultat positif,,Passif, 29999,Résultat négatif,,Passif, 3,Charges,,Charge, 30,Charges directes de projets,À subdiviser par projet,Charge, 31,Charges de personnel,,Charge, 310,Salaires,,Charge, 3100,Salaires,,Charge,Favori 311,Charges sociales,,Charge, 3110,AVS-AI-AC-AMAT,,Charge, 3111,LAA professionnelle,,Charge, 3112,LAA complémentaire,,Charge, 3113,Assurance indemnités journalières maladie,,Charge, 3114,LPP,,Charge, 312,Autres charges de personnel,,Charge, 3120,Frais de formation,,Charge,Favori 3128,Autres frais de personnel,,Charge, 315,Compensations charges de personnel (revenus présentés en déduction des charges de personnel),,Charge, 3150,Indemnités d'assurance pour charges du personnel,,Charge, 3151,Mise à disposition de personnes,,Charge, 3152,Commission perception IS,,Charge, 3159,Autres compensations charges salariales,,Charge, 32,Charges de locaux,,Charge, 3200,Loyers,,Charge,Favori 3210,"Charges – Eau, gaz, électricité ",,Charge,Favori 3220,Frais d'entretiens locaux,,Charge,Favori 3230,Assurance RC et choses,,Charge, 3280,Frais de location de salles,,Charge,Favori 33,Administration et informatique,,Charge, 330,Administration,,Charge, 3300,Fournitures de bureau,,Charge,Favori 3301,Télécommunications,,Charge,Favori 3302,Frais de port,,Charge,Favori 3303,Documents et abonnements,,Charge, 3304,Frais de cotisations,,Charge, 3306,Frais de réunion,,Charge, 335,Informatique,,Charge, 3350,Frais de licence,Par exemple pour décompter la contribution à un logiciel comptable ;-),Charge,Favori 3351,Frais de maintenance,,Charge, 3352,Petit matériel informatique,,Charge,Favori 34,Frais de promotion et de représentation,,Charge, 340,Matériel de promotion,,Charge, 3400,Impression de matériel de promotion,,Charge, 3401,Conception de matériel de promotion,,Charge, 3409,Autre matériel de promotion,,Charge, 341,Site internet et communication en ligne,,Charge, 3410,"Frais de maintenance de site internet, plateforme web, outils de communication en ligne",,Charge, 3411,"Conception de site internet, plateforme web, outils de communication en ligne",,Charge, 3419,Autres frais liés à la communication en ligne,,Charge, 346,Frais de représentation,,Charge, 3460,Frais de voyage,,Charge, 3461,Frais de repas,,Charge,Favori 3469,Autres frais de représentation,,Charge, 35,Mises à disposition gratuites (contrepartie : subventions non monétaires en 45),,Charge, 36,Autres charges d’exploitation,,Charge, 360,Amortissements et dépréciations d’actifs,,Charge, 3600,Amortissements,,Charge, 3601,Dépréciations d’actifs,,Charge, 361,Charges sur débiteurs douteux,,Charge, 3610,Variation provision sur débiteurs douteux,,Charge, 3620,Pertes sur débiteurs douteux,,Charge, 369,Autres charges d’exploitation,,Charge, 3690,Autres charges d’exploitation,,Charge, 37,"Charges hors exploitation, exceptionnelles, uniques ou hors périodes",,Charge, 370,Charges hors exploitation,,Charge, 3700,Charges hors exploitation,,Charge, 371,Charges exceptionnelles ou uniques,,Charge, 3710,Charges exceptionnelles ou uniques,,Charge, 372,Charges hors périodes,,Charge, 3720,Charges liées aux exercices précédents,,Charge, 38,Charges financières,,Charge, 3800,Charges d'intérêts,,Charge, 3810,Frais bancaires,,Charge,Favori 3820,Pertes de change,,Charge, 39,Variation des fonds affectés (Swiss GAAP RPC),,Charge, 3900,Attribution aux fonds affectés,,Charge, 3910,Produits internes de fonds affectés,,Produit, 4,Revenus,,Produit, 40,Revenus de ventes et de prestations,,Produit, 4000,Revenu de prestations,,Produit,Favori 4010,Revenu de ventes,,Produit,Favori 41,Revenus des fonds affectés,,Produit, 410,Subventions,,Produit,Favori 4130,Donateurs privés – fonds affectés,,Produit, 42,Dons non affectés,,Produit, 4220,Donateurs privés – non affectés,,Produit, 43,Cotisations de membres,,Produit, 4400,Cotisations de membres,,Produit,Favori 45,Subventions non monétaires (contrepartie : mises à disposition gratuites en 35),,Produit, 46,Autres produits d’exploitation,,Produit, 4600,Dissolutions de provisions,,Produit, 47,"Produits hors exploitation, exceptionnels, uniques ou hors périodes ",,Produit, 470,Produits hors exploitation,,Produit, 4700,Produits hors exploitation,,Produit, 471,Produits exceptionnels ou uniques,,Produit, 4710,Produits exceptionnels ou uniques,,Produit, 472,Produits hors période,,Produit, 4720,Produits liés aux exercices précédents,,Produit, 48,Revenus financiers,,Produit, 4800,Revenus d'intérêts,,Produit, 4820,Gains de change,,Produit, 49,Variation des fonds affectés (Swiss GAAP RPC),,Produit, 4900,Utilisation des fonds affectés,,Produit, 4910,Charges internes de fonds affectés,,Charge, 5,Comptes de tiers,,Actif ou passif, 9,Bilan,,, 9100,Bilan d'ouverture,,, 9101,Bilan de clôture,,, |
Modified src/include/lib/Garradin/Accounting/Accounts.php from [b8a87f7a26] to [8a6afef628].
︙ | ︙ | |||
82 83 84 85 86 87 88 | 'description' => [ 'label' => '', 'order' => null, ], 'level' => [ 'select' => 'CASE WHEN LENGTH(code) >= 6 THEN 6 ELSE LENGTH(code) END', ], | | | > > > > > > > > > > > | 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 | 'description' => [ 'label' => '', 'order' => null, ], 'level' => [ 'select' => 'CASE WHEN LENGTH(code) >= 6 THEN 6 ELSE LENGTH(code) END', ], 'report' => [ 'label' => ' ', 'select' => null, ], 'position' => [ 'label' => 'Position', ], 'user' => [ 'label' => 'Ajouté', ], 'bookmark' => [ 'label' => 'Favori', ], ]; $tables = 'acc_accounts'; $conditions = 'id_chart = ' . $this->chart_id; if (!empty($types)) { $types = array_map('intval', $types); $conditions .= ' AND ' . DB::getInstance()->where('type', $types); } $list = new DynamicList($columns, $tables, $conditions); $list->orderBy('code', false); $list->setPageSize(null); $list->setModifier(function (&$row) { $row->position_report = !$row->position ? '' : ($row->position <= Account::ASSET_OR_LIABILITY ? 'Bilan' : 'Résultat'); $row->position_name = Account::POSITIONS_NAMES[$row->position]; }); return $list; } public function listAll(array $types = null): array { $condition = ''; |
︙ | ︙ | |||
130 131 132 133 134 135 136 | /** * List common accounts, grouped by type * @return array */ public function listCommonGrouped(array $types = null): array { if (null === $types) { | > > | > > > | > > > > > > > > > > | | > > | > > > > > > > > > | > > > > > | | | | 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 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 | /** * List common accounts, grouped by type * @return array */ public function listCommonGrouped(array $types = null): array { if (null === $types) { // If we want all types, then we will get used or bookmarked accounts in common types // and only bookmarked accounts for other types, grouped in "Others" $target = Account::COMMON_TYPES; } else { $target = $types; } $out = []; foreach ($target as $type) { $out[$type] = (object) [ 'label' => Account::TYPES_NAMES[$type], 'type' => $type, 'accounts' => [], ]; } if (null === $types) { $out[0] = (object) [ 'label' => 'Autres', 'type' => 0, 'accounts' => [], ]; } $db = $this->em->DB(); $sql = sprintf('SELECT a.* FROM @TABLE a LEFT JOIN acc_transactions_lines b ON b.id_account = a.id WHERE a.id_chart = %d AND ((a.%s AND (a.bookmark = 1 OR b.id IS NOT NULL)) %s) GROUP BY a.id ORDER BY type, code COLLATE NOCASE;', $this->chart_id, $db->where('type', $target), (null === $types) ? 'OR (a.bookmark = 1)' : '' ); $query = $this->em->iterate($sql); foreach ($query as $row) { $t = in_array($row->type, $target) ? $row->type : 0; $out[$t]->accounts[] = $row; } foreach ($out as $key => $v) { if (!count($v->accounts)) { unset($out[$key]); } } return $out; } /** * List accounts from this type that are missing in current "usual" accounts list */ public function listMissing(int $type): array { if ($type != Account::TYPE_EXPENSE && $type != Account::TYPE_REVENUE && $type != Account::TYPE_THIRD_PARTY) { return []; } return $this->em->DB()->get($this->em->formatQuery('SELECT a.*, CASE WHEN LENGTH(a.code) >= 6 THEN 6 ELSE LENGTH(a.code) END AS level FROM @TABLE a LEFT JOIN acc_transactions_lines b ON b.id_account = a.id WHERE a.id_chart = ? AND a.type = ? AND NOT (a.bookmark = 1 OR a.user = 1 OR b.id IS NOT NULL) GROUP BY a.id ORDER BY type, code COLLATE NOCASE;'), $this->chart_id, $type); } public function countByType(int $type): int { return DB::getInstance()->count(Account::TABLE, 'id_chart = ? AND type = ?', $this->chart_id, $type); } public function getSingleAccountForType(int $type) { return DB::getInstance()->first('SELECT * FROM acc_accounts WHERE type = ? AND id_chart = ? LIMIT 1;', $type, $this->chart_id); } public function getIdForType(int $type): ?int { return DB::getInstance()->firstColumn('SELECT id FROM acc_accounts WHERE type = ? AND id_chart = ? LIMIT 1;', $type, $this->chart_id); } public function getOpeningAccountId(): ?int { return $this->getIdForType(Account::TYPE_OPENING); } public function getClosingAccountId(): ?int { return $this->getIdForType(Account::TYPE_CLOSING); } public function listUserAccounts(int $year_id): DynamicList { $columns = [ 'id' => [ 'select' => 'u.id', |
︙ | ︙ |
Modified src/include/lib/Garradin/Accounting/AdvancedSearch.php from [b700397268] to [ab39cd6bf3].
︙ | ︙ | |||
221 222 223 224 225 226 227 | 'desc' => true, ]; } public function schema(): array { $db = DB::getInstance(); | | | 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | 'desc' => true, ]; } public function schema(): array { $db = DB::getInstance(); $sql = sprintf('SELECT name, sql FROM sqlite_master WHERE %s ORDER BY name;', $db->where('name', ['acc_transactions', 'acc_transactions_lines', 'acc_accounts', 'acc_years', 'acc_projects'])); return $db->getAssoc($sql); } public function make(string $query): DynamicList { $tables = 'acc_transactions AS t INNER JOIN acc_transactions_lines AS l ON l.id_transaction = t.id |
︙ | ︙ |
Modified src/include/lib/Garradin/Accounting/Charts.php from [9d142b0f30] to [4110a572c9].
︙ | ︙ | |||
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 | const BUNDLED_CHARTS = [ 'fr_pca_1999' => 'Plan comptable associatif 1999', 'fr_pca_2018' => 'Plan comptable associatif 2020 (Règlement ANC n°2018-06)', 'fr_pcg_2014' => 'Plan comptable général, pour entreprises (Règlement ANC n° 2014-03, consolidé 1er janvier 2019)', 'fr_cse_2015' => 'Plan comptable des CSE (Comité Social et Économique) (Règlement ANC n°2015-01)', 'fr_pcc_2020' => 'Plan comptable des copropriétés (2005 révisé en 2020)', 'be_pcmn_2019' => 'Plan comptable minimum normalisé des associations et fondations 2019', ]; static public function updateInstalled(string $chart_code): ?Chart { $file = sprintf('%s/include/data/charts/%s.csv', ROOT, $chart_code); $country = strtoupper(substr($chart_code, 0, 2)); $code = strtoupper(substr($chart_code, 3)); $chart = EntityManager::findOne(Chart::class, 'SELECT * FROM @TABLE WHERE code = ? AND country = ?;', $code, $country); if (!$chart) { return null; } $chart->importCSV($file, true); return $chart; } static public function install(string $chart_code): Chart { if (!array_key_exists($chart_code, self::BUNDLED_CHARTS)) { throw new \InvalidArgumentException('Le plan comptable demandé n\'existe pas.'); } | > > > > > > > > > > | 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 | const BUNDLED_CHARTS = [ 'fr_pca_1999' => 'Plan comptable associatif 1999', 'fr_pca_2018' => 'Plan comptable associatif 2020 (Règlement ANC n°2018-06)', 'fr_pcg_2014' => 'Plan comptable général, pour entreprises (Règlement ANC n° 2014-03, consolidé 1er janvier 2019)', 'fr_cse_2015' => 'Plan comptable des CSE (Comité Social et Économique) (Règlement ANC n°2015-01)', 'fr_pcc_2020' => 'Plan comptable des copropriétés (2005 révisé en 2020)', 'be_pcmn_2019' => 'Plan comptable minimum normalisé des associations et fondations 2019', 'ch_asso' => 'Plan comptable associatif', ]; static public function updateInstalled(string $chart_code): ?Chart { $file = sprintf('%s/include/data/charts/%s.csv', ROOT, $chart_code); $country = strtoupper(substr($chart_code, 0, 2)); $code = strtoupper(substr($chart_code, 3)); $chart = EntityManager::findOne(Chart::class, 'SELECT * FROM @TABLE WHERE code = ? AND country = ?;', $code, $country); if (!$chart) { return null; } $chart->importCSV($file, true); return $chart; } static public function resetRules(array $country_list): void { foreach (self::list() as $c) { if (in_array($c->country, $country_list)) { $c->resetAccountsRules(); } } } static public function install(string $chart_code): Chart { if (!array_key_exists($chart_code, self::BUNDLED_CHARTS)) { throw new \InvalidArgumentException('Le plan comptable demandé n\'existe pas.'); } |
︙ | ︙ | |||
104 105 106 107 108 109 110 | { $where = $filter_archived ? ' AND archived = 0' : ''; $sql = sprintf('SELECT id, country, label FROM %s WHERE 1 %s ORDER BY country, code DESC, label;', Chart::TABLE, $where); $list = DB::getInstance()->getGrouped($sql); $out = []; foreach ($list as $row) { | | | 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | { $where = $filter_archived ? ' AND archived = 0' : ''; $sql = sprintf('SELECT id, country, label FROM %s WHERE 1 %s ORDER BY country, code DESC, label;', Chart::TABLE, $where); $list = DB::getInstance()->getGrouped($sql); $out = []; foreach ($list as $row) { $country = $row->country ? Utils::getCountryName($row->country) : 'Aucun'; if (!array_key_exists($country, $out)) { $out[$country] = []; } $out[$country][$row->id] = $row->label; } |
︙ | ︙ |
Modified src/include/lib/Garradin/Accounting/Graph.php from [68d1ce90b8] to [fd86b5931f].
︙ | ︙ | |||
49 50 51 52 53 54 55 | ], 'debts' => [ 'Comptes de tiers' => ['type' => Account::TYPE_THIRD_PARTY], ], ]; const PIE_TYPES = [ | | | 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | ], 'debts' => [ 'Comptes de tiers' => ['type' => Account::TYPE_THIRD_PARTY], ], ]; const PIE_TYPES = [ 'revenue' => ['position' => Account::REVENUE, 'exclude_type' => Account::TYPE_VOLUNTEERING_REVENUE], 'expense' => ['position' => Account::EXPENSE, 'exclude_type' => Account::TYPE_VOLUNTEERING_EXPENSE], 'assets' => ['type' => [Account::TYPE_BANK, Account::TYPE_CASH, Account::TYPE_OUTSTANDING]], ]; const WEEKLY_INTERVAL = 604800; // 7 days const MONTHLY_INTERVAL = 2635200; // 1 month |
︙ | ︙ | |||
235 236 237 238 239 240 241 | $c1 = $config->get('color1') ?: ADMIN_COLOR1; $c2 = $config->get('color2') ?: ADMIN_COLOR2; list($h, $s, $v) = Utils::rgbToHsv($c1); list($h1, $s, $v) = Utils::rgbToHsv($c2); $colors = []; | | | 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 | $c1 = $config->get('color1') ?: ADMIN_COLOR1; $c2 = $config->get('color2') ?: ADMIN_COLOR2; list($h, $s, $v) = Utils::rgbToHsv($c1); list($h1, $s, $v) = Utils::rgbToHsv($c2); $colors = []; for ($i = 0; $i < 5; $i++) { if ($i % 2 == 0) { $s = $v = 50; $h =& $h1; } else { $s = $v = 70; $h =& $h2; |
︙ | ︙ |
Modified src/include/lib/Garradin/Accounting/Reports.php from [22353fbb2e] to [ee934a3c82].
︙ | ︙ | |||
33 34 35 36 37 38 39 40 41 42 43 44 45 46 | $where[] = sprintf($accounts_alias . 'position NOT IN (%s)', implode(',', $criterias['exclude_position'])); } if (!empty($criterias['type'])) { $criterias['type'] = array_map('intval', (array)$criterias['type']); $where[] = sprintf($accounts_alias . 'type IN (%s)', implode(',', $criterias['type'])); } if (!empty($criterias['exclude_type'])) { $criterias['exclude_type'] = array_map('intval', (array)$criterias['exclude_type']); $where[] = sprintf($accounts_alias . 'type NOT IN (%s)', implode(',', $criterias['exclude_type'])); } if (!empty($criterias['user'])) { | > > > > > | 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | $where[] = sprintf($accounts_alias . 'position NOT IN (%s)', implode(',', $criterias['exclude_position'])); } if (!empty($criterias['type'])) { $criterias['type'] = array_map('intval', (array)$criterias['type']); $where[] = sprintf($accounts_alias . 'type IN (%s)', implode(',', $criterias['type'])); } if (!empty($criterias['type_or_bookmark'])) { $criterias['type'] = array_map('intval', (array)$criterias['type_or_bookmark']); $where[] = sprintf('(%stype IN (%s) OR %1$sbookmark = 1)', $accounts_alias, implode(',', $criterias['type'])); } if (!empty($criterias['exclude_type'])) { $criterias['exclude_type'] = array_map('intval', (array)$criterias['exclude_type']); $where[] = sprintf($accounts_alias . 'type NOT IN (%s)', implode(',', $criterias['exclude_type'])); } if (!empty($criterias['user'])) { |
︙ | ︙ | |||
440 441 442 443 444 445 446 | $out->foot_right = [self::getTotalLine($out->body_right, 'Total passif')]; return $out; } /** * Return list of favorite accounts (accounts with a type), grouped by type, with their current sum | | | | | | | > | | > | < | | | | | | | > | < | < | 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 | $out->foot_right = [self::getTotalLine($out->body_right, 'Total passif')]; return $out; } /** * Return list of favorite accounts (accounts with a type), grouped by type, with their current sum * @return array list of accounts grouped by type */ static public function getClosingSumsFavoriteAccounts(array $criterias): array { $types = Account::COMMON_TYPES; $accounts = self::getAccountsBalances($criterias + ['type_or_bookmark' => $types], 'type, code COLLATE NOCASE', false); $out = []; foreach ($types as $type) { $out[$type] = (object) [ 'label' => Account::TYPES_NAMES[$type], 'type' => $type, 'accounts' => [], ]; } $out[0] = (object) [ 'label' => 'Autres', 'type' => 0, 'accounts' => [], ]; foreach ($accounts as $row) { $t = in_array($row->type, $types, true) ? $row->type : 0; $out[$t]->accounts[] = $row; } return $out; } /** * Grand livre */ static public function getGeneralLedger(array $criterias): \Generator { |
︙ | ︙ |
Modified src/include/lib/Garradin/Accounting/Transactions.php from [226bbcc6e1] to [d89e888046].
︙ | ︙ | |||
166 167 168 169 170 171 172 173 174 175 176 177 | if (isset($row->type_label)) { $row->type_label = Transaction::TYPES_NAMES[(int)$row->type_label]; } }); $list->setExportCallback(function (&$row) { $row->change = Utils::money_format($row->change, '.', '', false); $row->projects = implode(', ', $row->projects); }); return $list; } } | > | 166 167 168 169 170 171 172 173 174 175 176 177 178 | if (isset($row->type_label)) { $row->type_label = Transaction::TYPES_NAMES[(int)$row->type_label]; } }); $list->setExportCallback(function (&$row) { $row->change = Utils::money_format($row->change, '.', '', false); $row->projects = implode(', ', $row->projects); unset($row->project_code, $row->id_project); }); return $list; } } |
Modified src/include/lib/Garradin/Accounting/Years.php from [ff8b86dbf1] to [cb467ec56a].
︙ | ︙ | |||
119 120 121 122 123 124 125 | /** * Crée une écriture d'affectation automatique * @param Year $year * @return Transaction|null */ static public function makeAppropriation(Year $year): ?Transaction { | > | | < < | | < < < < < < < | < | | | > | 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 182 183 184 | /** * Crée une écriture d'affectation automatique * @param Year $year * @return Transaction|null */ static public function makeAppropriation(Year $year): ?Transaction { $db = DB::getInstance(); $balances = $db->getGrouped('SELECT a.type, a.id, SUM(l.credit) - SUM(l.debit) AS balance FROM acc_accounts a INNER JOIN acc_transactions_lines l ON l.id_account = a.id INNER JOIN acc_transactions t ON t.id = l.id_transaction WHERE t.id_year = ? AND (a.type = ? OR a.type = ?) GROUP BY a.type;', $year->id, Account::TYPE_NEGATIVE_RESULT, Account::TYPE_POSITIVE_RESULT ); if (!count($balances)) { return null; } $appropriation_account = $db->firstColumn('SELECT id FROM acc_accounts WHERE type = ? AND id_chart = ?;', Account::TYPE_APPROPRIATION_RESULT, $year->id_chart); if (!$appropriation_account) { return null; } $t = new Transaction; $t->type = $t::TYPE_ADVANCED; $t->id_year = $year->id(); $t->label = 'Affectation automatique du résultat'; $t->notes = 'Le résultat a été affecté automatiquement lors de la balance d\'ouverture'; $t->date = $year->start_date; $sum = 0; if (!empty($balances[Account::TYPE_NEGATIVE_RESULT])) { $account = $balances[Account::TYPE_NEGATIVE_RESULT]; $line = Line::create($account->id, abs($account->balance), 0); $t->addLine($line); $sum += $account->balance; } if (!empty($balances[Account::TYPE_POSITIVE_RESULT])) { $account = $balances[Account::TYPE_POSITIVE_RESULT]; $line = Line::create($account->id, 0, abs($account->balance)); $t->addLine($line); $sum -= $account->balance; } if ($sum > 0) { $line = Line::create($appropriation_account, $sum, 0); } else { $line = Line::create($appropriation_account, 0, abs($sum)); } $t->addLine($line); return $t; } } |
Modified src/include/lib/Garradin/Entities/Accounting/Account.php from [0f59f5f876] to [88c4f5f3ae].
︙ | ︙ | |||
131 132 133 134 135 136 137 138 139 140 141 142 143 144 | '^7' => self::REVENUE, '^5' => self::ASSET_OR_LIABILITY, '^4' => self::ASSET_OR_LIABILITY, '^3' => self::ASSET, '^2' => self::ASSET, '^1' => self::LIABILITY, ], ]; /** * Codes that should be enforced according to type (and vice-versa) */ const LOCAL_TYPES = [ 'FR' => [ | > > > > > > > | 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | '^7' => self::REVENUE, '^5' => self::ASSET_OR_LIABILITY, '^4' => self::ASSET_OR_LIABILITY, '^3' => self::ASSET, '^2' => self::ASSET, '^1' => self::LIABILITY, ], 'CH' => [ '^1' => self::ASSET, '^2' => self::LIABILITY, '^3(?!910)|^4910' => self::EXPENSE, '^4(?!910)|^3910' => self::REVENUE, '^5' => self::ASSET_OR_LIABILITY, ], ]; /** * Codes that should be enforced according to type (and vice-versa) */ const LOCAL_TYPES = [ 'FR' => [ |
︙ | ︙ | |||
169 170 171 172 173 174 175 176 177 178 179 180 181 182 | self::TYPE_REVENUE => '7', self::TYPE_POSITIVE_RESULT => '692', self::TYPE_NEGATIVE_RESULT => '690', self::TYPE_THIRD_PARTY => '4', self::TYPE_OPENING => '890', self::TYPE_CLOSING => '891', ], ]; const LIST_COLUMNS = [ 'id' => [ 'select' => 't.id', 'label' => 'N°', ], | > > > > > > > > > > > > > > > | 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | self::TYPE_REVENUE => '7', self::TYPE_POSITIVE_RESULT => '692', self::TYPE_NEGATIVE_RESULT => '690', self::TYPE_THIRD_PARTY => '4', self::TYPE_OPENING => '890', self::TYPE_CLOSING => '891', ], 'CH' => [ self::TYPE_BANK => '102', self::TYPE_CASH => '100', self::TYPE_OUTSTANDING => '109', self::TYPE_THIRD_PARTY => '5', self::TYPE_EXPENSE => '3', self::TYPE_REVENUE => '4', self::TYPE_OPENING => '9100', self::TYPE_CLOSING => '9101', self::TYPE_POSITIVE_RESULT => '29991', self::TYPE_NEGATIVE_RESULT => '29999', self::TYPE_APPROPRIATION_RESULT => '2910', self::TYPE_CREDIT_REPORT => '2990', self::TYPE_DEBIT_REPORT => '2990', ], ]; const LIST_COLUMNS = [ 'id' => [ 'select' => 't.id', 'label' => 'N°', ], |
︙ | ︙ | |||
225 226 227 228 229 230 231 | 'select' => 'l.reference', ], 'id_project' => [ 'select' => 'l.id_project', ], 'project_code' => [ 'select' => 'IFNULL(p.code, SUBSTR(p.label, 1, 10) || \'…\')', | < < < > > > > | | > > < > > > | > | | > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | > > > > | > | > > | > | | | | < | | | | > < | | | | < < < < | < < | | 247 248 249 250 251 252 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 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 | 'select' => 'l.reference', ], 'id_project' => [ 'select' => 'l.id_project', ], 'project_code' => [ 'select' => 'IFNULL(p.code, SUBSTR(p.label, 1, 10) || \'…\')', 'label' => 'Projet', ], 'status' => [ 'select' => 't.status', ], ]; protected ?int $id; protected int $id_chart; protected string $code; protected string $label; protected ?string $description; protected int $position = 0; protected int $type; protected bool $user = false; protected bool $bookmark = false; protected $_position = []; protected ?Chart $_chart = null; static protected ?array $_charts; public function selfCheck(): void { $db = DB::getInstance(); $this->assert(trim($this->code) !== '', 'Le numéro de compte ne peut rester vide.'); $this->assert(trim($this->label) !== '', 'L\'intitulé de compte ne peut rester vide.'); // Only enforce code limits if the account is new, or if the code is changed if (!$this->exists() || $this->isModified('code')) { $this->assert(strlen($this->code) <= 20, 'Le numéro de compte ne peut faire plus de 20 caractères.'); $this->assert(preg_match('/^[a-z0-9_]+$/i', $this->code), 'Le numéro de compte ne peut comporter que des lettres et des chiffres.'); } $this->assert(strlen($this->label) <= 200, 'L\'intitulé de compte ne peut faire plus de 200 caractères.'); $this->assert(!isset($this->description) || strlen($this->description) <= 2000, 'La description de compte ne peut faire plus de 2000 caractères.'); $this->assert(!empty($this->id_chart), 'Aucun plan comptable lié'); $where = 'code = ? AND id_chart = ?'; $where .= $this->exists() ? sprintf(' AND id != %d', $this->id()) : ''; if ($db->test(self::TABLE, $where, $this->code, $this->id_chart)) { throw new ValidationException(sprintf('Le numéro "%s" est déjà utilisé par un autre compte.', $this->code)); } $this->assert(isset($this->type)); $this->checkLocalRules(); $this->assert(array_key_exists($this->type, self::TYPES_NAMES), 'Type invalide: ' . $this->type); $this->assert(array_key_exists($this->position, self::POSITIONS_NAMES), 'Position invalide'); parent::selfCheck(); } protected function getCountry(): ?string { if (!isset(self::$_charts)) { self::$_charts = DB::getInstance()->getGrouped('SELECT id, country, code FROM acc_charts;'); } return self::$_charts[$this->id_chart]->country ?? null; } protected function isChartOfficial(): bool { $country = $this->getCountry(); return !empty(self::$_charts[$this->id_chart]->code); } /** * This sets the account position according to local rules * if the chart is linked to a country, but only * if the account is user-created, or if the chart is non-official */ protected function getLocalPosition(string $country = null): ?int { if (!func_num_args()) { $country = $this->getCountry(); } $is_official = $this->isChartOfficial(); if (!$country) { return null; } // Do not change position of official chart accounts if (!$this->user && $is_official) { return null; } foreach (self::LOCAL_POSITIONS[$country] as $pattern => $position) { if (preg_match('/' . $pattern . '/', $this->code)) { return $position; } } return null; } protected function getLocalType(string $country = null): int { if (!func_num_args()) { $country = $this->getCountry(); } if (!$country) { return self::TYPE_NONE; } foreach (self::LOCAL_TYPES[$country] as $type => $number) { if ($this->matchType($type, $country)) { return $type; } } return self::TYPE_NONE; } protected function matchType(int $type, string $country = null): bool { if (func_num_args() < 2) { $country = $this->getCountry(); } $pattern = self::LOCAL_TYPES[$country][$type] ?? null; if (!$pattern) { return false; } if (in_array($type, self::COMMON_TYPES)) { $pattern = sprintf('/^%s.+/', $pattern); } else { $pattern = sprintf('/^%s$/', $pattern); } return (bool) preg_match($pattern, $this->code); } public function setLocalRules(string $country = null): void { if (!func_num_args()) { $country = $this->getCountry(); } if (!$country) { $this->set('type', 0); return; } $this->set('type', $this->getLocalType($country)); if (null !== ($p = $this->getLocalPosition($country))) { // If the allowed local position is asset OR liability, we allow either one of those 3 choices if ($p != self::ASSET_OR_LIABILITY || !in_array($this->position, [self::ASSET_OR_LIABILITY, self::ASSET, self::LIABILITY])) { $this->set('position', $p); } } if (!isset($this->type)) { $this->set('type', 0); } } public function checkLocalRules(): void { $country = $this->getCountry(); if (!$this->type) { return; } if (!isset(self::LOCAL_TYPES[$country][$this->type])) { return; } $this->assert($this->matchType($this->type), sprintf('Compte "%s - %s" : le numéro des comptes de type "%s" doit commencer par "%s" (%s).', $this->code, $this->label, self::TYPES_NAMES[$this->type], self::LOCAL_TYPES[$country][$this->type], $this->code)); } public function getNewNumberAvailable(?string $base = null): ?string { $base ??= $this->getNumberBase(); if (!$base) { |
︙ | ︙ | |||
704 705 706 707 708 709 710 711 712 713 714 715 716 717 | return DB::getInstance()->firstColumn('SELECT COUNT(*) FROM acc_transactions_lines l INNER JOIN acc_transactions t ON t.id = l.id_transaction WHERE t.id_year = ? AND l.id_account = ? AND l.credit = 0 AND NOT (t.status & ?) ORDER BY t.date, t.id;', $year_id, $this->id(), Transaction::STATUS_DEPOSIT); } public function getSum(int $year_id, bool $simple = false): ?\stdClass { $sum = DB::getInstance()->first('SELECT balance, credit, debit FROM acc_accounts_balances WHERE id = ? AND id_year = ?;', $this->id(), $year_id); | > > > > > > > > > > > > > | 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 | return DB::getInstance()->firstColumn('SELECT COUNT(*) FROM acc_transactions_lines l INNER JOIN acc_transactions t ON t.id = l.id_transaction WHERE t.id_year = ? AND l.id_account = ? AND l.credit = 0 AND NOT (t.status & ?) ORDER BY t.date, t.id;', $year_id, $this->id(), Transaction::STATUS_DEPOSIT); } public function getDepositMissingBalance(int $year_id): int { $deposit_balance = DB::getInstance()->firstColumn('SELECT SUM(l.debit) FROM acc_transactions_lines l INNER JOIN acc_transactions t ON t.id = l.id_transaction WHERE t.id_year = ? AND l.id_account = ? AND l.credit = 0 AND NOT (t.status & ?) ORDER BY t.date, t.id;', $year_id, $this->id(), Transaction::STATUS_DEPOSIT); $account_balance = $this->getSum($year_id)->balance; return $account_balance - $deposit_balance; } public function getSum(int $year_id, bool $simple = false): ?\stdClass { $sum = DB::getInstance()->first('SELECT balance, credit, debit FROM acc_accounts_balances WHERE id = ? AND id_year = ?;', $this->id(), $year_id); |
︙ | ︙ | |||
777 778 779 780 781 782 783 784 785 | if ($has_transactions_in_closed_year) { return false; } return true; } public function canSetAssetOrLiabilityPosition(): bool { | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > | > > > > > > | 865 866 867 868 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 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 | if ($has_transactions_in_closed_year) { return false; } return true; } /** * We can set account position if: * - account is not in a supported chart country * - account is not part of an official chart * - account is not affected by local position rules */ public function canSetPosition(): bool { if (!$this->getCountry()) { return true; } if ($this->isChartOfficial() && !$this->user) { return false; } if ($this->type || $this->getLocalType()) { return false; } if (null !== $this->getLocalPosition()) { return false; } return true; } /** * We can set account asset or liability if: * - local position rules allow for asset or liability */ public function canSetAssetOrLiabilityPosition(): bool { if (!$this->getCountry()) { return true; } if ($this->isChartOfficial() && !$this->user) { return false; } $type = $this->type ?: $this->getLocalType(); if ($type == self::TYPE_THIRD_PARTY) { return true; } $position = $this->getLocalPosition(); if ($position == self::ASSET_OR_LIABILITY) { return true; } return false; } public function chart(): Chart |
︙ | ︙ |
Modified src/include/lib/Garradin/Entities/Accounting/Chart.php from [f47d91877a] to [f46d8e9686].
︙ | ︙ | |||
17 18 19 20 21 22 23 | const NAME = 'Plan comptable'; const PRIVATE_URL = '!acc/charts/accounts/all.php?id=%d'; const TABLE = 'acc_charts'; protected ?int $id; protected string $label; | | > > > > > > | < | | 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 | const NAME = 'Plan comptable'; const PRIVATE_URL = '!acc/charts/accounts/all.php?id=%d'; const TABLE = 'acc_charts'; protected ?int $id; protected string $label; protected ?string $country = null; protected ?string $code; protected bool $archived = false; const COUNTRY_LIST = [ 'FR' => 'France', 'BE' => 'Belgique', 'CH' => 'Suisse', ]; const REQUIRED_COLUMNS = ['code', 'label', 'description', 'position', 'bookmark']; const COLUMNS = [ 'code' => 'Numéro', 'label' => 'Libellé', 'description' => 'Description', 'position' => 'Position', 'added' => 'Ajouté', 'bookmark' => 'Favori', ]; public function selfCheck(): void { $this->assert(trim($this->label) !== '', 'Le libellé ne peut rester vide.'); $this->assert(strlen($this->label) <= 200, 'Le libellé ne peut faire plus de 200 caractères.'); $this->assert(null === $this->country || array_key_exists($this->country, self::COUNTRY_LIST), 'Pays inconnu'); parent::selfCheck(); } public function accounts() { return new Accounts($this->id()); } public function importForm(array $source = null) { if (null === $source) { $source = $_POST; } // Don't allow to change country if (isset($this->code)) { unset($source['country']); } unset($source['code']); return Entity::importForm($source); } |
︙ | ︙ | |||
137 138 139 140 141 142 143 | foreach ($res as $row) { $row->position = Account::POSITIONS_NAMES[$row->position]; $row->user = $row->user ? 'Ajouté' : ''; $row->bookmark = $row->bookmark ? 'Favori' : ''; yield $row; } } | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 182 183 184 | foreach ($res as $row) { $row->position = Account::POSITIONS_NAMES[$row->position]; $row->user = $row->user ? 'Ajouté' : ''; $row->bookmark = $row->bookmark ? 'Favori' : ''; yield $row; } } public function resetAccountsRules(): void { $db = DB::getInstance(); $db->begin(); try { foreach ($this->accounts()->listAll() as $account) { $account->setLocalRules($this->country); $account->save(); } } catch (UserException $e) { $db->rollback(); throw $e; } $db->commit(); } public function save(bool $selfcheck = true): bool { $country_modified = $this->isModified('country'); $exists = $this->exists(); $ok = parent::save($selfcheck); // Change account types if ($ok && $exists && $country_modified) { $this->resetAccountsRules(); } return $ok; } } |
Modified src/include/lib/Garradin/Entities/Accounting/Transaction.php from [d63d73755a] to [fb45525c86].
︙ | ︙ | |||
341 342 343 344 345 346 347 | l.credit, l.debit, l.label, l.reference, b.id AS id_account, c.id AS id_project 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 = ? LEFT JOIN acc_projects c ON c.id = l.id_project WHERE l.id_transaction = ?;', $year->chart()->id, | < | 341 342 343 344 345 346 347 348 349 350 351 352 353 354 | l.credit, l.debit, l.label, l.reference, b.id AS id_account, c.id AS id_project 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 = ? LEFT JOIN acc_projects c ON c.id = l.id_project WHERE l.id_transaction = ?;', $year->chart()->id, $this->id() ); foreach ($lines as $l) { $line = new Line; foreach ($copy as $field) { // Do not copy id_account when it is null, as it will trigger an error (invalid entity) |
︙ | ︙ | |||
533 534 535 536 537 538 539 540 541 542 543 544 545 546 | } $db = DB::getInstance(); if ($db->test(Year::TABLE, 'id = ? AND closed = 1', $this->id_year)) { throw new ValidationException('Il n\'est pas possible de supprimer une écriture qui fait partie d\'un exercice clôturé'); } Files::delete($this->getAttachementsDirectory()); return parent::delete(); } public function selfCheck(): void | > > | 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 | } $db = DB::getInstance(); if ($db->test(Year::TABLE, 'id = ? AND closed = 1', $this->id_year)) { throw new ValidationException('Il n\'est pas possible de supprimer une écriture qui fait partie d\'un exercice clôturé'); } // FIXME when lettering is properly implemented: mark parent transaction non-deposited when deleting a deposit transaction Files::delete($this->getAttachementsDirectory()); return parent::delete(); } public function selfCheck(): void |
︙ | ︙ |
Modified src/include/lib/Garradin/Entities/Accounting/Year.php from [bb7f32b7b9] to [eac679dbfb].
︙ | ︙ | |||
180 181 182 183 184 185 186 | /** * List common accounts used in this year, grouped by type * @return array */ public function listCommonAccountsGrouped(array $types = null): array { if (null === $types) { | > > | > > > | > > > > > > > > | | > > | > > > > > > | 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 | /** * List common accounts used in this year, grouped by type * @return array */ public function listCommonAccountsGrouped(array $types = null): array { if (null === $types) { // If we want all types, then we will get used or bookmarked accounts in common types // and only bookmarked accounts for other types, grouped in "Others" $target = Account::COMMON_TYPES; } else { $target = $types; } $out = []; foreach ($target as $type) { $out[$type] = (object) [ 'label' => Account::TYPES_NAMES[$type], 'type' => $type, 'accounts' => [], ]; } if (null === $types) { $out[0] = (object) [ 'label' => 'Autres', 'type' => 0, 'accounts' => [], ]; } $db = DB::getInstance(); $sql = sprintf('SELECT a.* FROM acc_accounts a LEFT JOIN acc_transactions_lines b ON b.id_account = a.id LEFT JOIN acc_transactions c ON c.id = b.id_transaction AND c.id_year = %d WHERE a.id_chart = %d AND ((a.%s AND (a.bookmark = 1 OR a.user = 1 OR c.id IS NOT NULL)) %s) GROUP BY a.id ORDER BY type, code COLLATE NOCASE;', $this->id(), $this->id_chart, $db->where('type', $target), (null === $types) ? 'OR (a.bookmark = 1)' : '' ); $query = $db->iterate($sql); foreach ($query as $row) { $t = in_array($row->type, $target, true) ? $row->type : 0; $out[$t]->accounts[] = $row; } foreach ($out as $key => $v) { if (!count($v->accounts)) { unset($out[$key]); } } return $out; } } |
Modified src/include/lib/Garradin/Entities/Search.php from [118645601b] to [c19e2a6cec].
︙ | ︙ | |||
196 197 198 199 200 201 202 | public function getProtectedTables(): ?array { if ($this->type != self::TYPE_SQL) { return null; } if ($this->target == self::TARGET_ACCOUNTING) { | | | 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | public function getProtectedTables(): ?array { if ($this->type != self::TYPE_SQL) { return null; } if ($this->target == self::TARGET_ACCOUNTING) { return ['acc_transactions' => null, 'acc_transactions_lines' => null, 'acc_accounts' => null, 'acc_charts' => null, 'acc_years' => null, 'acc_transactions_users' => null, 'acc_projects' => null]; } else { return ['users' => null, 'users_search' => null, 'users_categories' => null]; } } public function getGroups(): array |
︙ | ︙ |
Modified src/include/lib/Garradin/Entities/Web/Page.php from [4be2c85354] to [c8d7bd2cd7].
︙ | ︙ | |||
34 35 36 37 38 39 40 | protected string $format; protected \DateTime $published; protected \DateTime $modified; protected string $content; const FORMATS_LIST = [ //Render::FORMAT_BLOCKS => 'Blocs (beta)', | < > | 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | protected string $format; protected \DateTime $published; protected \DateTime $modified; protected string $content; const FORMATS_LIST = [ //Render::FORMAT_BLOCKS => 'Blocs (beta)', Render::FORMAT_MARKDOWN => 'MarkDown', Render::FORMAT_SKRIV => 'SkrivML', Render::FORMAT_ENCRYPTED => 'Chiffré', ]; const STATUS_ONLINE = 'online'; const STATUS_DRAFT = 'draft'; const STATUS_LIST = [ |
︙ | ︙ | |||
281 282 283 284 285 286 287 | $source['parent'] = Form::getSelectorValue($source['parent']); $source['path'] = trim($source['parent'] . '/' . $uri, '/'); } if (!empty($source['encryption']) ) { $this->set('format', Render::FORMAT_ENCRYPTED); } | | | | 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 | $source['parent'] = Form::getSelectorValue($source['parent']); $source['path'] = trim($source['parent'] . '/' . $uri, '/'); } if (!empty($source['encryption']) ) { $this->set('format', Render::FORMAT_ENCRYPTED); } elseif (empty($source['format'])) { $this->set('format', Render::FORMAT_MARKDOWN); } return parent::importForm($source); } public function getBreadcrumbs(): array { |
︙ | ︙ |
Modified src/include/lib/Garradin/Install.php from [c00befd3ec] to [d6b3752310].
︙ | ︙ | |||
322 323 324 325 326 327 328 | // Create an example saved search (accounting) $query = (object) [ 'groups' => [[ 'operator' => 'AND', 'conditions' => [ [ | | | 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 | // Create an example saved search (accounting) $query = (object) [ 'groups' => [[ 'operator' => 'AND', 'conditions' => [ [ 'column' => 'p.code', 'operator' => 'IS NULL', 'values' => [], ], ], ]], 'order' => 't.id', 'desc' => false, |
︙ | ︙ |
Modified src/include/lib/Garradin/Template.php from [3f6b5ecc4a] to [8624340bc6].
︙ | ︙ | |||
126 127 128 129 130 131 132 | $this->register_function('display_dynamic_field', [$this, 'displayDynamicField']); $this->register_function('edit_dynamic_field', [$this, 'editDynamicField']); $this->register_function('csrf_field', function ($params) { return Form::tokenHTML($params['key']); }); | | | 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | $this->register_function('display_dynamic_field', [$this, 'displayDynamicField']); $this->register_function('edit_dynamic_field', [$this, 'editDynamicField']); $this->register_function('csrf_field', function ($params) { return Form::tokenHTML($params['key']); }); $this->register_modifier('strlen', fn($a) => strlen($a ?? '')); $this->register_modifier('dump', ['KD2\ErrorManager', 'dump']); $this->register_modifier('get_country_name', ['Garradin\Utils', 'getCountryName']); $this->register_modifier('format_tel', [$this, 'formatPhoneNumber']); $this->register_modifier('abs', function($a) { return abs($a ?? 0); }); $this->register_modifier('linkify_transactions', function ($str) { return preg_replace_callback('/(?<=^|\s)#(\d+)(?=\s|$)/', function ($m) { |
︙ | ︙ |
Modified src/include/lib/Garradin/Upgrade.php from [6e0478b3a0] to [5c7264227e].
︙ | ︙ | |||
124 125 126 127 128 129 130 | if (version_compare($v, '1.1.31', '<')) { $db->import(ROOT . '/include/migrations/1.1/31.sql'); } if (version_compare($v, '1.2.0', '<')) { $db->beginSchemaUpdate(); | | > > > > > > > | 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 | if (version_compare($v, '1.1.31', '<')) { $db->import(ROOT . '/include/migrations/1.1/31.sql'); } if (version_compare($v, '1.2.0', '<')) { $db->beginSchemaUpdate(); $db->import(ROOT . '/include/migrations/1.2/1.2.0.sql'); Charts::updateInstalled('fr_pca_2018'); Charts::updateInstalled('fr_pca_1999'); Charts::updateInstalled('fr_pcc_2020'); Charts::updateInstalled('fr_pcg_2014'); Charts::updateInstalled('be_pcmn_2019'); $db->commitSchemaUpdate(); } if (version_compare($v, '1.2.1', '<')) { $db->beginSchemaUpdate(); $db->import(ROOT . '/include/migrations/1.2/1.2.1.sql'); Charts::resetRules(['FR', 'CH', 'BE']); $db->commitSchemaUpdate(); } if (version_compare($v, '1.3.0', '<')) { require ROOT . '/include/migrations/1.3/1.3.0.php'; } Plugin::upgradeAllIfRequired(); |
︙ | ︙ |
Modified src/include/lib/Garradin/Utils.php from [bbf24b67ea] to [d0c16ac5a0].
︙ | ︙ | |||
822 823 824 825 826 827 828 829 830 831 832 833 834 835 | header_remove('Expires'); if ($last_change) { header(sprintf('Last-Modified: %s GMT', gmdate('D, d M Y H:i:s', $last_change)), true); } if ($hash) { header(sprintf('Etag: "%s"', $hash), true); } if (($etag && $etag === $hash) || ($last_modified && $last_modified >= $last_change)) { http_response_code(304); exit; } | > | 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 | header_remove('Expires'); if ($last_change) { header(sprintf('Last-Modified: %s GMT', gmdate('D, d M Y H:i:s', $last_change)), true); } if ($hash) { $hash = md5(Utils::getVersionHash() . $hash); header(sprintf('Etag: "%s"', $hash), true); } if (($etag && $etag === $hash) || ($last_modified && $last_modified >= $last_change)) { http_response_code(304); exit; } |
︙ | ︙ |
Added src/include/migrations/1.2/1.2.1.sql version [b8ed39033f].
> > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | ALTER TABLE acc_charts RENAME TO acc_charts_old; DROP VIEW acc_accounts_balances; .read schema.sql INSERT INTO acc_charts SELECT * FROM acc_charts_old; -- Reset country if code was official, changing the country should not have been possible UPDATE acc_charts SET country = 'FR' WHERE code IS NOT NULL AND code != 'PCMN_2019'; UPDATE acc_charts SET country = 'BE' WHERE code IS NOT NULL AND code = 'PCMN_2019'; -- Reset country to FR for countries using something similar UPDATE acc_charts SET country = 'FR' WHERE country IN ('GN', 'TN', 'RE', 'CN', 'PF', 'MW', 'CI', 'GP', 'GA', 'DE', 'NC'); -- Set country to NULL if outside of supported countries UPDATE acc_charts SET country = NULL WHERE country NOT IN ('FR', 'BE', 'CH'); -- Reset type to zero if not supported UPDATE acc_accounts SET type = 0 WHERE id_chart IN (SELECT id FROM acc_charts WHERE country IS NULL); -- Reset other charts is done in PHP code -- Fix some search UPDATE recherches SET contenu = REPLACE(contenu, 'a2.code', 'p.code') WHERE contenu LIKE '%a2.code%'; DROP TABLE acc_charts_old; |
Added src/include/migrations/1.2/schema.sql version [da30dfe665].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 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 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 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 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 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 | CREATE TABLE IF NOT EXISTS config ( key TEXT PRIMARY KEY NOT NULL, value TEXT NULL ); CREATE TABLE IF NOT EXISTS users_categories -- Users categories, mainly used to manage rights ( id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, -- Permissions, 0 = no access, 1 = read-only, 2 = read-write, 9 = admin perm_web INTEGER NOT NULL DEFAULT 1, perm_documents INTEGER NOT NULL DEFAULT 1, perm_users INTEGER NOT NULL DEFAULT 1, perm_accounting INTEGER NOT NULL DEFAULT 1, perm_subscribe INTEGER NOT NULL DEFAULT 0, perm_connect INTEGER NOT NULL DEFAULT 1, perm_config INTEGER NOT NULL DEFAULT 0, hidden INTEGER NOT NULL DEFAULT 0 ); CREATE INDEX IF NOT EXISTS users_categories_hidden ON users_categories (hidden); -- Membres de l'asso -- Table dynamique générée par l'application -- voir Garradin\Membres\Champs.php CREATE TABLE IF NOT EXISTS membres_sessions -- Sessions ( selecteur TEXT NOT NULL, hash TEXT NOT NULL, id_membre INTEGER NOT NULL REFERENCES membres (id) ON DELETE CASCADE, expire INT NOT NULL, PRIMARY KEY (selecteur, id_membre) ); CREATE TABLE IF NOT EXISTS services -- Types de services (cotisations) ( id INTEGER PRIMARY KEY NOT NULL, label TEXT NOT NULL, description TEXT NULL, duration INTEGER NULL CHECK (duration IS NULL OR duration > 0), -- En jours start_date TEXT NULL CHECK (start_date IS NULL OR date(start_date) = start_date), end_date TEXT NULL CHECK (end_date IS NULL OR (date(end_date) = end_date AND date(end_date) >= date(start_date))) ); CREATE TABLE IF NOT EXISTS services_fees ( id INTEGER PRIMARY KEY NOT NULL, label TEXT NOT NULL, description TEXT NULL, amount INTEGER NULL, formula TEXT NULL, -- Formule de calcul du montant de la cotisation, si cotisation dynamique (exemple : membres.revenu_imposable * 0.01) id_service INTEGER NOT NULL REFERENCES services (id) ON DELETE CASCADE, id_account INTEGER NULL REFERENCES acc_accounts (id) ON DELETE SET NULL CHECK (id_account IS NULL OR id_year IS NOT NULL), -- NULL if fee is not linked to accounting, this is reset using a trigger if the year is deleted id_year INTEGER NULL REFERENCES acc_years (id) ON DELETE SET NULL, -- NULL if fee is not linked to accounting id_project INTEGER NULL REFERENCES acc_projects (id) ON DELETE SET NULL ); CREATE TABLE IF NOT EXISTS services_users -- Enregistrement des cotisations et activités ( id INTEGER NOT NULL PRIMARY KEY, id_user INTEGER NOT NULL REFERENCES membres (id) ON DELETE CASCADE, id_service INTEGER NOT NULL REFERENCES services (id) ON DELETE CASCADE, id_fee INTEGER NULL REFERENCES services_fees (id) ON DELETE CASCADE, -- This can be NULL if there is no fee for the service paid INTEGER NOT NULL DEFAULT 0, expected_amount INTEGER NULL, date TEXT NOT NULL DEFAULT CURRENT_DATE CHECK (date(date) IS NOT NULL AND date(date) = date), expiry_date TEXT NULL CHECK (date(expiry_date) IS NULL OR date(expiry_date) = expiry_date) ); CREATE UNIQUE INDEX IF NOT EXISTS su_unique ON services_users (id_user, id_service, date); CREATE INDEX IF NOT EXISTS su_service ON services_users (id_service); CREATE INDEX IF NOT EXISTS su_fee ON services_users (id_fee); CREATE INDEX IF NOT EXISTS su_paid ON services_users (paid); CREATE INDEX IF NOT EXISTS su_expiry ON services_users (expiry_date); CREATE TABLE IF NOT EXISTS services_reminders -- Rappels de devoir renouveller une cotisation ( id INTEGER NOT NULL PRIMARY KEY, id_service INTEGER NOT NULL REFERENCES services (id) ON DELETE CASCADE, delay INTEGER NOT NULL, -- Délai en jours pour envoyer le rappel subject TEXT NOT NULL, body TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS services_reminders_sent -- Enregistrement des rappels envoyés à qui et quand ( id INTEGER NOT NULL PRIMARY KEY, id_user INTEGER NOT NULL REFERENCES membres (id) ON DELETE CASCADE, id_service INTEGER NOT NULL REFERENCES services (id) ON DELETE CASCADE, id_reminder INTEGER NOT NULL REFERENCES services_reminders (id) ON DELETE CASCADE, sent_date TEXT NOT NULL DEFAULT CURRENT_DATE CHECK (date(sent_date) IS NOT NULL AND date(sent_date) = sent_date), due_date TEXT NOT NULL CHECK (date(due_date) IS NOT NULL AND date(due_date) = due_date) ); CREATE UNIQUE INDEX IF NOT EXISTS srs_index ON services_reminders_sent (id_user, id_service, id_reminder, due_date); CREATE INDEX IF NOT EXISTS srs_reminder ON services_reminders_sent (id_reminder); CREATE INDEX IF NOT EXISTS srs_user ON services_reminders_sent (id_user); -- -- COMPTA -- CREATE TABLE IF NOT EXISTS acc_charts -- Plans comptables : il peut y en avoir plusieurs ( id INTEGER NOT NULL PRIMARY KEY, country TEXT NULL, code TEXT NULL, -- NULL = plan comptable créé par l'utilisateur label TEXT NOT NULL, archived INTEGER NOT NULL DEFAULT 0 -- 1 = archivé, non-modifiable ); CREATE TABLE IF NOT EXISTS acc_accounts -- Comptes des plans comptables ( id INTEGER NOT NULL PRIMARY KEY, id_chart INTEGER NOT NULL REFERENCES acc_charts ON DELETE CASCADE, code TEXT NOT NULL, -- peut contenir des lettres, eg. 53A, 53B, etc. label TEXT NOT NULL, description TEXT NULL, position INTEGER NOT NULL, -- position actif/passif/charge/produit type INTEGER NOT NULL DEFAULT 0, -- Type de compte spécial : banque, caisse, en attente d'encaissement, etc. user INTEGER NOT NULL DEFAULT 1, -- 0 = fait partie du plan comptable original, 1 = a été ajouté par l'utilisateur bookmark INTEGER NOT NULL DEFAULT 0 -- 1 = is marked as favorite ); CREATE UNIQUE INDEX IF NOT EXISTS acc_accounts_codes ON acc_accounts (code, id_chart); CREATE INDEX IF NOT EXISTS acc_accounts_type ON acc_accounts (type); CREATE INDEX IF NOT EXISTS acc_accounts_position ON acc_accounts (position); CREATE INDEX IF NOT EXISTS acc_accounts_bookmarks ON acc_accounts (id_chart, bookmark, code); -- Balance des comptes par exercice CREATE VIEW IF NOT EXISTS acc_accounts_balances AS SELECT id_year, id, label, code, type, debit, credit, bookmark, CASE -- 3 = dynamic asset or liability depending on balance WHEN position = 3 AND (debit - credit) > 0 THEN 1 -- 1 = Asset (actif) comptes fournisseurs, tiers créditeurs WHEN position = 3 THEN 2 -- 2 = Liability (passif), comptes clients, tiers débiteurs ELSE position END AS position, CASE WHEN position IN (1, 4) -- 1 = asset, 4 = expense OR (position = 3 AND (debit - credit) > 0) THEN debit - credit ELSE credit - debit END AS balance, CASE WHEN debit - credit > 0 THEN 1 ELSE 0 END AS is_debt FROM ( SELECT t.id_year, a.id, a.label, a.code, a.position, a.type, a.bookmark, SUM(l.credit) AS credit, SUM(l.debit) AS debit FROM acc_accounts a INNER JOIN acc_transactions_lines l ON l.id_account = a.id INNER JOIN acc_transactions t ON t.id = l.id_transaction GROUP BY t.id_year, a.id ); CREATE TABLE IF NOT EXISTS acc_projects -- Analytical projects ( id INTEGER NOT NULL PRIMARY KEY, code TEXT NULL, label TEXT NOT NULL, description TEXT NULL, archived INTEGER NOT NULL DEFAULT 0 ); CREATE UNIQUE INDEX IF NOT EXISTS acc_projects_code ON acc_projects (code); CREATE INDEX IF NOT EXISTS acc_projects_list ON acc_projects (archived, code); CREATE TABLE IF NOT EXISTS acc_years -- Exercices ( id INTEGER NOT NULL PRIMARY KEY, label TEXT NOT NULL, start_date TEXT NOT NULL CHECK (date(start_date) IS NOT NULL AND date(start_date) = start_date), end_date TEXT NOT NULL CHECK (date(end_date) IS NOT NULL AND date(end_date) = end_date), closed INTEGER NOT NULL DEFAULT 0, id_chart INTEGER NOT NULL REFERENCES acc_charts (id) ); CREATE INDEX IF NOT EXISTS acc_years_closed ON acc_years (closed); -- Make sure id_account is reset when a year is deleted CREATE TRIGGER IF NOT EXISTS acc_years_delete BEFORE DELETE ON acc_years BEGIN UPDATE services_fees SET id_account = NULL, id_year = NULL WHERE id_year = OLD.id; END; CREATE TABLE IF NOT EXISTS acc_transactions -- Opérations comptables ( id INTEGER PRIMARY KEY NOT NULL, type INTEGER NOT NULL DEFAULT 0, -- Type d'écriture, 0 = avancée (normale) status INTEGER NOT NULL DEFAULT 0, -- Statut (bitmask) label TEXT NOT NULL, notes TEXT NULL, reference TEXT NULL, -- N° de pièce comptable date TEXT NOT NULL DEFAULT CURRENT_DATE CHECK (date(date) IS NOT NULL AND date(date) = date), validated INTEGER NOT NULL DEFAULT 0, -- 1 = écriture validée, non modifiable hash TEXT NULL, prev_hash TEXT NULL, id_year INTEGER NOT NULL REFERENCES acc_years(id), id_creator INTEGER NULL REFERENCES membres(id) ON DELETE SET NULL, id_related INTEGER NULL REFERENCES acc_transactions(id) ON DELETE SET NULL -- écriture liée (par ex. remboursement d'une dette) ); CREATE INDEX IF NOT EXISTS acc_transactions_year ON acc_transactions (id_year); CREATE INDEX IF NOT EXISTS acc_transactions_date ON acc_transactions (date); CREATE INDEX IF NOT EXISTS acc_transactions_related ON acc_transactions (id_related); CREATE INDEX IF NOT EXISTS acc_transactions_type ON acc_transactions (type, id_year); CREATE INDEX IF NOT EXISTS acc_transactions_status ON acc_transactions (status); CREATE TABLE IF NOT EXISTS acc_transactions_lines -- Lignes d'écritures d'une opération ( id INTEGER PRIMARY KEY NOT NULL, id_transaction INTEGER NOT NULL REFERENCES acc_transactions (id) ON DELETE CASCADE, id_account INTEGER NOT NULL REFERENCES acc_accounts (id), -- N° du compte dans le plan comptable credit INTEGER NOT NULL, debit INTEGER NOT NULL, reference TEXT NULL, -- Référence de paiement, eg. numéro de chèque label TEXT NULL, reconciled INTEGER NOT NULL DEFAULT 0, id_project INTEGER NULL REFERENCES acc_projects(id) ON DELETE SET NULL, CONSTRAINT line_check1 CHECK ((credit * debit) = 0), CONSTRAINT line_check2 CHECK ((credit + debit) > 0) ); CREATE INDEX IF NOT EXISTS acc_transactions_lines_transaction ON acc_transactions_lines (id_transaction); CREATE INDEX IF NOT EXISTS acc_transactions_lines_account ON acc_transactions_lines (id_account); CREATE INDEX IF NOT EXISTS acc_transactions_lines_project ON acc_transactions_lines (id_project); CREATE INDEX IF NOT EXISTS acc_transactions_lines_reconciled ON acc_transactions_lines (reconciled); CREATE TABLE IF NOT EXISTS acc_transactions_users -- Liaison des écritures et des membres ( id_user INTEGER NOT NULL REFERENCES membres (id) ON DELETE CASCADE, id_transaction INTEGER NOT NULL REFERENCES acc_transactions (id) ON DELETE CASCADE, id_service_user INTEGER NULL REFERENCES services_users (id) ON DELETE SET NULL, PRIMARY KEY (id_user, id_transaction) ); CREATE INDEX IF NOT EXISTS acc_transactions_users_service ON acc_transactions_users (id_service_user); CREATE TABLE IF NOT EXISTS plugins ( id TEXT NOT NULL PRIMARY KEY, officiel INTEGER NOT NULL DEFAULT 0, nom TEXT NOT NULL, description TEXT NULL, auteur TEXT NULL, url TEXT NULL, version TEXT NOT NULL, menu INTEGER NOT NULL DEFAULT 0, menu_condition TEXT NULL, config TEXT NULL ); CREATE TABLE IF NOT EXISTS plugins_signaux -- Association entre plugins et signaux (hooks) ( signal TEXT NOT NULL, plugin TEXT NOT NULL REFERENCES plugins (id), callback TEXT NOT NULL, PRIMARY KEY (signal, plugin) ); CREATE TABLE IF NOT EXISTS api_credentials ( id INTEGER NOT NULL PRIMARY KEY, label TEXT NOT NULL, key TEXT NOT NULL, secret TEXT NOT NULL, created TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, last_use TEXT NULL, access_level INT NOT NULL ); CREATE UNIQUE INDEX IF NOT EXISTS api_credentials_key ON api_credentials (key); ---------- FILES ---------------- CREATE TABLE IF NOT EXISTS files -- Files metadata ( id INTEGER NOT NULL PRIMARY KEY, path TEXT NOT NULL, parent TEXT NOT NULL, name TEXT NOT NULL, -- File name type INTEGER NOT NULL, -- File type, 1 = file, 2 = directory mime TEXT NULL, size INT NULL, modified TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP CHECK (datetime(modified) = modified), image INT NOT NULL DEFAULT 0, CHECK (type = 2 OR (mime IS NOT NULL AND size IS NOT NULL)) ); -- Unique index as this is used to make up a file path CREATE UNIQUE INDEX IF NOT EXISTS files_unique ON files (path); CREATE INDEX IF NOT EXISTS files_parent ON files (parent); CREATE INDEX IF NOT EXISTS files_name ON files (name); CREATE INDEX IF NOT EXISTS files_modified ON files (modified); CREATE TABLE IF NOT EXISTS files_contents -- Files contents (empty if using another storage backend) ( id INTEGER NOT NULL PRIMARY KEY REFERENCES files(id) ON DELETE CASCADE, compressed INT NOT NULL DEFAULT 0, content BLOB NOT NULL ); CREATE VIRTUAL TABLE IF NOT EXISTS files_search USING fts4 -- Search inside files content ( tokenize=unicode61, -- Available from SQLITE 3.7.13 (2012) path TEXT NOT NULL, title TEXT NULL, content TEXT NOT NULL, -- Text content notindexed=path ); CREATE TABLE IF NOT EXISTS web_pages ( id INTEGER NOT NULL PRIMARY KEY, parent TEXT NOT NULL, -- Parent path, empty = web root path TEXT NOT NULL, -- Full page directory name uri TEXT NOT NULL, -- Page identifier file_path TEXT NOT NULL, -- Full file path for contents type INTEGER NOT NULL, -- 1 = Category, 2 = Page status TEXT NOT NULL, format TEXT NOT NULL, published TEXT NOT NULL CHECK (datetime(published) = published), modified TEXT NOT NULL CHECK (datetime(modified) = modified), title TEXT NOT NULL, content TEXT NOT NULL ); CREATE UNIQUE INDEX IF NOT EXISTS web_pages_path ON web_pages (path); CREATE UNIQUE INDEX IF NOT EXISTS web_pages_uri ON web_pages (uri); CREATE UNIQUE INDEX IF NOT EXISTS web_pages_file_path ON web_pages (file_path); CREATE INDEX IF NOT EXISTS web_pages_parent ON web_pages (parent); CREATE INDEX IF NOT EXISTS web_pages_published ON web_pages (published); CREATE INDEX IF NOT EXISTS web_pages_title ON web_pages (title); -- FIXME: rename to english CREATE TABLE IF NOT EXISTS recherches -- Recherches enregistrées ( id INTEGER NOT NULL PRIMARY KEY, id_membre INTEGER NULL REFERENCES membres (id) ON DELETE CASCADE, -- Si non NULL, alors la recherche ne sera visible que par le membre associé intitule TEXT NOT NULL, creation TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP CHECK (datetime(creation) IS NOT NULL AND datetime(creation) = creation), cible TEXT NOT NULL, -- "membres" ou "compta" type TEXT NOT NULL, -- "json" ou "sql" contenu TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS compromised_passwords_cache -- Cache des hash de mots de passe compromis ( hash TEXT NOT NULL PRIMARY KEY ); CREATE TABLE IF NOT EXISTS compromised_passwords_cache_ranges -- Cache des préfixes de mots de passe compromis ( prefix TEXT NOT NULL PRIMARY KEY, date INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS emails ( -- List of emails addresses -- We are not storing actual email addresses here for privacy reasons -- So that we can keep the record (for opt-out reasons) even when the -- email address has been removed from the users table id INTEGER NOT NULL PRIMARY KEY, hash TEXT NOT NULL, verified INTEGER NOT NULL DEFAULT 0, optout INTEGER NOT NULL DEFAULT 0, invalid INTEGER NOT NULL DEFAULT 0, fail_count INTEGER NOT NULL DEFAULT 0, sent_count INTEGER NOT NULL DEFAULT 0, fail_log TEXT NULL, last_sent TEXT NULL, added TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE UNIQUE INDEX IF NOT EXISTS emails_hash ON emails (hash); CREATE TABLE IF NOT EXISTS emails_queue ( -- List of emails waiting to be sent id INTEGER NOT NULL PRIMARY KEY, sender TEXT NULL, recipient TEXT NOT NULL, recipient_hash TEXT NOT NULL, subject TEXT NOT NULL, content TEXT NOT NULL, content_html TEXT NULL, sending INTEGER NOT NULL DEFAULT 0, -- Will be changed to 1 when the queue run will start sending_started TEXT NULL, -- Will be filled with the datetime when the email sending was started context INTEGER NOT NULL ); |
Modified src/include/migrations/1.3/schema.sql from [3c3e1f77d9] to [e5107d635f].
︙ | ︙ | |||
208 209 210 211 212 213 214 | CREATE TABLE IF NOT EXISTS services_users -- Records of services and fees linked to users ( id INTEGER NOT NULL PRIMARY KEY, id_user INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE, id_service INTEGER NOT NULL REFERENCES services (id) ON DELETE CASCADE, | | | 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | CREATE TABLE IF NOT EXISTS services_users -- Records of services and fees linked to users ( id INTEGER NOT NULL PRIMARY KEY, id_user INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE, id_service INTEGER NOT NULL REFERENCES services (id) ON DELETE CASCADE, id_fee INTEGER NULL REFERENCES services_fees (id) ON DELETE CASCADE, -- This can be NULL if there is no fee for the service paid INTEGER NOT NULL DEFAULT 0, expected_amount INTEGER NULL, date TEXT NOT NULL DEFAULT CURRENT_DATE CHECK (date(date) IS NOT NULL AND date(date) = date), expiry_date TEXT NULL CHECK (date(expiry_date) IS NULL OR date(expiry_date) = expiry_date) ); |
︙ | ︙ | |||
281 282 283 284 285 286 287 | code TEXT NOT NULL, -- can contain numbers and letters, eg. 53A, 53B... label TEXT NOT NULL, description TEXT NULL, position INTEGER NOT NULL, -- position in the balance sheet (position actif/passif/charge/produit) type INTEGER NOT NULL DEFAULT 0, -- type (category) of favourite account: bank, cash, third party, etc. | | > > | | > > > > > > > > > > > > > > > | 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 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 | code TEXT NOT NULL, -- can contain numbers and letters, eg. 53A, 53B... label TEXT NOT NULL, description TEXT NULL, position INTEGER NOT NULL, -- position in the balance sheet (position actif/passif/charge/produit) type INTEGER NOT NULL DEFAULT 0, -- type (category) of favourite account: bank, cash, third party, etc. user INTEGER NOT NULL DEFAULT 1, -- 0 = is part of the original chart, 0 = has been added by the user bookmark INTEGER NOT NULL DEFAULT 0 -- 1 = is marked as favorite ); CREATE UNIQUE INDEX IF NOT EXISTS acc_accounts_codes ON acc_accounts (code, id_chart); CREATE INDEX IF NOT EXISTS acc_accounts_type ON acc_accounts (type); CREATE INDEX IF NOT EXISTS acc_accounts_position ON acc_accounts (position); CREATE INDEX IF NOT EXISTS acc_accounts_bookmarks ON acc_accounts (id_chart, bookmark, code); -- Balance des comptes par exercice CREATE VIEW IF NOT EXISTS acc_accounts_balances AS SELECT id_year, id, label, code, type, debit, credit, bookmark, CASE -- 3 = dynamic asset or liability depending on balance WHEN position = 3 AND (debit - credit) > 0 THEN 1 -- 1 = Asset (actif) comptes fournisseurs, tiers créditeurs WHEN position = 3 THEN 2 -- 2 = Liability (passif), comptes clients, tiers débiteurs ELSE position END AS position, CASE WHEN position IN (1, 4) -- 1 = asset, 4 = expense OR (position = 3 AND (debit - credit) > 0) THEN debit - credit ELSE credit - debit END AS balance, CASE WHEN debit - credit > 0 THEN 1 ELSE 0 END AS is_debt FROM ( SELECT t.id_year, a.id, a.label, a.code, a.position, a.type, a.bookmark, SUM(l.credit) AS credit, SUM(l.debit) AS debit FROM acc_accounts a INNER JOIN acc_transactions_lines l ON l.id_account = a.id INNER JOIN acc_transactions t ON t.id = l.id_transaction GROUP BY t.id_year, a.id ); CREATE TABLE IF NOT EXISTS acc_projects -- Analytical projects ( id INTEGER NOT NULL PRIMARY KEY, code TEXT NULL, label TEXT NOT NULL, description TEXT NULL, archived INTEGER NOT NULL DEFAULT 0 ); CREATE UNIQUE INDEX IF NOT EXISTS acc_projects_code ON acc_projects (code); CREATE INDEX IF NOT EXISTS acc_projects_list ON acc_projects (archived, code); CREATE TABLE IF NOT EXISTS acc_years -- Years (exercices) ( id INTEGER NOT NULL PRIMARY KEY, label TEXT NOT NULL, |
︙ | ︙ | |||
384 385 386 387 388 389 390 | debit INTEGER NOT NULL, reference TEXT NULL, -- Usually a payment reference (par exemple numéro de chèque) label TEXT NULL, reconciled INTEGER NOT NULL DEFAULT 0, | | | 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 | debit INTEGER NOT NULL, reference TEXT NULL, -- Usually a payment reference (par exemple numéro de chèque) label TEXT NULL, reconciled INTEGER NOT NULL DEFAULT 0, id_project INTEGER NULL REFERENCES acc_projects(id) ON DELETE SET NULL, CONSTRAINT line_check1 CHECK ((credit * debit) = 0), CONSTRAINT line_check2 CHECK ((credit + debit) > 0) ); CREATE INDEX IF NOT EXISTS acc_transactions_lines_transaction ON acc_transactions_lines (id_transaction); CREATE INDEX IF NOT EXISTS acc_transactions_lines_account ON acc_transactions_lines (id_account); |
︙ | ︙ |
Modified src/templates/acc/accounts/_nav.tpl from [d478e2bcf4] to [84ce1f0f13].
1 2 3 4 | <nav class="tabs"> <aside> {if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN)} | > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <nav class="tabs"> <aside> {if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN)} <?php $page = $current == 'all' ? 'all.php' : ''; ?> {linkbutton shape="edit" href="!acc/charts/accounts/%s?id=%d"|args:$page,$current_year.id_chart label="Modifier les comptes"} {/if} {linkbutton shape="search" href="!acc/search.php?year=%d"|args:$current_year.id label="Recherche"} </aside> <ul> <li{if $current == 'index'} class="current"{/if}><a href="{$admin_url}acc/accounts/">Comptes favoris</a></li> <li{if $current == 'all'} class="current"{/if}><a href="{$admin_url}acc/accounts/all.php?year={$current_year.id}">Tous les comptes</a></li> <li{if $current == 'users'} class="current"{/if}><a href="{$admin_url}acc/accounts/users.php">Comptes de membres</a></li> <li><a href="{$admin_url}acc/reports/statement.php?year={$current_year.id}"><em>Compte de résultat</em></a></li> <li><a href="{$admin_url}acc/reports/balance_sheet.php?year={$current_year.id}"><em>Bilan</em></a></li> </ul> </nav> |
Modified src/templates/acc/accounts/deposit.tpl from [0e6c098f4c] to [db6e5c4575].
1 2 3 4 5 | {include file="_head.tpl" title="Dépôt en banque : %s — %s"|args:$account.code,$account.label current="acc/accounts"} {form_errors} {if !$journal_count} | > > > > > > > > > | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | {include file="_head.tpl" title="Dépôt en banque : %s — %s"|args:$account.code,$account.label current="acc/accounts"} {form_errors} {if $missing_balance > 0} <p class="alert block"> Il existe une différence de {$missing_balance|raw|money_currency} entre la liste des écritures à déposer et le solde du compte.<br /> Cette situation est généralement dûe à des écritures de dépôt qui ont été supprimées.<br /> {linkbutton shape="plus" label="Faire un virement pour régulariser" href="!acc/transactions/new.php?a0=%d&l=Régularisation%%20dépôt&account=%d"|args:$missing_balance,$account.id} </p> {/if} {if !$journal_count} <p class="alert block">Il n'y a aucune écriture qui nécessiterait un dépôt. </p> {else} <p class="help"> Cocher les cases correspondant aux montants à déposer, une nouvelle écriture sera générée. </p> <form method="post" action="{$self_url}" data-focus="1"> |
︙ | ︙ |
Modified src/templates/acc/accounts/index.tpl from [3ba932319b] to [92008ea2db].
︙ | ︙ | |||
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | {include file="acc/_simple_help.tpl" link="../reports/trial_balance.php?year=%d"|args:$current_year.id type=null} {if !empty($grouped_accounts)} <?php $has_accounts = false; ?> <table class="list"> <thead> <tr> <td class="num">Numéro</td> <th>Compte</th> <td class="money">Solde</td> <td></td> <td></td> </tr> </thead> {foreach from=$grouped_accounts item="group"} <tbody> <tr> <td colspan="5"><h2 class="ruler">{$group.label}</h2></td> </tr> {foreach from=$group.accounts item="account"} | > | > | 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 | {include file="acc/_simple_help.tpl" link="../reports/trial_balance.php?year=%d"|args:$current_year.id type=null} {if !empty($grouped_accounts)} <?php $has_accounts = false; ?> <table class="list"> <thead> <tr> <td></td> <td class="num">Numéro</td> <th>Compte</th> <td class="money">Solde</td> <td></td> <td></td> </tr> </thead> {foreach from=$grouped_accounts item="group"} <tbody> <tr> <td colspan="5"><h2 class="ruler">{$group.label}</h2></td> </tr> {foreach from=$group.accounts item="account"} <tr class="account"> <td class="bookmark">{if $account.bookmark}{icon shape="star" title="Compte favori"}{/if}</td> <td class="num"><a href="{$admin_url}acc/accounts/journal.php?id={$account.id}&year={$current_year.id}">{$account.code}</a></td> <th><a href="{$admin_url}acc/accounts/journal.php?id={$account.id}&year={$current_year.id}">{$account.label}</a></th> <td class="money"> {if $account.balance < 0 || ($account.balance > 0 && $account.position == Account::LIABILITY && ($account.type == Account::TYPE_BANK || $account.type == Account::TYPE_THIRD_PARTY || $account.type == Account::TYPE_CASH))} <strong class="error">-{$account.balance|raw|abs|money_currency:false}</strong> {else} |
︙ | ︙ | |||
80 81 82 83 84 85 86 | {linkbutton href="!acc/transactions/new.php" label="Saisir une écriture" shape="plus"} </p> </div> {/if} {/if} <p class="help"> | | | 82 83 84 85 86 87 88 89 90 91 92 93 94 | {linkbutton href="!acc/transactions/new.php" label="Saisir une écriture" shape="plus"} </p> </div> {/if} {/if} <p class="help"> Note : n'apparaissent ici que les comptes qui ont été utilisés dans cet exercice (au moins une écriture) de types banque, caisse, tiers, dépenses ou recettes. Les autres comptes n'apparaissent que s'ils ont été utilisés et sont marqués comme favoris.<br /> Pour voir le solde de tous les comptes, se référer à la <a href="all.php">liste de tous comptes de l'exercice</a>.<br /> Pour voir la liste complète des comptes, même ceux qui n'ont pas été utilisés, se référer au <a href="{$admin_url}acc/charts/accounts/?id={$current_year.id_chart}">plan comptable</a>. </p> {include file="_foot.tpl"} |
Modified src/templates/acc/accounts/journal.tpl from [5909c5b586] to [7c5da4fb99].
︙ | ︙ | |||
120 121 122 123 124 125 126 127 128 129 130 131 132 133 | <td class="money">{$line.sum|raw|money:false}</td> {/if} <td>{$line.reference}</td> <th>{$line.label}</th> {if !$simple}<td>{$line.line_label}</td>{/if} <td>{$line.line_reference}</td> <td class="num">{if $line.id_project}<a href="{$admin_url}acc/reports/statement.php?project={$line.id_project}">{$line.project_code}</a>{/if}</td> <td class="actions"> {if ($line.status & Entities\Accounting\Transaction::STATUS_WAITING)} {if $line.type == Entities\Accounting\Transaction::TYPE_DEBT} {linkbutton shape="check" label="Régler cette dette" href="!acc/transactions/payoff.php?for=%d"|args:$line.id} {elseif $line.type == Entities\Accounting\Transaction::TYPE_CREDIT} {linkbutton shape="export" label="Régler cette créance" href="!acc/transactions/payoff.php?for=%d"|args:$line.id} {/if} | > > > > > > > > > | 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | <td class="money">{$line.sum|raw|money:false}</td> {/if} <td>{$line.reference}</td> <th>{$line.label}</th> {if !$simple}<td>{$line.line_label}</td>{/if} <td>{$line.line_reference}</td> <td class="num">{if $line.id_project}<a href="{$admin_url}acc/reports/statement.php?project={$line.id_project}">{$line.project_code}</a>{/if}</td> {* Deposit status, might be consufing <td> {if $account.type == $account::TYPE_OUTSTANDING && $line.debit} {if !($line.status & Entities\Accounting\Transaction::STATUS_DEPOSIT)} {icon shape="alert" title="Cette opération n'a pas été déposée"} {/if} {/if} </td> *} <td class="actions"> {if ($line.status & Entities\Accounting\Transaction::STATUS_WAITING)} {if $line.type == Entities\Accounting\Transaction::TYPE_DEBT} {linkbutton shape="check" label="Régler cette dette" href="!acc/transactions/payoff.php?for=%d"|args:$line.id} {elseif $line.type == Entities\Accounting\Transaction::TYPE_CREDIT} {linkbutton shape="export" label="Régler cette créance" href="!acc/transactions/payoff.php?for=%d"|args:$line.id} {/if} |
︙ | ︙ |
Added src/templates/acc/charts/_country_input.tpl version [c98c1a13ef].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | <?php use Garradin\Entities\Accounting\Chart; use Garradin\Config; $country_list = Chart::COUNTRY_LIST + ['' => '— Autre']; if (!isset($chart)) { $chart = new Chart; $chart->country = Config::getInstance()->pays; } $name ??= 'country'; ?> {input type="select" name=$name label="Appliquer les règles comptables de ce pays" required=1 options=$country_list default=$chart.country} {if !$chart->exists()} <dd class="help">Ce choix ne pourra plus être modifié une fois le plan comptable créé.</dd> {else} <dd class="help">Si un pays est sélectionné, ce choix ne pourra plus être modifié.</dd> {/if} <dd class="alert block {$name}_empty hidden"><strong>Attention :</strong> si <em>« Autre »</em> est sélectionné, alors :<br /> - les comptes ne pourront pas être catégorisés automatiquement (banque, caisse, dépenses, recettes, etc.) ;<br /> - il faudra donc parcourir tout le plan comptable pour sélectionner un compte<br /> - la position des comptes au bilan ou compte de résultat ne pourra pas être contrôlée : des erreurs sont possibles<br /> <em>Si vous avez besoin d'ajouter les règles comptables d'un autre pays, merci de <a href="https://garradin.eu/contact" target="_blank">nous contacter</a>.</em> </dd> <dd class="help"> <script type="text/javascript"> (function () {ldelim} var n = {$name|escape:'json'}; var c = $('#f_' + n); {literal} var changeCountry = () => { g.toggle('.' + n + '_empty', c.value == '' ? true : false); g.resizeParentDialog(); }; c.onchange = changeCountry; changeCountry(); })(); {/literal} </script> </dd> |
Modified src/templates/acc/charts/accounts/_account_form.tpl from [6a136bee7d] to [0f5791955c].
1 2 3 4 5 6 | {if $create} <input type="hidden" name="type" value="{$account.type}" /> {/if} <dl> {if $can_edit} | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | {if $create} <input type="hidden" name="type" value="{$account.type}" /> {/if} <dl> {if $can_edit} {if $account->canSetPosition()} <dt><label for="f_position_0">Position au bilan ou résultat</label> <b>(obligatoire)</b></dt> <dd class="help">La position permet d'indiquer dans quelle partie du bilan ou du résultat doit figurer le compte.</dd> {input type="radio" label="Ne pas utiliser ce compte au bilan ni au résultat" name="position" value=0 source=$account} {input type="radio" label="Bilan : actif" name="position" value=Entities\Accounting\Account::ASSET source=$account help="ce que possède l'association : stocks, locaux, soldes bancaires, etc."} {input type="radio" label="Bilan : passif" name="position" value=Entities\Accounting\Account::LIABILITY source=$account help="ce que l'association doit : dettes, provisions, réserves, etc."} {input type="radio" label="Bilan : actif ou passif" name="position" value=Entities\Accounting\Account::ASSET_OR_LIABILITY source=$account help="le compte sera placé à l'actif si son solde est débiteur, ou au passif s'il est créditeur"} {input type="radio" label="Résultat : charge" name="position" value=Entities\Accounting\Account::EXPENSE source=$account help="dépenses"} |
︙ | ︙ |
Modified src/templates/acc/charts/accounts/_nav.tpl from [874d4ab505] to [a992f235ac].
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 | {if !$dialog || $dialog !== 'manage'} <nav class="tabs"> {if $dialog} {* JS trick to get back to the original iframe URL! *} <aside> {if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN)} {linkbutton href="!acc/charts/accounts/new.php?id=%d&%s"|args:$chart.id,$types_arg label="Ajouter un compte" shape="plus"} {/if} {linkbutton shape="left" label="Retour à la sélection de compte" href="#" onclick="g.reloadParentDialog(); return false;"} </aside> <ul> {else} <ul> <li><a href="{$admin_url}acc/years/">Exercices</a></li> <li><a href="{$admin_url}acc/projects/">Projets <em>(compta analytique)</em></a></li> <li class="current"><a href="{$admin_url}acc/charts/">Plans comptables</a></li> </ul> {if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN)} <aside>{linkbutton href="!acc/charts/accounts/new.php?id=%d&%s"|args:$chart.id,$types_arg label="Ajouter un compte" shape="plus" target=$dialog_target}</aside> {/if} <ul class="sub"> <li class="title">{$chart.label}</li> {/if} <li{if $current == 'favorites'} class="current"{/if}>{link href="!acc/charts/accounts/?id=%d&%s"|args:$chart.id,$types_arg label="Comptes usuels"}</li> <li{if $current == 'all'} class="current"{/if}>{link href="!acc/charts/accounts/all.php?id=%d&%s"|args:$chart.id,$types_arg label="Tous les comptes"}</li> </ul> </nav> {/if} | > > > > | 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 | {if !$dialog || $dialog !== 'manage'} <nav class="tabs"> {if $dialog} {* JS trick to get back to the original iframe URL! *} <aside> {if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN)} {linkbutton href="!acc/charts/accounts/new.php?id=%d&%s"|args:$chart.id,$types_arg label="Ajouter un compte" shape="plus"} {/if} {linkbutton shape="left" label="Retour à la sélection de compte" href="#" onclick="g.reloadParentDialog(); return false;"} </aside> <ul> <li class="title">{$chart.label}</li> {else} <ul> <li><a href="{$admin_url}acc/years/">Exercices</a></li> <li><a href="{$admin_url}acc/projects/">Projets <em>(compta analytique)</em></a></li> <li class="current"><a href="{$admin_url}acc/charts/">Plans comptables</a></li> </ul> {if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN)} <aside>{linkbutton href="!acc/charts/accounts/new.php?id=%d&%s"|args:$chart.id,$types_arg label="Ajouter un compte" shape="plus" target=$dialog_target}</aside> {/if} <ul class="sub"> <li class="title">{$chart.label}</li> {/if} {if $chart.country} <li{if $current == 'favorites'} class="current"{/if}>{link href="!acc/charts/accounts/?id=%d&%s"|args:$chart.id,$types_arg label="Comptes usuels"}</li> {/if} <li{if $current == 'all'} class="current"{/if}>{link href="!acc/charts/accounts/all.php?id=%d&%s"|args:$chart.id,$types_arg label="Tous les comptes"}</li> </ul> </nav> {/if} |
Modified src/templates/acc/charts/accounts/all.tpl from [ac2fae1241] to [ea6b21e47e].
︙ | ︙ | |||
18 19 20 21 22 23 24 25 26 27 28 29 30 31 | {foreach from=$list->iterate() item="account"} <tr class="account account-level-{$account.level}"> <td class="num">{$account.code}</td> <th{if !$account.description} colspan=2{/if}>{$account.label}</th> {if $account.description} <td class="help">{$account.description|escape|nl2br}</td> {/if} <td> <?php $shape = $account->bookmark ? 'check' : 'uncheck'; $title = $account->bookmark ? 'Ôter des favoris' : 'Marquer comme favori'; ?> {button shape=$shape name="bookmark[%d]"|args:$account.id value=$account.bookmark label="Favori" title=$title type="submit"} </td> | > > > > > > > > < < < | 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 | {foreach from=$list->iterate() item="account"} <tr class="account account-level-{$account.level}"> <td class="num">{$account.code}</td> <th{if !$account.description} colspan=2{/if}>{$account.label}</th> {if $account.description} <td class="help">{$account.description|escape|nl2br}</td> {/if} <td> {$account.position_report} <td> {$account.position_name} </td> <td> {if $account.user}<em>Ajouté</em>{/if} </td> <td> <?php $shape = $account->bookmark ? 'check' : 'uncheck'; $title = $account->bookmark ? 'Ôter des favoris' : 'Marquer comme favori'; ?> {button shape=$shape name="bookmark[%d]"|args:$account.id value=$account.bookmark label="Favori" title=$title type="submit"} </td> <td class="actions"> {if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN) && !$chart.archived} {if $account.user || !$chart.code} {linkbutton shape="delete" label="Supprimer" href="!acc/charts/accounts/delete.php?id=%d&%s"|args:$account.id,$types_arg target=$dialog_target} {/if} {linkbutton shape="edit" label="Modifier" href="!acc/charts/accounts/edit.php?id=%d%s"|args:$account.id,$types_arg target=$dialog_target} {/if} |
︙ | ︙ |
Modified src/templates/acc/charts/accounts/index.tpl from [d633437db9] to [fae8c70754].
︙ | ︙ | |||
16 17 18 19 20 21 22 | <table class="list"> {foreach from=$accounts_grouped item="group"} <tbody> <tr> <td colspan="4"><h2 class="ruler">{$group.label}</h2></td> <td class="actions"> | | | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <table class="list"> {foreach from=$accounts_grouped item="group"} <tbody> <tr> <td colspan="4"><h2 class="ruler">{$group.label}</h2></td> <td class="actions"> {if !$chart.archived && $group.type} {linkbutton label="Ajouter un compte" shape="plus" href="!acc/charts/accounts/new.php?id=%d&type=%d&%s"|args:$chart.id,$group.type,$types_arg target=$dialog_target} {/if} </td> </tr> {foreach from=$group.accounts item="account"} <tr class="account"> |
︙ | ︙ |
Modified src/templates/acc/charts/accounts/selector.tpl from [ea4c622934] to [ffae656512].
1 2 3 4 5 6 | {include file="_head.tpl" title="Sélectionner un compte"} <div class="selector"> {if empty($grouped_accounts) && empty($accounts)} <p class="block alert">Le plan comptable ne comporte aucun compte de ce type.<br /> | < > > > > < | | 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 | {include file="_head.tpl" title="Sélectionner un compte"} <div class="selector"> {if empty($grouped_accounts) && empty($accounts)} <p class="block alert">Le plan comptable ne comporte aucun compte de ce type.<br /> </p> {if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN)} <p class="edit">{linkbutton label="Modifier les comptes" href=$edit_url shape="edit"}</p> {/if} {else} <header> <h2 class="quick-search"> <input type="text" placeholder="Recherche rapide…" title="Filtrer la liste" />{button shape="delete" type="reset" title="Effacer la recherche"} {* We can't use input type="search" because Firefox sucks *} </h2> {if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN)} <p class="edit">{linkbutton label="Modifier les comptes" href=$edit_url shape="edit"}</p> {/if} <p>{input type="select" name="filter" options=$filter_options default=$filter}</p> </header> {if isset($grouped_accounts)} <?php $index = 1; ?> |
︙ | ︙ |
Modified src/templates/acc/charts/edit.tpl from [599431b8b3] to [b40615c384].
1 2 3 4 5 6 7 8 9 | {include file="_head.tpl" title="Modifier un plan comptable" current="acc/years"} {form_errors} <form method="post" action="{$self_url}" data-focus="1"> <fieldset> <legend>Modifier un plan comptable</legend> <dl> {input type="text" name="label" label="Libellé" required=1 source=$chart} | | | | | 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="_head.tpl" title="Modifier un plan comptable" current="acc/years"} {form_errors} <form method="post" action="{$self_url}" data-focus="1"> <fieldset> <legend>Modifier un plan comptable</legend> <dl> {input type="text" name="label" label="Libellé" required=1 source=$chart} {if !$chart.code && !$chart.country} {include file="./_country_input.tpl"} {/if} <dt><label for="f_archived_1">Archivage</label></dt> {input type="checkbox" name="archived" value="1" source=$chart label="Plan comptable archivé" help="Ce plan comptable ne pourra plus être modifié ni utilisé dans un nouvel exercice"} </dl> <p class="submit"> {csrf_field key="acc_charts_edit_%d"|args:$chart.id} {button type="submit" name="save" label="Enregistrer" shape="right" class="main"} </p> </fieldset> </form> {include file="_foot.tpl"} |
Modified src/templates/acc/charts/index.tpl from [30e14ccc12] to [f5f33a7d9c].
︙ | ︙ | |||
23 24 25 26 27 28 29 | <td>Type</td> <td>Archivé</td> <td></td> </thead> <tbody> {foreach from=$list item="item"} <tr{if $item.archived} class="disabled"{/if}> | | > | > | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <td>Type</td> <td>Archivé</td> <td></td> </thead> <tbody> {foreach from=$list item="item"} <tr{if $item.archived} class="disabled"{/if}> <td>{if $item.country}{$item.country|get_country_name}{else}-Autre-{/if}</td> <th><a href="{$admin_url}acc/charts/accounts/?id={$item.id}">{$item.label}</a></th> <td>{if $item.code}Officiel{else}Personnel{/if}</td> <td>{if $item.archived}<em>Archivé</em>{/if}</td> <td class="actions"> {if $item.country} {linkbutton shape="star" label="Comptes usuels" href="!acc/charts/accounts/?id=%d"|args:$item.id} {/if} {linkbutton shape="menu" label="Tous les comptes" href="!acc/charts/accounts/all.php?id=%d"|args:$item.id} {if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN)} {linkbutton shape="edit" label="Modifier" href="!acc/charts/edit.php?id=%d"|args:$item.id target="_dialog"} {if $item->canDelete()} {linkbutton shape="delete" label="Supprimer" href="!acc/charts/delete.php?id=%d"|args:$item.id target="_dialog"} {/if} {/if} |
︙ | ︙ | |||
67 68 69 70 71 72 73 | {input type="radio-btn" name="type" value="import" label="Importer un plan comptable personnel" help="À partir d'un tableau (CSV, Office, etc.)"} </dl> </fieldset> <fieldset class="type-copy hidden"> <legend>Créer un nouveau plan comptable à partir d'un existant</legend> <dl> | | | < | | | > > | 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 | {input type="radio-btn" name="type" value="import" label="Importer un plan comptable personnel" help="À partir d'un tableau (CSV, Office, etc.)"} </dl> </fieldset> <fieldset class="type-copy hidden"> <legend>Créer un nouveau plan comptable à partir d'un existant</legend> <dl> {input type="select_groups" name="copy" options=$charts_grouped label="Recopier depuis" required=1 default=$from} {input type="text" name="label" label="Libellé" required=1} {include file="./_country_input.tpl"} </fieldset> <fieldset class="type-install hidden"> <legend>Installer un nouveau plan comptable officiel</legend> <dl> {input type="select" name="install" label="Plan comptable" required=true options=$install_list} </dl> </fieldset> <fieldset class="type-import hidden"> <legend>Importer un plan comptable personnel</legend> <dl> {input type="text" name="label" label="Libellé" required=1} {include file="./_country_input.tpl" name="import_country"} {input type="file" name="file" label="Fichier à importer" accept="csv" required=1} <dd class="help"> {* FIXME utiliser _csv_help.tpl ici ! *} Règles à suivre pour créer le fichier : <ul> <li>Le fichier doit comporter les colonnes suivantes : <em>{$columns}</em></li> <li>Suggestion : pour obtenir un exemple du format attendu, faire un export d'un plan comptable existant</li> </ul> </dd> </dl> </fieldset> <p class="submit type-all"> {csrf_field key=$csrf_key} {button type="submit" name="new" label="Créer" shape="right" class="main"} </p> </form> <script type="text/javascript"> {literal} function toggleFormOption() { var v = $('input[name="type"]:checked'); if (!v.length) { return; } v = v[0].value; g.toggle('.type-import, .type-copy, .type-install', false); g.toggle('.type-' + v, true); g.toggle('.type-all', true); } $('input[name="type"]').forEach((e) => { |
︙ | ︙ |
Modified src/templates/acc/transactions/_form.tpl from [0ac1ca0222] to [71b8925321].
︙ | ︙ | |||
39 40 41 42 43 44 45 | </fieldset> {foreach from=$types_details item="type"} <fieldset data-types="t{$type.id}"{if $is_new} class="hidden"{/if}> <legend>{$type.label}</legend> {if $type.id == $transaction::TYPE_ADVANCED} {* Saisie avancée *} | | | | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | </fieldset> {foreach from=$types_details item="type"} <fieldset data-types="t{$type.id}"{if $is_new} class="hidden"{/if}> <legend>{$type.label}</legend> {if $type.id == $transaction::TYPE_ADVANCED} {* Saisie avancée *} {include file="acc/transactions/_lines_form.tpl" chart_id=$chart.id} {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.selector_name label=$account.label required=1 default=$account.selector_value} {/foreach} </dl> {/if} </fieldset> {/foreach} <fieldset{if $is_new} class="hidden"{/if}> |
︙ | ︙ |
Modified src/templates/acc/transactions/_lines_form.tpl from [f29d6d7ce6] to [002ecab651].
︙ | ︙ | |||
18 19 20 21 22 23 24 | <td></td> </tr> </thead> <tbody> {foreach from=$lines key="k" item="line"} <tr> <td class="account"> | | | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <td></td> </tr> </thead> <tbody> {foreach from=$lines key="k" item="line"} <tr> <td class="account"> {input type="list" target="!acc/charts/accounts/selector.php?year=%d"|args:$transaction.id_year name="lines[account_selector][]" default=$line.account_selector} </td> <td class="money">{input type="money" name="lines[debit][]" default=$line.debit size=5}</td> <td class="money">{input type="money" name="lines[credit][]" default=$line.credit size=5}</td> <td>{input type="text" name="lines[label][]" default=$line.label}</td> <td>{input type="text" name="lines[reference][]" default=$line.reference size=10}</td> {if count($projects) > 1} <td>{input default=$line.id_project type="select" name="lines[id_project][]" options=$projects}</td> |
︙ | ︙ |
Modified src/templates/acc/years/balance.tpl from [c17b5db189] to [3b8dda4a1a].
︙ | ︙ | |||
77 78 79 80 81 82 83 | <td> {$line.code} — {$line.label} <input type="hidden" name="lines[code][]" value="{$line.code}" /> <input type="hidden" name="lines[label][]" value="{$line.label}" /> </td> {/if} <th> | | > | > > > | 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 | <td> {$line.code} — {$line.label} <input type="hidden" name="lines[code][]" value="{$line.code}" /> <input type="hidden" name="lines[label][]" value="{$line.label}" /> </td> {/if} <th> {input type="list" target="!acc/charts/accounts/selector.php?chart=%d"|args:$year.id_chart name="lines[account_selector][]" default=$line.account_selector} </th> <td>{input type="money" name="lines[debit][]" default=$line.debit size=5}</td> <td>{input type="money" name="lines[credit][]" default=$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> {if $chart_change} <td></td> {/if} <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 $can_appropriate} <dl> {input type="checkbox" name="appropriation" value="1" checked="checked" label="Affecter automatiquement le résultat (conseillé)"} <dd class="help">Si cette case est cochée, le résultat sera automatiquement affecté au compte « {$appropriation_account.code} — {$appropriation_account.label} ».</dd> </dl> {/if} {/if} </fieldset> <p class="submit"> {if null === $previous_year} {button type="submit" name="next" label="Continuer" shape="right" class="main"} - ou - {if $_GET.from} |
︙ | ︙ |
Modified src/templates/services/user/_service_user_form.tpl from [90998a1ecf] to [80d09033a2].
︙ | ︙ | |||
87 88 89 90 91 92 93 | {foreach from=$grouped_services item="service"} <?php if (!count($service->fees)) { continue; } ?> <dl data-service="s{$service.id}"> <dt><label for="f_fee">Tarif</label> <b>(obligatoire)</b></dt> {foreach from=$service.fees key="service_id" item="fee"} <dd class="radio-btn"> | | | 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | {foreach from=$grouped_services item="service"} <?php if (!count($service->fees)) { continue; } ?> <dl data-service="s{$service.id}"> <dt><label for="f_fee">Tarif</label> <b>(obligatoire)</b></dt> {foreach from=$service.fees key="service_id" item="fee"} <dd class="radio-btn"> {input type="radio" name="id_fee" value=$fee.id data-user-amount=$fee.user_amount data-account=$fee.id_account data-year=$fee.id_year label=null data-project=$fee.id_project source=$service_user } <label for="f_id_fee_{$fee.id}"> <div> <h3>{$fee.label}</h3> <p> {if $fee.user_amount && $fee.formula} <strong>{$fee.user_amount|raw|money_currency}</strong> (montant calculé) {elseif $fee.formula} |
︙ | ︙ | |||
144 145 146 147 148 149 150 | {input type="money" name="amount" label="Montant réglé par le membre" required=true help="En cas de règlement en plusieurs fois il sera possible d'ajouter des règlements via la page de suivi des activités de ce membre."} {input type="list" target="!acc/charts/accounts/selector.php?targets=%s"|args:$account_targets name="account_selector" label="Compte de règlement" required=true} {input type="text" name="reference" label="Numéro de pièce comptable" help="Numéro de facture, de reçu, de note de frais, etc."} {input type="text" name="payment_reference" label="Référence de paiement" help="Numéro de chèque, numéro de transaction CB, etc."} {input type="textarea" name="notes" label="Remarques"} {if count($projects) > 1} | | | 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | {input type="money" name="amount" label="Montant réglé par le membre" required=true help="En cas de règlement en plusieurs fois il sera possible d'ajouter des règlements via la page de suivi des activités de ce membre."} {input type="list" target="!acc/charts/accounts/selector.php?targets=%s"|args:$account_targets name="account_selector" label="Compte de règlement" required=true} {input type="text" name="reference" label="Numéro de pièce comptable" help="Numéro de facture, de reçu, de note de frais, etc."} {input type="text" name="payment_reference" label="Référence de paiement" help="Numéro de chèque, numéro de transaction CB, etc."} {input type="textarea" name="notes" label="Remarques"} {if count($projects) > 1} {input type="select" options=$projects name="id_project" label="Projet analytique" required=false} {/if} </dl> </fieldset> {/if} <p class="submit"> {csrf_field key=$csrf_key} |
︙ | ︙ |
Modified src/www/admin/acc/accounts/deposit.php from [0f05ff8022] to [b43f97e6b8].
︙ | ︙ | |||
48 49 50 51 52 53 54 55 56 57 58 59 60 | $date = $current_year->end_date; } $target = $account::TYPE_BANK; $journal_count = $account->countDepositJournal(CURRENT_YEAR_ID); $tpl->assign(compact( 'account', 'journal', 'date', 'target', 'checked', | > > | > > | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | $date = $current_year->end_date; } $target = $account::TYPE_BANK; $journal_count = $account->countDepositJournal(CURRENT_YEAR_ID); $missing_balance = $account->getDepositMissingBalance(CURRENT_YEAR_ID); $tpl->assign(compact( 'account', 'journal', 'date', 'target', 'checked', 'journal_count', 'missing_balance', 'transaction' )); $tpl->display('acc/accounts/deposit.tpl'); |
Modified src/www/admin/acc/accounts/index.php from [c826784eaa] to [f19ff030ea].
1 2 3 4 5 6 7 8 | <?php namespace Garradin; use Garradin\Accounting\Reports; require_once __DIR__ . '/../_inc.php'; if (!CURRENT_YEAR_ID) { | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?php namespace Garradin; use Garradin\Accounting\Reports; require_once __DIR__ . '/../_inc.php'; if (!CURRENT_YEAR_ID) { Utils::redirect('!acc/years/?msg=OPEN'); } $tpl->assign('chart_id', $current_year->id_chart); $tpl->assign('grouped_accounts', Reports::getClosingSumsFavoriteAccounts(['year' => CURRENT_YEAR_ID])); $tpl->display('acc/accounts/index.tpl'); |
Modified src/www/admin/acc/charts/accounts/index.php from [9349ac07de] to [61b1caaf37].
︙ | ︙ | |||
14 15 16 17 18 19 20 21 22 23 24 25 26 | $year = $current_year; $chart = $year->chart(); } if (!$chart) { throw new UserException('Aucun plan comptable spécifié'); } $accounts = $chart->accounts(); $tpl->assign(compact('chart')); $tpl->assign('accounts_grouped', $accounts->listCommonGrouped($types, true)); $tpl->display('acc/charts/accounts/index.tpl'); | > > > > | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | $year = $current_year; $chart = $year->chart(); } if (!$chart) { throw new UserException('Aucun plan comptable spécifié'); } if (!$chart->country) { Utils::redirect('!acc/charts/accounts/all.php?id=' . $chart->id); } $accounts = $chart->accounts(); $tpl->assign(compact('chart')); $tpl->assign('accounts_grouped', $accounts->listCommonGrouped($types, true)); $tpl->display('acc/charts/accounts/index.tpl'); |
Modified src/www/admin/acc/charts/accounts/new.php from [9e5d2bdbfd] to [18867cde04].
︙ | ︙ | |||
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 | throw new UserException("Il n'est pas possible de modifier un plan comptable archivé."); } $accounts = $chart->accounts(); $account = new Account; $account->bookmark = true; $account->id_chart = $chart->id(); $type = f('type') ?? qg('type'); // Simple creation with pre-determined account type if ($type !== null) { $account->type = (int)$type; } elseif (isset($types) && is_array($types) && count($types) == 1) { $account->type = (int)current($types); } $csrf_key = 'account_new'; $form->runIf('toggle_bookmark', function () use ($accounts, $chart) { $a = $accounts->get(f('toggle_bookmark')); $a->bookmark = true; $a->save(); | > > > > > | 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 | throw new UserException("Il n'est pas possible de modifier un plan comptable archivé."); } $accounts = $chart->accounts(); $account = new Account; $account->bookmark = true; $account->user = true; $account->code = ''; $account->id_chart = $chart->id(); $type = f('type') ?? qg('type'); // Simple creation with pre-determined account type if ($type !== null) { $account->type = (int)$type; } elseif (isset($types) && is_array($types) && count($types) == 1) { $account->type = (int)current($types); } elseif (!$chart->country) { $account->type = $account::TYPE_NONE; } $csrf_key = 'account_new'; $form->runIf('toggle_bookmark', function () use ($accounts, $chart) { $a = $accounts->get(f('toggle_bookmark')); $a->bookmark = true; $a->save(); |
︙ | ︙ |
Modified src/www/admin/acc/charts/accounts/selector.php from [c9b4113a17] to [12ae13ee1e].
︙ | ︙ | |||
22 23 24 25 26 27 28 | $filter_options = [ // 'bookmark' => 'Voir seulement les comptes favoris', 'usual' => 'Voir seulement les comptes favoris et usuels', 'all' => 'Voir tous les comptes', ]; if (!count($targets)) { | | > | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | $filter_options = [ // 'bookmark' => 'Voir seulement les comptes favoris', 'usual' => 'Voir seulement les comptes favoris et usuels', 'all' => 'Voir tous les comptes', ]; if (!count($targets)) { $filter_options['all'] = 'Voir tout le plan comptable'; $targets = null; } if (null !== $filter) { if (!array_key_exists($filter, $filter_options)) { $filter = 'usual'; } |
︙ | ︙ | |||
61 62 63 64 65 66 67 68 69 70 | $chart = $current_year->chart(); $year = $current_year; } if (!$chart) { throw new UserException('Aucun exercice ouvert disponible'); } $accounts = $chart->accounts(); | > > > > > < | < < | < | | | | | 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 | $chart = $current_year->chart(); $year = $current_year; } if (!$chart) { throw new UserException('Aucun exercice ouvert disponible'); } // Charts with no country don't allow to use types if (!$chart->country) { $targets = null; } $accounts = $chart->accounts(); $edit_url = sprintf('!acc/charts/accounts/%s?id=%d&types=%s', isset($grouped_accounts) ? '' : 'all.php', $chart->id(), $targets_str); $tpl->assign(compact('chart', 'targets', 'targets_str', 'filter_options', 'filter', 'edit_url')); if ($filter == 'all') { $tpl->assign('accounts', $accounts->listAll($targets)); } elseif ($year) { $tpl->assign('grouped_accounts', $year->listCommonAccountsGrouped($targets)); } else { $tpl->assign('grouped_accounts', $accounts->listCommonGrouped($targets)); } $tpl->display('acc/charts/accounts/selector.tpl'); |
Modified src/www/admin/acc/charts/edit.php from [2bb82f5b29] to [d85bd0205b].
1 2 3 4 5 6 7 8 9 10 | <?php namespace Garradin; use Garradin\Accounting\Charts; require_once __DIR__ . '/../_inc.php'; $session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN); $chart = Charts::get((int) qg('id')); | > | 1 2 3 4 5 6 7 8 9 10 11 | <?php namespace Garradin; use Garradin\Entities\Accounting\Chart; use Garradin\Accounting\Charts; require_once __DIR__ . '/../_inc.php'; $session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN); $chart = Charts::get((int) qg('id')); |
︙ | ︙ | |||
18 19 20 21 22 23 24 | $form->runIf('save', function() use ($chart) { $chart->importForm(); $chart->set('archived', (bool) f('archived')); $chart->save(); }, $csrf_key, '!acc/charts/'); $tpl->assign(compact('chart')); | < | 19 20 21 22 23 24 25 26 27 | $form->runIf('save', function() use ($chart) { $chart->importForm(); $chart->set('archived', (bool) f('archived')); $chart->save(); }, $csrf_key, '!acc/charts/'); $tpl->assign(compact('chart')); $tpl->display('acc/charts/edit.tpl'); |
Modified src/www/admin/acc/charts/index.php from [ef02d60edc] to [5060965ccd].
︙ | ︙ | |||
19 20 21 22 23 24 25 | }, $csrf_key, '!acc/charts/'); $form->runIf(f('type') == 'install', function () { Charts::install(f('install')); }, $csrf_key, '!acc/charts/'); $form->runIf(f('type') == 'import', function () { | | | | | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | }, $csrf_key, '!acc/charts/'); $form->runIf(f('type') == 'install', function () { Charts::install(f('install')); }, $csrf_key, '!acc/charts/'); $form->runIf(f('type') == 'import', function () { Charts::import('file', f('label'), f('country_import')); }, $csrf_key, '!acc/charts/'); $tpl->assign(compact('csrf_key')); $tpl->assign('columns', implode(', ', Chart::COLUMNS)); $tpl->assign('country_list', Utils::getCountryList()); $tpl->assign('from', (int)qg('from')); $tpl->assign('charts_grouped', Charts::listByCountry()); $tpl->assign('country_list', Chart::COUNTRY_LIST + ['' => '— Autre']); $tpl->assign('install_list', Charts::listInstallable()); } $tpl->display('acc/charts/index.tpl'); |
Modified src/www/admin/acc/transactions/new.php from [919507f411] to [8ee9e95235].
︙ | ︙ | |||
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | } $chart = $current_year->chart(); $accounts = $chart->accounts(); $csrf_key = 'acc_transaction_new'; $transaction = new Transaction; $amount = 0; $id_project = null; $linked_users = null; $lines = isset($_POST['lines']) ? Transaction::getFormLines() : [[], []]; $types_details = $transaction->getTypesDetails(); // Quick-fill transaction from query parameters if (qg('a')) { $amount = Utils::moneyToInteger(qg('a')); } if (qg('l')) { $transaction->label = qg('l'); } if (qg('d')) { $transaction->date = new Date(qg('d')); | > > > > > | 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 | } $chart = $current_year->chart(); $accounts = $chart->accounts(); $csrf_key = 'acc_transaction_new'; $transaction = new Transaction; $amount = 0; $id_project = null; $linked_users = null; $lines = isset($_POST['lines']) ? Transaction::getFormLines() : [[], []]; $types_details = $transaction->getTypesDetails(); // Quick-fill transaction from query parameters if (qg('a')) { $amount = Utils::moneyToInteger(qg('a')); } if (qg('a0')) { $amount = (int)qg('a0'); } if (qg('l')) { $transaction->label = qg('l'); } if (qg('d')) { $transaction->date = new Date(qg('d')); |
︙ | ︙ | |||
63 64 65 66 67 68 69 70 71 72 73 74 75 76 | $id_project = $old->getProjectId(); $amount = $transaction->getLinesCreditSum(); $linked_users = $old->listLinkedUsersAssoc(); $tpl->assign('duplicate_from', $old->id()); } // Set last used date if (empty($transaction->date) && $session->get('acc_last_date') && $date = Date::createFromFormat('!Y-m-d', $session->get('acc_last_date'))) { $transaction->date = $date; } // Set date of the day if no date was set elseif (empty($transaction->date)) { | > > | 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | $id_project = $old->getProjectId(); $amount = $transaction->getLinesCreditSum(); $linked_users = $old->listLinkedUsersAssoc(); $tpl->assign('duplicate_from', $old->id()); } $transaction->id_year = $current_year->id(); // Set last used date if (empty($transaction->date) && $session->get('acc_last_date') && $date = Date::createFromFormat('!Y-m-d', $session->get('acc_last_date'))) { $transaction->date = $date; } // Set date of the day if no date was set elseif (empty($transaction->date)) { |
︙ | ︙ | |||
100 101 102 103 104 105 106 | else { $lines = [['account_selector' => $s], []]; } } $form->runIf('save', function () use ($transaction, $session, $current_year) { $transaction->importFromNewForm(); | < | | 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 | else { $lines = [['account_selector' => $s], []]; } } $form->runIf('save', function () use ($transaction, $session, $current_year) { $transaction->importFromNewForm(); $transaction->id_creator = $session->getUser()->id; $transaction->save(); // Link members if (null !== f('users') && is_array(f('users'))) { $transaction->updateLinkedUsers(array_keys(f('users'))); } $session->set('acc_last_date', $transaction->date->format('Y-m-d')); Utils::redirect(sprintf('!acc/transactions/details.php?id=%d&created', $transaction->id())); }, $csrf_key); $tpl->assign(compact('csrf_key', 'transaction', 'amount', 'lines', 'id_project', 'types_details', 'linked_users')); $tpl->assign('chart', $chart); $tpl->assign('projects', Projects::listAssocWithEmpty()); $tpl->display('acc/transactions/new.tpl'); |
Modified src/www/admin/acc/years/balance.php from [e6e0a94e71] to [af28d593f6].
︙ | ︙ | |||
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | } if ($year->closed) { throw new UserException('Impossible de modifier un exercice clôturé.'); } $csrf_key = 'acc_years_balance_' . $year->id(); $form->runIf('save', function () use ($year, $session) { $transaction = new Transaction; $transaction->id_creator = Session::getUserId(); $transaction->importFromBalanceForm($year); $transaction->save(); if (f('appropriation')) { // (affectation du résultat) $t2 = Years::makeAppropriation($year); if ($t2) { $t2->id_creator = $transaction->id_creator; $t2->save(); } | > > > > > | > > > > | 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 | } if ($year->closed) { throw new UserException('Impossible de modifier un exercice clôturé.'); } $csrf_key = 'acc_years_balance_' . $year->id(); $accounts = $year->accounts(); $form->runIf('save', function () use ($year, $session) { $db = DB::getInstance(); // Fail everything if appropriation failed $db->begin(); $transaction = new Transaction; $transaction->id_creator = Session::getUserId(); $transaction->importFromBalanceForm($year); $transaction->save(); if (f('appropriation')) { // (affectation du résultat) $t2 = Years::makeAppropriation($year); if ($t2) { $t2->id_creator = $transaction->id_creator; $t2->save(); } } $db->commit(); if (f('appropriation')) { Utils::redirect('!acc/reports/journal.php?year=' . $year->id()); } Utils::redirect('!acc/transactions/details.php?id=' . $transaction->id()); }, $csrf_key); |
︙ | ︙ | |||
72 73 74 75 76 77 78 | $chart_change = true; $codes = []; foreach ($lines as $line) { $codes[] = $line->code; } | | | | | 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 | $chart_change = true; $codes = []; foreach ($lines as $line) { $codes[] = $line->code; } $matching_accounts = $accounts->listForCodes($codes); } // Append result $result = Reports::getResult(['year' => $previous_year->id()]); if ($result > 0) { $account = $accounts->getSingleAccountForType(Account::TYPE_POSITIVE_RESULT); } else { $account = $accounts->getSingleAccountForType(Account::TYPE_NEGATIVE_RESULT); } if (!$account) { $account = (object) [ 'id' => null, 'code' => null, 'label' => null, |
︙ | ︙ | |||
108 109 110 111 112 113 114 | foreach ($lines as $k => &$line) { $line->credit = !$line->is_debt ? abs($line->balance) : 0; $line->debit = $line->is_debt ? abs($line->balance) : 0; if ($chart_change) { if (array_key_exists($line->code, $matching_accounts)) { $acc = $matching_accounts[$line->code]; | | | > < | < < < | | > > | | 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 | foreach ($lines as $k => &$line) { $line->credit = !$line->is_debt ? abs($line->balance) : 0; $line->debit = $line->is_debt ? abs($line->balance) : 0; if ($chart_change) { if (array_key_exists($line->code, $matching_accounts)) { $acc = $matching_accounts[$line->code]; $line->account_selector = [$acc->id => sprintf('%s — %s', $acc->code, $acc->label)]; } } else { $line->account_selector = $line->id ? [$line->id => sprintf('%s — %s', $line->code, $line->label)] : null; } $line = (array) $line; } unset($line); } if (!empty($_POST['lines']) && is_array($_POST['lines'])) { $lines = Transaction::getFormLines(); } $appropriation_account = $accounts->getSingleAccountForType(Account::TYPE_APPROPRIATION_RESULT); $can_appropriate = $accounts->getIdForType(Account::TYPE_NEGATIVE_RESULT) && $accounts->getIdForType(Account::TYPE_POSITIVE_RESULT); $tpl->assign(compact('lines', 'years', 'chart_change', 'previous_year', 'year_selected', 'year', 'csrf_key', 'can_appropriate', 'appropriation_account')); $tpl->display('acc/years/balance.tpl'); |
Modified src/www/admin/docs/new_file.php from [345eeb0542] to [2cd6b3a91e].
︙ | ︙ | |||
15 16 17 18 19 20 21 | $csrf_key = 'create_file'; $form->runIf('create', function () use ($parent) { $name = trim((string) f('name')); if (!strpos($name, '.')) { | | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | $csrf_key = 'create_file'; $form->runIf('create', function () use ($parent) { $name = trim((string) f('name')); if (!strpos($name, '.')) { $name .= '.md'; } $file = Files::createFromString($parent . '/' . $name, ''); Utils::redirect('!common/files/edit.php?p=' . rawurlencode($file->path)); }, $csrf_key); $tpl->assign(compact('csrf_key')); $tpl->display('docs/new_file.tpl'); |
Modified src/www/admin/static/scripts/accounts_list.js from [590fc24309] to [c39abd9210].
︙ | ︙ | |||
25 26 27 28 29 30 31 | var rows = document.querySelectorAll('table tr.account'); rows.forEach((e, k) => { var l = e.querySelector('td.num').innerText + ' ' + e.querySelector('th').innerText; e.setAttribute('data-search-label', normalizeString(l)); }); | | > > > | 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | var rows = document.querySelectorAll('table tr.account'); rows.forEach((e, k) => { var l = e.querySelector('td.num').innerText + ' ' + e.querySelector('th').innerText; e.setAttribute('data-search-label', normalizeString(l)); }); q.addEventListener('keyup', (e) => { filterTableList(); return true; }); document.querySelector('.quick-search button[type=reset]').onclick = () => { q.value = ''; q.focus(); return filterTableList(); }; q.focus(); } |
︙ | ︙ |
Modified src/www/admin/static/scripts/service_form.js from [f636d7ed2f] to [50e70f7536].
︙ | ︙ | |||
42 43 44 45 46 47 48 49 50 51 52 53 54 55 | btn.value = btn.value.replace(/&year=\d+/, '') + '&year=' + elm.getAttribute('data-year'); } // Fill the amount paid by the user if (amount && create) { $('#f_amount').value = g.formatMoney(amount); } } function initForm() { $('input[name=id_service]').forEach((e) => { e.onchange = () => { selectService(e); }; }); | > > > > | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | btn.value = btn.value.replace(/&year=\d+/, '') + '&year=' + elm.getAttribute('data-year'); } // Fill the amount paid by the user if (amount && create) { $('#f_amount').value = g.formatMoney(amount); } if (elm.dataset.project) { $('#f_id_project').value = elm.dataset.project; } } function initForm() { $('input[name=id_service]').forEach((e) => { e.onchange = () => { selectService(e); }; }); |
︙ | ︙ |
Modified src/www/admin/static/styles/05-navigation.css from [70387da6cb] to [799e56eaa8].
︙ | ︙ | |||
18 19 20 21 22 23 24 | nav.tabs .sub { margin: -1em 0 1em 2em; padding-top: 1em; border-left: 2px solid rgb(var(--gMainColor)); border-bottom-left-radius: .5em; } | | | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | nav.tabs .sub { margin: -1em 0 1em 2em; padding-top: 1em; border-left: 2px solid rgb(var(--gMainColor)); border-bottom-left-radius: .5em; } nav.tabs .title { margin: 0 1em 0 -1em; font-weight: bold; padding: .1em .5em; } nav.tabs li { margin: .3em .2em 0 .2em; |
︙ | ︙ | |||
88 89 90 91 92 93 94 | main nav.menu-right span { right: -1em; } main nav.menu button { text-align: left; | < | | 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | main nav.menu-right span { right: -1em; } main nav.menu button { text-align: left; }nav.home ul { display: flex; flex-wrap: wrap; align-items: stretch; } nav.home ul li { display: block; |
︙ | ︙ |
Modified src/www/admin/static/styles/10-accounting.css from [c27ff0ad62] to [b87d1b0cc6].
︙ | ︙ | |||
98 99 100 101 102 103 104 105 106 107 108 109 110 111 | } .year-infos .graphs.small figure img { max-width: 500px; } tr.account td.num { font-family: monospace; text-align: left; width: 8%;} table tr.account th { font-weight: normal; } tr.account-level-1 th { font-size: 1.6em; } tr.account-level-2 th { padding-left: 1em; font-size: 1.3em; } tr.account-level-3 th { padding-left: 2em; } tr.account-level-4 th { padding-left: 3em; } tr.account-level-5 th { padding-left: 4em; } tr.account-level-6 th { padding-left: 5em; } | > | 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | } .year-infos .graphs.small figure img { max-width: 500px; } tr.account td.num { font-family: monospace; text-align: left; width: 8%;} tr.account td.num a { font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; } table tr.account th { font-weight: normal; } tr.account-level-1 th { font-size: 1.6em; } tr.account-level-2 th { padding-left: 1em; font-size: 1.3em; } tr.account-level-3 th { padding-left: 2em; } tr.account-level-4 th { padding-left: 3em; } tr.account-level-5 th { padding-left: 4em; } tr.account-level-6 th { padding-left: 5em; } |
︙ | ︙ |