Comment: | Implement: quick deposit of cheques |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | dev |
Files: | files | file ages | folders |
SHA1: |
2b6dc32a675a857f74e4a186362ecde9 |
User & Date: | bohwaz on 2020-10-15 14:39:54 |
Other Links: | branch diff | manifest | tags |
2020-10-16
| ||
01:13 | Implementation start of graphs check-in: 9b0d5d20d0 user: bohwaz tags: dev | |
2020-10-15
| ||
14:39 | Implement: quick deposit of cheques check-in: 2b6dc32a67 user: bohwaz tags: dev | |
12:49 | Fix: use default instead of value in input widgets check-in: 705e128caf user: bohwaz tags: dev | |
Modified src/include/lib/Garradin/Accounting/Transactions.php from [89df683976] to [f4ffbb7df1].
1 2 3 4 5 6 7 8 9 10 11 | <?php namespace Garradin\Accounting; use Garradin\Entities\Accounting\Transaction; use KD2\DB\EntityManager; use Garradin\DB; class Transactions { static public function get(int $id) | > | 1 2 3 4 5 6 7 8 9 10 11 12 | <?php namespace Garradin\Accounting; use Garradin\Entities\Accounting\Line; use Garradin\Entities\Accounting\Transaction; use KD2\DB\EntityManager; use Garradin\DB; class Transactions { static public function get(int $id) |
︙ | ︙ | |||
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | $st->bindValue(':id', (int)$row->id_line, \SQLITE3_INTEGER); $st->bindValue(':r', !empty($checked[$row->id_line]) ? 1 : 0, \SQLITE3_INTEGER); $st->execute(); } $db->commit(); } static public function countForUser(int $user_id): int { return DB::getInstance()->count('acc_transactions_users', 'id_user = ?', $user_id); } static public function countForCreator(int $user_id): int { return DB::getInstance()->count('acc_transactions', 'id_creator = ?', $user_id); } } | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | $st->bindValue(':id', (int)$row->id_line, \SQLITE3_INTEGER); $st->bindValue(':r', !empty($checked[$row->id_line]) ? 1 : 0, \SQLITE3_INTEGER); $st->execute(); } $db->commit(); } static public function saveDeposit(Transaction $transaction, \Generator $journal, array $checked) { $db = DB::getInstance(); $db->begin(); try { $ids = []; foreach ($journal as $row) { if (!array_key_exists($row->id, $checked)) { continue; } $ids[] = (int)$row->id; $line = new Line; $line->importForm([ 'reference' => $row->line_reference, 'id_account' => $row->id_account, ]); $line->credit = $row->debit; $transaction->add($line); } $transaction->save(); $ids = implode(',', $ids); $db->exec(sprintf('UPDATE acc_transactions SET status = (status | %d) WHERE id IN (%s);', Transaction::STATUS_DEPOSIT, $ids)); $db->commit(); } catch (\Exception $e) { $db->rollback(); throw $e; } } static public function countForUser(int $user_id): int { return DB::getInstance()->count('acc_transactions_users', 'id_user = ?', $user_id); } static public function countForCreator(int $user_id): int { return DB::getInstance()->count('acc_transactions', 'id_creator = ?', $user_id); } } |
Modified src/include/lib/Garradin/Entities/Accounting/Account.php from [b296019730] to [c0c34a0207].
︙ | ︙ | |||
135 136 137 138 139 140 141 | $row->running_sum = $sum; $row->date = \DateTime::createFromFormat('Y-m-d', $row->date); } return $rows; } | | | 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | $row->running_sum = $sum; $row->date = \DateTime::createFromFormat('Y-m-d', $row->date); } return $rows; } public function getReconcileJournal(int $year_id, DateTimeInterface $start_date, DateTimeInterface $end_date) { if ($end_date < $start_date) { throw new ValidationException('La date de début ne peut être avant la date de fin.'); } $db = DB::getInstance(); $sql = 'SELECT l.debit, l.credit, t.id, t.date, t.reference, l.reference AS line_reference, t.label, l.label AS line_label, l.reconciled, l.id AS id_line |
︙ | ︙ | |||
168 169 170 171 172 173 174 175 176 177 178 179 180 181 | $row->running_sum = $sum; yield $row; } yield ['sum' => $sum, 'date' => $end_date]; } public function getSumAtDate(int $year_id, DateTimeInterface $date): int { return (int) DB::getInstance()->firstColumn('SELECT SUM(l.credit) - SUM(l.debit) FROM acc_transactions_lines l INNER JOIN acc_transactions t ON t.id = l.id_transaction wHERE l.id_account = ? AND t.id_year = ? AND t.date < ? | > > > > > > > > > > > > > > > > > > | 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 | $row->running_sum = $sum; yield $row; } yield ['sum' => $sum, 'date' => $end_date]; } public function getDepositJournal(int $year_id): \Generator { $res = DB::getInstance()->iterate('SELECT l.debit, l.credit, t.id, t.date, t.reference, l.reference AS line_reference, t.label, l.label AS line_label, l.reconciled, l.id AS id_line, l.id_account 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 & ?);', $year_id, $this->id(), Transaction::STATUS_DEPOSIT); $sum = 0; foreach ($res as $row) { $row->date = \DateTime::createFromFormat('Y-m-d', $row->date); $sum += ($row->credit - $row->debit); $row->running_sum = $sum; yield $row; } } public function getSumAtDate(int $year_id, DateTimeInterface $date): int { return (int) DB::getInstance()->firstColumn('SELECT SUM(l.credit) - SUM(l.debit) FROM acc_transactions_lines l INNER JOIN acc_transactions t ON t.id = l.id_transaction wHERE l.id_account = ? AND t.id_year = ? AND t.date < ? |
︙ | ︙ |
Modified src/include/lib/Garradin/Entities/Accounting/Transaction.php from [02e2fdcd8b] to [d109570628].
︙ | ︙ | |||
21 22 23 24 25 26 27 28 29 30 31 32 33 34 | const TYPE_TRANSFER = 3; const TYPE_DEBT = 4; const TYPE_CREDIT = 5; const TYPE_PAYOFF = 6; const STATUS_WAITING = 1; const STATUS_PAID = 2; const TYPES_NAMES = [ 'Avancé', 'Recette', 'Dépense', 'Virement', 'Dette', | > | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | const TYPE_TRANSFER = 3; const TYPE_DEBT = 4; const TYPE_CREDIT = 5; const TYPE_PAYOFF = 6; const STATUS_WAITING = 1; const STATUS_PAID = 2; const STATUS_DEPOSIT = 4; const TYPES_NAMES = [ 'Avancé', 'Recette', 'Dépense', 'Virement', 'Dette', |
︙ | ︙ | |||
233 234 235 236 237 238 239 | foreach ($lines as $line) { $total += $line->credit; $total -= $line->debit; } if (0 !== $total) { | | > > > > > > > > > > > > > > > > > > > > > > > > | | 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 | foreach ($lines as $line) { $total += $line->credit; $total -= $line->debit; } if (0 !== $total) { throw new ValidationException(sprintf('Écriture non équilibrée : déséquilibre (%s) entre débits et crédits', Utils::money_format($total))); } if (!array_key_exists($this->type, self::TYPES_NAMES)) { throw new ValidationException('Type d\'écriture inconnu'); } } public function importFromDepositForm(?array $source = null): void { if (null === $source) { $source = $_POST; } $this->type = self::TYPE_TRANSFER; $amount = $source['amount']; $key = 'account_transfer'; $account = isset($source[$key]) && @count($source[$key]) ? key($source[$key]) : null; $line = new Line; $line->importForm([ 'debit' => $amount, 'credit' => 0, 'id_account' => $account, ]); $this->add($line); $this->importForm($source); } public function importFromNewForm(?array $source = null): void { if (null === $source) { $source = $_POST; } if (empty($source['type'])) { throw new ValidationException('Type d\'écriture inconnu'); |
︙ | ︙ |
Modified src/include/lib/Garradin/Template.php from [a556e64691] to [838003c89e].
︙ | ︙ | |||
302 303 304 305 306 307 308 | $current_value = Utils::money_format($current_value, ',', ''); } $currency = Config::getInstance()->get('monnaie'); $input = sprintf('<input type="text" pattern="[0-9]*([.,][0-9]{1,2})?" inputmode="decimal" size="8" class="money" %s value="%s" /><b>%s</b>', $attributes_string, $this->escape($current_value), $currency); } else { | > | | 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 | $current_value = Utils::money_format($current_value, ',', ''); } $currency = Config::getInstance()->get('monnaie'); $input = sprintf('<input type="text" pattern="[0-9]*([.,][0-9]{1,2})?" inputmode="decimal" size="8" class="money" %s value="%s" /><b>%s</b>', $attributes_string, $this->escape($current_value), $currency); } else { $value = isset($attributes['value']) ? '' : sprintf(' value="%s"', $this->escape($current_value)); $input = sprintf('<input type="%s" %s %s />', $type, $attributes_string, $value); } // No label? then we only want the input without the widget if (empty($label)) { return $input; } |
︙ | ︙ |
Added src/templates/acc/accounts/deposit.tpl version [981f4f25b1].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | {include file="admin/_head.tpl" title="Dépôt en banque : %s — %s"|args:$account.code,$account.label current="acc/accounts" js=1} <p class="help"> Cocher les cases correspondant aux montants à déposer, une nouvelle écriture sera générée. </p> {form_errors} <form method="post" action="{$self_url}"> <table class="list"> <thead> <tr> <td class="check"><input type="checkbox" title="Tout cocher / décocher" /></td> <td></td> <td>Date</td> <td>Réf. écriture</td> <td>Réf. ligne</td> <th>Libellé</th> <td class="money">Montant</td> <td class="money">Solde cumulé</td> </tr> </thead> <tbody> {foreach from=$journal item="line"} {if isset($line.sum)} <tr> <td colspan="5"></td> <td class="money">{if $line.sum > 0}-{/if}{$line.sum|abs|raw|html_money:false}</td> <th>Solde au {$line.date|date_fr:'d/m/Y'}</th> <td colspan="2"></td> </tr> {else} <tr> <td class="check"><input type="checkbox" name="deposit[{$line.id}]" value="1" data-debit="{$line.debit}" data-credit="{$line.credit}" /></td> <td class="num"><a href="{$admin_url}acc/transactions/details.php?id={$line.id}">#{$line.id}</a></td> <td>{$line.date|date_fr:'d/m/Y'}</td> <td>{$line.reference}</td> <td>{$line.line_reference}</td> <th>{$line.label}</th> <td class="money">{$line.debit|raw|html_money}</td> {* Not a bug! Credit/debit is reversed here to reflect the bank statement *} <td class="money">{if $line.running_sum > 0}-{/if}{$line.running_sum|abs|raw|html_money:false}</td> </tr> {/if} {/foreach} </tbody> </table> <fieldset> <legend>Détails de l'écriture de dépôt</legend> <dl> {input type="text" name="label" label="Libellé" required=1 default="Dépôt en banque"} {input type="date" name="date" default=$date label="Date" required=1} {input type="money" name="amount" label="Montant" required=1} {input type="list" target="acc/charts/accounts/selector.php?chart=%d&targets=%d"|args:$account.id_chart,$target name="account_transfer" label="Compte de dépôt" required=1} {input type="text" name="reference" label="Numéro de pièce comptable"} {input type="textarea" name="notes" label="Remarques" rows=4 cols=30} </dl> </fieldset> <p class="submit"> {csrf_field key="acc_deposit_%s"|args:$account.id} <input type="submit" name="save" value="Enregistrer le dépôt →" /> </p> </form> {literal} <script type="text/javascript"> var total = 0; $('tbody input[type=checkbox]').forEach((e) => { e.addEventListener('change', () => { var v = e.getAttribute('data-debit') || e.getAttribute('data-credit'); v = parseInt(v, 10); total += e.checked ? v : -v; $('#f_amount').value = g.formatMoney(total); }); }); </script> {/literal} {include file="admin/_foot.tpl"} |
Modified src/templates/acc/accounts/index.tpl from [c0634827f4] to [dcc06b4c3f].
︙ | ︙ | |||
38 39 40 41 42 43 44 | {$account.sum|abs|raw|html_money:false} {$config.monnaie} {else} {$account.sum|raw|html_money:false} {$config.monnaie} {/if} </td> <td class="actions"> {linkbutton label="Journal" shape="menu" href="acc/accounts/journal.php?id=%d&year=%d"|args:$account.id,$current_year.id} | > | | > > > | 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | {$account.sum|abs|raw|html_money:false} {$config.monnaie} {else} {$account.sum|raw|html_money:false} {$config.monnaie} {/if} </td> <td class="actions"> {linkbutton label="Journal" shape="menu" href="acc/accounts/journal.php?id=%d&year=%d"|args:$account.id,$current_year.id} {if $session->canAccess('compta', Membres::DROIT_ADMIN)} {if $account.type == Entities\Accounting\Account::TYPE_BANK} {linkbutton label="Rapprocher" shape="check" href="acc/accounts/reconcile.php?id=%d"|args:$account.id} {elseif $account.type == Entities\Accounting\Account::TYPE_OUTSTANDING} {linkbutton label="Dépôt en banque" shape="check" href="acc/accounts/deposit.php?id=%d"|args:$account.id} {/if} {/if} </td> </tr> {/foreach} </tbody> {/foreach} </table> |
︙ | ︙ |
Modified src/templates/acc/accounts/reconcile.tpl from [823d2348a3] to [29c73c04df].
︙ | ︙ | |||
23 24 25 26 27 28 29 | <input type="submit" value="Afficher" /> </p> </fieldset> </form> <p class="alert"> <strong>Attention !</strong> | | | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | <input type="submit" value="Afficher" /> </p> </fieldset> </form> <p class="alert"> <strong>Attention !</strong> Afin de simplifier les choses, les écritures apparaissent ici dans le sens de la banque, à l'inverse des journaux comptables. </p> {form_errors} <form method="post" action="{$self_url}"> <table class="list"> <thead> |
︙ | ︙ |
Modified src/templates/acc/transactions/new.tpl from [a144607242] to [1b39cbd3bf].
︙ | ︙ | |||
23 24 25 26 27 28 29 | {input type="list" target="%sacc/charts/accounts/selector.php?targets=%s&chart=%d"|args:$admin_url,$payoff_targets,$chart_id name="account_payoff" label="Compte de règlement" required=1} </dl> </fieldset> {else} <fieldset> <legend>Type d'écriture</legend> <dl> | | | | | | | | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | {input type="list" target="%sacc/charts/accounts/selector.php?targets=%s&chart=%d"|args:$admin_url,$payoff_targets,$chart_id name="account_payoff" label="Compte de règlement" required=1} </dl> </fieldset> {else} <fieldset> <legend>Type d'écriture</legend> <dl> {input type="radio" name="type" value=$transaction::TYPE_REVENUE default=-1 label="Recette"} {input type="radio" name="type" value=$transaction::TYPE_EXPENSE default=-1 label="Dépense"} {input type="radio" name="type" value=$transaction::TYPE_TRANSFER default=-1 label="Virement" help="Faire un virement entre comptes, déposer des espèces en banque, etc."} {input type="radio" name="type" value=$transaction::TYPE_DEBT default=-1 label="Dette" help="Quand l'association doit de l'argent à un membre ou un fournisseur"} {input type="radio" name="type" value=$transaction::TYPE_CREDIT default=-1 label="Créance" help="Quand un membre ou un fournisseur doit de l'argent à l'association"} {input type="radio" name="type" value=$transaction::TYPE_ADVANCED default=-1 label="Saisie avancée" help="Choisir les comptes du plan comptable, ventiler une écriture sur plusieurs comptes, etc."} </dl> </fieldset> {foreach from=$types item="type"} <fieldset data-types="t{$type.id}"> <legend>{$type.label}</legend> <dl> |
︙ | ︙ |
Added src/www/admin/acc/accounts/deposit.php version [6e7b17b57a].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | <?php namespace Garradin; use Garradin\Accounting\Accounts; use Garradin\Accounting\Transactions; use Garradin\Entities\Accounting\Transaction; require_once __DIR__ . '/../_inc.php'; $session->requireAccess('compta', Membres::DROIT_ADMIN); $account = Accounts::get((int)qg('id')); if (!$account) { throw new UserException("Le compte demandé n'existe pas."); } $journal = $account->getDepositJournal(CURRENT_YEAR_ID); $transaction = new Transaction; $transaction->id_year = CURRENT_YEAR_ID; $rules = [ 'deposit' => 'array', ]; // Enregistrement des cases cochées if (f('save') && $form->check('acc_deposit_' . $account->id, $rules)) { try { $transaction->importFromDepositForm(); Transactions::saveDeposit($transaction, $journal, f('deposit')); Utils::redirect(ADMIN_URL . 'acc/transactions/details.php?id=' . $transaction->id()); } catch (UserException $e) { $journal = $account->getDepositJournal(CURRENT_YEAR_ID); $form->addError($e->getMessage()); } } $date = new \DateTime; if ($date > $current_year->end_date) { $date = $current_year->end_date; } $target = $account::TYPE_BANK; $tpl->assign(compact( 'account', 'journal', 'date', 'target' )); $tpl->display('acc/accounts/deposit.tpl'); |
Modified src/www/admin/acc/accounts/reconcile.php from [6821b3b7e5] to [23ea2afcaf].
︙ | ︙ | |||
35 36 37 38 39 40 41 | if ($start < $current_year->start_date || $start > $current_year->end_date || $end < $current_year->start_date || $end > $current_year->end_date) { $start = clone $current_year->start_date; $end = clone $start; $end->modify('last day of this month'); } | < | | 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | if ($start < $current_year->start_date || $start > $current_year->end_date || $end < $current_year->start_date || $end > $current_year->end_date) { $start = clone $current_year->start_date; $end = clone $start; $end->modify('last day of this month'); } $journal = $account->getReconcileJournal(CURRENT_YEAR_ID, $start, $end); // Enregistrement des cases cochées if ((f('save') || f('save_next')) && $form->check('acc_reconcile_' . $account->id)) { try { Transactions::saveReconciled($journal, f('reconcile')); |
︙ | ︙ | |||
74 75 76 77 78 79 80 | 'start', 'end', 'prev', 'next', 'journal', )); | < < < | 73 74 75 76 77 78 79 80 | 'start', 'end', 'prev', 'next', 'journal', )); $tpl->display('acc/accounts/reconcile.tpl'); |
Modified src/www/admin/acc/transactions/new.php from [52c4d14451] to [a9db35407e].
︙ | ︙ | |||
25 26 27 28 29 30 31 | $payoff_for = $transaction->payOffFrom($id); $amount = $payoff_for->sum(); } if (f('save') && $form->check('acc_transaction_new')) { try { $transaction->id_year = $current_year->id(); | | | 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | $payoff_for = $transaction->payOffFrom($id); $amount = $payoff_for->sum(); } if (f('save') && $form->check('acc_transaction_new')) { try { $transaction->id_year = $current_year->id(); $transaction->importFromNewForm(); $transaction->id_creator = $session->getUser()->id; $transaction->save(); // Append file if (!empty($_FILES['file']['name'])) { $file = Fichiers::upload($_FILES['file']); $file->linkTo(Fichiers::LIEN_COMPTA, $transaction->id()); |
︙ | ︙ |
Modified src/www/admin/static/scripts/accounting.js from [0075f54b09] to [52244ec66b].
︙ | ︙ | |||
72 73 74 75 76 77 78 | if (m = $('#lines_message')) { m.innerHTML = (debit === credit) ? '' : '<span class="alert">Écriture non équilibrée</span>'; } debit = debit ? debit + '' : '000'; credit = credit ? credit + '' : '000'; | | | | 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | if (m = $('#lines_message')) { m.innerHTML = (debit === credit) ? '' : '<span class="alert">Écriture non équilibrée</span>'; } debit = debit ? debit + '' : '000'; credit = credit ? credit + '' : '000'; $('#f_debit_total').value = g.formatMoney(debit); $('#f_credit_total').value = g.formatMoney(credit); } // Add row button $('.transaction-lines tfoot button')[0].onclick = () => { var line = $('.transaction-lines tbody tr')[0]; var n = line.cloneNode(true); n.querySelectorAll('input').forEach((e) => { |
︙ | ︙ |
Modified src/www/admin/static/scripts/global.js from [830dad5a1e] to [dc757df630].
︙ | ︙ | |||
234 235 236 237 238 239 240 241 242 243 244 245 246 247 | i.removeChild(old); } i.appendChild(span); g.closeDialog(); i.firstChild.focus(); }; // Sélecteurs de listes g.onload(() => { var inputs = $('form .input-list > button'); inputs.forEach((i) => { i.onclick = () => { | > > > > > | 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 | i.removeChild(old); } i.appendChild(span); g.closeDialog(); i.firstChild.focus(); }; g.formatMoney = (v) => { v = '' + v; return (v.substr(0, v.length-2) || '0') + ',' + (v + '00').substr(-2); }; // Sélecteurs de listes g.onload(() => { var inputs = $('form .input-list > button'); inputs.forEach((i) => { i.onclick = () => { |
︙ | ︙ |