Fix: protection to not delete invoice if invoice is in bookkeeping

This commit is contained in:
Laurent Destailleur 2017-12-05 10:01:30 +01:00
parent 78c4b7f912
commit 8019f73f99
5 changed files with 139 additions and 89 deletions

View File

@ -3888,7 +3888,6 @@ else if ($id > 0 || ! empty($ref))
print '</td>';
}
print '<td align="right">' . price($sign * $objp->amount) . '</td>';
// TODO Add link to delete payment
print '<td align="center">';
if ($object->statut == Facture::STATUS_VALIDATED && $object->paye == 0 && $user->societe_id == 0)
{
@ -4380,13 +4379,30 @@ else if ($id > 0 || ! empty($ref))
// Delete
if ($user->rights->facture->supprimer)
{
if ($object->is_erasable() <= 0) {
print '<div class="inline-block divButAction"><a class="butActionRefused" href="#" title="' . $langs->trans("DisabledBecauseNotErasable") . '">' . $langs->trans('Delete') . '</a></div>';
} else if ($objectidnext) {
print '<div class="inline-block divButAction"><a class="butActionRefused" href="#" title="' . $langs->trans("DisabledBecauseReplacedInvoice") . '">' . $langs->trans('Delete') . '</a></div>';
} elseif ($object->getSommePaiement()) {
$isErasable = $object->is_erasable();
//var_dump($isErasable);
if ($isErasable == -4) {
print '<div class="inline-block divButAction"><a class="butActionRefused" href="#" title="' . $langs->trans("DisabledBecausePayments") . '">' . $langs->trans('Delete') . '</a></div>';
} else {
}
elseif ($isErasable == -3) {
print '<div class="inline-block divButAction"><a class="butActionRefused" href="#" title="' . $langs->trans("DisabledBecauseNotLastSituationInvoice") . '">' . $langs->trans('Delete') . '</a></div>';
}
elseif ($isErasable == -2) {
print '<div class="inline-block divButAction"><a class="butActionRefused" href="#" title="' . $langs->trans("DisabledBecauseNotLastInvoice") . '">' . $langs->trans('Delete') . '</a></div>';
}
elseif ($isErasable == -1) {
print '<div class="inline-block divButAction"><a class="butActionRefused" href="#" title="' . $langs->trans("DisabledBecauseDispatchedInBookkeeping") . '">' . $langs->trans('Delete') . '</a></div>';
}
elseif ($isErasable <= 0) // Any other cases
{
print '<div class="inline-block divButAction"><a class="butActionRefused" href="#" title="' . $langs->trans("DisabledBecauseNotErasable") . '">' . $langs->trans('Delete') . '</a></div>';
}
elseif ($objectidnext)
{
print '<div class="inline-block divButAction"><a class="butActionRefused" href="#" title="' . $langs->trans("DisabledBecauseReplacedInvoice") . '">' . $langs->trans('Delete') . '</a></div>';
}
else
{
print '<div class="inline-block divButAction"><a class="butActionDelete" href="' . $_SERVER["PHP_SELF"] . '?facid=' . $object->id . '&amp;action=delete">' . $langs->trans('Delete') . '</a></div>';
}
} else {

View File

@ -3367,85 +3367,6 @@ class Facture extends CommonInvoice
}
}
/**
* Renvoi si les lignes de facture sont ventilees et/ou exportees en compta
*
* @return int <0 if KO, 0=no, 1=yes
*/
function getVentilExportCompta()
{
// On verifie si les lignes de factures ont ete exportees en compta et/ou ventilees
$ventilExportCompta = 0 ;
$num=count($this->lines);
for ($i = 0; $i < $num; $i++)
{
if (! empty($this->lines[$i]->export_compta) && ! empty($this->lines[$i]->code_ventilation))
{
$ventilExportCompta++;
}
}
if ($ventilExportCompta <> 0)
{
return 1;
}
else
{
return 0;
}
}
/**
* Return if an invoice can be deleted
* Rule is:
* If invoice is draft and ha a temporary ref -> yes
* If hidden option INVOICE_CAN_ALWAYS_BE_REMOVED is on, we can. If hidden option INVOICE_CAN_NEVER_BE_REMOVED is on, we can't.
* If invoice has a definitive ref, is last, without payment and not dipatched into accountancy -> yes end of rule
*
* @return int <0 if KO, 0=no, >0=yes
*/
function is_erasable()
{
global $conf;
// we check if invoice is a temporary number (PROVxxxx)
$tmppart = substr($this->ref, 1, 4);
if ($this->statut == self::STATUS_DRAFT && $tmppart === 'PROV') // If draft invoice and ref not yet defined
{
return 1;
}
if (! empty($conf->global->INVOICE_CAN_ALWAYS_BE_REMOVED)) return 2;
if (! empty($conf->global->INVOICE_CAN_NEVER_BE_REMOVED)) return 0;
// TODO Test if there is at least one payment. If yes, refuse to delete.
// ...
// If not a draft invoice and not temporary invoice
if ($tmppart !== 'PROV')
{
// We need to have this->thirdparty defined, in case of numbering rule use tags that depend on thirdparty (like {t} tag).
if (empty($this->thirdparty)) $this->fetch_thirdparty();
$maxfacnumber = $this->getNextNumRef($this->thirdparty,'last');
$ventilExportCompta = $this->getVentilExportCompta();
// If there is no invoice into the reset range and not already dispatched, we can delete
if ($maxfacnumber == '' && $ventilExportCompta == 0) return 3;
// If invoice to delete is last one and not already dispatched, we can delete
if ($maxfacnumber == $this->ref && $ventilExportCompta == 0) return 4;
if ($this->situation_cycle_ref) {
$last = $this->is_last_in_cycle();
return $last;
}
}
return 0;
}
/**
* Return list of invoices (eventually filtered on a user) into an array

View File

@ -274,6 +274,100 @@ abstract class CommonInvoice extends CommonObject
}
}
/**
* Return if an invoice can be deleted
* Rule is:
* If invoice is draft and has a temporary ref -> yes
* If hidden option INVOICE_CAN_NEVER_BE_REMOVED is on -> no (0)
* If invoice is dispatched in bookkeeping -> no (-1)
* If invoice has a definitive ref, is not last and INVOICE_CAN_ALWAYS_BE_REMOVED off -> no (-2)
* If invoice not last in a cycle -> no (-3)
* If there is payment -> no (-4)
*
* @return int <=0 if no, >0 if yes
*/
function is_erasable()
{
global $conf;
// We check if invoice is a temporary number (PROVxxxx)
$tmppart = substr($this->ref, 1, 4);
if ($this->statut == self::STATUS_DRAFT && $tmppart === 'PROV') // If draft invoice and ref not yet defined
{
return 1;
}
if (! empty($conf->global->INVOICE_CAN_NEVER_BE_REMOVED)) return 0;
// If not a draft invoice and not temporary invoice
if ($tmppart !== 'PROV')
{
$ventilExportCompta = $this->getVentilExportCompta();
if ($ventilExportCompta != 0) return -1;
// Get last number of validated invoice
if ($this->element != 'invoice_supplier')
{
if (empty($this->thirdparty)) $this->fetch_thirdparty(); // We need to have this->thirdparty defined, in case of numbering rule use tags that depend on thirdparty (like {t} tag).
$maxfacnumber = $this->getNextNumRef($this->thirdparty,'last');
// If there is no invoice into the reset range and not already dispatched, we can delete
// If invoice to delete is last one and not already dispatched, we can delete
if (empty($conf->global->INVOICE_CAN_ALWAYS_BE_REMOVED) && $maxfacnumber != '' && $maxfacnumber != $this->ref) return -2;
// TODO If there is payment in bookkeeping, check payment is not dispatched in accounting
// ...
if ($this->situation_cycle_ref) {
$last = $this->is_last_in_cycle();
if (! $last) return -3;
}
}
}
// Test if there is at least one payment. If yes, refuse to delete.
if (empty($conf->global->INVOICE_CAN_ALWAYS_BE_REMOVED) && $this->getSommePaiement() > 0) return -4;
return 1;
}
/**
* Return if an invoice was dispatched in bookkeeping
*
* @return int <0 if KO, 0=no, 1=yes
*/
function getVentilExportCompta()
{
$alreadydispatched = 0;
$type = 'customer_invoice';
if ($this->element == 'invoice_supplier') $type = 'supplier_invoice';
$sql = " SELECT COUNT(ab.rowid) as nb FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as ab WHERE ab.doc_type='".$type."' AND ab.fk_doc = ".$this->id;
$resql = $this->db->query($sql);
if ($resql)
{
$obj = $this->db->fetch_object($resql);
if ($obj)
{
$alreadydispatched = $obj->nb;
}
}
else
{
$this->error = $this->db->lasterror();
return -1;
}
if ($alreadydispatched)
{
return 1;
}
return 0;
}
/**
* Return label of type of invoice
*

View File

@ -2836,9 +2836,26 @@ else
// Delete
if ($action != 'confirm_edit' && $user->rights->fournisseur->facture->supprimer)
{
if ($object->getSommePaiement()) {
print '<div class="inline-block divButAction"><a class="butActionRefused" href="#" title="' . $langs->trans("DisabledBecausePayments") . '">' . $langs->trans('Delete') . '</a></div>';
} else {
$isErasable=$object->is_erasable();
//var_dump($isErasable);
if ($isErasable == -4) {
print '<div class="inline-block divButAction"><a class="butActionRefused" href="#" title="' . $langs->trans("DisabledBecausePayments") . '">' . $langs->trans('Delete') . '</a></div>';
}
elseif ($isErasable == -3) { // Should never happen with supplier invoice
print '<div class="inline-block divButAction"><a class="butActionRefused" href="#" title="' . $langs->trans("DisabledBecauseNotLastSituationInvoice") . '">' . $langs->trans('Delete') . '</a></div>';
}
elseif ($isErasable == -2) { // Should never happen with supplier invoice
print '<div class="inline-block divButAction"><a class="butActionRefused" href="#" title="' . $langs->trans("DisabledBecauseNotLastInvoice") . '">' . $langs->trans('Delete') . '</a></div>';
}
elseif ($isErasable == -1) {
print '<div class="inline-block divButAction"><a class="butActionRefused" href="#" title="' . $langs->trans("DisabledBecauseDispatchedInBookkeeping") . '">' . $langs->trans('Delete') . '</a></div>';
}
elseif ($isErasable <= 0) // Any other cases
{
print '<div class="inline-block divButAction"><a class="butActionRefused" href="#" title="' . $langs->trans("DisabledBecauseNotErasable") . '">' . $langs->trans('Delete') . '</a></div>';
}
else
{
print '<div class="inline-block divButAction"><a class="butActionDelete" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&amp;action=delete">'.$langs->trans('Delete').'</a></div>';
}
}

View File

@ -11,6 +11,8 @@ BillsSuppliersUnpaidForCompany=Unpaid supplier invoices for %s
BillsLate=Late payments
BillsStatistics=Customers invoices statistics
BillsStatisticsSuppliers=Suppliers invoices statistics
DisabledBecauseDispatchedInBookkeeping=Disabled because invoice was dispatched into bookkeeping
DisabledBecauseNotLastInvoice=Disabled because invoice is not erasable. Some invoices were recorded after this one and it will create holes in the counter.
DisabledBecauseNotErasable=Disabled because cannot be erased
InvoiceStandard=Standard invoice
InvoiceStandardAsk=Standard invoice