diff --git a/htdocs/core/modules/dons/doc/doc_generic_don_odt.modules.php b/htdocs/core/modules/dons/doc/doc_generic_don_odt.modules.php index 4bdeff99fe3fc..38a33c6f7914f 100644 --- a/htdocs/core/modules/dons/doc/doc_generic_don_odt.modules.php +++ b/htdocs/core/modules/dons/doc/doc_generic_don_odt.modules.php @@ -261,10 +261,11 @@ public function write_file($object, $outputlangs, $srctemplatepath, $hidedetails $dir = $conf->don->dir_output; $objectref = dol_sanitizeFileName($object->ref); + $objectid = dol_sanitizeFileName($object->id); if (!preg_match('/specimen/i', $objectref)) { - $dir .= "/".$objectref; + $dir .= "/".$objectid; } - $file = $dir."/".$objectref.".odt"; + $file = $dir."/".$objectid.".odt"; if (!file_exists($dir)) { if (dol_mkdir($dir) < 0) { diff --git a/htdocs/core/modules/dons/mod_don_terre.php b/htdocs/core/modules/dons/mod_don_terre.php new file mode 100644 index 0000000000000..7fac069f192e7 --- /dev/null +++ b/htdocs/core/modules/dons/mod_don_terre.php @@ -0,0 +1,233 @@ + + * Copyright (C) 2005-2015 Regis Houssin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * or see https://www.gnu.org/ + */ + +/** + * \file htdocs/core/modules/don/mod_don_terre.php + * \ingroup don + * \brief File containing class for numbering module Terre + */ +require_once DOL_DOCUMENT_ROOT.'/core/modules/dons/modules_don.php'; + +/** + * \class mod_don_terre + * \brief Class of numbering module Terre for dons + */ +class mod_don_terre extends ModeleNumRefDons +{ + /** + * Dolibarr version of the loaded document 'development', 'experimental', 'dolibarr' + * @var string + */ + public $version = 'dolibarr'; + + /** + * Prefix for dons + * @var string + */ + public $prefixdon = 'DON'; + + /** + * Prefix for replacement dons + * @var string + */ + public $prefixreplacement = 'FA'; + + /** + * Prefix for credit note + * @var string + */ + public $prefixcreditnote = 'AV'; + + /** + * Prefix for deposit + * @var string + */ + public $prefixdeposit = 'AC'; + + /** + * @var string Error code (or message) + */ + public $error = ''; + + + /** + * Constructor + */ + public function __construct() + { + global $conf, $mysoc; + + } + + /** + * Returns the description of the numbering model + * + * @param Translate $langs Object langs + * @return string Texte descripif + */ + public function info($langs) + { + $langs->load("bills"); + return $langs->trans('TerreNumRefModelDesc1', $this->prefixdon, $this->prefixcreditnote, $this->prefixdeposit); + } + + /** + * Return an example of numbering + * + * @return string Example + */ + public function getExample() + { + return $this->prefixdon."0501-0001"; + } + + /** + * Checks if the numbers already in the database do not + * cause conflicts that would prevent this numbering working. + * + * @return boolean false if conflict, true if ok + */ + public function canBeActivated($object) + { + global $langs, $conf, $db; + + $langs->load("bills"); + + // Check don num + $fayymm = ''; + $max = ''; + + $posindice = strlen($this->prefixdon) + 6; + $sql = "SELECT MAX(CAST(SUBSTRING(ref FROM ".$posindice.") AS SIGNED)) as max"; // This is standard SQL + $sql .= " FROM ".MAIN_DB_PREFIX."don"; + $sql .= " WHERE ref LIKE '".$db->escape($this->prefixdon)."____-%'"; + $sql .= " AND entity = ".$conf->entity; + + $resql = $db->query($sql); + if ($resql) { + $row = $db->fetch_row($resql); + if ($row) { + $fayymm = substr($row[0], 0, 6); + $max = $row[0]; + } + } + if ($fayymm && !preg_match('/'.$this->prefixdon.'[0-9][0-9][0-9][0-9]/i', $fayymm)) { + $langs->load("errors"); + $this->error = $langs->trans('ErrorNumRefModel', $max); + return false; + } + + return true; + } + + /** + * Return next value not used or last value used. + * Note to increase perf of this numbering engine, you can create a calculated column and modify request to use this field instead for select: + * ALTER TABLE llx_don ADD COLUMN calculated_numrefonly INTEGER AS (CASE SUBSTRING(ref FROM 1 FOR 2) WHEN 'FA' THEN CAST(SUBSTRING(ref FROM 10) AS SIGNED) ELSE 0 END) PERSISTENT; + * ALTER TABLE llx_don ADD INDEX calculated_numrefonly_idx (calculated_numrefonly); + * + * @param Societe $objsoc Object third party + * @param Don $don Object don + * @param string $mode 'next' for next value or 'last' for last value + * @return string Next ref value or last ref if $mode is 'last', <= 0 if KO + */ + public function getNextValue($objsoc, $don, $mode = 'next') + { + global $db; + + dol_syslog(get_class($this)."::getNextValue mode=".$mode, LOG_DEBUG); + + $prefix = $this->prefixdon; + + // First we get the max value + $posindice = strlen($prefix) + 6; + $sql = "SELECT MAX(CAST(SUBSTRING(ref FROM ".$posindice.") AS SIGNED)) as max"; // This is standard SQL + $sql .= " FROM ".MAIN_DB_PREFIX."don"; + $sql .= " WHERE ref LIKE '".$db->escape($prefix)."____-%'"; + $sql .= " AND entity IN (".getEntity('donnumber', 1, $don).")"; + + $resql = $db->query($sql); + if ($resql) { + $obj = $db->fetch_object($resql); + if ($obj) { + $max = intval($obj->max); + } else { + $max = 0; + } + } else { + return -1; + } + + if ($mode == 'last') { + if ($max >= (pow(10, 4) - 1)) { + $num = $max; // If counter > 9999, we do not format on 4 chars, we take number as it is + } else { + $num = sprintf("%04s", $max); + } + + $ref = ''; + $sql = "SELECT ref as ref"; + $sql .= " FROM ".MAIN_DB_PREFIX."don"; + $sql .= " WHERE ref LIKE '".$db->escape($prefix)."____-".$num."'"; + $sql .= " AND entity IN (".getEntity('donnumber', 1, $don).")"; + $sql .= " ORDER BY ref DESC"; + + $resql = $db->query($sql); + if ($resql) { + $obj = $db->fetch_object($resql); + if ($obj) { + $ref = $obj->ref; + } + } else { + dol_print_error($db); + } + + return $ref; + } elseif ($mode == 'next') { + $date = $don->date; // This is don date (not creation date) + $yymm = strftime("%y%m", $date); + + if ($max >= (pow(10, 4) - 1)) { + $num = $max + 1; // If counter > 9999, we do not format on 4 chars, we take number as it is + } else { + $num = sprintf("%04s", $max + 1); + } + + dol_syslog(get_class($this)."::getNextValue return ".$prefix.$yymm."-".$num); + return $prefix.$yymm."-".$num; + } else { + dol_print_error('', 'Bad parameter for getNextValue'); + } + + return 0; + } + + /** + * Return next free value + * + * @param Societe $objsoc Object third party + * @param string $objforref Object for number to search + * @param string $mode 'next' for next value or 'last' for last value + * @return string Next free value + */ + public function getNumRef($objsoc, $objforref, $mode = 'next') + { + return $this->getNextValue($objsoc, $objforref, $mode); + } +} diff --git a/htdocs/don/admin/donation.php b/htdocs/don/admin/donation.php index 66573989d6deb..c1fc9ef57f30e 100644 --- a/htdocs/don/admin/donation.php +++ b/htdocs/don/admin/donation.php @@ -104,6 +104,11 @@ dolibarr_del_const($db, 'DON_ADDON_MODEL', $conf->entity); } } +} elseif ($action == 'setmod') { + // TODO Verifier si module numerotation choisi peut etre active + // par appel methode canBeActivated + + dolibarr_set_const($db, "DON_ADDON", $value, 'chaine', 0, '', $conf->entity); } // Options @@ -327,6 +332,135 @@ print '
'; +/* + * Numbering module + */ + +print load_fiche_titre($langs->trans("BillsNumberingModule"), '', ''); + +print '
'; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''."\n"; + +clearstatcache(); + +foreach ($dirmodels as $reldir) { + $dir = dol_buildpath($reldir."core/modules/dons/"); + if (is_dir($dir)) { + $handle = opendir($dir); + if (is_resource($handle)) { + while (($file = readdir($handle)) !== false) { + if (!is_dir($dir.$file) || (substr($file, 0, 1) <> '.' && substr($file, 0, 3) <> 'CVS')) { + $filebis = $file; + $classname = preg_replace('/\.php$/', '', $file); + // For compatibility + if (!is_file($dir.$filebis)) { + $filebis = $file."/".$file.".modules.php"; + $classname = "mod_don_".$file; + } + // Check if there is a filter on country + preg_match('/\-(.*)_(.*)$/', $classname, $reg); + if (!empty($reg[2]) && $reg[2] != strtoupper($mysoc->country_code)) { + continue; + } + + $classname = preg_replace('/\-.*$/', '', $classname); + if (!class_exists($classname) && is_readable($dir.$filebis) && (preg_match('/mod_/', $filebis) || preg_match('/mod_/', $classname)) && substr($filebis, dol_strlen($filebis) - 3, 3) == 'php') { + // Charging the numbering class + require_once $dir.$filebis; + + $module = new $classname($db); + + // Show modules according to features level + if ($module->version == 'development' && $conf->global->MAIN_FEATURES_LEVEL < 2) { + continue; + } + if ($module->version == 'experimental' && $conf->global->MAIN_FEATURES_LEVEL < 1) { + continue; + } + + if ($module->isEnabled()) { + print ''; + + // Show example of numbering module + print ''."\n"; + + print ''; + + $don = new Don($db); + $don->initAsSpecimen(); + + // Example for standard invoice + $htmltooltip = ''; + $htmltooltip .= ''.$langs->trans("Version").': '.$module->getVersion().'
'; + $don->type = 0; + $nextval = $module->getNextValue($mysoc, $don); + if ("$nextval" != $langs->trans("NotAvailable")) { // Keep " on nextval + $htmltooltip .= $langs->trans("NextValueForInvoices").': '; + if ($nextval) { + if (preg_match('/^Error/', $nextval) || $nextval == 'NotConfigured') { + $nextval = $langs->trans($nextval); + } + $htmltooltip .= $nextval.'
'; + } else { + $htmltooltip .= $langs->trans($module->error).'
'; + } + } + print ''; + + print "\n"; + } + } + } + } + closedir($handle); + } + } +} + +print '
'.$langs->trans("Name").''.$langs->trans("Description").''.$langs->trans("Example").''.$langs->trans("Status").''.$langs->trans("ShortInfo").'
'; + echo preg_replace('/\-.*$/', '', preg_replace('/mod_don_/', '', preg_replace('/\.php$/', '', $file))); + print "\n"; + + print $module->info($langs); + + print ''; + $tmp = $module->getExample(); + if (preg_match('/^Error/', $tmp)) { + $langs->load("errors"); + print '
'.$langs->trans($tmp).'
'; + } elseif ($tmp == 'NotConfigured') { + print ''.$langs->trans($tmp).''; + } else { + print $tmp; + } + print '
'; + //print "> ".$conf->global->DON_ADDON." - ".$file; + if ($conf->global->DON_ADDON == $file || $conf->global->DON_ADDON.'.php' == $file) { + print img_picto($langs->trans("Activated"), 'switch_on'); + } else { + print ''.img_picto($langs->trans("Disabled"), 'switch_off').''; + } + print ''; + print $form->textwithpicto('', $htmltooltip, 1, 0); + + if ($conf->global->DON_ADDON.'.php' == $file) { // If module is the one used, we show existing errors + if (!empty($module->error)) { + dol_htmloutput_mesg($module->error, '', 'error', 1); + } + } + + print '
'; +print '
'; + + + /* * Params */ diff --git a/htdocs/don/class/don.class.php b/htdocs/don/class/don.class.php index 6eaf8b76c5c1d..a24c1b5afbed8 100644 --- a/htdocs/don/class/don.class.php +++ b/htdocs/don/class/don.class.php @@ -641,6 +641,7 @@ public function delete($user, $notrigger = 0) */ public function fetch($id, $ref = '') { + global $conf; $sql = "SELECT d.rowid, d.datec, d.date_valid, d.tms as datem, d.datedon,"; $sql .= " d.fk_soc as socid, d.firstname, d.lastname, d.societe, d.amount, d.fk_statut as status, d.address, d.zip, d.town, "; $sql .= " d.fk_country, d.public, d.amount, d.fk_payment, d.paid, d.note_private, d.note_public, d.email, d.phone, "; @@ -666,7 +667,7 @@ public function fetch($id, $ref = '') $obj = $this->db->fetch_object($resql); $this->id = $obj->rowid; - $this->ref = $obj->rowid; + $this->ref = $obj->ref != null ? $obj->ref : $obj->rowid; $this->date_creation = $this->db->jdate($obj->datec); $this->datec = $this->db->jdate($obj->datec); $this->date_validation = $this->db->jdate($obj->date_valid); @@ -743,8 +744,10 @@ public function valid_promesse($id, $userid, $notrigger = 0) $error = 0; $this->db->begin(); + $num = $this->getNextNumRef($this->thirdparty); + $this->ref = dol_sanitizeFileName($num); - $sql = "UPDATE ".MAIN_DB_PREFIX."don SET fk_statut = 1, fk_user_valid = ".((int) $userid)." WHERE rowid = ".((int) $id)." AND fk_statut = 0"; + $sql = "UPDATE ".MAIN_DB_PREFIX."don SET fk_statut = 1, fk_user_valid = ".((int) $userid).", ref = '". $this->ref ."' WHERE rowid = ".((int) $id)." AND fk_statut = 0"; $resql = $this->db->query($sql); if ($resql) { @@ -902,6 +905,94 @@ public function loadStateBoard() } } + /** + * Return next reference of customer invoice not already used (or last reference) + * according to numbering module defined into constant FACTURE_ADDON + * + * @param Societe $soc object company + * @param string $mode 'next' for next value or 'last' for last value + * @return string free ref or last ref + */ + public function getNextNumRef($soc, $mode = 'next') + { + global $conf, $langs; + + $langs->load('don'); + + $moduleName = 'don'; + $moduleSourceName = 'Don'; + $addonConstName = 'DON_ADDON'; + + // Clean parameters (if not defined or using deprecated value) + if ($conf->global->DON_ADDON == 'terre') { + $conf->DON_ADDON = 'mod_facture_terre'; + } + + $addon = $conf->global->DON_ADDON; + + if (!empty($addon)) { + dol_syslog("Call getNextNumRef with ".$addonConstName." = ".$conf->global->FACTURE_ADDON.", thirdparty=".$soc->name.", type=".$soc->typent_code.", mode=".$mode, LOG_DEBUG); + + $mybool = false; + + $file = $addon.'.php'; + $classname = $addon; + + + // Include file with class + $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']); + foreach ($dirmodels as $reldir) { + $dir = dol_buildpath($reldir.'core/modules/'.$moduleName.'s/'); + + // Load file with numbering class (if found) + if (is_file($dir.$file) && is_readable($dir.$file)) { + $mybool |= include_once $dir.$file; + } + } + + // For compatibility + if (!$mybool) { + $file = $addon.'/'.$addon.'.modules.php'; + $classname = 'mod_'.$moduleName.'_'.$addon; + $classname = preg_replace('/\-.*$/', '', $classname); + // Include file with class + foreach ($conf->file->dol_document_root as $dirroot) { + $dir = $dirroot.'/core/modules/'.$moduleName.'s/'; + + // Load file with numbering class (if found) + if (is_file($dir.$file) && is_readable($dir.$file)) { + $mybool |= include_once $dir.$file; + } + } + } + + if (!$mybool) { + dol_print_error('', 'Failed to include file '.$file); + return ''; + } + + $obj = new $classname(); + + $numref = $obj->getNextValue($soc, $this, $mode); + + + /** + * $numref can be empty in case we ask for the last value because if there is no invoice created with the + * set up mask. + */ + if ($mode != 'last' && !$numref) { + $this->error = $obj->error; + return ''; + } + + return $numref; + } else { + $langs->load('errors'); + print $langs->trans('Error').' '.$langs->trans('ErrorModuleSetupNotComplete', $langs->transnoentitiesnoconv($moduleSourceName)); + return ''; + } + } + /** * Return clicable name (with picto eventually) * @@ -924,7 +1015,10 @@ public function getNomUrl($withpicto = 0, $notooltip = 0, $moretitle = '', $save if (isset($this->status)) { $label .= ' '.$this->getLibStatut(5); } - if (!empty($this->id)) { + if (!empty($this->ref)) { + $label .= '
'.$langs->trans('Ref').': '.$this->ref; + $label .= '
'.$langs->trans('Date').': '.dol_print_date($this->date, 'day'); + } elseif (!empty($this->id)) { $label .= '
'.$langs->trans('Ref').': '.$this->id; $label .= '
'.$langs->trans('Date').': '.dol_print_date($this->date, 'day'); } @@ -1084,6 +1178,45 @@ public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hided } $obj = new $classname($this->db); + // If generator is ODT, we must have srctemplatepath defined, if not we set it. + if ($obj->type == 'odt' && empty($srctemplatepath)) { + $varfortemplatedir = $obj->scandir; + if ($varfortemplatedir && !empty($conf->global->$varfortemplatedir)) { + $dirtoscan = $conf->global->$varfortemplatedir; + + $listoffiles = array(); + + // Now we add first model found in directories scanned + $listofdir = explode(',', $dirtoscan); + foreach ($listofdir as $key => $tmpdir) { + $tmpdir = trim($tmpdir); + $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir); + if (!$tmpdir) { + unset($listofdir[$key]); + continue; + } + if (is_dir($tmpdir)) { + $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0); + if (count($tmpfiles)) { + $listoffiles = array_merge($listoffiles, $tmpfiles); + } + } + } + + if (count($listoffiles)) { + foreach ($listoffiles as $record) { + $srctemplatepath = $record['fullname']; + break; + } + } + } + + if (empty($srctemplatepath)) { + $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined'; + return -1; + } + } + // We save charset_output to restore it because write_file can change it if needed for // output format that does not support UTF8. $sav_charset_output = $outputlangs->charset_output; @@ -1093,6 +1226,23 @@ public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hided // we delete preview files require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; dol_delete_preview($object); + if (!empty($obj->result['fullpath'])) { + $destfull = $obj->result['fullpath']; + + // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set) + $update_main_doc_field = 0; + if (!empty($obj->update_main_doc_field)) { + $update_main_doc_field = 1; + } + + $this->indexFile($destfull, $update_main_doc_field); + } else { + dol_syslog('Method ->write_file was called on object '.get_class($obj).' and return a success but the return array ->result["fullpath"] was not set.', LOG_WARNING); + } + + // Success in building document. We build meta file. + dol_meta_create($this); + return 1; } else { $outputlangs->charset_output = $sav_charset_output; diff --git a/htdocs/don/list.php b/htdocs/don/list.php index 2d9767d1263ed..b9d1cd102739c 100644 --- a/htdocs/don/list.php +++ b/htdocs/don/list.php @@ -168,7 +168,7 @@ // Build and execute select // -------------------------------------------------------------------- $sql = "SELECT d.rowid, d.datedon, d.fk_soc as socid, d.firstname, d.lastname, d.societe,"; -$sql .= " d.amount, d.fk_statut as status,"; +$sql .= " d.amount, d.fk_statut as status, d.ref as don_ref, "; $sql .= " p.rowid as pid, p.ref, p.title, p.public"; // Add fields from hooks $parameters = array(); @@ -502,7 +502,7 @@ print ''; } $donationstatic->id = $obj->rowid; - $donationstatic->ref = $obj->rowid; + $donationstatic->ref = $objp->don_ref ? $objp->don_ref : $objp->rowid; $donationstatic->lastname = $obj->lastname; $donationstatic->firstname = $obj->firstname; print "".$donationstatic->getNomUrl(1).""; diff --git a/htdocs/public/payment/paymentok.php b/htdocs/public/payment/paymentok.php index cb33e38b8ed6e..25bc0da3c5fde 100644 --- a/htdocs/public/payment/paymentok.php +++ b/htdocs/public/payment/paymentok.php @@ -1151,6 +1151,11 @@ } elseif (array_key_exists('DON', $tmptag) && $tmptag['DON'] > 0) { include_once DOL_DOCUMENT_ROOT.'/don/class/don.class.php'; $don = new Don($db); + if ((int) $tmptag['DON'] > 0) { + $result = $don->fetch((int) $tmptag['DON']); + } else { + $result = $don->fetch(null, $tmptag['DON']); + } $result = $don->fetch((int) $tmptag['DON']); if ($result) { $paymentTypeId = 0;