From 362f37d783ad783131f840b6b74920096048e2eb Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 5 Jan 2026 00:18:43 +0100 Subject: [PATCH] Finish cash control feature of v23 --- .../blockedlog/admin/blockedlog_archives.php | 2 +- htdocs/blockedlog/class/blockedlog.class.php | 6 +- htdocs/categories/class/categorie.class.php | 2 +- .../compta/cashcontrol/cashcontrol_card.php | 352 +++++++++++++----- .../cashcontrol/class/cashcontrol.class.php | 120 +++++- htdocs/compta/cashcontrol/report.php | 12 +- ..._modBlockedlog_ActionsBlockedLog.class.php | 4 +- .../install/mysql/migration/22.0.0-23.0.0.sql | 8 +- .../mysql/tables/llx_pos_cash_fence.sql | 11 +- htdocs/langs/en_US/bills.lang | 1 + htdocs/langs/en_US/blockedlog.lang | 3 +- htdocs/langs/en_US/cashdesk.lang | 2 + htdocs/langs/en_US/main.lang | 1 + 13 files changed, 403 insertions(+), 121 deletions(-) diff --git a/htdocs/blockedlog/admin/blockedlog_archives.php b/htdocs/blockedlog/admin/blockedlog_archives.php index 02a9725e6ae..d07f0ee1995 100644 --- a/htdocs/blockedlog/admin/blockedlog_archives.php +++ b/htdocs/blockedlog/admin/blockedlog_archives.php @@ -505,7 +505,7 @@ if (GETPOST('action') == 'export' && $user->hasRight('blockedlog', 'read')) { / $sql = "SELECT action, module_source, object_format, MIN(date_creation) as datemin, SUM(amounts_taxexcl) as sumamounts_taxexcl, SUM(amounts) as sumamounts"; $sql .= " FROM ".MAIN_DB_PREFIX."blockedlog"; $sql .= " WHERE entity = ".((int) $conf->entity); - //$sql .= " AND action IN ('BILL_VALIDATE', 'BILL_SENTBYMAIL', 'PAYMENT_CUSTOMER_CREATE', 'CASHCONTROL_VALIDATE', 'PAYMENT_CUSTOMER_DELETE', 'DOC_DOWNLOAD', 'DOC_PREVIEW')"; + //$sql .= " AND action IN ('BILL_VALIDATE', 'BILL_SENTBYMAIL', 'PAYMENT_CUSTOMER_CREATE', 'CASHCONTROL_CLOSE', 'PAYMENT_CUSTOMER_DELETE', 'DOC_DOWNLOAD', 'DOC_PREVIEW')"; $sql .= " AND action IN ('BILL_VALIDATE', 'PAYMENT_CUSTOMER_CREATE', 'PAYMENT_CUSTOMER_DELETE')"; // Only event into lifetime total //$sql .= " AND action IN ('PAYMENT_CUSTOMER_CREATE')"; $sql .= " GROUP BY action, module_source, object_format"; diff --git a/htdocs/blockedlog/class/blockedlog.class.php b/htdocs/blockedlog/class/blockedlog.class.php index e0cdb511e61..3a321caffae 100644 --- a/htdocs/blockedlog/class/blockedlog.class.php +++ b/htdocs/blockedlog/class/blockedlog.class.php @@ -295,8 +295,10 @@ class BlockedLog $sep++; $this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '----- '.$langs->trans("CashControl").'', 'disabled' => 1); } - - $this->trackedevents['CASHCONTROL_VALIDATE'] = array('id' => 'CASHCONTROL_VALIDATE', 'label' => 'logCASHCONTROL_VALIDATE', 'labelhtml' => img_picto('', 'pos', 'class="pictofixedwidth").').$langs->trans('logCASHCONTROL_VALIDATE')); + if (getDolGlobalString('BLOCKEDLOG_ADD_OLD_CASHCONTROL_VALIDATE')) { + $this->trackedevents['CASHCONTROL_VALIDATE'] = array('id' => 'CASHCONTROL_VALIDATE', 'label' => 'logCASHCONTROL_VALIDATE', 'labelhtml' => img_picto('', 'pos', 'class="pictofixedwidth").').$langs->trans('logCASHCONTROL_VALIDATE')); + } + $this->trackedevents['CASHCONTROL_CLOSE'] = array('id' => 'CASHCONTROL_CLOSE', 'label' => 'logCASHCONTROL_CLOSE', 'labelhtml' => img_picto('', 'pos', 'class="pictofixedwidth").').$langs->trans('logCASHCONTROL_CLOSE')); } // Add more action to track from a conf variable. For the case we want to track other actions into the unalterable log. diff --git a/htdocs/categories/class/categorie.class.php b/htdocs/categories/class/categorie.class.php index 4314858490b..dfbaf1570ac 100644 --- a/htdocs/categories/class/categorie.class.php +++ b/htdocs/categories/class/categorie.class.php @@ -1623,7 +1623,7 @@ class Categorie extends CommonObject $w[] = ''.($addpicto ? img_object('', 'category') : '').$cat->label.''; } } - $newcategwithpath = preg_replace('/colortoreplace/', $forced_color, implode(''.$sep.'', $w)); + $newcategwithpath = preg_replace('/colortoreplace/', $forced_color, implode(''.$sep.'', $w)); $ways[] = $newcategwithpath; } diff --git a/htdocs/compta/cashcontrol/cashcontrol_card.php b/htdocs/compta/cashcontrol/cashcontrol_card.php index 30d4746be2b..07d93a38546 100644 --- a/htdocs/compta/cashcontrol/cashcontrol_card.php +++ b/htdocs/compta/cashcontrol/cashcontrol_card.php @@ -52,13 +52,9 @@ $backtopage = GETPOST('backtopage', 'aZ09'); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); -$categid = GETPOST('categid'); $label = GETPOST("label"); $now = dol_now(); -$syear = (GETPOSTISSET('closeyear') ? GETPOSTINT('closeyear') : dol_print_date($now, "%Y")); -$smonth = (GETPOSTISSET('closemonth') ? GETPOSTINT('closemonth') : dol_print_date($now, "%m")); -$sday = (GETPOSTISSET('closeday') ? GETPOSTINT('closeday') : dol_print_date($now, "%d")); $limit = GETPOSTINT('limit') ? GETPOSTINT('limit') : $conf->liste_limit; $sortfield = GETPOST('sortfield', 'aZ09comma'); @@ -118,6 +114,75 @@ $permissiontoadd = ($user->hasRight("cashdesk", "run") || $user->hasRight("takep $permissiontodelete = ($user->hasRight("cashdesk", "run") || $user->hasRight("takepos", "run")) || ($permissiontoadd && $object->status == 0); +// Must be after the fetch +$datestart = null; +$dateend = null; +$syear = (GETPOSTISSET('closeyear') ? GETPOSTINT('closeyear') : dol_print_date($now, "%Y", 'tzuserrel')); +$smonth = (GETPOSTISSET('closemonth') ? GETPOSTINT('closemonth') : dol_print_date($now, "%m", 'tzuserrel')); +$sday = (GETPOSTISSET('closeday') ? GETPOSTINT('closeday') : dol_print_date($now, "%d", 'tzuserrel')); +// TODO Add a global option to define the end hours when doing a cash control +$shour = 0; +$smin = 0; +$ssec = 0; + +if ($object->id > 0) { + // When object is know, we must define first the end date (stored in database with different components) and deduct the start date + if (empty($object->day_close) && !empty($object->month_close)) { + $dateend = dol_mktime($object->hour_close, $object->min_close, $object->sec_close, $object->month_close, $object->day_close, $object->year_close, 'gmt'); + $datestart = dol_time_plus_duree($dateend, -1, 'y', 0); + } elseif (empty($object->day_close) && empty($object->month_close)) { + $dateend = dol_mktime($object->hour_close, $object->min_close, $object->sec_close, 12, 31, $object->year_close, 'gmt'); + $datestart = dol_mktime($object->hour_close, $object->min_close, $object->sec_close, 12, 1, $object->year_close, 'gmt'); + $datestart = dol_time_plus_duree($datestart, -1, 'm', 0); + } else { + $dateend = dol_mktime($object->hour_close, $object->min_close, $object->sec_close, $object->month_close, $object->day_close, $object->year_close, 'gmt'); + $datestart = dol_time_plus_duree($dateend, -1, 'd', 0); + } + $datestart += 1; // Add 1 second +} else { + if ($syear && !$smonth) { + $datestart = dol_get_first_day($syear, 1, 'tzuserrel'); + $dateend = dol_get_last_day($syear, 12, 'tzuserrel'); + $sql .= " AND dateo < '".$db->idate($datestart)."'"; + } elseif ($syear && $smonth && !$sday) { + $datestart = dol_get_first_day($syear, $smonth, 'tzuserrel'); + $dateend = dol_get_last_day($syear, $smonth, 'tzuserrel'); + $sql .= " AND dateo < '".$db->idate($datestart)."'"; + } elseif ($syear && $smonth && $sday) { + $datestart = dol_mktime($shour, $smin, $ssec, $smonth, $sday, $syear, 'tzuserrel'); + $dateend = dol_mktime(23, 59, 59, $smonth, $sday, $syear, 'tzuserrel'); + $sql .= " AND dateo < '".$db->idate($datestart)."'"; + } else { + setEventMessages($langs->trans('YearNotDefined'), null, 'errors'); + } +} +//var_dump(dol_print_date($datestart, 'dayhour', 'gmt'), dol_print_date($dateend, 'dayhour', 'gmt')); + + +// Define dates and terminal +$terminalid = ''; +$terminaltouse = ''; +if ($action == "create" || $action == "start" || $action == 'valid' || $action == 'close') { + if ($action == 'valid' || $action == 'close') { + $posmodule = $object->posmodule; + $terminalid = $object->posnumber; + $terminaltouse = $terminalid; + + $syear = $object->year_close; + $smonth = $object->month_close; + $sday = $object->day_close; + } elseif (GETPOST('posnumber', 'alpha') != '' && GETPOST('posnumber', 'alpha') != '-1') { + $posmodule = GETPOST('posmodule', 'alpha'); + $terminalid = GETPOST('posnumber', 'alpha'); + $terminaltouse = $terminalid; + + if ($terminaltouse == '1' && $posmodule == 'cashdesk') { + $terminaltouse = ''; + } + } +} + + /* * Actions */ @@ -179,13 +244,17 @@ if ($action == "start" && $permissiontoadd) { } if (!$error) { - $dateclosegmt = dol_mktime(GETPOSTISSET('closehour') ? GETPOSTINT('closehour') : 23, GETPOSTISSET('closemin') ? GETPOSTINT('closemin') : 59, GETPOSTISSET('closesec') ? GETPOSTINT('closesec') : 59, GETPOSTINT('closemonth'), GETPOSTINT('closeday'), GETPOSTINT('closeyear'), 'tzuserrel'); - dol_syslog('The closing date must be '.dol_print_date($dateclosegmt, 'standard', 'gmt').' UTC'); + if (GETPOSTINT('closeday')) { + $dateclosegmt = dol_mktime(GETPOSTISSET('closehour') ? GETPOSTINT('closehour') : 23, GETPOSTISSET('closemin') ? GETPOSTINT('closemin') : 59, GETPOSTISSET('closesec') ? GETPOSTINT('closesec') : 59, GETPOSTINT('closemonth') ? GETPOSTINT('closemonth') : 12, GETPOSTINT('closeday'), GETPOSTINT('closeyear'), 'tzuserrel'); + } else { + $dateclosegmt = dol_mktime(GETPOSTISSET('closehour') ? GETPOSTINT('closehour') : 23, GETPOSTISSET('closemin') ? GETPOSTINT('closemin') : 59, GETPOSTISSET('closesec') ? GETPOSTINT('closesec') : 59, GETPOSTINT('closemonth') ? GETPOSTINT('closemonth') : 12, 15, GETPOSTINT('closeyear'), 'tzuserrel'); + } + dol_syslog('The closing date will be '.dol_print_date($dateclosegmt, 'standard', 'gmt').' UTC'); $tmparray = dol_getdate($dateclosegmt, false, 'gmt'); - $object->day_close = $tmparray['mday']; - $object->month_close = $tmparray['mon']; + $object->day_close = GETPOSTINT('closeday') ? $tmparray['mday'] : null; + $object->month_close = GETPOSTINT('closemonth') ? $tmparray['mon'] : null; $object->year_close = $tmparray['year']; $object->hour_close = $tmparray['hours']; @@ -222,26 +291,126 @@ if ($action == "valid" && $permissiontoadd) { // validate = close $db->begin(); - /* - $object->day_close = GETPOST('closeday', 'int'); - $object->month_close = GETPOST('closemonth', 'int'); - $object->year_close = GETPOST('closeyear', 'int'); - */ + // Save the calculated amount + // It will also be saved automatically into llx_blockedlog by the trigger in valid(). + $object->cash = (float) price2num(GETPOST('cash_calculated', 'alpha')); + $object->card = (float) price2num(GETPOST('card_calculated', 'alpha')); + $object->cheque = (float) price2num(GETPOST('cheque_calculated', 'alpha')); // Save the real amount in llx_pos_cash_fence. - // It will also be saved automatically into llx_blockedlog by the trigger in valid(). - $object->cash = (float) price2num(GETPOST('cash_amount', 'alpha')); - $object->card = (float) price2num(GETPOST('card_amount', 'alpha')); - $object->cheque = (float) price2num(GETPOST('cheque_amount', 'alpha')); + $object->cash_declared = (float) price2num(GETPOST('cash_amount', 'alpha')); + $object->card_declared = (float) price2num(GETPOST('card_amount', 'alpha')); + $object->cheque_declared = (float) price2num(GETPOST('cheque_amount', 'alpha')); - // TODO Add perpetual amount + // Add also perpetual amount into cash_lifetime, card_lifetime, cheque_lifetime + $cash_lifetime = $card_lifetime = $cheque_lifetime = 0; + //$dates = $datestart; + $datee = $dateend; + $datefilter = 'p.datep'; + $modulesourcefilter = 'f.module_source'; + $amountfield = 'pf.amount'; + $joinleft = 'LEFT '; + if (isALNERunningVersion() && $mysoc->country_code == 'FR') { + $datefilter = 'bl.date_creation'; // By using this as a filter, it is like the LEFT JOIN is an INNER JOIN + $modulesourcefilter = 'bl.module_source'; + $amountfield = 'bl.amounts'; + $joinleft = ''; + } + + $lifetimeamount = array(); + $lifetimenb = array(); + + // Calculate $theoricalamountforterminal at end of period + // Sum of payment + Initial amount in bank + foreach ($arrayofpaymentmode as $key => $val) { + // NOTE: Must be same request than into report.php, except it does an aggregate and do the request 3 times, once per payment type. + + /*$sql = "SELECT p.rowid, p.datep as datep, cp.code,"; + $sql .= " f.rowid as facid, f.ref, f.datef as datef, pf.amount as amount,"; + $sql .= " b.fk_account as bankid,"; + $sql .= " bl.signature"; */ + $sql = "SELECT SUM(".$db->sanitize($amountfield).") as total, COUNT(*) as nb"; + $sql .= " FROM ".MAIN_DB_PREFIX."paiement_facture as pf, ".MAIN_DB_PREFIX."facture as f,"; + $sql .= " ".MAIN_DB_PREFIX."paiement as p"; + //$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."blockedlog as bl ON bl.ref_object = p.ref AND bl.entity = ".((int) $conf->entity).","; + $sql .= " ".$joinleft." JOIN ".MAIN_DB_PREFIX."blockedlog as bl ON bl.action = 'PAYMENT_CUSTOMER_CREATE'"; + $sql .= " AND bl.element = 'payment' AND bl.fk_object = p.rowid AND bl.entity = ".((int) $conf->entity).","; + //$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."bank as b ON p.fk_bank = b.rowid,"; + $sql .= " ".MAIN_DB_PREFIX."c_paiement as cp"; + $sql .= " WHERE pf.fk_facture = f.rowid AND p.rowid = pf.fk_paiement AND cp.id = p.fk_paiement"; + $sql .= " AND ".$db->sanitize($modulesourcefilter)." = '".$db->escape($posmodule)."'"; + $sql .= " AND f.pos_source = '".$db->escape($terminalid)."'"; + $sql .= " AND p.entity = ".((int) $conf->entity); // Never share entities for features related to accountancy + $sql .= " AND ".$db->sanitize($datefilter)." <= '".$db->idate($datee)."'"; + if ($key == 'cash') { + $sql .= " AND cp.code = 'LIQ'"; + } elseif ($key == 'cheque') { + $sql .= " AND cp.code = 'CHQ'"; + } elseif ($key == 'card') { + $sql .= " AND cp.code = 'CB'"; + } else { + dol_print_error(null, 'Value for key = '.$key.' not supported'); + exit; + } + //print $sql."
\n"; + + $resql = $db->query($sql); + if ($resql) { + $lifetimeamount[$terminalid][$key] = 0; + $lifetimenb[$terminalid][$key] = 0; + + $obj = $db->fetch_object($resql); + if ($obj) { + $lifetimeamount[$terminalid][$key] = price2num($lifetimeamount[$terminalid][$key] + $obj->total); + $lifetimenb[$terminalid][$key] = $obj->nb; + } + } else { + dol_print_error($db); + } + } + + $cash_lifetime = $lifetimeamount[$terminalid]['cash']; + $card_lifetime = $lifetimeamount[$terminalid]['card']; + $cheque_lifetime = $lifetimeamount[$terminalid]['cheque']; + + $object->cash_lifetime = $cash_lifetime; + $object->card_lifetime = $card_lifetime; + $object->cheque_lifetime = $cheque_lifetime; + + + // Get the date of first record for the lifetime calculation + $sql = "SELECT action, module_source, object_format, date_creation"; + $sql .= " FROM ".MAIN_DB_PREFIX."blockedlog"; + $sql .= " WHERE entity = ".((int) $conf->entity); + $sql .= " AND action IN ('PAYMENT_CUSTOMER_CREATE')"; // Only this event + $sql .= " AND module_source = '".$db->escape($posmodule)."'"; + //$sql .= " AND pos_source = '".$db->escape($terminalid)."'"; + $sql .= $db->order("date_creation", "ASC"); + $sql .= $db->plimit(1); + + $firstrecorddate = 0; + $resql = $db->query($sql); + if ($resql) { + $obj = $db->fetch_object($resql); + if ($obj) { + $firstrecorddate = $obj->date_creation; + } + } + if ($firstrecorddate) { + $object->lifetime_start = $firstrecorddate; + } $result = $object->update($user); - $result = $object->valid($user); // This also save data into the Unalterable Log table by the trigger CASHCONTROL_VALIDATE. + $result2 = $object->close($user); // This also save data into the Unalterable Log table by the trigger CASHCONTROL_CLOSE. - if ($result <= 0) { + + // TODO + // Add an entry into bank to fix difference between amount and declared and if user ask it with a checkbox ? + + + if ($result <= 0 || $result2 <= 0) { setEventMessages($object->error, $object->errors, 'errors'); $db->rollback(); } else { @@ -294,16 +463,6 @@ $initialbalanceforterminal = array(); $theoricalamountforterminal = array(); $theoricalnbofinvoiceforterminal = array(); -$terminalid = ''; -$terminaltouse = ''; - -// TODO Ask hours to use for the range date -$shour = 0; -$smin = 0; -$ssec = 0; -$datestart = null; -$dateend = null; - llxHeader('', $langs->trans("CashControl")); @@ -351,21 +510,6 @@ if ($action == "create" || $action == "start" || $action == 'close') { if ($bankid > 0) { $sql = "SELECT SUM(amount) as total FROM ".MAIN_DB_PREFIX."bank"; $sql .= " WHERE fk_account = ".((int) $bankid); - if ($syear && !$smonth) { - $datestart = dol_get_first_day($syear, 1); - $dateend = dol_get_last_day($syear, 12); - $sql .= " AND dateo < '".$db->idate($datestart)."'"; - } elseif ($syear && $smonth && !$sday) { - $datestart = dol_get_first_day($syear, $smonth); - $dateend = dol_get_last_day($syear, $smonth); - $sql .= " AND dateo < '".$db->idate($datestart)."'"; - } elseif ($syear && $smonth && $sday) { - $datestart = dol_mktime($shour, $smin, $ssec, $smonth, $sday, $syear); - $dateend = dol_mktime(23, 59, 59, $smonth, $sday, $syear); - $sql .= " AND dateo < '".$db->idate($datestart)."'"; - } else { - setEventMessages($langs->trans('YearNotDefined'), null, 'errors'); - } $resql = $db->query($sql); if ($resql) { @@ -384,17 +528,6 @@ if ($action == "create" || $action == "start" || $action == 'close') { $dates = $datestart; $datee = $dateend; - /* - if ($syear && !$smonth) { - $dates = dol_get_first_day($syear, 1); $datee = dol_get_last_day($syear, 12); - } elseif ($syear && $smonth && !$sday) { - $dates = dol_get_first_day($syear, $smonth); $datee = dol_get_last_day($syear, $smonth); - } elseif ($syear && $smonth && $sday) { - $dates = dol_mktime(0, 0, 0, $smonth, $sday, $syear); $datee = dol_mktime(23, 59, 59, $smonth, $sday, $syear); - } else { - dol_print_error(null, 'Year not defined'); - } - */ $datefilter = 'p.datep'; $modulesourcefilter = 'f.module_source'; $amountfield = 'pf.amount'; @@ -441,11 +574,9 @@ if ($action == "create" || $action == "start" || $action == 'close') { $resql = $db->query($sql); if ($resql) { - $theoricalamountforterminal[$terminalid][$key] = $initialbalanceforterminal[$terminalid][$key]; - $obj = $db->fetch_object($resql); if ($obj) { - $theoricalamountforterminal[$terminalid][$key] = price2num($theoricalamountforterminal[$terminalid][$key] + $obj->total); + $theoricalamountforterminal[$terminalid][$key] = $obj->total; $theoricalnbofinvoiceforterminal[$terminalid][$key] = $obj->nb; } } else { @@ -659,7 +790,7 @@ if ($action == "create" || $action == "start") { $object->fetch($id); print $object->opening; } else { - print(GETPOSTISSET('opening') ? price2num(GETPOST('opening', 'alpha')) : price($initialbalanceforterminal[$terminalid]['cash'])); + print (GETPOSTISSET('opening') ? price2num(GETPOST('opening', 'alpha')) : price($initialbalanceforterminal[$terminalid]['cash'])); } print '">'; print ''; @@ -692,6 +823,7 @@ if ($action == "create" || $action == "start") { print ''; } +// View if (empty($action) || $action == "view" || $action == "close") { $result = $object->fetch($id); @@ -732,22 +864,18 @@ if (empty($action) || $action == "view" || $action == "close") { print $object->posnumber; print ""; + print ''; + print $langs->trans("DateCreationShort"); + print ''; + print dol_print_date($object->date_creation, 'dayhour'); + print ''; + print ''; print $langs->trans("Period"); print ''; print $object->year_close; - print($object->month_close ? "-" : "").sprintf("%02d", $object->month_close); - print($object->day_close ? "-" : "").sprintf("%02d", $object->day_close); - - $dateend = dol_mktime($object->hour_close, $object->min_close, $object->sec_close, $object->month_close, $object->day_close, $object->year_close, 'gmt'); - if (empty($object->day_close) && !empty($object->month_close)) { - $datestart = dol_get_last_day(($object->month_close == 1 ? $object->year_close - 1 : $object->year_close), ($object->month_close == 1 ? 12 : $object->month_close - 1), 'gmt'); - } elseif (empty($object->day_close) && empty($object->month_close)) { - $datestart = dol_get_last_day($object->year_close - 1, $object->month_close, 'gmt'); - } else { - $datestart = dol_time_plus_duree($dateend, -1, 'd', 0); - } - $datestart += 1; // Add 1 second + print($object->month_close ? "-".sprintf("%02d", $object->month_close) : ""); + print($object->day_close ? "-".sprintf("%02d", $object->day_close) : ""); //print '     '; $htmltooltip = ''; @@ -760,32 +888,60 @@ if (empty($action) || $action == "view" || $action == "close") { print $form->textwithpicto('', $htmltooltip); print ''; + if ($object->lifetime_start) { + print ''; + print $langs->trans("LifetimeAmount"); + print ''; + print ''.price($object->card_lifetime + $object->cheque_lifetime + $object->cash_lifetime, 0, $langs, 1, -1, -1, $conf->currency).''; + print '   '.$langs->trans("since").' '.dol_print_date($object->lifetime_start, 'dayhour').' ('.$langs->trans("AllTerminals").')'; + print ''; + } + print ''; print ''; print '
'; print '
'; - print ''; - - print ''; - - print '"; + print '
'; - print $langs->trans("DateCreationShort"); - print ''; - print dol_print_date($object->date_creation, 'dayhour'); - print '
'.$langs->trans("InitialBankBalance").' - '.$langs->trans("Cash").''; - print ''.price($object->opening, 0, $langs, 1, -1, -1, $conf->currency).''; - print "
'; if ($object->status == $object::STATUS_CLOSED) { + print ''; + print ''; + print ''; + print ''; + print ""; + foreach ($arrayofpaymentmode as $key => $val) { - $realamountforpaymentmode = $object->$key; - print '"; + + $realamountforpaymentmode = $object->$key; + $declaredamountforpaymentmode = $object->$newkey; + + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; } } @@ -840,6 +996,7 @@ if (empty($action) || $action == "view" || $action == "close") { print '
'; + print ''; print '
'; print '
'.$langs->trans("Summary").''.$langs->trans("InitialBankBalance").''.$langs->trans("Sales").''.$langs->trans("EndBankBalance").'
'.$langs->trans($val).''; - if ($realamountforpaymentmode) { - print ''.price($realamountforpaymentmode, 0, $langs, 1, -1, -1, $conf->currency).''; + $newkey = $key.'_declared'; + if (!property_exists($object, $key)) { + continue; } - print "
'.$langs->trans($val).''; + if ($key == 'cash') { + print ''.price($object->opening, 0, $langs, 1, -1, -1, $conf->currency).''; + } + print ''; + if ($realamountforpaymentmode) { + print ''.($realamountforpaymentmode >= 0 ? '+' : '').price($realamountforpaymentmode, 0, $langs, 1, -1, -1, $conf->currency).''; + } + print ''; + print ''; + print price($declaredamountforpaymentmode, 0, $langs, 1, -1, -1, $conf->currency).''; + print '
'; @@ -875,7 +1032,7 @@ if (empty($action) || $action == "view" || $action == "close") { print ''; // Initial amount - print ''; + print ''; print ''; // Amount per payment type @@ -896,25 +1053,30 @@ if (empty($action) || $action == "view" || $action == "close") { print ''; - // Amount per payment type + + // Amount calculated per payment type $i = 0; foreach ($arrayofpaymentmode as $key => $val) { - print ''; $i++; } + // Save print ''; print ''; + print ''; print ''; + // Initial amount print ''; + // Amount per payment type $i = 0; foreach ($arrayofpaymentmode as $key => $val) { - print '
'.$langs->trans("NbOfInvoices").''.$langs->trans("NbOfPayments").''; print ''; print price($initialbalanceforterminal[$terminalid]['cash']).'
'; print '
'; + print ''; if ($key == 'cash') { - $deltaforcash = ((float) $object->opening - $initialbalanceforterminal[$terminalid]['cash']); - print price($theoricalamountforterminal[$terminalid][$key] + $deltaforcash).'
'; + print price($object->opening + $theoricalamountforterminal[$terminalid][$key]).'
'; + print '('.price($object->opening).' + '.price($theoricalamountforterminal[$terminalid][$key]).')'; } else { print price($theoricalamountforterminal[$terminalid][$key]).'
'; } + print ''; print '
'.$langs->trans("RealAmount").''; print ''; print ''; + print ''; print ''; $i++; } + // Save print ''; print ''; diff --git a/htdocs/compta/cashcontrol/class/cashcontrol.class.php b/htdocs/compta/cashcontrol/class/cashcontrol.class.php index 7a54274527e..62062d82aac 100644 --- a/htdocs/compta/cashcontrol/class/cashcontrol.class.php +++ b/htdocs/compta/cashcontrol/class/cashcontrol.class.php @@ -82,6 +82,13 @@ class CashControl extends CommonObject 'cash' => array('type' => 'price', 'label' => 'Cash', 'enabled' => 1, 'visible' => 1, 'position' => 30, 'csslist' => 'amount'), 'cheque' => array('type' => 'price', 'label' => 'Cheque', 'enabled' => 1, 'visible' => 1, 'position' => 33, 'csslist' => 'amount'), 'card' => array('type' => 'price', 'label' => 'CreditCard', 'enabled' => 1, 'visible' => 1, 'position' => 36, 'csslist' => 'amount'), + 'cash_declared' => array('type' => 'price', 'label' => 'CashDeclared', 'enabled' => 1, 'visible' => 1, 'position' => 40, 'csslist' => 'amount'), + 'cheque_declared' => array('type' => 'price', 'label' => 'ChequeDeclared', 'enabled' => 1, 'visible' => 1, 'position' => 41, 'csslist' => 'amount'), + 'card_declared' => array('type' => 'price', 'label' => 'CreditCardDeclared', 'enabled' => 1, 'visible' => 1, 'position' => 42, 'csslist' => 'amount'), + 'cash_lifetime' => array('type' => 'price', 'label' => 'CashLifetime', 'enabled' => 1, 'visible' => 0, 'position' => 45, 'csslist' => 'amount'), + 'cheque_lifetime' => array('type' => 'price', 'label' => 'ChequeLifetime', 'enabled' => 1, 'visible' => 0, 'position' => 46, 'csslist' => 'amount'), + 'card_lifetime' => array('type' => 'price', 'label' => 'CreditCardLifetime', 'enabled' => 1, 'visible' => 0, 'position' => 47, 'csslist' => 'amount'), + 'lifetime_start' => array('type' => 'datetime', 'label' => 'LifetimeStartDate', 'enabled' => 1, 'visible' => 0, 'position' => 48, 'csslist' => 'center'), 'year_close' => array('type' => 'integer', 'label' => 'Year', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'position' => 50, 'css' => 'center'), 'month_close' => array('type' => 'integer', 'label' => 'Month', 'enabled' => 1, 'visible' => 1, 'position' => 55, 'css' => 'center'), 'day_close' => array('type' => 'integer', 'label' => 'Day', 'enabled' => 1, 'visible' => 1, 'position' => 60, 'css' => 'center'), @@ -158,20 +165,55 @@ class CashControl extends CommonObject public $posnumber; /** - * @var float Cash amount + * @var float Cash amount earned during period (calculated) */ public $cash; /** - * @var float cheque amount + * @var float Cheque amount earned during period (calculated) */ public $cheque; /** - * @var float Card amountS + * @var float Card amount earned during period (calculated) */ public $card; + /** + * @var ?float Cash found/declared in account (is the + */ + public $cash_declared; + + /** + * @var ?float Cheque found/declared in account + */ + public $cheque_declared; + + /** + * @var ?float Card found/declared in account + */ + public $card_declared; + + /** + * @var ?float Lifetime cash earned + */ + public $cash_lifetime; + + /** + * @var ?float Lifetime cheque amount + */ + public $cheque_lifetime; + + /** + * @var ?float Lifetime card amountS + */ + public $card_lifetime; + + /** + * @var ?int Date when lifetime value start + */ + public $lifetime_start; + /** * @var int User ID create */ @@ -301,7 +343,7 @@ class CashControl extends CommonObject } /** - * Validate cash fence + * Validate a cash register control * * @param User $user User * @param int $notrigger No trigger @@ -309,7 +351,6 @@ class CashControl extends CommonObject */ public function valid(User $user, $notrigger = 0) { - global $conf, $langs; require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; $error = 0; @@ -324,7 +365,7 @@ class CashControl extends CommonObject // Update request $sql = "UPDATE ".MAIN_DB_PREFIX."pos_cash_fence"; - $sql .= " SET status = ".self::STATUS_VALIDATED.","; + $sql .= " SET status = ".((int) self::STATUS_VALIDATED).","; $sql .= " date_valid='".$this->db->idate($now)."',"; $sql .= " fk_user_valid = ".((int) $user->id); $sql .= " WHERE rowid=".((int) $this->id); @@ -368,6 +409,73 @@ class CashControl extends CommonObject } + /** + * Close a cash register control + * + * @param User $user User + * @param int $notrigger No trigger + * @return int Return integer <0 if KO, >0 if OK + */ + public function close(User $user, $notrigger = 0) + { + require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; + + $error = 0; + + // Protection + if ($this->status == self::STATUS_CLOSED) { + $this->error = get_class($this)."::valid action abandoned: already validated"; + dol_syslog($this->error, LOG_WARNING); + return 0; + } + + $now = dol_now(); + + // Update request + $sql = "UPDATE ".MAIN_DB_PREFIX."pos_cash_fence"; + $sql .= " SET status = ".((int) self::STATUS_CLOSED).","; + $sql .= " date_valid = '".$this->db->idate($now)."',"; + $sql .= " fk_user_valid = ".((int) $user->id); + $sql .= " WHERE rowid=".((int) $this->id); + + $this->db->begin(); + + dol_syslog(get_class($this)."::close", LOG_DEBUG); + $resql = $this->db->query($sql); + if (!$resql) { + $error++; + $this->errors[] = "Error ".$this->db->lasterror(); + } + + if (!$error) { + $this->status = self::STATUS_CLOSED; + $this->date_valid = $now; + $this->fk_user_valid = $user->id; + } + + if (!$error && !$notrigger) { + // Call trigger + $result = $this->call_trigger('CASHCONTROL_CLOSE', $user); + if ($result < 0) { + $error++; + } + // End call triggers + } + + // Commit or rollback + if ($error) { + foreach ($this->errors as $errmsg) { + dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR); + $this->error .= ($this->error ? ', '.$errmsg : $errmsg); + } + $this->db->rollback(); + return -1 * $error; + } else { + $this->db->commit(); + return $this->id; + } + } + /** * Load object in memory from the database * diff --git a/htdocs/compta/cashcontrol/report.php b/htdocs/compta/cashcontrol/report.php index b2c0cbf5a27..f6bd1240e9a 100644 --- a/htdocs/compta/cashcontrol/report.php +++ b/htdocs/compta/cashcontrol/report.php @@ -392,8 +392,8 @@ if ($resql) { print '
'; print '
'.price($cash).'
'; } - if (!$summaryonly && $object->status == $object::STATUS_VALIDATED && price2num($newcash) != price2num($object->cash)) { - print '
<> '.$langs->trans("Declared").': '.price($object->cash).'
'; + if (!$summaryonly && $object->status == $object::STATUS_CLOSED && price2num($newcash) != price2num((float) $object->cash_declared)) { + print '
<> '.$langs->trans("Declared").': '.price($object->cash_declared).'
'; } print "
"; @@ -401,8 +401,8 @@ if ($resql) { print $langs->trans("PaymentTypeCHQ").(!empty($transactionspertype['CHQ']) ? ' ('.$transactionspertype['CHQ'].' '.$langs->trans("Articles").')' : '').' : '; print '
'; print '
'.price($cheque).'
'; - if (!$summaryonly && $object->status == $object::STATUS_VALIDATED && price2num($cheque) != price2num($object->cheque)) { - print '
<> '.$langs->trans("Declared").' : '.price($object->cheque).'
'; + if (!$summaryonly && $object->status == $object::STATUS_CLOSED && price2num($cheque) != price2num((float) $object->cheque_declared)) { + print '
<> '.$langs->trans("Declared").' : '.price($object->cheque_declared).'
'; } print "
"; @@ -410,8 +410,8 @@ if ($resql) { print $langs->trans("PaymentTypeCB").(!empty($transactionspertype['CB']) ? ' ('.$transactionspertype['CB'].' '.$langs->trans("Articles").')' : '').' : '; print '
'; print '
'.price($bank).'
'; - if (!$summaryonly && $object->status == $object::STATUS_VALIDATED && price2num($bank) != price2num($object->card)) { - print '
<> '.$langs->trans("Declared").': '.price($object->card).'
'; + if (!$summaryonly && $object->status == $object::STATUS_CLOSED && price2num($bank) != price2num((float) $object->card_declared)) { + print '
<> '.$langs->trans("Declared").': '.price($object->card_declared).'
'; } print "
"; diff --git a/htdocs/core/triggers/interface_50_modBlockedlog_ActionsBlockedLog.class.php b/htdocs/core/triggers/interface_50_modBlockedlog_ActionsBlockedLog.class.php index 621ae8d5bb7..df703ce0bc4 100644 --- a/htdocs/core/triggers/interface_50_modBlockedlog_ActionsBlockedLog.class.php +++ b/htdocs/core/triggers/interface_50_modBlockedlog_ActionsBlockedLog.class.php @@ -107,7 +107,7 @@ class InterfaceActionsBlockedLog extends DolibarrTriggers || $action === 'BILL_SUPPLIER_VALIDATE' || (($action === 'BILL_SUPPLIER_DELETE' || $action === 'BILL_SUPPLIER_SENTBYMAIL') && ($object->statut != 0 || $object->status != 0)) || $action === 'MEMBER_SUBSCRIPTION_CREATE' || $action === 'MEMBER_SUBSCRIPTION_MODIFY' || $action === 'MEMBER_SUBSCRIPTION_DELETE' || $action === 'DON_VALIDATE' || (($action === 'DON_MODIFY' || $action === 'DON_DELETE') && ($object->statut != 0 || $object->status != 0)) - || $action === 'CASHCONTROL_VALIDATE' + || $action === 'CASHCONTROL_CLOSE' || (in_array($object->element, array('facture', 'supplier_invoice')) && $action === 'DOC_DOWNLOAD' && ($object->statut != 0 || $object->status != 0)) || (in_array($object->element, array('facture', 'supplier_invoice')) && $action === 'DOC_PREVIEW' && ($object->statut != 0 || $object->status != 0)) || (getDolGlobalString('BLOCKEDLOG_ADD_ACTIONS_SUPPORTED') && in_array($action, explode(',', getDolGlobalString('BLOCKEDLOG_ADD_ACTIONS_SUPPORTED')))) @@ -119,7 +119,7 @@ class InterfaceActionsBlockedLog extends DolibarrTriggers 'DON_VALIDATE', 'DON_MODIFY', 'DON_DELETE'))) { /** @var Don|Subscription $object */ $amounts = (float) $object->amount; - } elseif ($action == 'CASHCONTROL_VALIDATE') { + } elseif ($action == 'CASHCONTROL_CLOSE') { /** @var CashControl $object */ $amounts = (float) $object->cash + (float) $object->cheque + (float) $object->card; } else { diff --git a/htdocs/install/mysql/migration/22.0.0-23.0.0.sql b/htdocs/install/mysql/migration/22.0.0-23.0.0.sql index 74f44d2bc1e..64842a39f6d 100644 --- a/htdocs/install/mysql/migration/22.0.0-23.0.0.sql +++ b/htdocs/install/mysql/migration/22.0.0-23.0.0.sql @@ -404,12 +404,14 @@ ALTER TABLE llx_pos_cash_fence ADD COLUMN hour_close INTEGER DEFAULT null after ALTER TABLE llx_pos_cash_fence ADD COLUMN min_close INTEGER DEFAULT null after hour_close; ALTER TABLE llx_pos_cash_fence ADD COLUMN sec_close INTEGER DEFAULT null after min_close; -ALTER TABLE llx_pos_cash_fence ADD COLUMN cash_calculated double(24,8) DEFAULT null; -ALTER TABLE llx_pos_cash_fence ADD COLUMN card_calculated double(24,8) DEFAULT null; -ALTER TABLE llx_pos_cash_fence ADD COLUMN cheque_calculated double(24,8) DEFAULT null; +ALTER TABLE llx_pos_cash_fence ADD COLUMN cash_declared double(24,8) DEFAULT null; +ALTER TABLE llx_pos_cash_fence ADD COLUMN card_declared double(24,8) DEFAULT null; +ALTER TABLE llx_pos_cash_fence ADD COLUMN cheque_declared double(24,8) DEFAULT null; ALTER TABLE llx_pos_cash_fence ADD COLUMN cash_lifetime double(24,8) DEFAULT null; ALTER TABLE llx_pos_cash_fence ADD COLUMN card_lifetime double(24,8) DEFAULT null; ALTER TABLE llx_pos_cash_fence ADD COLUMN cheque_lifetime double(24,8) DEFAULT null; +ALTER TABLE llx_pos_cash_fence ADD COLUMN lifetime_start datetime DEFAULT NULL; + -- end of migration diff --git a/htdocs/install/mysql/tables/llx_pos_cash_fence.sql b/htdocs/install/mysql/tables/llx_pos_cash_fence.sql index e3bb0625994..66796d11fbf 100644 --- a/htdocs/install/mysql/tables/llx_pos_cash_fence.sql +++ b/htdocs/install/mysql/tables/llx_pos_cash_fence.sql @@ -20,18 +20,19 @@ CREATE TABLE llx_pos_cash_fence( ref VARCHAR(64), label VARCHAR(255), opening double(24,8) default 0, - -- amount calculated - cash_calculated double(24,8) default NULL, - card_calculated double(24,8) default NULL, - cheque_calculated double(24,8) default NULL, - -- amount declared + -- amount cash double(24,8) default 0, card double(24,8) default 0, cheque double(24,8) default 0, + -- amount declared + cash_declared double(24,8) default NULL, + card_declared double(24,8) default NULL, + cheque_declared double(24,8) default NULL, -- lifetime amount (not used) cash_lifetime double(24,8) default NULL, card_lifetime double(24,8) default NULL, cheque_lifetime double(24,8) default NULL, + lifetime_start datetime default NULL, status INTEGER, date_creation DATETIME NOT NULL, date_valid DATETIME, diff --git a/htdocs/langs/en_US/bills.lang b/htdocs/langs/en_US/bills.lang index b98f2532cb3..57681119360 100644 --- a/htdocs/langs/en_US/bills.lang +++ b/htdocs/langs/en_US/bills.lang @@ -706,3 +706,4 @@ TypeAmountOfEachNewDiscountSplit=Input amount for each part SplitDiscountTitle=Split discount into several parts DisputeStatus=Dispute status DisputeOpen=Dispute open +Sales=Sales diff --git a/htdocs/langs/en_US/blockedlog.lang b/htdocs/langs/en_US/blockedlog.lang index 5a28f7faabd..0ac71fd6bb8 100644 --- a/htdocs/langs/en_US/blockedlog.lang +++ b/htdocs/langs/en_US/blockedlog.lang @@ -63,7 +63,8 @@ logBILL_PAYED=Customer invoice paid logBILL_SENTBYMAIL=Customer invoice send by mail logBILL_UNPAYED=Customer invoice set unpaid logBILL_VALIDATE=Customer invoice validated -logCASHCONTROL_VALIDATE=Cash register closing +logCASHCONTROL_VALIDATE=Cash register validation +logCASHCONTROL_CLOSE=Cash register closing logDOC_DOWNLOAD=Download of a validated document in order to print or send logDOC_PREVIEW=Preview of a validated document in order to print or download logDONATION_PAYMENT_CREATE=Donation payment created diff --git a/htdocs/langs/en_US/cashdesk.lang b/htdocs/langs/en_US/cashdesk.lang index 831b8463a39..4a996848dee 100644 --- a/htdocs/langs/en_US/cashdesk.lang +++ b/htdocs/langs/en_US/cashdesk.lang @@ -180,3 +180,5 @@ CashControlEndDateMustBeBeforeNow=It is not possible to make a monthly or yearly CashControlEndDayMustNotBeInPast=It is not possible to make a daily cash control on a day in the future. TheoricalView=Theorical view SentOrderToKitchen=Send order to kitchen +LifetimeAmount=Lifetime amount +AllTerminals=All terminals diff --git a/htdocs/langs/en_US/main.lang b/htdocs/langs/en_US/main.lang index 3b4222173e8..75cb6ec1c63 100644 --- a/htdocs/langs/en_US/main.lang +++ b/htdocs/langs/en_US/main.lang @@ -1408,3 +1408,4 @@ KO=KO AddThisPageToBookmarks = Add current page to bookmarks EditBookmarks = List/edit bookmarks Why=Why +since=since