| Server IP : 172.173.179.141 / Your IP : 216.73.216.196 Web Server : Apache System : Linux mail.lomejor.cr 6.8.0-1059-azure #65~22.04.1-Ubuntu SMP Thu May 28 16:59:19 UTC 2026 x86_64 User : www-data ( 33) PHP Version : 8.2.31 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /var/www/dev/htdocs/custom/paymentschedule/class/ |
Upload File : |
<?php
/* Copyright (C) 2019 ATM Consulting <support@atm-consulting.fr>
*
* 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 <http://www.gnu.org/licenses/>.
*/
if (!class_exists('SeedObject'))
{
/**
* Needed if $form->showLinkedObjectBlock() is call or for session timeout on our module page
*/
define('INC_FROM_DOLIBARR', true);
require_once dirname(__FILE__).'/../config.php';
}
class PaymentSchedule extends SeedObject
{
/**
* Draft status
*/
const STATUS_DRAFT = 0;
/**
* Validated status
*/
const STATUS_VALIDATED = 1;
/**
* Closed status
*/
const STATUS_CLOSED = 2;
/** @var array $TStatus Array of translate key for each const */
public static $TStatus = array(
self::STATUS_DRAFT => 'PaymentScheduleStatusDraftShort'
,self::STATUS_VALIDATED => 'PaymentScheduleStatusValidatedShort'
,self::STATUS_CLOSED => 'PaymentScheduleStatusClosedShort'
);
/** @var string $table_element Table name in SQL */
public $table_element = 'paymentschedule';
/** @var string $element Name of the element (tip for better integration in Dolibarr: this value should be the reflection of the class name with ucfirst() function) */
public $element = 'paymentschedule';
/** @var int $isextrafieldmanaged Enable the fictionalises of extrafields */
public $isextrafieldmanaged = 1;
/** @var int $ismultientitymanaged 0=No test on entity, 1=Test with field entity, 2=Test with link by societe */
public $ismultientitymanaged = 1;
// periodicity unit
const PERIODICITY_VALUE_DAY = 'day';
const PERIODICITY_VALUE_MONTH = 'month';
const PERIODICITY_VALUE_YEAR = 'year';
public static $TPeriodicityString = array(
self::PERIODICITY_VALUE_DAY => 'day'
,self::PERIODICITY_VALUE_MONTH => 'month'
,self::PERIODICITY_VALUE_YEAR => 'year'
);
public $fields = array(
'status' => array('type' => 'integer', 'visible' => 0, 'notnull' => 1, 'enabled' => 1, 'default' => 0, 'index' => 1, 'position' => 30)
,'fk_facture' => array('type' => 'integer', 'visible' => 0, 'notnull' => 1, 'enabled' => 1, 'position' => 50, 'index' => 1)
,'date_start' => array('type' => 'date', 'label' => 'DateStart', 'visible' => 1, 'notnull' => 1)
// ,'date_end' => array('type' => 'date', 'label' => 'DateEnd', 'visible' => 1, 'notnull' => 1)
,'periodicity_unit' => array('type' => 'list', 'label' => 'PeriodicityUnit', 'visible' => 1, 'notnull' => 1, 'default' => self::PERIODICITY_VALUE_MONTH) // day, month, year (value for strtotime)
,'periodicity_value' => array('type' => 'integer', 'label' => 'PeriodicityValue', 'visible' => 1, 'notnull' => 1)
,'nb_term' => array('type' => 'integer', 'label' => 'NbTerm', 'visible' => 1, 'notnull' => 1)
//,'fk_user_valid' => array('type'=>'integer', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>512)
,'model_pdf'=>array('type' => 'varchar(255)', 'length' => 255, 'label' => 'model_pdf', 'visible' => 0)
,'last_main_doc'=>array('type' => 'varchar(255)', 'length' => 255, 'label' => 'last_main_doc', 'visible' => 0)
,'import_key' => array('type' => 'varchar(14)', 'length' => 14, 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'index' => 0, 'position' => 1000)
);
/** @var int $status Object status */
public $status;
/** @var int $fk_facture Object link to invoice */
public $fk_facture;
/** @var integer $date_start timestamp */
public $date_start;
// /** @var integer $date_start timestamp */
// public $date_end;
/** @var integer $nb_term */
public $nb_term;
/** @var integer $periodicity_unit */
public $periodicity_unit;
/** @var integer $periodicity_value */
public $periodicity_value;
public $childtables = array('paymentscheduledet'=>'PaymentScheduleDet');
public $fk_element = 'fk_payment_schedule';
/** @var PaymentScheduleDet[] $TPaymentScheduleDet */
public $TPaymentScheduleDet = array();
/** @var Facture $facture Object */
public $facture;
/**
* PaymentSchedule constructor.
* @param DoliDB $db Database connector
*/
public function __construct($db)
{
global $langs;
$this->db = $db;
$this->init();
$this->status = self::STATUS_DRAFT;
$this->fields['periodicity_unit']['arrayofkeyval'] = array(
self::PERIODICITY_VALUE_DAY => $langs->trans('paymentschedule_periodicityDay')
, self::PERIODICITY_VALUE_MONTH => $langs->trans('paymentschedule_periodicityMonth')
, self::PERIODICITY_VALUE_YEAR => $langs->trans('paymentschedule_periodicityYear')
);
}
public function fetch($id=0, $loadChild = true, $ref='')
{
$ret = parent::fetch($id, $loadChild, $ref);
if ($ret > 0)
{
$this->facture = new facture($this->db);
$this->facture->fetch($this->fk_facture);
$this->ref = $this->facture->ref."_ps";
$this->socid = $this->facture->socid;
}
return $ret;
}
/**
* Get object and children from database on custom field
*
* @param string $key key of object to load
* @param string $field field of object used to load
* @param bool $loadChild used to load children from database
* @return int >0 if OK, <0 if KO, 0 if not found
*/
public function fetchBy($key, $field, $loadChild = true)
{
if (empty($this->fields[$field])) return false;
$resql = $this->db->query("SELECT rowid FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE ".$field."=".$this->quote($key, $this->fields[$field])." LIMIT 1 ");
if ($resql)
{
if (($obj = $this->db->fetch_object($resql)))
{
return $this->fetch($obj->rowid, $loadChild);
}
}
else
{
$this->error = $this->db->lastqueryerror();
$this->errors[] = $this->error;
return -1;
}
return 0;
}
/**
* @param string $ref facture ref
* @param bool $loadChild
* @return int
*/
public function fetchByFactureRef($ref, $loadChild = true)
{
$field = (float) DOL_VERSION < 10.0 ? 'facnumber' : 'ref';
$sql = 'SELECT p.rowid FROM '.MAIN_DB_PREFIX.'facture f INNER JOIN '.MAIN_DB_PREFIX.$this->table_element.' p ON (p.fk_facture = f.rowid) WHERE f.'.$field.' = \''.$this->db->escape($ref).'\'';
$resql = $this->db->query($sql);
if ($resql)
{
if (($obj = $this->db->fetch_object($resql)))
{
return $this->fetch($obj->rowid, $loadChild);
}
}
else
{
$this->error = $this->db->lastqueryerror();
$this->errors[] = $this->error;
return -1;
}
return 0;
}
/**
* @param User $user User object
* @param bool $notrigger false=launch triggers after, true=disable triggers
* @return int
*/
public function save($user, $notrigger = false)
{
if (!empty($this->is_clone)) {}
return $this->create($user, $notrigger);
}
/**
* @see cloneObject
* @return void
*/
public function clearUniqueFields()
{
}
/**
* @param User $user User object
* @param bool $notrigger false=launch triggers after, true=disable triggers
* @return int
*/
public function delete(User &$user, $notrigger = false)
{
global $conf;
$this->deleteObjectLinked();
require_once DOL_DOCUMENT_ROOT."/compta/facture/class/facture.class.php";
$fac = new Facture($this->db);
$fac->fetch($this->fk_facture);
$ref = dol_sanitizeFileName($fac->ref)."_ps";
if ($conf->paymentschedule->dir_output)
{
require_once DOL_DOCUMENT_ROOT."/core/lib/files.lib.php";
$dir = $conf->paymentschedule->dir_output . "/" . $ref;
$file = $conf->paymentschedule->dir_output . "/" . $ref . "/" . $ref . ".pdf";
if (file_exists($file)) // We must delete all files before deleting directory
{
$ret=dol_delete_preview($this);
dol_delete_file($file,0,0,0,$this); // For triggers
}
if (file_exists($dir))
{
dol_delete_dir_recursive($dir); // For remove dir and meta
}
}
unset($this->fk_element); // avoid conflict with standard Dolibarr comportment
return parent::delete($user, $notrigger);
}
/**
* @param User $user User object
* @param bool $notrigger false=launch triggers after, true=disable triggers
* @return int
*/
public function setDraft($user, $notrigger = false)
{
if ($this->status === self::STATUS_VALIDATED)
{
$this->status = self::STATUS_DRAFT;
$this->withChild = false;
return $this->update($user, $notrigger);
}
return 0;
}
/**
* @param User $user User object
* @param bool $notrigger false=launch triggers after, true=disable triggers
* @return int
*/
public function setValid($user, $notrigger = false)
{
if ($this->status === self::STATUS_DRAFT)
{
// $this->fk_user_valid = $user->id;
$this->status = self::STATUS_VALIDATED;
$this->withChild = false;
return $this->update($user, $notrigger);
}
return 0;
}
/**
* function for massaction compatibility
*
* @param User $user
* @param bool $notrigger false=launch triggers after, true=disable triggers
* @return int
*/
public function validate($user, $notrigger = false)
{
return $this->setValid($user, $notrigger);
}
/**
* @param User $user User object
* @param bool $notrigger false=launch triggers after, true=disable triggers
* @return int
*/
public function setClose($user, $notrigger = false)
{
if ($this->status === self::STATUS_VALIDATED)
{
// $this->fk_user_valid = $user->id;
$this->status = self::STATUS_CLOSED;
$this->withChild = false;
return $this->update($user, $notrigger);
}
return 0;
}
/**
* @param User $user User object
* @param bool $notrigger false=launch triggers after, true=disable triggers
* @return int
*/
public function setReopen($user, $notrigger = false)
{
if ($this->status === self::STATUS_VALIDATED || $this->status === self::STATUS_CLOSED)
{
$this->status = self::STATUS_VALIDATED;
$this->withChild = false;
return $this->update($user, $notrigger);
}
return 0;
}
/**
* @param int $mode 0=Long label, 1=Short label, 2=Picto + Short label, 3=Picto, 4=Picto + Long label, 5=Short label + Picto, 6=Long label + Picto
* @return string
*/
public function getLibStatut($mode = 0)
{
return self::LibStatut($this->status, $mode);
}
/**
* @param int $status Status
* @param int $mode 0=Long label, 1=Short label, 2=Picto + Short label, 3=Picto, 4=Picto + Long label, 5=Short label + Picto, 6=Long label + Picto
* @return string
*/
public static function LibStatut($status, $mode)
{
global $langs;
$langs->load('paymentschedule@paymentschedule');
$res = '';
if ($status==self::STATUS_DRAFT) { $statusType='status6'; $statusLabel=$langs->trans('PaymentScheduleStatusDraft'); $statusLabelShort=$langs->trans('PaymentScheduleStatusDraftShort'); }
elseif ($status==self::STATUS_VALIDATED) { $statusType='status4'; $statusLabel=$langs->trans('PaymentScheduleStatusValidated'); $statusLabelShort=$langs->trans('PaymentScheduleStatusValidateShort'); }
elseif ($status==self::STATUS_CLOSED) { $statusType='status6'; $statusLabel=$langs->trans('PaymentScheduleStatusClosed'); $statusLabelShort=$langs->trans('PaymentScheduleStatusClosedShort'); }
if (function_exists('dolGetStatus'))
{
$res = dolGetStatus($statusLabel, $statusLabelShort, '', $statusType, $mode);
}
else
{
$statusType = str_replace('status', 'statut', $statusType);
if ($mode == 0) $res = $statusLabel;
elseif ($mode == 1) $res = $statusLabelShort;
elseif ($mode == 2) $res = img_picto($statusLabel, $statusType).$statusLabelShort;
elseif ($mode == 3) $res = img_picto($statusLabel, $statusType);
elseif ($mode == 4) $res = img_picto($statusLabel, $statusType).$statusLabel;
elseif ($mode == 5) $res = $statusLabelShort.img_picto($statusLabel, $statusType);
elseif ($mode == 6) $res = $statusLabel.img_picto($statusLabel, $statusType);
}
return $res;
}
/**
* Méthode permettant de savoir si une facture correspond aux critères permettant de créer un échéancier
* @param Facture $facture
*
* @return array array(bool, array(msgs))
*/
public static function checkFactureCondition($facture)
{
global $conf, $langs, $user;
$langs->load('paymentschedule@paymentschedule');
$TRestrictMessage = array();
//Interdire la création d'échéancier si les lignes de la facture ont plusieurs taux de TVA différents
$i = 0;
foreach($facture->lines as $line){
if(empty($i)) $tx_tva = $line->tva_tx;
if($conf->subtotal->enabled) { // Pas de vérification de TVA sur les lignes de sous total
dol_include_once('/subtotal/class/subtotal.class.php');
if(TSubtotal::isModSubtotalLine($line)) continue;
}
if($tx_tva != $line->tva_tx) {
$TRestrictMessage[] = $langs->trans('CheckErrorInvoiceSeveralTva');
break;
}
$i++;
}
if (empty($user->rights->paymentschedule->write)) $TRestrictMessage[] = $langs->trans('CheckErrorInvoiceInsufficientPermission');
if ($facture->statut == Facture::STATUS_DRAFT) $TRestrictMessage[] = $langs->trans('CheckErrorInvoiceIsDraft');
$TPaymentId = array();
if (!empty($conf->global->PAYMENTSCHEDULE_MODE_REGLEMENT_TO_USE)) $TPaymentId[] = $conf->global->PAYMENTSCHEDULE_MODE_REGLEMENT_TO_USE;
if (!empty($conf->global->PAYMENTSCHEDULE_MODE_REGLEMENT_TO_USE_SECOND)) $TPaymentId = array_merge($TPaymentId, explode(',', $conf->global->PAYMENTSCHEDULE_MODE_REGLEMENT_TO_USE_SECOND));
if (!in_array($facture->mode_reglement_id, $TPaymentId))
{
$TRestrictMessage[] = $langs->trans('CheckErrorModeRgltNotMatch');
}
$echeancier = new PaymentSchedule($facture->db);
$echeancier->fetchBy($facture->id, 'fk_facture');
if (!empty($echeancier->id)) $TRestrictMessage[] = $langs->trans('CheckErrorTimetableAlreadyExists');
if (empty($facture->array_options)) $facture->fetch_optionals();
if (empty($facture->linkedObjects)) $facture->fetchObjectLinked();
// vérifier qu'on a bien l'extrafield isecheancier à true
if (empty($facture->array_options['options_isecheancier']))
{
$TRestrictMessage[] = $langs->trans('CheckErrorIsNotPaymentSchedule');
}
// TODO à vérifier mais peut être que le test sur link contrat pas utile
// vérifier qu'on a bien un contrat lié à cette facture avec au moins une ligne active
if (array_key_exists('contrat', $facture->linkedObjects))
{
// si y a un contrat, on valide qu'il y a une ligne active sur ce contrat
// je prend le premier qui vient pour test
$keys = array_keys($facture->linkedObjects['contrat']);
$contrat = &$facture->linkedObjects['contrat'][$keys[0]];
if ($contrat->nbofservicesopened == 0)
{
$TRestrictMessage[] = $langs->trans('CheckErrorNoActiveLineOnContract');
}
}
else
{
$TRestrictMessage[] = $langs->trans('CheckErrorIsNotLinkedToContract');
}
// TODO vérifier que le tiers de la facture a bien un compte bancaire avec les infos nécessaires au prélèvement
// IBAN valide + BIC + RUM + MODE (FRST ou RECUR)...
if (empty($conf->global->PAYMENTSCHEDULE_DISABLE_RESTRICTION_ON_IBAN))
{
require_once DOL_DOCUMENT_ROOT.'/societe/class/companypaymentmode.class.php';
$companypaymentmode = new CompanyPaymentMode($facture->db);
if ($companypaymentmode->fetch(null, null, $facture->socid) <= 0)
{
$TRestrictMessage[] = $langs->trans('CheckErrorCustomerHasNoIBAN');
}
}
return $TRestrictMessage;
}
/**
* Crée l'objet échéancier depuis la facture en récupérant les infos du contrat lié
*
* @param Facture $facture
* @param bool $notrigger false=launch triggers after, true=disable triggers
* @return int <0 if KO, > 0 if OK
*/
public function createFromFacture($facture, $date_start, $periodicity_unit, $periodicity_value, $nb_term, $notrigger = false)
{
global $user;
// check la facture
$TRestrictMessage = self::checkFactureCondition($facture);
if (!empty($TRestrictMessage))
{
$this->errors = $TRestrictMessage;
return -1;
}
else
{
$this->fk_facture = $facture->id;
$this->date_start = $date_start;
// TODO control values
$this->periodicity_unit = $periodicity_unit;
$this->periodicity_value = $periodicity_value;
$this->nb_term = $nb_term;
$this->db->begin();
$res = $this->save($user, $notrigger);
if ($res > 0)
{
$res = $this->initDetailEcheancier();
if ($res >= 0)
{
$this->db->commit();
return $this->id;
}
}
$this->db->rollback();
return $res;
}
}
public function initDetailEcheancier($reset = false, $fill_amount = 'onlast')
{
global $user, $conf, $langs;
$langs->load('paymentschedule@paymentschedule');
$start = $this->date_start;
if ((empty($this->date_start) && $this->date_start !== 0) || empty($this->fk_facture) || $this->fk_facture < 0 || empty($this->nb_term))
{
$this->error = "initDetail : missing parameters";
$this->errors[] = $this->error;
return -1;
}
require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
$facture = new Facture($this->db);
$ret = $facture->fetch($this->fk_facture);
if ($ret <= 0)
{
$this->errors[] = $langs->trans('CanNotRetrieveInvoice');
return -2;
}
if (empty($facture->thirdparty)) $facture->fetch_thirdparty();
$nb_term = $this->nb_term;
if ($nb_term < 0) $nb_term = 0;
$TDatesEcheance = array();
while ($nb_term--)
{
$TDatesEcheance[] = $start;
$facture->date_lim_reglement = $start;
$start = strtotime('+'.$this->periodicity_value.' '.$this->periodicity_unit, $start);
}
$facture->update($user);
//Calcul reste à payer
$totalpaye = 0;
// On verifie si la facture a des paiements
$sql = 'SELECT pf.amount';
$sql .= ' FROM ' . MAIN_DB_PREFIX . 'paiement_facture as pf';
$sql .= ' WHERE pf.fk_facture = ' . $facture->id;
$result = $this->db->query($sql);
if ($result) {
$i = 0;
$num = $this->db->num_rows($result);
while ($i < $num) {
$objp = $this->db->fetch_object($result);
$totalpaye += $objp->amount;
$i ++;
}
} else {
dol_print_error($this->db, '');
}
$totalcreditnotes = $facture->getSumCreditNotesUsed();
$totaldeposits = $facture->getSumDepositsUsed();
$resteapayer_TTC = price2num($facture->total_ttc - $totalpaye - $totalcreditnotes - $totaldeposits, 'MT');
//Calcul montant TVA et HT du reste à payer
$resteapayer_HT = $resteapayer_TTC / (1+($facture->lines[0]->tva_tx/100));
$resteapayer_TVA = $resteapayer_TTC - $resteapayer_HT;
$TDefaultAmountToPay = array(
'HT' => round($resteapayer_HT / $this->nb_term, 2)
,'VAT' => round($resteapayer_TVA / $this->nb_term, 2)
,'TTC' => round($resteapayer_TTC / $this->nb_term, 2)
);
$TLeftAmountToPay = array(
'HT' => $resteapayer_HT - (($this->nb_term-1) * $TDefaultAmountToPay['HT'])
,'VAT' => $resteapayer_TVA - (($this->nb_term-1) * $TDefaultAmountToPay['VAT'])
,'TTC' => $resteapayer_TTC - (($this->nb_term-1) * $TDefaultAmountToPay['TTC'])
);
// si reset à true, alors on supprime toutes les lignes avant de les recréer (uniquement celles qui sont en attente de traitement)
if ($reset)
{
foreach ($this->TPaymentScheduleDet as $det)
{
if ($det->status == PaymentScheduleDet::STATUS_WAITING) $det->delete($user);
}
}
// on crée les lignes d'échéance SEPA (base des demandes de prélévement généré en auto)
foreach ($TDatesEcheance as $i => $time)
{
$k = $this->addChild('PaymentScheduleDet');
$det = $this->TPaymentScheduleDet[$k];
$det->fk_payment_schedule = $this->id;
$facnumber = ((float) DOL_VERSION < 9.0 ) ? $facture->facnumber : $facture->ref;
// {INDICE} {SOCNAME} - {FACNUMBER} {REFCLIENT}
if (!empty($conf->global->PAYMENTSCHEDULE_LABEL_PATTERN))
{
$det->label = $conf->global->PAYMENTSCHEDULE_LABEL_PATTERN;
$det->label = strtr($det->label, array(
'{INDICE}' => $i+1
, '{SOCNAME}' => $facture->thirdparty->name
, '{FACNUMBER}' => $facnumber
, '{REFCLIENT}' => $facture->ref_client
));
}
else
{
$det->label = $langs->trans('PaymentScheduleDet').' '.$facture->thirdparty->name.' - '.$facnumber;
}
$det->date_demande = $time;
$det->fk_mode_reglement = $facture->mode_reglement_id;
if ($fill_amount === 'onfirst' && $i == 0)
{
$det->amount_ht = $TLeftAmountToPay['HT'];
$det->amount_tva = $TLeftAmountToPay['VAT'];
$det->amount_ttc = $TLeftAmountToPay['TTC'];
}
else if ($fill_amount === 'onlast' && $i == ($this->nb_term - 1))
{
$det->amount_ht = $TLeftAmountToPay['HT'];
$det->amount_tva = $TLeftAmountToPay['VAT'];
$det->amount_ttc = $TLeftAmountToPay['TTC'];
}
else
{
$det->amount_ht = $TDefaultAmountToPay['HT'];
$det->amount_tva = $TDefaultAmountToPay['VAT'];
$det->amount_ttc = $TDefaultAmountToPay['TTC'];
}
$ret = $det->create($user);
if ($ret < 0)
{
$this->error = $det->error;
$this->errors[] = $this->error;
return -4;
}
}
return count($this->TPaymentScheduleDet);
}
/**
* Create a document onto disk according to template module.
*
* @param string $modele Generator to use. Caller must set it to obj->modelpdf or GETPOST('modelpdf') for example.
* @param Translate $outputlangs objet lang a utiliser pour traduction
* @param int $hidedetails Hide details of lines
* @param int $hidedesc Hide description
* @param int $hideref Hide ref
* @param null|array $moreparams Array to provide more information
* @return int <0 if KO, >0 if OK
*/
public function generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
{
global $conf,$langs;
$langs->loadLangs(array("bills", "paymentschedule@paymentschedule"));
if (! dol_strlen($modele))
{
$modele = 'surimi';
if ($this->modelpdf) {
$modele = $this->modelpdf;
} elseif (! empty($conf->global->PAYMENTSCHEDULE_ADDON_PDF)) {
$modele = $conf->global->PAYMENTSCHEDULE_ADDON_PDF;
}
}
$modelpath = "core/modules/paymentschedule/doc/";
return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
}
/**
* Return clicable link of object (with eventually picto)
*
* @param int $withpicto Add picto into link
* @param string $option Where point the link
* @param int $max Maxlength of ref
* @param int $short 1=Return just URL
* @param string $moretitle Add more text to title tooltip
* @param int $notooltip 1=Disable tooltip
* @param int $addlinktonotes 1=Add link to notes
* @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
* @return string String with URL
*/
function getNomUrl($withpicto=0, $option='', $max=0, $short=0, $moretitle='', $notooltip=0, $addlinktonotes=0, $save_lastsearch_value=-1)
{
global $langs, $conf, $user, $form;
if (! empty($conf->dol_no_mouse_hover)) $notooltip=1; // Force disable tooltips
$result='';
$url = dol_buildpath("/paymentschedule/card.php?facid=".$this->fk_facture, 2);//DOL_URL_ROOT.'/compta/facture/card.php?facid='.$this->id;
if (!$user->rights->facture->lire)
$option = 'nolink';
if ($option !== 'nolink')
{
// Add param to save lastsearch_values or not
$add_save_lastsearch_values=($save_lastsearch_value == 1 ? 1 : 0);
if ($save_lastsearch_value == -1 && preg_match('/list\.php/',$_SERVER["PHP_SELF"])) $add_save_lastsearch_values=1;
if ($add_save_lastsearch_values) $url.='&save_lastsearch_values=1';
}
if ($short) return $url;
$picto='bill';
$label='';
if ($user->rights->paymentschedule->read) {
$label = '<u>' . $langs->trans("ShowInvoice") . '</u>';
if (! empty($this->ref))
$label .= '<br><b>'.$langs->trans('Ref') . ':</b> ' . $this->ref;
if (! empty($this->ref_client))
$label .= '<br><b>' . $langs->trans('RefCustomer') . ':</b> ' . $this->ref_client;
if (! empty($this->total_ht))
$label.= '<br><b>' . $langs->trans('AmountHT') . ':</b> ' . price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
if (! empty($this->total_tva))
$label.= '<br><b>' . $langs->trans('VAT') . ':</b> ' . price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
if (! empty($this->total_localtax1) && $this->total_localtax1 != 0) // We keep test != 0 because $this->total_localtax1 can be '0.00000000'
$label.= '<br><b>' . $langs->trans('LT1') . ':</b> ' . price($this->total_localtax1, 0, $langs, 0, -1, -1, $conf->currency);
if (! empty($this->total_localtax2) && $this->total_localtax2 != 0)
$label.= '<br><b>' . $langs->trans('LT2') . ':</b> ' . price($this->total_localtax2, 0, $langs, 0, -1, -1, $conf->currency);
if (! empty($this->total_ttc))
$label.= '<br><b>' . $langs->trans('AmountTTC') . ':</b> ' . price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
if ($moretitle) $label.=' - '.$moretitle;
}
$linkclose='';
if (empty($notooltip) && $user->rights->facture->lire)
{
if (! empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER))
{
$label=$langs->trans("ShowInvoice");
$linkclose.=' alt="'.dol_escape_htmltag($label, 1).'"';
}
$linkclose.= ' title="'.dol_escape_htmltag($label, 1).'"';
$linkclose.=' class="classfortooltip"';
}
$linkstart='<a href="'.$url.'"';
$linkstart.=$linkclose.'>';
$linkend='</a>';
if ($option == 'nolink') {
$linkstart = '';
$linkend = '';
}
$result .= $linkstart;
if ($withpicto) $result.=img_object(($notooltip?'':$label), $picto, ($notooltip?(($withpicto != 2) ? 'class="paddingright"' : ''):'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip?0:1);
if ($withpicto != 2) $result.= ($max?dol_trunc($this->ref,$max):$this->ref);
$result .= $linkend;
if ($addlinktonotes)
{
$txttoshow=($user->socid > 0 ? $this->note_public : $this->note_private);
if ($txttoshow)
{
$notetoshow=$langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow,1);
$result.=' <span class="note inline-block">';
$result.='<a href="'.DOL_URL_ROOT.'/compta/facture/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
$result.=img_picto('','note');
$result.='</a>';
//$result.=img_picto($langs->trans("ViewNote"),'object_generic');
//$result.='</a>';
$result.='</span>';
}
}
return $result;
}
/**
* @param User $user User object
* @param int $fk_payment_schedule_det id
* @param bool $create_payment create payment object
* @param bool $notrigger false=launch triggers after, true=disable triggers
* @return int
*/
public function setLineAccepted($user, $fk_payment_schedule_det, $create_payment = false, $notrigger = false)
{
$res = 0;
foreach ($this->TPaymentScheduleDet as $paymentScheduleDet)
{
if ($paymentScheduleDet->id == $fk_payment_schedule_det)
{
$res = $paymentScheduleDet->setAccepted($user, $notrigger);
if ($res && $create_payment)
{
include_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';
include_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
$thirdparty = new Societe($this->db);
if ($this->socid > 0) $thirdparty->fetch($this->socid);
$paiement = new Paiement($this->db);
$paiement->datepaye = dol_now();
$paiement->amounts = array($this->fk_facture => $paymentScheduleDet->amount_ttc); // Array with all payments dispatching with invoice id
$paiement->multicurrency_amounts = array(); // Array with all payments dispatching
$paiement->paiementid = $paymentScheduleDet->fk_mode_reglement;
$paiement->num_payment = '';
$paiement->num_paiement = $paiement->num_payment; // backward compatibility Dolibarr < 13.0
$paiement->note = '';
$fk_paiement = $paiement->create($user, 1, $thirdparty); // This include closing invoices and regenerating documents
if ($fk_paiement > 0)
{
$sql = 'SELECT rowid AS fk_paiement_facture FROM ' . MAIN_DB_PREFIX . 'paiement_facture WHERE fk_paiement = '.$fk_paiement;
$resql = $this->db->query($sql);
if ($resql)
{
$obj = $this->db->fetch_object($resql);
if ($obj)
{
$paymentScheduleDet->add_object_linked('paymentdet', $obj->fk_paiement_facture);
}
}
$label='(CustomerInvoicePayment)';
if ($this->facture->type == Facture::TYPE_CREDIT_NOTE) $label='(CustomerInvoicePaymentBack)'; // Refund of a credit note
$paiement->addPaymentToBank($user, 'payment', $label, $this->facture->fk_account, '', '', $notrigger);
}
}
break;
}
}
return $res;
}
/**
* @param User $user User object
* @param int $fk_payment_schedule_det id
* @param bool $create_reject create reject object
* @param bool $notrigger false=launch triggers after, true=disable triggers
* @return int
*/
public function setLineRefused($user, $fk_payment_schedule_det, $create_reject = false, $notrigger = false)
{
$res = 0;
foreach ($this->TPaymentScheduleDet as $paymentScheduleDet)
{
if ($paymentScheduleDet->id == $fk_payment_schedule_det)
{
$res = $paymentScheduleDet->setRefused($user, $notrigger);
if ($res && $create_reject)
{
$paymentScheduleDet->fetchObjectLinked();
if (!empty($paymentScheduleDet->linkedObjectsIds['widthdraw_line']))
{
$daterej = dol_now();
reset($paymentScheduleDet->linkedObjectsIds['widthdraw_line']);
$k = key($paymentScheduleDet->linkedObjectsIds['widthdraw_line']);
$fk_widthdraw_line = $paymentScheduleDet->linkedObjectsIds['widthdraw_line'][$k];
include_once DOL_DOCUMENT_ROOT.'/compta/prelevement/class/ligneprelevement.class.php';
include_once DOL_DOCUMENT_ROOT.'/compta/prelevement/class/rejetprelevement.class.php';
include_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
/** COMPATIBILITY DOL_VERSION 12 et plus */
if(versioncompare(versiondolibarrarray(), explode('.', '12.0.0')) >= 0 ){
$lipre = new LignePrelevement($this->db);
}else{
$lipre = new LignePrelevement($this->db, $user);
}
$lipre->fetch($fk_widthdraw_line);
if ($lipre->id > 0)
{
/** COMPATIBILITY DOL_VERSION 11 et plus */
if(versioncompare(versiondolibarrarray(), explode('.', '11.0.0')) >= 0 ){
$rej = new RejetPrelevement($this->db, $user, 'prelevement'); // Type ('direct-debit' for direct debit or 'bank-transfer' for credit transfer)
}else{
$rej = new RejetPrelevement($this->db, $user);
}
/** @see RejetPrelevement::motifs 1 = Provision insuffisante; 2 = Prélèvement contesté; ...*/
$rej->create($user, $lipre->id, 2, $daterej, $lipre->bon_rowid, 0);
$sql = 'SELECT MAX(rowid) as fk_paiement_facture FROM '.MAIN_DB_PREFIX.'paiement_facture';
$resql = $this->db->query($sql);
if ($resql)
{
$obj = $this->db->fetch_object($resql);
if ($obj)
{
$paymentScheduleDet->add_object_linked('paymentdet', $obj->fk_paiement_facture);
}
}
}
}
}
break;
}
}
return $res;
}
}
class PaymentScheduleDet extends SeedObject
{
/**
* Waiting status
*/
const STATUS_WAITING = 0;
/**
* Demande de prelevement faite
*/
const STATUS_IN_PROCESS = 1;
/**
* La demande fait partie d'un bon de prelevement
*/
const STATUS_REQUESTED = 2;
/**
* Accepted status
*/
const STATUS_ACCEPTED = 3;
/**
* Refused status
*/
const STATUS_REFUSED = -1;
public static $TStatusTransKey = array(
self::STATUS_WAITING => 'PaymentScheduleDetStatusWaiting'
, self::STATUS_IN_PROCESS => 'PaymentScheduleDetStatusInProcess'
, self::STATUS_REQUESTED => 'PaymentScheduleDetStatusRequested'
, self::STATUS_ACCEPTED => 'PaymentScheduleDetStatusAccepted'
, self::STATUS_REFUSED => 'PaymentScheduleDetStatusRefused'
);
/** @var int $isextrafieldmanaged Enable the fictionalises of extrafields */
public $isextrafieldmanaged = 1;
public $table_element = 'paymentscheduledet';
public $element = 'paymentscheduledet';
public $fields = array(
'fk_payment_schedule' => array('type'=>'integer', 'index'=>1)
,'status' => array('type'=>'integer', 'notnull' => 1, 'default' => 0)
,'label' => array('type'=>'varchar(255)', 'length'=>255)
,'date_demande' => array('type'=>'date')
,'fk_mode_reglement' => array('type'=>'integer')
,'tva_tx' => array('type'=>'double')
// ,'amount_ht' => array('type'=>'double')
// ,'amount_tva' => array('type'=>'double')
,'amount_ttc' => array('type'=>'double')
);
public $fk_payment_schedule;
public $status;
public $label;
public $date_demande;
public $fk_mode_reglement;
// public $amount_ht;
// public $amount_tva;
public $amount_ttc;
/**
* PaymentScheduleDet constructor.
* @param DoliDB $db Database connector
*/
public function __construct($db)
{
$this->db = $db;
$this->init();
}
public function fetchBySourceElement($fk_source, $sourcetype)
{
$sql = 'SELECT fk_target FROM '.MAIN_DB_PREFIX.'element_element';
$sql.= ' WHERE fk_source = '.$fk_source;
$sql.= ' AND sourcetype = \''.$sourcetype.'\'';
$sql.= ' AND targettype = \''.$this->element.'\'';
$resql = $this->db->query($sql);
if ($resql)
{
if (($obj = $this->db->fetch_object($resql)))
{
return $this->fetch($obj->fk_target);
}
}
else
{
$this->error = $this->db->lastqueryerror();
$this->errors[] = $this->error;
return -1;
}
return 0;
}
public function fetchByPrelevementFactureDemandeId($fk_prelevement_facture_demande)
{
return $this->fetchBySourceElement($fk_prelevement_facture_demande, 'prelevement_facture_demande');
}
/**
* @param User $user User object
* @param bool $notrigger false=launch triggers after, true=disable triggers
* @return int
*/
public function delete(User &$user, $notrigger = false)
{
$this->deleteObjectLinked();
// unset($this->fk_element);
return parent::delete($user, $notrigger);
}
/**
* @param User $user User object
* @param bool $notrigger false=launch triggers after, true=disable triggers
* @return int
*/
public function setWaiting($user, $notrigger = false)
{
$this->status = self::STATUS_WAITING;
$this->withChild = false;
return $this->update($user, $notrigger);
}
/**
* @param User $user User object
* @param bool $notrigger false=launch triggers after, true=disable triggers
* @return int
*/
public function setInProcess($user, $fk_prelevement_facture_demande=null, $notrigger = false)
{
$this->status = self::STATUS_IN_PROCESS;
$this->withChild = false;
if ($fk_prelevement_facture_demande > 0)
{
$this->add_object_linked('prelevement_facture_demande', $fk_prelevement_facture_demande);
}
return $this->update($user, $notrigger);
}
/**
* @param User $user User object
* @param bool $notrigger false=launch triggers after, true=disable triggers
* @return int
*/
public function setRequested($user, $fk_prelevement_bons = null, $notrigger = false)
{
$this->status = self::STATUS_REQUESTED;
$this->withChild = false;
if ($fk_prelevement_bons > 0)
{
$this->add_object_linked('widthdraw', $fk_prelevement_bons);
}
return $this->update($user, $notrigger);
}
/**
* @param User $user User object
* @param bool $notrigger false=launch triggers after, true=disable triggers
* @return int
*/
public function setAccepted($user, $notrigger = false)
{
$this->status = self::STATUS_ACCEPTED;
$this->withChild = false;
return $this->update($user, $notrigger);
}
/**
* @param User $user User object
* @param bool $notrigger false=launch triggers after, true=disable triggers
* @return int
*/
public function setRefused($user, $notrigger = false)
{
$this->status = self::STATUS_REFUSED;
$this->withChild = false;
return $this->update($user, $notrigger);
}
/**
* @param int $mode 0=Long label, 1=Short label, 2=Picto + Short label, 3=Picto, 4=Picto + Long label, 5=Short label + Picto, 6=Long label + Picto
* @return string
*/
public function getLibStatut($mode = 0)
{
return self::LibStatut($this->status, $mode);
}
/**
* @param int $status Status
* @param int $mode 0=Long label, 1=Short label, 2=Picto + Short label, 3=Picto, 4=Picto + Long label, 5=Short label + Picto, 6=Long label + Picto
* @return string
*/
public static function LibStatut($status, $mode)
{
global $langs;
$langs->load('paymentschedule@paymentschedule');
$res = '';
$statusLabel=$langs->trans(self::$TStatusTransKey[$status]);
$statusLabelShort=$langs->trans(self::$TStatusTransKey[$status]);
if ($status==self::STATUS_WAITING) $statusType='status6';
elseif ($status==self::STATUS_IN_PROCESS) $statusType='status3';
elseif ($status==self::STATUS_REQUESTED) $statusType='status1';
elseif ($status==self::STATUS_ACCEPTED) $statusType='status4';
elseif ($status==self::STATUS_REFUSED) $statusType='status8';
if (function_exists('dolGetStatus'))
{
$res = dolGetStatus($statusLabel, $statusLabelShort, '', $statusType, $mode);
}
else
{
$statusType = str_replace('status', 'statut', $statusType);
if ($mode == 0) $res = $statusLabel;
elseif ($mode == 1) $res = $statusLabelShort;
elseif ($mode == 2) $res = img_picto($statusLabel, $statusType).$statusLabelShort;
elseif ($mode == 3) $res = img_picto($statusLabel, $statusType);
elseif ($mode == 4) $res = img_picto($statusLabel, $statusType).$statusLabel;
elseif ($mode == 5) $res = $statusLabelShort.img_picto($statusLabel, $statusType);
elseif ($mode == 6) $res = $statusLabel.img_picto($statusLabel, $statusType);
}
return $res;
}
/**
* @param BonPrelevement $object
* @return PaymentScheduleDet[] array
*/
public static function getAllFromBonPrelevement($object)
{
$TRes = array();
$sql = 'SELECT fk_target FROM '.MAIN_DB_PREFIX.'element_element
WHERE fk_source = '.$object->id.'
AND sourcetype = \''.$object->element.'\'
AND targettype = \'paymentscheduledet\'';
$resql = $object->db->query($sql);
if ($resql)
{
while ($obj = $object->db->fetch_object($resql))
{
$det = new PaymentScheduleDet($object->db);
$det->fetch($obj->fk_target);
$TRes[] = $det;
}
}
return $TRes;
}
function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
{
global $conf;
foreach (array('paymentdet', 'prelevement_facture_demande', 'widthdraw', 'widthdraw_line') as $element)
{
if (empty($conf->{$element}))
{
$conf->{$element} = new stdClass();
$conf->{$element}->enabled = 1;
}
}
return parent::fetchObjectLinked($sourceid, $sourcetype, $targetid, $targettype, $clause, $alsosametype, $orderby, $loadalsoobjects); // TODO: Change the autogenerated stub
}
/**
* @param $fk_paymentdet
* @return Paiement|false
*/
public static function getPaymentObjectFromDetId($fk_paymentdet)
{
global $db, $TPaymentCache;
if (empty($TPaymentCache)) $TPaymentCache = array();
if (!isset($TPaymentCache[$fk_paymentdet]))
{
$sql = 'SELECT fk_paiement FROM '.MAIN_DB_PREFIX.'paiement_facture WHERE rowid = '.$fk_paymentdet;
$resql = $db->query($sql);
if ($resql)
{
if (($obj = $db->fetch_object($resql)))
{
require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';
$paiement = new Paiement($db);
$paiement->fetch($obj->fk_paiement);
$TPaymentCache[$fk_paymentdet] = $paiement;
}
}
else
{
setEventMessage($db->lastqueryerror(), 'errors');
return false;
}
}
return $TPaymentCache[$fk_paymentdet];
}
}
class PaymentScheduleUpdateStatus extends SeedObject
{
public $output = '';
public function run()
{
global $user, $conf, $langs;
//Hack to be sure to get translation working during CRON run
$langs = new Translate('', $conf);
$langs->setDefaultLang('fr_FR');
$langs->loadLangs(array('main', 'admin', 'cron', 'dict'));
$langs->load('paymentschedule@paymentschedule');
$this->output = $langs->trans('PaymentScheduleUpdateStatus_start', date('Y-m-d H:i:s'));
if (empty($user->rights->paymentschedule->write))
{
$this->output.= '<br />'.$langs->trans('PaymentScheduleUpdateStatus_ErrorNotEnoughPermission');
}
elseif (empty($conf->global->PAYMENTSCHEDULE_MODE_REGLEMENT_TO_USE))
{
$this->output.= '<br />'.$langs->trans('PaymentScheduleUpdateStatus_ErrorMainConfigurationMissing');
}
else
{
$today = date('Y-m-d 12:00:00');
$today_timestamp = strtotime($today);
$sql = 'SELECT DISTINCT pb.rowid AS fk_prelevement_bons';
$sql.= ' FROM '.MAIN_DB_PREFIX.'prelevement_bons pb';
$sql.= ' INNER JOIN '.MAIN_DB_PREFIX.'element_element ee ON (ee.fk_source = pb.rowid AND ee.sourcetype = \'widthdraw\')';
$sql.= ' INNER JOIN '.MAIN_DB_PREFIX.'paymentscheduledet pd ON (ee.fk_target = pd.rowid AND ee.targettype = \'paymentscheduledet\')';
$sql.= ' WHERE statut = 1'; // 1 = En attente du passage en crédité
$sql.= ' AND pd.date_demande <= \''.$this->db->idate($today).'\'';
$resql = $this->db->query($sql);
if ($resql)
{
$num = $this->db->num_rows($resql);
$this->output.= '<br />'.$langs->trans('PaymentScheduleUpdateStatus_QueryFoundNum', $num);
while ($obj = $this->db->fetch_object($resql))
{
$bonprelevement = new BonPrelevement($this->db);
if ($bonprelevement->fetch($obj->fk_prelevement_bons) > 0)
{
$bonprelevement->set_infocredit($user, $today_timestamp);
}
}
}
else
{
$this->output.= '<br />'.$langs->trans('PaymentScheduleUpdateStatus_ErrorQuery', $this->db->lastqueryerror());
}
}
$this->output.= '<br />'.$langs->trans('PaymentScheduleUpdateStatus_end', date('Y-m-d H:i:s'));
return 0;
}
}
require_once DOL_DOCUMENT_ROOT.'/compta/prelevement/class/bonprelevement.class.php';
class PaymentScheduleBonPrelevement extends BonPrelevement
{
/**
* Get invoice list
*
* @param int $amounts If you want to get the amount of the order for each invoice
* @return array Id of invoices
*/
public function getListInvoices($amounts=0)
{
global $conf;
$arr = array();
/*
* Returns all invoices presented
* within a withdrawal receipt
*/
$sql = "SELECT fk_facture";
if ($amounts)
{
$sql .= ", SUM(pl.amount)";
$sql .= ", pf.fk_prelevement_lignes";
}
$sql.= " FROM ".MAIN_DB_PREFIX."prelevement_bons as p";
$sql.= " , ".MAIN_DB_PREFIX."prelevement_lignes as pl";
$sql.= " , ".MAIN_DB_PREFIX."prelevement_facture as pf";
$sql.= " WHERE pf.fk_prelevement_lignes = pl.rowid";
$sql.= " AND pl.fk_prelevement_bons = p.rowid";
$sql.= " AND p.rowid = ".$this->id;
$sql.= " AND p.entity = ".$conf->entity;
if ($amounts) $sql.= " GROUP BY fk_facture";
$resql=$this->db->query($sql);
if ($resql)
{
$num = $this->db->num_rows($resql);
if ($num)
{
$i = 0;
while ($i < $num)
{
$row = $this->db->fetch_row($resql);
if (!$amounts) $arr[$i] = $row[0];
else
{
$arr[$i] = array(
$row[0],
$row[1],
$row[2]
);
}
$i++;
}
}
$this->db->free($resql);
}
else
{
dol_syslog(get_class($this)."::getListInvoices Erreur");
}
return $arr;
}
}