dolibarr 24.0.0-beta
payment.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2014-2024 Alexandre Spangaro <alexandre@inovea-conseil.com>
3 * Copyright (C) 2015-2026 Frédéric France <frederic.france@free.fr>
4 * Copyright (C) 2020 Maxime DEMAREST <maxime@indelog.fr>
5 * Copyright (C) 2025 MDW <mdeweerd@users.noreply.github.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
27// Load Dolibarr environment
28require '../../main.inc.php';
36require_once DOL_DOCUMENT_ROOT.'/loan/class/loan.class.php';
37require_once DOL_DOCUMENT_ROOT.'/loan/class/loanschedule.class.php';
38require_once DOL_DOCUMENT_ROOT.'/loan/class/paymentloan.class.php';
39require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
40require_once DOL_DOCUMENT_ROOT.'/core/lib/loan.lib.php';
41
42$langs->loadLangs(array("bills", "loan"));
43
44$action = GETPOST('action', 'aZ09');
45$confirm = GETPOST('confirm', 'alpha');
46$cancel = GETPOST('cancel', 'alpha');
47
48$chid = GETPOSTINT('id');
49$datepaid = dol_mktime(12, 0, 0, GETPOSTINT('remonth'), GETPOSTINT('reday'), GETPOSTINT('reyear'));
50
51// Security check
52$socid = 0;
53if ($user->socid > 0) {
54 $socid = $user->socid;
55} elseif (GETPOSTISSET('socid')) {
56 $socid = GETPOSTINT('socid');
57}
58if (!$user->hasRight('loan', 'write')) {
60}
61
62$loan = new Loan($db);
63$loan->fetch($chid);
64
65$line_id = 0;
66$echance = 0;
67$amount_capital = 0;
68$amount_insurance = 0;
69$amount_interest = 0;
70
71$ls = new LoanSchedule($db);
72// grab all loanschedule
73$res = $ls->fetchAll($chid);
74if ($res > 0) {
75 foreach ($ls->lines as $l) {
76 $echance++; // Count term pos
77 // last unpaid term
78 if (empty($l->fk_bank)) {
79 $line_id = $l->id;
80 break;
81 } elseif ($line_id == $l->id) {
82 // If line_id provided, only count temp pos
83 break;
84 }
85 }
86}
87
88// Set current line with last unpaid line (only if schedule is used)
89if (!empty($line_id)) {
90 $line = new LoanSchedule($db);
91 $res = $line->fetch($line_id);
92 if ($res > 0) {
93 $amount_capital = price($line->amount_capital);
94 $amount_insurance = price($line->amount_insurance);
95 $amount_interest = price($line->amount_interest);
96 if (empty($datepaid)) {
97 $ts_temppaid = $line->datep;
98 }
99 }
100}
101
102$permissiontoadd = $user->hasRight('loan', 'write');
103
104
105/*
106 * Actions
107 */
108
109if ($action == 'add_payment' && $permissiontoadd) {
110 $error = 0;
111
112 if ($cancel) {
113 $loc = DOL_URL_ROOT.'/loan/card.php?id='.$chid;
114 header("Location: ".$loc);
115 exit;
116 }
117
118 if (!GETPOSTINT('paymenttype') > 0) {
119 setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentities("PaymentMode")), null, 'errors');
120 $error++;
121 }
122 if ($datepaid == '') {
123 setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentities("Date")), null, 'errors');
124 $error++;
125 }
126 if (isModEnabled("bank") && !GETPOSTINT('accountid') > 0) {
127 setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentities("AccountToCredit")), null, 'errors');
128 $error++;
129 }
130
131 if (!$error) {
132 $paymentid = 0;
133
134 $pay_amount_capital = (float) price2num(GETPOST('amount_capital'));
135 $pay_amount_insurance = (float) price2num(GETPOST('amount_insurance'));
136 // User can't set interest him self if schedule is set (else value in schedule can be incoherent)
137 if (!empty($line)) {
138 $pay_amount_interest = $line->amount_interest;
139 } else {
140 $pay_amount_interest = (float) price2num(GETPOST('amount_interest'));
141 }
142 $remaindertopay = (float) price2num(GETPOST('remaindertopay'));
143 $amount = (float) price2num($pay_amount_capital + $pay_amount_insurance + $pay_amount_interest, 'MT');
144
145 // This term is already paid
146 if (!empty($line) && !empty($line->fk_bank)) {
147 setEventMessages($langs->trans('TermPaidAllreadyPaid'), null, 'errors');
148 $error++;
149 }
150
151 if (empty($remaindertopay)) {
152 setEventMessages('Empty sumpaid', null, 'errors');
153 $error++;
154 }
155
156 if ($amount == 0) {
157 setEventMessages($langs->trans('ErrorNoPaymentDefined'), null, 'errors');
158 $error++;
159 }
160
161 if (!$error) {
162 $db->begin();
163
164 // Create a line of payments
165 $payment = new PaymentLoan($db);
166 $payment->chid = $chid;
167 $payment->datep = $datepaid;
168 $payment->label = $loan->label;
169 $payment->amount_capital = $pay_amount_capital;
170 $payment->amount_insurance = $pay_amount_insurance;
171 $payment->amount_interest = $pay_amount_interest;
172 $payment->fk_bank = GETPOSTINT('accountid');
173 $payment->paymenttype = GETPOSTINT('paymenttype');
174 $payment->num_payment = GETPOST('num_payment', 'alphanohtml');
175 $payment->note_private = GETPOST('note_private', 'restricthtml');
176 $payment->note_public = GETPOST('note_public', 'restricthtml');
177
178 if (!$error) {
179 $paymentid = $payment->create($user);
180 if ($paymentid < 0) {
181 setEventMessages($payment->error, $payment->errors, 'errors');
182 $error++;
183 }
184 }
185
186 if (!$error) {
187 // @phan-suppress-next-line PhanPluginSuspiciousParamOrder
188 $result = $payment->addPaymentToBank($user, $chid, 'payment_loan', '(LoanPayment)', $payment->fk_bank, '', '');
189 if (!($result > 0)) {
190 setEventMessages($payment->error, $payment->errors, 'errors');
191 $error++;
192 }
193 }
194
195 // Update loan schedule with payment value
196 if (!$error && !empty($line)) {
197 // If payment values are modified, recalculate schedule
198 if (($line->amount_capital != $pay_amount_capital) || ($line->amount_insurance != $pay_amount_insurance) || ($line->amount_interest != $pay_amount_interest)) {
199 $arr_term = loanCalcMonthlyPayment(($pay_amount_capital + $pay_amount_interest), $remaindertopay, ($loan->rate / 100), $echance, (int) $loan->nbterm);
200 foreach ($arr_term as $k => $v) {
201 // Update fk_bank for current line
202 if ($k == $echance) {
203 $ls->lines[$k - 1]->fk_bank = $payment->fk_bank;
204 $ls->lines[$k - 1]->fk_payment_loan = $payment->id;
205 }
206 $ls->lines[$k - 1]->amount_capital = ((float) price2num($v['mens'])) - $v['interet'];
207 $ls->lines[$k - 1]->amount_interest = $v['interet'];
208 $ls->lines[$k - 1]->tms = dol_now();
209 $ls->lines[$k - 1]->fk_user_modif = $user->id;
210 $result = $ls->lines[$k - 1]->update($user, 0);
211 if ($result < 1) {
212 setEventMessages(null, $ls->errors, 'errors');
213 $error++;
214 break;
215 }
216 }
217 } else { // Only add fk_bank bank to schedule line (mark as paid)
218 $line->fk_bank = $payment->fk_bank;
219 $line->fk_payment_loan = $payment->id;
220 $result = $line->update($user, 0);
221 if ($result < 1) {
222 setEventMessages(null, $line->errors, 'errors');
223 $error++;
224 }
225 }
226 }
227
228 if (!$error) {
229 $db->commit();
230 $loc = DOL_URL_ROOT.'/loan/card.php?id='.$chid;
231 header('Location: '.$loc);
232 exit;
233 } else {
234 $db->rollback();
235 }
236 }
237 }
238
239 $action = 'create';
240}
241
242
243/*
244 * View
245 */
246$form = new Form($db);
247
248$title = $langs->trans('Loans');
249$help_url = "EN:Module_Loan|FR:Module_Emprunt";
250
251llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'bodyforlist mod-loan page-payment-list');
252
253
254// Form to create loan's payment
255if ($action == 'create') {
256 $total = $loan->capital;
257 $sumpaid = 0;
258
259 print load_fiche_titre($langs->trans("DoPayment"));
260
261 $sql = "SELECT SUM(amount_capital) as total";
262 $sql .= " FROM ".MAIN_DB_PREFIX."payment_loan";
263 $sql .= " WHERE fk_loan = ".((int) $chid);
264 $resql = $db->query($sql);
265 if ($resql) {
266 $obj = $db->fetch_object($resql);
267 $sumpaid = $obj->total;
268 $db->free($resql);
269 }
270
271 print '<form name="add_payment" action="'.$_SERVER['PHP_SELF'].'" method="post">';
272 print '<input type="hidden" name="token" value="'.newToken().'">';
273 print '<input type="hidden" name="id" value="'.$chid.'">';
274 print '<input type="hidden" name="chid" value="'.$chid.'">';
275 print '<input type="hidden" name="line_id" value="'.$line_id.'">';
276 print '<input type="hidden" name="remaindertopay" value="'.($total - $sumpaid).'">';
277 print '<input type="hidden" name="action" value="add_payment">';
278
279 print dol_get_fiche_head();
280
281 /*
282 print '<table class="border centpercent">';
283
284 print '<tr><td class="titlefield">'.$langs->trans("Ref").'</td><td colspan="2"><a href="'.DOL_URL_ROOT.'/loan/card.php?id='.$chid.'">'.$chid.'</a></td></tr>';
285 if ($echance > 0)
286 {
287 print '<tr><td>'.$langs->trans("Term").'</td><td colspan="2"><a href="'.DOL_URL_ROOT.'/loan/schedule.php?loanid='.$chid.'#n'.$echance.'">'.$echance.'</a></td></tr>'."\n";
288 }
289 print '<tr><td>'.$langs->trans("DateStart").'</td><td colspan="2">'.dol_print_date($loan->datestart, 'day')."</td></tr>\n";
290 print '<tr><td>'.$langs->trans("Label").'</td><td colspan="2">'.$loan->label."</td></tr>\n";
291 print '<tr><td>'.$langs->trans("Amount").'</td><td colspan="2">'.price($loan->capital, 0, $outputlangs, 1, -1, -1, $conf->currency).'</td></tr>';
292
293 print '<tr><td>'.$langs->trans("AlreadyPaid").'</td><td colspan="2">'.price($sumpaid, 0, $outputlangs, 1, -1, -1, $conf->currency).'</td></tr>';
294 print '<tr><td class="tdtop">'.$langs->trans("RemainderToPay").'</td><td colspan="2">'.price($total - $sumpaid, 0, $outputlangs, 1, -1, -1, $conf->currency).'</td></tr>';
295 print '</tr>';
296
297 print '</table>';
298 */
299
300 print '<table class="border centpercent">';
301
302 print '<tr><td class="titlefield fieldrequired">'.$langs->trans("Date").'</td><td colspan="2">';
303 if (empty($datepaid)) {
304 if (empty($ts_temppaid)) {
305 $datepayment = (getDolGlobalString('MAIN_AUTOFILL_DATE') ? dol_now() : -1);
306 } else {
307 $datepayment = $ts_temppaid;
308 }
309 } else {
310 $datepayment = $datepaid;
311 }
312 print $form->selectDate($datepayment, '', 0, 0, 0, "add_payment", 1, 1);
313 print "</td>";
314 print '</tr>';
315
316 print '<tr><td class="fieldrequired">'.$langs->trans("PaymentMode").'</td><td colspan="2">';
317 print img_picto('', 'money-bill-alt', 'class="pictofixedwidth"');
318 $form->select_types_paiements(GETPOSTISSET("paymenttype") ? GETPOST("paymenttype", 'alphanohtml') : $loan->fk_typepayment, "paymenttype");
319 print "</td>\n";
320 print '</tr>';
321
322 print '<tr>';
323 print '<td class="fieldrequired">'.$langs->trans('AccountToDebit').'</td>';
324 print '<td colspan="2">';
325 print img_picto('', 'bank_account', 'class="pictofixedwidth"');
326 $form->select_comptes(GETPOSTISSET("accountid") ? GETPOSTINT("accountid") : $loan->accountid, "accountid", 0, '(courant:=:'.Account::TYPE_CURRENT.')', 1); // Show opened bank account list
327 print '</td></tr>';
328
329 // Number
330 print '<tr><td>'.$langs->trans('Numero');
331 print ' <em>('.$langs->trans("ChequeOrTransferNumber").')</em>';
332 print '</td>';
333 print '<td colspan="2"><input name="num_payment" type="text" value="'.GETPOST('num_payment', 'alphanohtml').'"></td>'."\n";
334 print "</tr>";
335
336 print '<tr>';
337 print '<td class="tdtop">'.$langs->trans("NotePrivate").'</td>';
338 print '<td valign="top" colspan="2"><textarea name="note_private" wrap="soft" cols="60" rows="'.ROWS_3.'"></textarea></td>';
339 print '</tr>';
340
341 print '<tr>';
342 print '<td class="tdtop">'.$langs->trans("NotePublic").'</td>';
343 print '<td valign="top" colspan="2"><textarea name="note_public" wrap="soft" cols="60" rows="'.ROWS_3.'"></textarea></td>';
344 print '</tr>';
345
346 print '</table>';
347
348 print dol_get_fiche_end();
349
350
351 print '<table class="noborder centpercent">';
352 print '<tr class="liste_titre">';
353 print '<td class="left">'.$langs->trans("DateDue").'</td>';
354 print '<td class="right">'.$langs->trans("LoanCapital").'</td>';
355 print '<td class="right">'.$langs->trans("AlreadyPaid").'</td>';
356 print '<td class="right">'.$langs->trans("RemainderToPay").'</td>';
357 print '<td class="right">'.$langs->trans("Amount").'</td>';
358 print "</tr>\n";
359
360 print '<tr class="oddeven">';
361
362 if ($loan->datestart > 0) {
363 print '<td class="left" valign="center">'.dol_print_date($loan->datestart, 'day').'</td>';
364 } else {
365 print '<td class="center" valign="center"><b>!!!</b></td>';
366 }
367
368 print '<td class="right" valign="center">'.price($loan->capital)."</td>";
369
370 print '<td class="right" valign="center">'.price($sumpaid)."</td>";
371
372 print '<td class="right" valign="center">'.price($loan->capital - $sumpaid)."</td>";
373
374 print '<td class="right">';
375 if ($sumpaid < $loan->capital) {
376 print $langs->trans("LoanCapital").': <input type="text" size="8" name="amount_capital" value="'.(GETPOSTISSET('amount_capital') ? GETPOST('amount_capital') : $amount_capital).'">';
377 } else {
378 print '-';
379 }
380 print '<br>';
381 if ($sumpaid < $loan->capital) {
382 print $langs->trans("Insurance").': <input type="text" size="8" name="amount_insurance" value="'.(GETPOSTISSET('amount_insurance') ? GETPOST('amount_insurance') : $amount_insurance).'">';
383 } else {
384 print '-';
385 }
386 print '<br>';
387 if ($sumpaid < $loan->capital) {
388 print $langs->trans("Interest").': <input type="text" size="8" name="amount_interest" value="'.(GETPOSTISSET('amount_interest') ? GETPOST('amount_interest') : $amount_interest).'" '.(!empty($line) ? 'disabled title="'.$langs->trans('CantModifyInterestIfScheduleIsUsed').'"' : '').'>';
389 } else {
390 print '-';
391 }
392 print "</td>";
393
394 print "</tr>\n";
395
396 print '</table>';
397
398 print $form->buttonsSaveCancel();
399
400 print "</form>\n";
401}
402
403llxFooter();
404$db->close();
llxFooter($comment='', $zone='private', $disabledoutputofmessages=0)
Empty footer.
Definition wrapper.php:91
if(!defined('NOREQUIRESOC')) if(!defined( 'NOREQUIRETRAN')) if(!defined('NOTOKENRENEWAL')) if(!defined( 'NOREQUIREMENU')) if(!defined('NOREQUIREHTML')) if(!defined( 'NOREQUIREAJAX')) llxHeader($head='', $title='', $help_url='', $target='', $disablejs=0, $disablehead=0, $arrayofjs='', $arrayofcss='', $morequerystring='', $morecssonbody='', $replacemainareaby='', $disablenofollow=0, $disablenoindex=0)
Empty header.
Definition wrapper.php:73
Class to manage bank accounts.
Class to manage generation of HTML components Only common components must be here.
Loan.
Class to manage Schedule of loans.
Class to manage payments of loans.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='', $dragdropfile=0, $morecssdiv='')
Show tabs of a record.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_get_fiche_end($notab=0)
Return tab footer of a card.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
load_fiche_titre($title, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='', $morecssonpicto='widthpictotitle')
Load a title with picto.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
loanCalcMonthlyPayment($mens, $capital, $rate, $numactualloadterm, $nbterm)
Calculate remaining loan mensuality and interests.
Definition loan.lib.php:103
accessforbidden($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Show a message to say access is forbidden and stop program.