diff --git a/htdocs/contrat/class/contrat.class.php b/htdocs/contrat/class/contrat.class.php index 63bedcfdcbc..088d8f9b6d5 100644 --- a/htdocs/contrat/class/contrat.class.php +++ b/htdocs/contrat/class/contrat.class.php @@ -2600,6 +2600,177 @@ class Contrat extends CommonObject return -1; } } + + + /** + * Action executed by scheduler + * CAN BE A CRON TASK + * Loop on each contract lines and update the end of date. Do not execute the update if there is one pending invoice linked to contract. + * + * @param int $thirdparty_id Thirdparty id + * @param int $delayindaysshort To renew the resources x day before (positive value) or after (negative value) the end of date (default is 0) + * @return int 0 if OK, <>0 if KO (this function is used also by cron so only 0 is OK) + */ + public function doAutoRenewContracts($thirdparty_id = 0, $delayindaysshort = 0) + { + global $langs, $user; + + $langs->load("agenda"); + + $now = dol_now(); + + $enddatetoscan = dol_time_plus_duree($now, -1 * abs($delayindaysshort), 'd'); + + $error = 0; + $this->output = ''; + $this->error=''; + + $contractlineprocessed = array(); + $contractignored = array(); + $contracterror = array(); + + dol_syslog(__METHOD__, LOG_DEBUG); + + $sql = 'SELECT c.rowid, c.ref_customer, cd.rowid as lid, cd.date_fin_validite, p.duration'; + $sql.= ' FROM '.MAIN_DB_PREFIX.'contrat as c, '.MAIN_DB_PREFIX.'contratdet as cd'; + $sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON p.rowid = cd.fk_product'; + $sql.= ' WHERE cd.fk_contrat = c.rowid'; + $sql.= " AND date_format(cd.date_fin_validite, '%Y-%m-%d') <= date_format('".$this->db->idate($enddatetoscan)."', '%Y-%m-%d')"; + $sql.= " AND cd.statut = 4"; + if ($thirdparty_id > 0) $sql.=" AND c.fk_soc = ".((int) $thirdparty_id); + //print $sql; + + $resql = $this->db->query($sql); + if ($resql) { + $num = $this->db->num_rows($resql); + + include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php'; + + $i=0; + while ($i < $num) { + $obj = $this->db->fetch_object($resql); + if ($obj) { + if (! empty($contractlineprocessed[$obj->lid]) || ! empty($contractignored[$obj->rowid]) || ! empty($contracterror[$obj->rowid])) { + continue; + } + + // Load contract + $object = new Contrat($this->db); + $object->fetch($obj->rowid); // fetch also lines + $object->fetch_thirdparty(); + + if ($object->id <= 0) { + $error++; + $this->errors[] = 'Failed to load contract with id='.$obj->rowid; + continue; + } + + dol_syslog("* Process contract line in doRenewalContracts for contract id=".$object->id." ref=".$object->ref." ref_customer=".$object->ref_customer." contract line id=".$obj->lid); + + // Update expiration date of line + $expirationdate = $this->db->jdate($obj->date_fin_validite); + $duration_value = preg_replace('/[^0-9]/', '', $obj->duration); + $duration_unit = preg_replace('/\d/', '', $obj->duration); + //var_dump($expirationdate.' '.$enddatetoscan); + + // Test if there is pending invoice + $object->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 1); + + if (is_array($object->linkedObjects['facture']) && count($object->linkedObjects['facture']) > 0) { + usort($object->linkedObjects['facture'], "cmp"); + + //dol_sort_array($contract->linkedObjects['facture'], 'date'); + $someinvoicenotpaid=0; + foreach ($object->linkedObjects['facture'] as $idinvoice => $invoice) { + if ($invoice->statut == Facture::STATUS_DRAFT) continue; // Draft invoice are not invoice not paid + + if (empty($invoice->paye)) { + $someinvoicenotpaid++; + } + } + if ($someinvoicenotpaid) { + $this->output .= 'Contract '.$object->ref.' is qualified for renewal but there is '.$someinvoicenotpaid.' invoice(s) unpayed so we cancel renewal'."\n"; + $contractignored[$object->id]=$object->ref; + continue; + } + } + + if ($expirationdate && $expirationdate < $enddatetoscan) { + dol_syslog("Define the newdate of end of services from expirationdate=".$expirationdate); + $newdate = $expirationdate; + $protecti=0; //$protecti is to avoid infinite loop + while ($newdate < $enddatetoscan && $protecti < 1000) { + $newdate = dol_time_plus_duree($newdate, $duration_value, $duration_unit); + $protecti++; + } + + if ($protecti < 1000) { // If not, there is a pb + // We will update the end of date of contrat, so first we refresh contract data + dol_syslog("We will update the end of date of contract with newdate = ".dol_print_date($newdate, 'dayhourrfc')); + + $this->db->begin(); + + $errorforlocaltransaction = 0; + + $label = 'Renewal of contrat '.$object->ref.' line '.$obj->lid; + $comment = 'Renew date of contract '.$object->ref.' line '.$obj->lid.' by doAutoRenewContracts'; + + $sqlupdate = 'UPDATE '.MAIN_DB_PREFIX."contratdet SET date_fin_validite = '".$this->db->idate($newdate)."'"; + $sqlupdate.= ' WHERE rowid = '.((int) $obj->lid); + $resqlupdate = $this->db->query($sqlupdate); + if ($resqlupdate) { + $contractlineprocessed[$obj->lid]=$object->ref; + + $actioncode = 'RENEW_CONTRACT'; + $now = dol_now(); + + // Create an event + $actioncomm = new ActionComm($this->db); + $actioncomm->type_code = 'AC_OTH_AUTO'; // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...) + $actioncomm->code = 'AC_'.$actioncode; + $actioncomm->label = $label; + $actioncomm->datep = $now; + $actioncomm->datef = $now; + $actioncomm->percentage = -1; // Not applicable + $actioncomm->socid = $object->thirdparty->id; + $actioncomm->authorid = $user->id; // User saving action + $actioncomm->userownerid = $user->id; // Owner of action + $actioncomm->fk_element = $object->id; + $actioncomm->elementtype = 'contract'; + $actioncomm->note_private = $comment; + + $ret = $actioncomm->create($user); // User creating action + } else { + $contracterror[$object->id]=$object->ref; + + $error++; + $errorforlocaltransaction++; + $this->error = $this->db->lasterror(); + } + + if (! $errorforlocaltransaction) { + $this->db->commit(); + } else { + $this->db->rollback(); + } + } else { + $error++; + $this->error = "Bad value for newdate in doAutoRenewContracts - expirationdate=".$expirationdate." enddatetoscan=".$enddatetoscan." duration_value=".$duration_value." duration_unit=".$duration_value; + dol_syslog($this->error, LOG_ERR); + } + } + } + $i++; + } + } else { + $error++; + $this->error = $this->db->lasterror(); + } + + $this->output .= count($contractlineprocessed).' contract line(s) with end date before '.dol_print_date($enddatetoscan, 'day').' were renewed'.(count($contractlineprocessed)>0 ? ' : '.join(',', $contractlineprocessed) : ''); + + return ($error ? 1: 0); + } }