diff --git a/dev/tools/phan/config.php b/dev/tools/phan/config.php index 9537d29f98d..40c4c0ce814 100644 --- a/dev/tools/phan/config.php +++ b/dev/tools/phan/config.php @@ -161,6 +161,7 @@ $VALID_MODULE_MAPPING = array( 'stock' => 'Stock', 'stocktransfer' => 'StockTransfer', 'stripe' => 'Stripe', + 'subtotals' => 'Subtotals', 'supplier_invoice' => null, // Special case, uses invoice 'supplier_order' => null, // Special case, uses invoice 'supplier_proposal' => 'SupplierProposal', diff --git a/htdocs/admin/subtotals.php b/htdocs/admin/subtotals.php new file mode 100644 index 00000000000..b095df572a6 --- /dev/null +++ b/htdocs/admin/subtotals.php @@ -0,0 +1,222 @@ + + * Copyright (C) 2005-2012 Regis Houssin + * Copyright (C) 2012-2013 Juanjo Menent + * Copyright (C) 2019 Christophe Battarel + * Copyright (C) 2024 Frédéric France + * Copyright (C) 2024 MDW + * + * 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 . + */ + +/** + * \file htdocs/admin/subtotals.php + * \ingroup subtotals + * \brief Activation page for the subtotals module in the other modules + */ + +// Load Dolibarr environment +require '../main.inc.php'; +require_once DOL_DOCUMENT_ROOT . '/core/lib/admin.lib.php'; +require_once DOL_DOCUMENT_ROOT . '/core/lib/doleditor.lib.php'; +require_once DOL_DOCUMENT_ROOT . '/core/class/doleditor.class.php'; +require_once DOL_DOCUMENT_ROOT . '/core/class/html.formother.class.php'; + +/** + * @var Conf $conf + * @var DoliDB $db + * @var Form $form + * @var HookManager $hookmanager + * @var Translate $langs + * @var User $user + */ + +// Load translation files required by the page +$langs->loadLangs(array('main', 'admin', 'subtotals', 'errors')); +$action = GETPOST('action', 'aZ09'); + +if (!$user->admin) { + accessforbidden(); +} + +$formother = new FormOther($db); + +// default color const +$default = 'ffffff'; + +// Constant and translation of the module description +$modules = array( + 'PROPAL' => array('lang' => 'propal', 'key' => 'Proposal', 'old_pdf' => '(azur model)'), + 'COMMANDE' => array('lang' => 'orders', 'key' => 'CustomerOrder', 'old_pdf' => '(einstein model)'), + 'FACTURE' => array('lang' => 'bills', 'key' => 'CustomerInvoice', 'old_pdf' => '(crabe model)'), + 'FACTUREREC' => array('lang' => 'bills', 'key' => 'RecurringInvoiceTemplate'), +); +// Conditions for the option to be offered +$conditions = array( + 'PROPAL' => (isModEnabled("propal")), + 'COMMANDE' => (isModEnabled("order")), + 'FACTURE' => (isModEnabled("invoice")), + 'FACTUREREC' => (isModEnabled("invoice")), +); + +$max_depth = 0; + +foreach ($modules as $const => $desc) { + $const_depth = getDolGlobalString('SUBTOTAL_' . $const . '_MAX_DEPTH'); + $max_depth = max($const_depth, $max_depth); +} + +$colors = array(); + +for ($i = 0; $i < $max_depth; $i++) { + $colors['SUBTOTAL_BACK_COLOR_LEVEL_' . ($i + 1)] = array('level' => $i + 1, 'color' => getDolGlobalString('SUBTOTAL_BACK_COLOR_LEVEL_' . ($i + 1), $default)); +} + +/* + * Actions + */ + +if (preg_match('/^SUBTOTAL_.*$/', $action)) { + if (preg_match('/^.*_MAX_DEPTH$/', $action)) { + dolibarr_set_const($db, $action, GETPOSTINT($action), 'int', 0, '', $conf->entity); + header("Location: " . $_SERVER['PHP_SELF']); + setEventMessages($langs->trans("SetupSaved"), null); + exit; + } else { + $value = getDolGlobalInt($action, 0); + $value == 0 ? $value = 1 : $value = 0; + dolibarr_set_const($db, $action, $value, 'chaine', 0, '', $conf->entity); + header("Location: " . $_SERVER['PHP_SELF']); + setEventMessages($langs->trans("SetupSaved"), null); + exit; + } +} + +if ($action == 'update_colors') { + foreach ($colors as $const => $color) { + $color_to_update = GETPOST($const, 'aZ09'); + if ($color_to_update != $color['color']) { + dolibarr_set_const($db, $const, $color_to_update, 'chaine', 0, '', $conf->entity); + } + } + + header("Location: " . $_SERVER["PHP_SELF"]); + exit; +} + + +/* + * View + */ + +llxHeader('', '', '', '', 0, 0, '', '', '', 'mod-admin page-subtotals'); +$linkback = '' . $langs->trans("BackToModuleList") . ''; + +print load_fiche_titre($langs->trans("SubtotalSetup"), $linkback, 'title_setup'); + +if (empty($conf->use_javascript_ajax)) { + setEventMessages(null, array($langs->trans("NotAvailable"), $langs->trans("JavascriptDisabled")), 'errors'); +} else { + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print "\n"; + + // Modules + foreach ($modules as $const => $desc) { + // If this condition is not met, the option is not offered + if (!$conditions[$const]) { + continue; + } + + $langs->load($desc['lang']); + + $constante_title = 'SUBTOTAL_TITLE_' . $const; + $constante_subtotal = 'SUBTOTAL_' . $const; + print '' . "\n"; + print ''; + print ''; + + print ''; + + print ''; + + print ''; + + print ''; + } + + print '
' . $langs->trans("Settings") . '' . $langs->trans("Title") . '' . $langs->trans("Subtotal") . '' . $langs->trans("MaxSubtotalLevel") . '
'; + if (isset($desc['old_pdf'])) { + print $form->textwithpicto($langs->trans($desc['key']), $langs->trans("NotSupportedByAllPDF", $desc['old_pdf'])); + } else { + print $langs->trans($desc['key']); + } + print ''; + $value_title = getDolGlobalInt($constante_title, 0); + print ''; + print $value_title == 0 ? img_picto($langs->trans("Disabled"), 'switch_off') : img_picto($langs->trans("Enabled"), 'switch_on') . ''; + print ''; + $value_subtotal = getDolGlobalInt($constante_subtotal, 0); + print ''; + print $value_subtotal == 0 ? img_picto($langs->trans("Disabled"), 'switch_off') : img_picto($langs->trans("Enabled"), 'switch_on') . ''; + print ''; + $can_modify = !($value_subtotal == 0 && $value_title == 0); + print '
'; + print ''; + print ''; + print ''; + print $can_modify ? '' : ''; + print '
'; + print '
'; + + // Other options + + print '
'; + print ''; + print ''; + + print ''; + print ''; + print ''; + print ''; + print "\n"; + + foreach ($colors as $key => $value) { + print ''; + print ''; + print ''; + print ''; + } + + print '
' . $langs->trans("Other") . '
' . $langs->trans("SubtotalLineBackColor", $value['level']) . ''; + print $formother->selectColor(colorArrayToHex(colorStringToArray($value['color'], array()), $default), $key, '', 1, array(), '', '', $default) . ' '; + print '   ' . $langs->trans("Default") . ': ' . $default . ''; + print $form->textwithpicto('', $langs->trans("NotSupportedByAllThemes") . ', ' . $langs->trans("PressF5AfterChangingThis")); + print '
' . "\n"; +} + +print '
'; +print ''; +print ''; +print '
'; + +// End of page +llxFooter(); +$db->close(); diff --git a/htdocs/comm/propal/card.php b/htdocs/comm/propal/card.php index 8ff2fc5133c..050fe09f444 100644 --- a/htdocs/comm/propal/card.php +++ b/htdocs/comm/propal/card.php @@ -323,6 +323,41 @@ if (empty($reshook)) { header('Location: '.$_SERVER["PHP_SELF"].'?id='.$object->id); exit(); + } elseif ($action == 'confirm_delete_subtotalline' && $confirm == 'yes' && $usercancreate) { + // Delete line + $object->fetch($id); + $object->fetch_thirdparty(); + + $result = $object->deleteSubtotalLine($langs, GETPOSTINT('lineid'), (bool) GETPOST('deletecorrespondingsubtotalline')); + if ($result > 0) { + // reorder lines + $object->line_order(true); + // Define output language + $outputlangs = $langs; + $newlang = ''; + if (getDolGlobalInt('MAIN_MULTILANGS') /* && empty($newlang) */ && GETPOST('lang_id')) { + $newlang = GETPOST('lang_id'); + } + if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) { + $newlang = $object->thirdparty->default_lang; + } + if (!empty($newlang)) { + $outputlangs = new Translate("", $conf); + $outputlangs->setDefaultLang($newlang); + $outputlangs->load('products'); + } + if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) { + $ret = $object->fetch($id); // Reload to get new records + $result = $object->generateDocument($object->model_pdf, $outputlangs, $hidedetails, $hidedesc, $hideref); + } + if ($result >= 0) { + header('Location: '.$_SERVER["PHP_SELF"].'?id='.$id); + exit(); + } + } else { + setEventMessages($object->error, $object->errors, 'errors'); + $action = ''; + } } elseif ($action == 'confirm_validate' && $confirm == 'yes' && $usercanvalidate) { // Validation $idwarehouse = GETPOSTINT('idwarehouse'); @@ -934,6 +969,12 @@ if (empty($reshook)) { setEventMessages($langs->trans('ErrorsOnXLines', $error), null, 'errors'); } } + } elseif ($action == 'addline' && GETPOST('updateallvatlinesblock', 'alpha') && GETPOST('vatforblocklines', 'alpha') !== '' && $usercancreate) { + $tx_tva = GETPOST('vatforblocklines') ? GETPOST('vatforblocklines') : 0; + $object->updateSubtotalLineBlockLines($langs, $object->getRangOfLine($lineid), 'tva', $tx_tva); + } elseif ($action == 'addline' && GETPOST('updatealldiscountlinesblock', 'alpha') && GETPOST('discountforblocklines', 'alpha') !== '' && $usercancreate) { + $discount = GETPOST('discountforblocklines') ? GETPOST('discountforblocklines') : 0; + $object->updateSubtotalLineBlockLines($langs, $object->getRangOfLine($lineid), 'discount', $discount); } include DOL_DOCUMENT_ROOT.'/core/actions_printing.inc.php'; @@ -981,6 +1022,9 @@ if (empty($reshook)) { $alldate_start = dol_mktime(GETPOSTINT('alldate_starthour'), GETPOSTINT('alldate_startmin'), 0, GETPOSTINT('alldate_startmonth'), GETPOSTINT('alldate_startday'), GETPOSTINT('alldate_startyear')); $alldate_end = dol_mktime(GETPOSTINT('alldate_endhour'), GETPOSTINT('alldate_endmin'), 0, GETPOSTINT('alldate_endmonth'), GETPOSTINT('alldate_endday'), GETPOSTINT('alldate_endyear')); foreach ($object->lines as $key => $line) { + if ($line->special_code == SUBTOTALS_SPECIAL_CODE) { + continue; + } if ($line->product_type == 1) { // only service line $result = $object->updateline($line->id, $line->subprice, $line->qty, $line->remise_percent, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, $line->desc, 'HT', $line->info_bits, $line->special_code, $line->fk_parent_line, 0, $line->fk_fournprice, $line->pa_ht, $line->label, $line->product_type, $alldate_start, $alldate_end, $line->array_options, $line->fk_unit, $line->multicurrency_subprice); $object->lines[$key] = $object->line; @@ -993,6 +1037,9 @@ if (empty($reshook)) { $localtax1_rate = get_localtax($vat_rate, 1, $object->thirdparty, $mysoc); $localtax2_rate = get_localtax($vat_rate, 2, $object->thirdparty, $mysoc); foreach ($object->lines as $key => $line) { + if ($line->special_code == SUBTOTALS_SPECIAL_CODE) { + continue; + } $result = $object->updateline($line->id, $line->subprice, $line->qty, $line->remise_percent, $vat_rate, $localtax1_rate, $localtax2_rate, $line->desc, 'HT', $line->info_bits, $line->special_code, $line->fk_parent_line, 0, $line->fk_fournprice, $line->pa_ht, $line->label, $line->product_type, $line->date_start, $line->date_end, $line->array_options, $line->fk_unit, $line->multicurrency_subprice); $object->lines[$key] = $object->line; } @@ -1001,6 +1048,9 @@ if (empty($reshook)) { $remise_percent = (GETPOST('remiseforalllines') ? GETPOST('remiseforalllines') : 0); $remise_percent = str_replace('*', '', $remise_percent); foreach ($object->lines as $key => $line) { + if ($line->special_code == SUBTOTALS_SPECIAL_CODE) { + continue; + } $tvatx = $line->tva_tx; if (!empty($line->vat_src_code)) { $tvatx .= ' ('.$line->vat_src_code.')'; @@ -1012,6 +1062,9 @@ if (empty($reshook)) { // Define margin $margin_rate = (GETPOST('marginforalllines', 'alpha') ? GETPOST('marginforalllines', 'alpha') : 0); foreach ($object->lines as $key => $line) { + if ($line->special_code == SUBTOTALS_SPECIAL_CODE) { + continue; + } $subprice = price2num($line->pa_ht * (1 + $margin_rate / 100), 'MU'); $prod = new Product($db); $prod->fetch($line->fk_product); @@ -1044,6 +1097,104 @@ if (empty($reshook)) { // $line->subprice = (float) $subprice; // $line->multicurrency_subprice = $multicurrency_subprice; } + } elseif ($action == 'confirm_addtitleline' && $usercancreate) { + // Handling adding a new title line for subtotals module + + $langs->load('subtotals'); + + $desc = GETPOST('subtotallinedesc', 'alphanohtml'); + $depth = GETPOSTINT('subtotallinelevel') ?? 1; + + $subtotal_options = array(); + + foreach (Propal::$TITLE_OPTIONS as $option) { + $value = GETPOST($option, 'alphanohtml'); + if ($value) { + $subtotal_options[$option] = $value == 'on' ? 1 : $value; + } + } + + // Insert line + $result = $object->addSubtotalLine($langs, $desc, (int) $depth, $subtotal_options); + + if ($result >= 0) { + if ($result == 0) { + setEventMessages($object->error, $object->errors, 'warnings'); + } + $ret = $object->fetch($object->id); // Reload to get new records + $object->fetch_thirdparty(); + + if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) { + // Define output language + $outputlangs = $langs; + $newlang = GETPOST('lang_id', 'alpha'); + if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) { + $newlang = $object->thirdparty->default_lang; + } + if (!empty($newlang)) { + $outputlangs = new Translate("", $conf); + $outputlangs->setDefaultLang($newlang); + } + + $object->generateDocument($object->model_pdf, $outputlangs, $hidedetails, $hidedesc, $hideref); + } + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } + header('Location: '.$_SERVER["PHP_SELF"].'?id='.$id); + exit(); + } elseif ($action == 'confirm_addsubtotalline' && $usercancreate) { + // Handling adding a new subtotal line for subtotals module + + $langs->load('subtotals'); + + $choosen_line = GETPOST('subtotaltitleline', 'alphanohtml'); + foreach ($object->lines as $line) { + if ($line->desc == $choosen_line && $line->special_code == SUBTOTALS_SPECIAL_CODE) { + $desc = $line->desc; + $depth = -$line->qty; + } + } + + $subtotal_options = array(); + + foreach (Propal::$SUBTOTAL_OPTIONS as $option) { + $value = GETPOST($option, 'alphanohtml'); + if ($value) { + $subtotal_options[$option] = $value == 'on' ? 1 : $value; + } + } + + // Insert line + if (isset($desc) && isset($depth)) { + $result = $object->addSubtotalLine($langs, $desc, (int) $depth, $subtotal_options); + } else { + $object->errors[] = $langs->trans("CorrespondingTitleNotFound"); + } + + if (isset($result) && $result >= 0) { + $ret = $object->fetch($object->id); // Reload to get new records + $object->fetch_thirdparty(); + + if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) { + // Define output language + $outputlangs = $langs; + $newlang = GETPOST('lang_id', 'alpha'); + if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) { + $newlang = $object->thirdparty->default_lang; + } + if (!empty($newlang)) { + $outputlangs = new Translate("", $conf); + $outputlangs->setDefaultLang($newlang); + } + + $object->generateDocument($object->model_pdf, $outputlangs, $hidedetails, $hidedesc, $hideref); + } + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } + header('Location: '.$_SERVER["PHP_SELF"].'?id='.$id); + exit(); } elseif ( $action == 'addline' && !GETPOST('submitforalllines', 'alpha') && !GETPOST('submitforallmargins', 'alpha') && !GETPOST('markforalllines', 'alpha') && $usercancreate) { // Add line @@ -1553,6 +1704,9 @@ if (empty($reshook)) { $margin_rate = GETPOSTISSET('marginforalllines') ? GETPOST('marginforalllines', 'int') : ''; $mark_rate = GETPOSTISSET('markforalllines') ? GETPOST('markforalllines', 'int') : ''; foreach ($object->lines as &$line) if ($line->subprice > 0) { + if ($line->special_code == SUBTOTALS_SPECIAL_CODE) { + continue; + } $subprice_multicurrency = $line->subprice; if (is_numeric($margin_rate) && $margin_rate > 0) { $line->subprice = floatval(price2num(floatval($line->pa_ht) * (1 + floatval($margin_rate) / 100), 'MU')); @@ -1600,6 +1754,91 @@ if (empty($reshook)) { setEventMessages($object->error, $object->errors, 'errors'); } } + } elseif ($action == 'updatetitleline' && GETPOSTISSET("save") && $usercancreate && !GETPOST('cancel', 'alpha')) { + // Handling updating a title line for subtotals module + + $langs->load('subtotals'); + + $desc = GETPOST('line_desc', 'alphanohtml') ?? $langs->trans("Title"); + $depth = GETPOSTINT('line_depth') ?? 1; + + $subtotal_options = array(); + + foreach (Propal::$TITLE_OPTIONS as $option) { + $value = GETPOST($option, 'alphanohtml'); + if ($value) { + $subtotal_options[$option] = $value == 'on' ? 1 : $value; + } + } + + // Update line + $result = $object->updateSubtotalLine($langs, GETPOSTINT('lineid'), $desc, $depth, $subtotal_options); + + if ($result >= 0) { + if ($result == 0) { + setEventMessages($object->error, $object->errors, 'warnings'); + } + $ret = $object->fetch($object->id); // Reload to get new records + $object->fetch_thirdparty(); + + if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) { + // Define output language + $outputlangs = $langs; + $newlang = GETPOST('lang_id', 'alpha'); + if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) { + $newlang = $object->thirdparty->default_lang; + } + if (!empty($newlang)) { + $outputlangs = new Translate("", $conf); + $outputlangs->setDefaultLang($newlang); + } + + $object->generateDocument($object->model_pdf, $outputlangs, $hidedetails, $hidedesc, $hideref); + } + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } + } elseif ($action == 'updatesubtotalline' && GETPOSTISSET("save") && $usercancreate && !GETPOST('cancel', 'alpha')) { + // Handling updating a subtotal line for subtotals module + + $langs->load('subtotals'); + + $desc = GETPOST('line_desc', 'alphanohtml'); + $depth = GETPOSTINT('line_depth'); + + $subtotal_options = array(); + + foreach (Propal::$SUBTOTAL_OPTIONS as $option) { + $value = GETPOST($option, 'alphanohtml'); + if ($value) { + $subtotal_options[$option] = $value == 'on' ? 1 : $value; + } + } + + // Update line + $result = $object->updateSubtotalLine($langs, GETPOSTINT('lineid'), $desc, $depth, $subtotal_options); + + if ($result > 0) { + $ret = $object->fetch($object->id); // Reload to get new records + $object->fetch_thirdparty(); + + if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) { + // Define output language + $outputlangs = $langs; + $newlang = GETPOST('lang_id', 'alpha'); + if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) { + $newlang = $object->thirdparty->default_lang; + } + if (!empty($newlang)) { + $outputlangs = new Translate("", $conf); + $outputlangs->setDefaultLang($newlang); + } + + $object->generateDocument($object->model_pdf, $outputlangs, $hidedetails, $hidedesc, $hideref); + } + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } } elseif ($action == 'updateline' && $usercancreate && GETPOST('save')) { // Update a line within proposal @@ -2466,6 +2705,19 @@ if ($action == 'create') { $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('ToClone'), $langs->trans('ConfirmClonePropal', $object->ref), 'confirm_clone', $formquestion, 'yes', 1, 250, 600); } + // Subtotal line form + if ($action == 'add_title_line') { + $langs->load('subtotals'); + $type = 'title'; + $depth_array = $object->getPossibleLevels($langs); + require dol_buildpath('/core/tpl/subtotal_create.tpl.php'); + } elseif ($action == 'add_subtotal_line') { + $langs->load('subtotals'); + $type = 'subtotal'; + $titles = $object->getPossibleTitles(); + require dol_buildpath('/core/tpl/subtotal_create.tpl.php'); + } + if ($action == 'closeas') { //Form to close proposal (signed or not) $formquestion = array(); @@ -2630,6 +2882,17 @@ if ($action == 'create') { } elseif ($action == 'ask_deleteline') { // Confirmation delete product/service line $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id.'&lineid='.$lineid, $langs->trans('DeleteProductLine'), $langs->trans('ConfirmDeleteProductLine'), 'confirm_deleteline', '', 0, 1); + } elseif ($action == 'ask_subtotal_deleteline') { + // Confirmation de la suppression d'une ligne subtotal + $langs->load("subtotals"); + $title = "DeleteSubtotalLine"; + $question = "ConfirmDeleteSubtotalLine"; + if (GETPOST('type') == 'title') { + $formconfirm = array(array('type' => 'checkbox', 'name' => 'deletecorrespondingsubtotalline', 'label' => $langs->trans("DeleteCorrespondingSubtotalLine"), 'value' => 0)); + $title = "DeleteTitleLine"; + $question = "ConfirmDeleteTitleLine"; + } + $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id.'&lineid='.$lineid, $langs->trans($title), $langs->trans($question), 'confirm_delete_subtotalline', $formconfirm, 'no', 1); } elseif ($action == 'validate') { // Confirm validate proposal $error = 0; @@ -3134,7 +3397,11 @@ if ($action == 'create') { '; if (!empty($conf->use_javascript_ajax) && $object->status == Propal::STATUS_DRAFT) { - include DOL_DOCUMENT_ROOT.'/core/tpl/ajaxrow.tpl.php'; + if (isModEnabled('subtotals')) { + include DOL_DOCUMENT_ROOT.'/core/tpl/subtotal_ajaxrow.tpl.php'; + } else { + include DOL_DOCUMENT_ROOT . '/core/tpl/ajaxrow.tpl.php'; + } } print '
'; @@ -3187,6 +3454,31 @@ if ($action == 'create') { // modified by hook if (empty($reshook)) { if ($action != 'editline') { + // Subtotal + if ($object->status == Propal::STATUS_DRAFT && isModEnabled('subtotals') && getDolGlobalString('SUBTOTAL_TITLE_'.strtoupper($object->element))) { + $langs->load('subtotals'); + + $url_button = array(); + + $url_button[] = array( + 'lang' => 'subtotals', + 'enabled' => (isModEnabled('propal') && $object->status == Propal::STATUS_DRAFT), + 'perm' => (bool) $usercancreate, + 'label' => $langs->trans('AddTitleLine'), + 'url' => '/comm/propal/card.php?id='.$object->id.'&action=add_title_line&token='.newToken() + ); + + $url_button[] = array( + 'lang' => 'subtotals', + 'enabled' => (isModEnabled('propal') && $object->status == Propal::STATUS_DRAFT), + 'perm' => (bool) $usercancreate, + 'label' => $langs->trans('AddSubtotalLine'), + 'url' => '/comm/propal/card.php?id='.$object->id.'&action=add_subtotal_line&token='.newToken() + ); + + print dolGetButtonAction('', $langs->trans('Subtotal'), 'default', $url_button, '', true); + } + // Validate if (($object->status == Propal::STATUS_DRAFT && $object->total_ttc >= 0 && count($object->lines) > 0) || ($object->status == Propal::STATUS_DRAFT && getDolGlobalString('PROPAL_ENABLE_NEGATIVE') && count($object->lines) > 0)) { diff --git a/htdocs/comm/propal/class/propal.class.php b/htdocs/comm/propal/class/propal.class.php index dc0ac1cfd98..35c1732460e 100644 --- a/htdocs/comm/propal/class/propal.class.php +++ b/htdocs/comm/propal/class/propal.class.php @@ -47,13 +47,14 @@ require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php'; require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php'; +require_once DOL_DOCUMENT_ROOT.'/subtotals/class/commonsubtotal.class.php'; /** * Class to manage proposals */ class Propal extends CommonObject { - use CommonIncoterm; + use CommonIncoterm, CommonSubtotal; /** * @var string code @@ -632,8 +633,35 @@ class Propal extends CommonObject * @return int >0 if OK, <0 if KO * @see add_product() */ - public function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1 = 0.0, $txlocaltax2 = 0.0, $fk_product = 0, $remise_percent = 0.0, $price_base_type = 'HT', $pu_ttc = 0.0, $info_bits = 0, $type = 0, $rang = -1, $special_code = 0, $fk_parent_line = 0, $fk_fournprice = 0, $pa_ht = 0, $label = '', $date_start = '', $date_end = '', $array_options = array(), $fk_unit = null, $origin = '', $origin_id = 0, $pu_ht_devise = 0, $fk_remise_except = 0, $noupdateafterinsertline = 0) - { + public function addline( + $desc, + $pu_ht, + $qty, + $txtva, + $txlocaltax1 = 0.0, + $txlocaltax2 = 0.0, + $fk_product = 0, + $remise_percent = 0.0, + $price_base_type = 'HT', + $pu_ttc = 0.0, + $info_bits = 0, + $type = 0, + $rang = -1, + $special_code = 0, + $fk_parent_line = 0, + $fk_fournprice = 0, + $pa_ht = 0, + $label = '', + $date_start = '', + $date_end = '', + $array_options = array(), + $fk_unit = null, + $origin = '', + $origin_id = 0, + $pu_ht_devise = 0, + $fk_remise_except = 0, + $noupdateafterinsertline = 0 + ) { global $mysoc, $langs; dol_syslog(get_class($this)."::addline propalid=$this->id, desc=$desc, pu_ht=$pu_ht, qty=$qty, txtva=$txtva, fk_product=$fk_product, remise_except=$remise_percent, price_base_type=$price_base_type, pu_ttc=$pu_ttc, info_bits=$info_bits, type=$type, fk_remise_except=".$fk_remise_except); @@ -841,6 +869,13 @@ class Propal extends CommonObject } $this->lines[] = $this->line; + } else { + foreach ($this->lines as $line) { + if ($line->id == $origin_id) { + $this->line->extraparams = $line->extraparams; + $this->line->setExtraParameters(); + } + } } // Update denormalized fields at the order level diff --git a/htdocs/commande/card.php b/htdocs/commande/card.php index a19b942a2e5..83e125ee445 100644 --- a/htdocs/commande/card.php +++ b/htdocs/commande/card.php @@ -307,6 +307,34 @@ if (empty($reshook)) { $object->generateDocument($object->model_pdf, $outputlangs, $hidedetails, $hidedesc, $hideref); } + header('Location: '.$_SERVER["PHP_SELF"].'?id='.$object->id); + exit; + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } + } elseif ($action == 'confirm_delete_subtotalline' && $confirm == 'yes' && $usercancreate) { + $result = $object->deleteSubtotalLine($langs, GETPOSTINT('lineid'), (bool) GETPOST('deletecorrespondingsubtotalline'), $user); + if ($result > 0) { + // reorder lines + $object->line_order(true); + // Define output language + $outputlangs = $langs; + $newlang = ''; + if (getDolGlobalInt('MAIN_MULTILANGS') /* && empty($newlang) */ && GETPOST('lang_id', 'aZ09')) { + $newlang = GETPOST('lang_id', 'aZ09'); + } + if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) { + $newlang = $object->thirdparty->default_lang; + } + if (!empty($newlang)) { + $outputlangs = new Translate("", $conf); + $outputlangs->setDefaultLang($newlang); + } + if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) { + $ret = $object->fetch($object->id); // Reload to get new records + $object->generateDocument($object->model_pdf, $outputlangs, $hidedetails, $hidedesc, $hideref); + } + header('Location: '.$_SERVER["PHP_SELF"].'?id='.$object->id); exit; } else { @@ -490,7 +518,10 @@ if (empty($reshook)) { $array_options, $lines[$i]->fk_unit, $object->origin, - $lines[$i]->rowid + $lines[$i]->rowid, + 0, + $lines[$i]->ref_ext, + 0 ); if ($result < 0) { @@ -498,6 +529,13 @@ if (empty($reshook)) { break; } + foreach ($object->lines as $line) { + if ($line->id == $result) { + $line->extraparams = $lines[$i]->extraparams; + $line->setExtraParameters(); + } + } + // Defined the new fk_parent_line if ($result > 0 && $lines[$i]->product_type == 9) { $fk_parent_line = $result; @@ -708,11 +746,20 @@ if (empty($reshook)) { if ($result < 0) { setEventMessages($object->error, $object->errors, 'errors'); } + } elseif ($action == 'addline' && GETPOST('updateallvatlinesblock', 'alpha') && GETPOST('vatforblocklines', 'alpha') !== '' && $usercancreate) { + $tx_tva = GETPOST('vatforblocklines') ? GETPOST('vatforblocklines') : 0; + $object->updateSubtotalLineBlockLines($langs, $object->getRangOfLine($lineid), 'tva', $tx_tva); + } elseif ($action == 'addline' && GETPOST('updatealldiscountlinesblock', 'alpha') && GETPOST('discountforblocklines', 'alpha') !== '' && $usercancreate) { + $discount = GETPOST('discountforblocklines') ? GETPOST('discountforblocklines') : 0; + $object->updateSubtotalLineBlockLines($langs, $object->getRangOfLine($lineid), 'discount', $discount); } elseif ($action == 'addline' && GETPOST('submitforalllines', 'aZ09') && (GETPOST('alldate_start', 'alpha') || GETPOST('alldate_end', 'alpha')) && $usercancreate) { // Define date start and date end for all line $alldate_start = dol_mktime(GETPOSTINT('alldate_starthour'), GETPOSTINT('alldate_startmin'), 0, GETPOSTINT('alldate_startmonth'), GETPOSTINT('alldate_startday'), GETPOSTINT('alldate_startyear')); $alldate_end = dol_mktime(GETPOSTINT('alldate_endhour'), GETPOSTINT('alldate_endmin'), 0, GETPOSTINT('alldate_endmonth'), GETPOSTINT('alldate_endday'), GETPOSTINT('alldate_endyear')); foreach ($object->lines as $line) { + if ($line->special_code == SUBTOTALS_SPECIAL_CODE) { + continue; + } if ($line->product_type == 1) { // only service line $result = $object->updateline($line->id, $line->desc, $line->subprice, $line->qty, $line->remise_percent, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $alldate_start, $alldate_end, $line->product_type, $line->fk_parent_line, 0, $line->fk_fournprice, $line->pa_ht, $line->label, $line->special_code, $line->array_options, $line->fk_unit, $line->multicurrency_subprice); } @@ -724,6 +771,9 @@ if (empty($reshook)) { $localtax1_rate = get_localtax($vat_rate, 1, $object->thirdparty, $mysoc); $localtax2_rate = get_localtax($vat_rate, 2, $object->thirdparty, $mysoc); foreach ($object->lines as $line) { + if ($line->special_code == SUBTOTALS_SPECIAL_CODE) { + continue; + } $result = $object->updateline($line->id, $line->desc, $line->subprice, $line->qty, $line->remise_percent, (float) $vat_rate, $localtax1_rate, $localtax2_rate, 'HT', $line->info_bits, $line->date_start, $line->date_end, $line->product_type, $line->fk_parent_line, 0, $line->fk_fournprice, $line->pa_ht, $line->label, $line->special_code, $line->array_options, $line->fk_unit, $line->multicurrency_subprice); } } elseif ($action == 'addline' && GETPOST('submitforalllines', 'alpha') && GETPOST('remiseforalllines', 'alpha') !== '' && $usercancreate) { @@ -731,6 +781,9 @@ if (empty($reshook)) { $remise_percent = (GETPOST('remiseforalllines') ? GETPOST('remiseforalllines') : 0); $remise_percent = str_replace('*', '', $remise_percent); foreach ($object->lines as $line) { + if ($line->special_code == SUBTOTALS_SPECIAL_CODE) { + continue; + } $tvatx = $line->tva_tx; if (!empty($line->vat_src_code)) { $tvatx .= ' ('.$line->vat_src_code.')'; @@ -745,6 +798,9 @@ if (empty($reshook)) { $margin_rate = GETPOSTISSET('marginforalllines') ? GETPOST('marginforalllines', 'int') : ''; $mark_rate = GETPOSTISSET('markforalllines') ? GETPOST('markforalllines', 'int') : ''; foreach ($object->lines as &$line) if ($line->subprice > 0) { + if ($line->special_code == SUBTOTALS_SPECIAL_CODE) { + continue; + } $subprice_multicurrency = $line->subprice; if (is_numeric($margin_rate) && $margin_rate > 0) { $line->subprice = floatval(price2num(floatval($line->pa_ht) * (1 + floatval($margin_rate) / 100), 'MU')); @@ -791,6 +847,104 @@ if (empty($reshook)) { setEventMessages($object->error, $object->errors, 'errors'); } } + } elseif ($action == 'confirm_addtitleline' && $usercancreate) { + // Handling adding a new title line for subtotals module + + $langs->load('subtotals'); + + $desc = GETPOST('subtotallinedesc', 'alphanohtml'); + $depth = GETPOSTINT('subtotallinelevel') ?? 1; + + $subtotal_options = array(); + + foreach (Commande::$TITLE_OPTIONS as $option) { + $value = GETPOST($option, 'alphanohtml'); + if ($value) { + $subtotal_options[$option] = $value == 'on' ? 1 : $value; + } + } + + // Insert line + $result = $object->addSubtotalLine($langs, $desc, (int) $depth, $subtotal_options); + + if ($result >= 0) { + if ($result == 0) { + setEventMessages($object->error, $object->errors, 'warnings'); + } + $ret = $object->fetch($object->id); // Reload to get new records + $object->fetch_thirdparty(); + + if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) { + // Define output language + $outputlangs = $langs; + $newlang = GETPOST('lang_id', 'alpha'); + if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) { + $newlang = $object->thirdparty->default_lang; + } + if (!empty($newlang)) { + $outputlangs = new Translate("", $conf); + $outputlangs->setDefaultLang($newlang); + } + + $object->generateDocument($object->model_pdf, $outputlangs, $hidedetails, $hidedesc, $hideref); + } + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } + header('Location: '.$_SERVER["PHP_SELF"].'?id='.$id); + exit(); + } elseif ($action == 'confirm_addsubtotalline' && $usercancreate) { + // Handling adding a new subtotal line for subtotals module + + $langs->load('subtotals'); + + $choosen_line = GETPOST('subtotaltitleline', 'alphanohtml'); + foreach ($object->lines as $line) { + if ($line->desc == $choosen_line && $line->special_code == SUBTOTALS_SPECIAL_CODE) { + $desc = $line->desc; + $depth = -$line->qty; + } + } + + $subtotal_options = array(); + + foreach (Commande::$SUBTOTAL_OPTIONS as $option) { + $value = GETPOST($option, 'alphanohtml'); + if ($value) { + $subtotal_options[$option] = $value == 'on' ? 1 : $value; + } + } + + // Insert line + if (isset($desc) && isset($depth)) { + $result = $object->addSubtotalLine($langs, $desc, (int) $depth, $subtotal_options); + } else { + $object->errors[] = $langs->trans("CorrespondingTitleNotFound"); + } + + if (isset($result) && $result >= 0) { + $ret = $object->fetch($object->id); // Reload to get new records + $object->fetch_thirdparty(); + + if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) { + // Define output language + $outputlangs = $langs; + $newlang = GETPOST('lang_id', 'alpha'); + if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) { + $newlang = $object->thirdparty->default_lang; + } + if (!empty($newlang)) { + $outputlangs = new Translate("", $conf); + $outputlangs->setDefaultLang($newlang); + } + + $object->generateDocument($object->model_pdf, $outputlangs, $hidedetails, $hidedesc, $hideref); + } + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } + header('Location: '.$_SERVER["PHP_SELF"].'?id='.$id); + exit(); } elseif ($action == 'addline' && !GETPOST('submitforalllines', 'alpha') && $usercancreate) { // Add a new line $langs->load('errors'); $error = 0; @@ -1296,6 +1450,91 @@ if (empty($reshook)) { } } } + } elseif ($action == 'updatetitleline' && GETPOSTISSET("save") && $usercancreate && !GETPOST('cancel', 'alpha')) { + // Handling updating a title line for subtotals module + + $langs->load('subtotals'); + + $desc = GETPOST('line_desc', 'alphanohtml') ?? $langs->trans("Title"); + $depth = GETPOSTINT('line_depth') ?? 1; + + $subtotal_options = array(); + + foreach (Commande::$TITLE_OPTIONS as $option) { + $value = GETPOST($option, 'alphanohtml'); + if ($value) { + $subtotal_options[$option] = $value == 'on' ? 1 : $value; + } + } + + // Update line + $result = $object->updateSubtotalLine($langs, GETPOSTINT('lineid'), $desc, $depth, $subtotal_options); + + if ($result >= 0) { + if ($result == 0) { + setEventMessages($object->error, $object->errors, 'warnings'); + } + $ret = $object->fetch($object->id); // Reload to get new records + $object->fetch_thirdparty(); + + if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) { + // Define output language + $outputlangs = $langs; + $newlang = GETPOST('lang_id', 'alpha'); + if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) { + $newlang = $object->thirdparty->default_lang; + } + if (!empty($newlang)) { + $outputlangs = new Translate("", $conf); + $outputlangs->setDefaultLang($newlang); + } + + $object->generateDocument($object->model_pdf, $outputlangs, $hidedetails, $hidedesc, $hideref); + } + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } + } elseif ($action == 'updatesubtotalline' && GETPOSTISSET("save") && $usercancreate && !GETPOST('cancel', 'alpha')) { + // Handling updating a subtotal line for subtotals module + + $langs->load('subtotals'); + + $desc = GETPOST('line_desc', 'alphanohtml'); + $depth = GETPOSTINT('line_depth'); + + $subtotal_options = array(); + + foreach (Commande::$SUBTOTAL_OPTIONS as $option) { + $value = GETPOST($option, 'alphanohtml'); + if ($value) { + $subtotal_options[$option] = $value == 'on' ? 1 : $value; + } + } + + // Update line + $result = $object->updateSubtotalLine($langs, GETPOSTINT('lineid'), $desc, $depth, $subtotal_options); + + if ($result > 0) { + $ret = $object->fetch($object->id); // Reload to get new records + $object->fetch_thirdparty(); + + if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) { + // Define output language + $outputlangs = $langs; + $newlang = GETPOST('lang_id', 'alpha'); + if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) { + $newlang = $object->thirdparty->default_lang; + } + if (!empty($newlang)) { + $outputlangs = new Translate("", $conf); + $outputlangs->setDefaultLang($newlang); + } + + $object->generateDocument($object->model_pdf, $outputlangs, $hidedetails, $hidedesc, $hideref); + } + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } } elseif ($action == 'updateline' && $usercancreate && GETPOST('save')) { // Update a line // Clean parameters @@ -2630,6 +2869,19 @@ if ($action == 'create' && $usercancreate) { $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id.'&lineid='.$lineid, $langs->trans('DeleteProductLine'), $langs->trans('ConfirmDeleteProductLine'), 'confirm_deleteline', '', 0, 1); } + // Confirmation de la suppression d'une ligne subtotal + if ($action == 'ask_subtotal_deleteline') { + $langs->load("subtotals"); + $title = "DeleteSubtotalLine"; + $question = "ConfirmDeleteSubtotalLine"; + if (GETPOST('type') == 'title') { + $formconfirm = array(array('type' => 'checkbox', 'name' => 'deletecorrespondingsubtotalline', 'label' => $langs->trans("DeleteCorrespondingSubtotalLine"), 'value' => 0)); + $title = "DeleteTitleLine"; + $question = "ConfirmDeleteTitleLine"; + } + $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id.'&lineid='.$lineid, $langs->trans($title), $langs->trans($question), 'confirm_delete_subtotalline', $formconfirm, 'no', 1); + } + // Clone confirmation if ($action == 'clone') { $filter = '(s.client:IN:1,2,3)'; @@ -2640,6 +2892,19 @@ if ($action == 'create' && $usercancreate) { $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('ToClone'), $langs->trans('ConfirmCloneOrder', $object->ref), 'confirm_clone', $formquestion, 'yes', 1); } + // Subtotal line form + if ($action == 'add_title_line') { + $langs->load('subtotals'); + $type = 'title'; + $depth_array = $object->getPossibleLevels($langs); + require dol_buildpath('/core/tpl/subtotal_create.tpl.php'); + } elseif ($action == 'add_subtotal_line') { + $langs->load('subtotals'); + $type = 'subtotal'; + $titles = $object->getPossibleTitles(); + require dol_buildpath('/core/tpl/subtotal_create.tpl.php'); + } + // Call Hook formConfirm $parameters = array('formConfirm' => $formconfirm, 'lineid' => $lineid); // Note that $action and $object may be modified by hook @@ -3087,7 +3352,11 @@ if ($action == 'create' && $usercancreate) { '; if (!empty($conf->use_javascript_ajax) && $object->status == Commande::STATUS_DRAFT) { - include DOL_DOCUMENT_ROOT.'/core/tpl/ajaxrow.tpl.php'; + if (isModEnabled('subtotals')) { + include DOL_DOCUMENT_ROOT.'/core/tpl/subtotal_ajaxrow.tpl.php'; + } else { + include DOL_DOCUMENT_ROOT.'/core/tpl/ajaxrow.tpl.php'; + } } print '
'; @@ -3155,6 +3424,30 @@ if ($action == 'create' && $usercancreate) { } } + // Subtotal + if ($object->status == Commande::STATUS_DRAFT && isModEnabled('subtotals') && getDolGlobalString('SUBTOTAL_TITLE_'.strtoupper($object->element))) { + $langs->load('subtotals'); + + $url_button = array(); + + $url_button[] = array( + 'lang' => 'subtotals', + 'enabled' => (isModEnabled('order') && $object->status == Commande::STATUS_DRAFT), + 'perm' => (bool) $usercancreate, + 'label' => $langs->trans('AddTitleLine'), + 'url' => '/commande/card.php?id='.$object->id.'&action=add_title_line&token='.newToken() + ); + + $url_button[] = array( + 'lang' => 'subtotals', + 'enabled' => (isModEnabled('order') && $object->status == Commande::STATUS_DRAFT), + 'perm' => (bool) $usercancreate, + 'label' => $langs->trans('AddSubtotalLine'), + 'url' => '/commande/card.php?id='.$object->id.'&action=add_subtotal_line&token='.newToken() + ); + print dolGetButtonAction('', $langs->trans('Subtotal'), 'default', $url_button, '', true); + } + // Valid if ($object->status == Commande::STATUS_DRAFT && ($object->total_ttc >= 0 || getDolGlobalString('ORDER_ENABLE_NEGATIVE')) && $usercanvalidate) { if ($numlines > 0) { diff --git a/htdocs/commande/class/commande.class.php b/htdocs/commande/class/commande.class.php index c459d4fc975..9bdd1df6f4c 100644 --- a/htdocs/commande/class/commande.class.php +++ b/htdocs/commande/class/commande.class.php @@ -42,6 +42,7 @@ require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php'; require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php'; require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; +require_once DOL_DOCUMENT_ROOT.'/subtotals/class/commonsubtotal.class.php'; /** @@ -49,6 +50,8 @@ require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; */ class Commande extends CommonOrder { + use CommonSubtotal; + /** * @var string ID to identify managed object */ @@ -1827,6 +1830,13 @@ class Commande extends CommonOrder } $this->lines[] = $this->line; + } else { + foreach ($this->lines as $line) { + if ($line->id == $origin_id) { + $this->line->extraparams = $line->extraparams; + $this->line->setExtraParameters(); + } + } } $this->db->commit(); diff --git a/htdocs/compta/facture/card-rec.php b/htdocs/compta/facture/card-rec.php index ea53df5c8c0..40380d6152f 100644 --- a/htdocs/compta/facture/card-rec.php +++ b/htdocs/compta/facture/card-rec.php @@ -488,6 +488,28 @@ if (empty($reshook)) { $db->rollback(); setEventMessages($line->error, $line->errors, 'errors'); } + } elseif ($action == 'confirm_delete_subtotalline' && $confirm == 'yes' && $usercancreate) { + // Delete line + $object->fetch($id); + $object->fetch_thirdparty(); + + $result = $object->deleteSubtotalLine($langs, GETPOSTINT('lineid'), (bool) GETPOST('deletecorrespondingsubtotalline'), $user); + if ($result > 0) { + $result = $object->update_price(1); + + if ($result > 0) { + $db->commit(); + $object->fetch($object->id); // Reload lines + header('Location: '.$_SERVER["PHP_SELF"].'?id='.$id); + exit(); + } else { + $db->rollback(); + setEventMessages($db->lasterror(), null, 'errors'); + } + } else { + setEventMessages($object->error, $object->errors, 'errors'); + $action = ''; + } } elseif ($action == 'update_extras' && $permissiontoeditextra) { $object->oldcopy = dol_clone($object, 2); // @phan-suppress-current-line PhanTypeMismatchProperty @@ -804,6 +826,74 @@ if (empty($reshook)) { $action = ''; } } + } elseif ($action == 'confirm_addtitleline' && $usercancreate) { + // Handling adding a new title line for subtotals module + + $langs->load('subtotals'); + + $desc = GETPOST('subtotallinedesc', 'alphanohtml'); + $depth = GETPOSTINT('subtotallinelevel') ?? 1; + + $subtotal_options = array(); + + foreach (FactureRec::$TITLE_OPTIONS as $option) { + $value = GETPOST($option, 'alphanohtml'); + if ($value) { + $subtotal_options[$option] = $value == 'on' ? 1 : $value; + } + } + + // Insert line + $result = $object->addSubtotalLine($langs, $desc, (int) $depth, $subtotal_options); + + if ($result >= 0) { + if ($result == 0) { + setEventMessages($object->error, $object->errors, 'warnings'); + } + $ret = $object->fetch($object->id); // Reload to get new records + $object->fetch_thirdparty(); + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } + header('Location: '.$_SERVER["PHP_SELF"].'?id='.$id); + exit(); + } elseif ($action == 'confirm_addsubtotalline' && $usercancreate) { + // Handling adding a new subtotal line for subtotals module + + $langs->load('subtotals'); + + $choosen_line = GETPOST('subtotaltitleline', 'alphanohtml'); + foreach ($object->lines as $line) { + if ($line->desc == $choosen_line && $line->special_code == SUBTOTALS_SPECIAL_CODE) { + $desc = $line->desc; + $depth = -$line->qty; + } + } + + $subtotal_options = array(); + + foreach (FactureRec::$SUBTOTAL_OPTIONS as $option) { + $value = GETPOST($option, 'alphanohtml'); + if ($value) { + $subtotal_options[$option] = $value == 'on' ? 1 : $value; + } + } + + // Insert line + if (isset($desc) && isset($depth)) { + $result = $object->addSubtotalLine($langs, $desc, (int) $depth, $subtotal_options); + } else { + $object->errors[] = $langs->trans("CorrespondingTitleNotFound"); + } + + if (isset($result) && $result >= 0) { + $ret = $object->fetch($object->id); // Reload to get new records + $object->fetch_thirdparty(); + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } + header('Location: '.$_SERVER["PHP_SELF"].'?id='.$id); + exit(); } elseif ($action == 'updateline' && $usercancreate && !GETPOST('cancel', 'alpha')) { if (!$object->fetch($id) > 0) { dol_print_error($db); @@ -1015,6 +1105,61 @@ if (empty($reshook)) { setEventMessages($object->error, $object->errors, 'errors'); } } + } elseif ($action == 'updatetitleline' && GETPOSTISSET("save") && $usercancreate && !GETPOST('cancel', 'alpha')) { + // Handling updating a title line for subtotals module + + $langs->load('subtotals'); + + $desc = GETPOST('line_desc', 'alphanohtml') ?? $langs->trans("Title"); + $depth = GETPOSTINT('line_depth') ?? 1; + + $subtotal_options = array(); + + foreach (Facture::$TITLE_OPTIONS as $option) { + $value = GETPOST($option, 'alphanohtml'); + if ($value) { + $subtotal_options[$option] = $value == 'on' ? 1 : $value; + } + } + + // Update line + $result = $object->updateSubtotalLine($langs, GETPOSTINT('lineid'), $desc, $depth, $subtotal_options); + + if ($result >= 0) { + if ($result == 0) { + setEventMessages($object->error, $object->errors, 'warnings'); + } + $ret = $object->fetch($object->id); // Reload to get new records + $object->fetch_thirdparty(); + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } + } elseif ($action == 'updatesubtotalline' && GETPOSTISSET("save") && $usercancreate && !GETPOST('cancel', 'alpha')) { + // Handling updating a subtotal line for subtotals module + + $langs->load('subtotals'); + + $desc = GETPOST('line_desc', 'alphanohtml'); + $depth = GETPOSTINT('line_depth'); + + $subtotal_options = array(); + + foreach (Facture::$SUBTOTAL_OPTIONS as $option) { + $value = GETPOST($option, 'alphanohtml'); + if ($value) { + $subtotal_options[$option] = $value == 'on' ? 1 : $value; + } + } + + // Update line + $result = $object->updateSubtotalLine($langs, GETPOSTINT('lineid'), $desc, $depth, $subtotal_options); + + if ($result > 0) { + $ret = $object->fetch($object->id); // Reload to get new records + $object->fetch_thirdparty(); + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } } } @@ -1340,6 +1485,31 @@ if ($action == 'create') { if ($action == 'delete') { $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('DeleteRepeatableInvoice'), $langs->trans('ConfirmDeleteRepeatableInvoice'), 'confirm_delete', '', 'no', 1); } + // Confirmation de la suppression d'une ligne subtotal + if ($action == 'ask_subtotal_deleteline') { + $langs->load("subtotals"); + $title = "DeleteSubtotalLine"; + $question = "ConfirmDeleteSubtotalLine"; + if (GETPOST('type') == 'title') { + $formconfirm = array(array('type' => 'checkbox', 'name' => 'deletecorrespondingsubtotalline', 'label' => $langs->trans("DeleteCorrespondingSubtotalLine"), 'value' => 0)); + $title = "DeleteTitleLine"; + $question = "ConfirmDeleteTitleLine"; + } + $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id.'&lineid='.$lineid, $langs->trans($title), $langs->trans($question), 'confirm_delete_subtotalline', $formconfirm, 'no', 1); + } + + // Subtotal line form + if ($action == 'add_title_line') { + $langs->load('subtotals'); + $type = 'title'; + $depth_array = $object->getPossibleLevels($langs); + require dol_buildpath('/core/tpl/subtotal_create.tpl.php'); + } elseif ($action == 'add_subtotal_line') { + $langs->load('subtotals'); + $type = 'subtotal'; + $titles = $object->getPossibleTitles(); + require dol_buildpath('/core/tpl/subtotal_create.tpl.php'); + } // Call Hook formConfirm $parameters = array('formConfirm' => $formconfirm, 'lineid' => $lineid); @@ -1852,7 +2022,11 @@ if ($action == 'create') { print ''; if (!empty($conf->use_javascript_ajax) && $object->statut == 0) { - include DOL_DOCUMENT_ROOT.'/core/tpl/ajaxrow.tpl.php'; + if (isModEnabled('subtotals')) { + include DOL_DOCUMENT_ROOT.'/core/tpl/subtotal_ajaxrow.tpl.php'; + } else { + include DOL_DOCUMENT_ROOT . '/core/tpl/ajaxrow.tpl.php'; + } } print '
'; @@ -1900,6 +2074,31 @@ if ($action == 'create') { 'class' => 'classfortooltip', ), ); + + // Subtotal + if (empty($object->suspended) && isModEnabled('subtotals') && getDolGlobalString('SUBTOTAL_TITLE_'.strtoupper($object->element))) { + $langs->load("subtotals"); + + $url_button = array(); + + $url_button[] = array( + 'lang' => 'subtotals', + 'enabled' => (isModEnabled('invoice') && $object->status == Facture::STATUS_DRAFT), + 'perm' => (bool) $usercancreate, + 'label' => $langs->trans('AddTitleLine'), + 'url' => '/compta/facture/card-rec.php?id='.$object->id.'&action=add_title_line&token='.newToken() + ); + + $url_button[] = array( + 'lang' => 'subtotals', + 'enabled' => (isModEnabled('invoice') && $object->status == Facture::STATUS_DRAFT), + 'perm' => (bool) $usercancreate, + 'label' => $langs->trans('AddSubtotalLine'), + 'url' => '/compta/facture/card-rec.php?id='.$object->id.'&action=add_subtotal_line&token='.newToken() + ); + print dolGetButtonAction('', $langs->trans('Subtotal'), 'default', $url_button, '', true); + } + if (empty($object->suspended)) { if ($user->hasRight('facture', 'creer')) { if (!empty($object->frequency) && $object->nb_gen_max > 0 && ($object->nb_gen_done >= $object->nb_gen_max)) { diff --git a/htdocs/compta/facture/card.php b/htdocs/compta/facture/card.php index 7eff9b45f2b..58cb49c108b 100644 --- a/htdocs/compta/facture/card.php +++ b/htdocs/compta/facture/card.php @@ -355,6 +355,41 @@ if (empty($reshook)) { setEventMessages($object->error, $object->errors, 'errors'); $action = ''; } + } elseif ($action == 'confirm_delete_subtotalline' && $confirm == 'yes' && $usercancreate) { + // Delete line + $object->fetch($id); + $object->fetch_thirdparty(); + + $result = $object->deleteSubtotalLine($langs, GETPOSTINT('lineid'), (bool) GETPOST('deletecorrespondingsubtotalline')); + if ($result > 0) { + // reorder lines + $object->line_order(true); + // Define output language + $outputlangs = $langs; + $newlang = ''; + if (getDolGlobalInt('MAIN_MULTILANGS') /* && empty($newlang) */ && GETPOST('lang_id')) { + $newlang = GETPOST('lang_id'); + } + if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) { + $newlang = $object->thirdparty->default_lang; + } + if (!empty($newlang)) { + $outputlangs = new Translate("", $conf); + $outputlangs->setDefaultLang($newlang); + $outputlangs->load('products'); + } + if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) { + $ret = $object->fetch($id); // Reload to get new records + $result = $object->generateDocument($object->model_pdf, $outputlangs, $hidedetails, $hidedesc, $hideref); + } + if ($result >= 0) { + header('Location: '.$_SERVER["PHP_SELF"].'?facid='.$id); + exit(); + } + } else { + setEventMessages($object->error, $object->errors, 'errors'); + $action = ''; + } } elseif ($action == 'unlinkdiscount' && $usercancreate) { // Delete link of credit note to invoice $discount = new DiscountAbsolute($db); @@ -1917,10 +1952,17 @@ if (empty($reshook)) { $lines[$i]->fk_unit, 0, '', - 1 + 0 ); if ($result > 0) { + foreach ($object->lines as $line) { + if ($line->id == $result) { + $line->extraparams = $lines[$i]->extraparams; + $line->setExtraParameters(); + } + } + $lineid = $result; } else { $lineid = 0; @@ -1942,6 +1984,8 @@ if (empty($reshook)) { $object->update_price(1, 'auto', 0, $mysoc); + $object->line_order(true, 'DESC'); + // Now we create same links to contact than the ones found on origin object /* Useless, already into the create if (getDolGlobalString('MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN')) { @@ -2176,6 +2220,9 @@ if (empty($reshook)) { $alldate_start = dol_mktime(GETPOSTINT('alldate_starthour'), GETPOSTINT('alldate_startmin'), 0, GETPOSTINT('alldate_startmonth'), GETPOSTINT('alldate_startday'), GETPOSTINT('alldate_startyear')); $alldate_end = dol_mktime(GETPOSTINT('alldate_endhour'), GETPOSTINT('alldate_endmin'), 0, GETPOSTINT('alldate_endmonth'), GETPOSTINT('alldate_endday'), GETPOSTINT('alldate_endyear')); foreach ($object->lines as $line) { + if ($line->special_code == SUBTOTALS_SPECIAL_CODE) { + continue; + } if ($line->product_type == 1) { // only service line $result = $object->updateline($line->id, $line->desc, $line->subprice, $line->qty, $line->remise_percent, $alldate_start, $alldate_end, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->product_type, $line->fk_parent_line, 0, $line->fk_fournprice, $line->pa_ht, $line->label, $line->special_code, $line->array_options, $line->situation_percent, $line->fk_unit, $line->multicurrency_subprice); } @@ -2187,6 +2234,9 @@ if (empty($reshook)) { $localtax1_rate = get_localtax($vat_rate, 1, $object->thirdparty, $mysoc); $localtax2_rate = get_localtax($vat_rate, 2, $object->thirdparty, $mysoc); foreach ($object->lines as $line) { + if ($line->special_code == SUBTOTALS_SPECIAL_CODE) { + continue; + } $result = $object->updateline($line->id, $line->desc, $line->subprice, $line->qty, $line->remise_percent, $line->date_start, $line->date_end, $vat_rate, $localtax1_rate, $localtax2_rate, 'HT', $line->info_bits, $line->product_type, $line->fk_parent_line, 0, $line->fk_fournprice, $line->pa_ht, $line->label, $line->special_code, $line->array_options, $line->situation_percent, $line->fk_unit, $line->multicurrency_subprice); } } elseif ($action == 'addline' && GETPOST('submitforalllines', 'alpha') && GETPOST('remiseforalllines', 'alpha') !== '' && $usercancreate) { @@ -2194,12 +2244,119 @@ if (empty($reshook)) { $remise_percent = (GETPOST('remiseforalllines') ? GETPOST('remiseforalllines') : 0); $remise_percent = str_replace('*', '', $remise_percent); foreach ($object->lines as $line) { + if ($line->special_code == SUBTOTALS_SPECIAL_CODE) { + continue; + } $tvatx = $line->tva_tx; if (!empty($line->vat_src_code)) { $tvatx .= ' ('.$line->vat_src_code.')'; } $result = $object->updateline($line->id, $line->desc, $line->subprice, $line->qty, (float) $remise_percent, $line->date_start, $line->date_end, $tvatx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->product_type, $line->fk_parent_line, 0, $line->fk_fournprice, $line->pa_ht, $line->label, $line->special_code, $line->array_options, $line->situation_percent, $line->fk_unit, $line->multicurrency_subprice); } + } elseif ($action == 'confirm_addtitleline' && $usercancreate) { + // Handling adding a new title line for subtotals module + + $langs->load('subtotals'); + + $desc = GETPOST('subtotallinedesc', 'alphanohtml'); + $depth = GETPOSTINT('subtotallinelevel') ?? 1; + + $subtotal_options = array(); + + foreach (Facture::$TITLE_OPTIONS as $option) { + $value = GETPOST($option, 'alphanohtml'); + if ($value) { + $subtotal_options[$option] = $value == 'on' ? 1 : $value; + } + } + + // Insert line + $result = $object->addSubtotalLine($langs, $desc, (int) $depth, $subtotal_options); + + if ($result >= 0) { + if ($result == 0) { + setEventMessages($object->error, $object->errors, 'warnings'); + } + $ret = $object->fetch($object->id); // Reload to get new records + $object->fetch_thirdparty(); + + if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) { + // Define output language + $outputlangs = $langs; + $newlang = GETPOST('lang_id', 'alpha'); + if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) { + $newlang = $object->thirdparty->default_lang; + } + if (!empty($newlang)) { + $outputlangs = new Translate("", $conf); + $outputlangs->setDefaultLang($newlang); + } + + $object->generateDocument($object->model_pdf, $outputlangs, $hidedetails, $hidedesc, $hideref); + } + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } + header('Location: '.$_SERVER["PHP_SELF"].'?facid='.$id); + exit(); + } elseif ($action == 'confirm_addsubtotalline' && $usercancreate) { + // Handling adding a new subtotal line for subtotals module + + $langs->load('subtotals'); + + $choosen_line = GETPOST('subtotaltitleline', 'alphanohtml'); + foreach ($object->lines as $line) { + if ($line->desc == $choosen_line && $line->special_code == SUBTOTALS_SPECIAL_CODE) { + $desc = $line->desc; + $depth = -$line->qty; + } + } + + $subtotal_options = array(); + + foreach (Facture::$SUBTOTAL_OPTIONS as $option) { + $value = GETPOST($option, 'alphanohtml'); + if ($value) { + $subtotal_options[$option] = $value == 'on' ? 1 : $value; + } + } + + // Insert line + if (isset($desc) && isset($depth)) { + $result = $object->addSubtotalLine($langs, $desc, (int) $depth, $subtotal_options); + } else { + $object->errors[] = $langs->trans("CorrespondingTitleNotFound"); + } + + if (isset($result) && $result >= 0) { + $ret = $object->fetch($object->id); // Reload to get new records + $object->fetch_thirdparty(); + + if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) { + // Define output language + $outputlangs = $langs; + $newlang = GETPOST('lang_id', 'alpha'); + if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) { + $newlang = $object->thirdparty->default_lang; + } + if (!empty($newlang)) { + $outputlangs = new Translate("", $conf); + $outputlangs->setDefaultLang($newlang); + } + + $object->generateDocument($object->model_pdf, $outputlangs, $hidedetails, $hidedesc, $hideref); + } + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } + header('Location: '.$_SERVER["PHP_SELF"].'?facid='.$id); + exit(); + } elseif ($action == 'addline' && GETPOST('updateallvatlinesblock', 'alpha') && GETPOST('vatforblocklines', 'alpha') !== '' && $usercancreate) { + $tx_tva = GETPOST('vatforblocklines') ? GETPOST('vatforblocklines') : 0; + $object->updateSubtotalLineBlockLines($langs, $object->getRangOfLine($lineid), 'tva', $tx_tva); + } elseif ($action == 'addline' && GETPOST('updatealldiscountlinesblock', 'alpha') && GETPOST('discountforblocklines', 'alpha') !== '' && $usercancreate) { + $discount = GETPOST('discountforblocklines') ? GETPOST('discountforblocklines') : 0; + $object->updateSubtotalLineBlockLines($langs, $object->getRangOfLine($lineid), 'discount', $discount); } elseif ($action == 'addline' && !GETPOST('submitforalllines', 'alpha') && !GETPOST('submitforallmargins', 'alpha') && !GETPOST('submitforallmark', 'alpha') && $usercancreate) { // Add a new line $langs->load('errors'); $error = 0; @@ -2638,6 +2795,9 @@ if (empty($reshook)) { $margin_rate = GETPOSTISSET('marginforalllines') ? GETPOST('marginforalllines', 'int') : ''; $mark_rate = GETPOSTISSET('markforalllines') ? GETPOST('markforalllines', 'int') : ''; foreach ($object->lines as &$line) if ($line->subprice > 0) { + if ($line->special_code == SUBTOTALS_SPECIAL_CODE) { + continue; + } $subprice_multicurrency = $line->subprice; if (is_numeric($margin_rate) && $margin_rate > 0) { $line->subprice = floatval(price2num(floatval($line->pa_ht) * (1 + floatval($margin_rate) / 100), 'MU')); @@ -2684,6 +2844,91 @@ if (empty($reshook)) { setEventMessages($object->error, $object->errors, 'errors'); } } + } elseif ($action == 'updatetitleline' && GETPOSTISSET("save") && $usercancreate && !GETPOST('cancel', 'alpha')) { + // Handling updating a title line for subtotals module + + $langs->load('subtotals'); + + $desc = GETPOST('line_desc', 'alphanohtml') ?? $langs->trans("Title"); + $depth = GETPOSTINT('line_depth') ?? 1; + + $subtotal_options = array(); + + foreach (Facture::$TITLE_OPTIONS as $option) { + $value = GETPOST($option, 'alphanohtml'); + if ($value) { + $subtotal_options[$option] = $value == 'on' ? 1 : $value; + } + } + + // Update line + $result = $object->updateSubtotalLine($langs, GETPOSTINT('lineid'), $desc, $depth, $subtotal_options); + + if ($result >= 0) { + if ($result == 0) { + setEventMessages($object->error, $object->errors, 'warnings'); + } + $ret = $object->fetch($object->id); // Reload to get new records + $object->fetch_thirdparty(); + + if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) { + // Define output language + $outputlangs = $langs; + $newlang = GETPOST('lang_id', 'alpha'); + if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) { + $newlang = $object->thirdparty->default_lang; + } + if (!empty($newlang)) { + $outputlangs = new Translate("", $conf); + $outputlangs->setDefaultLang($newlang); + } + + $object->generateDocument($object->model_pdf, $outputlangs, $hidedetails, $hidedesc, $hideref); + } + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } + } elseif ($action == 'updatesubtotalline' && GETPOSTISSET("save") && $usercancreate && !GETPOST('cancel', 'alpha')) { + // Handling updating a subtotal line for subtotals module + + $langs->load('subtotals'); + + $desc = GETPOST('line_desc', 'alphanohtml'); + $depth = GETPOSTINT('line_depth'); + + $subtotal_options = array(); + + foreach (Facture::$SUBTOTAL_OPTIONS as $option) { + $value = GETPOST($option, 'alphanohtml'); + if ($value) { + $subtotal_options[$option] = $value == 'on' ? 1 : $value; + } + } + + // Update line + $result = $object->updateSubtotalLine($langs, GETPOSTINT('lineid'), $desc, $depth, $subtotal_options); + + if ($result > 0) { + $ret = $object->fetch($object->id); // Reload to get new records + $object->fetch_thirdparty(); + + if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) { + // Define output language + $outputlangs = $langs; + $newlang = GETPOST('lang_id', 'alpha'); + if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) { + $newlang = $object->thirdparty->default_lang; + } + if (!empty($newlang)) { + $outputlangs = new Translate("", $conf); + $outputlangs->setDefaultLang($newlang); + } + + $object->generateDocument($object->model_pdf, $outputlangs, $hidedetails, $hidedesc, $hideref); + } + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } } elseif ($action == 'updateline' && $usercancreate && !GETPOST('cancel', 'alpha')) { if (!$object->fetch($id) > 0) { dol_print_error($db); @@ -3189,6 +3434,7 @@ if (empty($reshook)) { } } + // Actions when printing a doc from card include DOL_DOCUMENT_ROOT.'/core/actions_printing.inc.php'; @@ -4761,6 +5007,19 @@ if ($action == 'create') { $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?facid='.$object->id.'&lineid='.$lineid, $langs->trans('DeleteProductLine'), $langs->trans('ConfirmDeleteProductLine'), 'confirm_deleteline', '', 'no', 1); } + // Confirmation de la suppression d'une ligne subtotal + if ($action == 'ask_subtotal_deleteline') { + $langs->load("subtotals"); + $title = "DeleteSubtotalLine"; + $question = "ConfirmDeleteSubtotalLine"; + if (GETPOST('type') == 'title') { + $formconfirm = array(array('type' => 'checkbox', 'name' => 'deletecorrespondingsubtotalline', 'label' => $langs->trans("DeleteCorrespondingSubtotalLine"), 'value' => 0)); + $title = "DeleteTitleLine"; + $question = "ConfirmDeleteTitleLine"; + } + $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?facid='.$object->id.'&lineid='.$lineid, $langs->trans($title), $langs->trans($question), 'confirm_delete_subtotalline', $formconfirm, 'no', 1); + } + // Clone confirmation if ($action == 'clone') { $filter = '(s.client:IN:1,2,3)'; @@ -4773,6 +5032,19 @@ if ($action == 'create') { $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?facid='.$object->id, $langs->trans('ToClone'), $langs->trans('ConfirmCloneInvoice', $object->ref), 'confirm_clone', $formquestion, 'yes', 1, 250); } + // Subtotal line form + if ($action == 'add_title_line') { + $langs->load('subtotals'); + $type = 'title'; + $depth_array = $object->getPossibleLevels($langs); + require dol_buildpath('/core/tpl/subtotal_create.tpl.php'); + } elseif ($action == 'add_subtotal_line') { + $langs->load('subtotals'); + $type = 'subtotal'; + $titles = $object->getPossibleTitles(); + require dol_buildpath('/core/tpl/subtotal_create.tpl.php'); + } + if ($action == "remove_file_comfirm") { $file = GETPOST('file', 'alpha'); @@ -5923,7 +6195,11 @@ if ($action == 'create') { '; if (!empty($conf->use_javascript_ajax) && $object->status == 0) { - include DOL_DOCUMENT_ROOT.'/core/tpl/ajaxrow.tpl.php'; + if (isModEnabled('subtotals')) { + include DOL_DOCUMENT_ROOT.'/core/tpl/subtotal_ajaxrow.tpl.php'; + } else { + include DOL_DOCUMENT_ROOT . '/core/tpl/ajaxrow.tpl.php'; + } } print '
'; @@ -6034,6 +6310,30 @@ if ($action == 'create') { } } + // Subtotal + if ($object->status == Facture::STATUS_DRAFT && isModEnabled('subtotals') && getDolGlobalString('SUBTOTAL_TITLE_'.strtoupper($object->element))) { + $langs->load("subtotals"); + + $url_button = array(); + + $url_button[] = array( + 'lang' => 'subtotals', + 'enabled' => (isModEnabled('invoice') && $object->status == Facture::STATUS_DRAFT), + 'perm' => (bool) $usercancreate, + 'label' => $langs->trans('AddTitleLine'), + 'url' => '/compta/facture/card.php?facid='.$object->id.'&action=add_title_line&token='.newToken() + ); + + $url_button[] = array( + 'lang' => 'subtotals', + 'enabled' => (isModEnabled('invoice') && $object->status == Facture::STATUS_DRAFT), + 'perm' => (bool) $usercancreate, + 'label' => $langs->trans('AddSubtotalLine'), + 'url' => '/compta/facture/card.php?facid='.$object->id.'&action=add_subtotal_line&token='.newToken() + ); + print dolGetButtonAction('', $langs->trans('Subtotal'), 'default', $url_button, '', true); + } + // Validate if ($object->status == Facture::STATUS_DRAFT && count($object->lines) > 0 && ((($object->type == Facture::TYPE_STANDARD || $object->type == Facture::TYPE_REPLACEMENT || $object->type == Facture::TYPE_DEPOSIT || $object->type == Facture::TYPE_PROFORMA || $object->type == Facture::TYPE_SITUATION) && (getDolGlobalString('FACTURE_ENABLE_NEGATIVE') || $object->total_ttc >= 0)) diff --git a/htdocs/compta/facture/class/facture-rec.class.php b/htdocs/compta/facture/class/facture-rec.class.php index 7694b54635b..b2ba01ea9d4 100644 --- a/htdocs/compta/facture/class/facture-rec.class.php +++ b/htdocs/compta/facture/class/facture-rec.class.php @@ -37,6 +37,7 @@ require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/factureligne.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/subtotals/class/commonsubtotal.class.php'; /** @@ -44,6 +45,8 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; */ class FactureRec extends CommonInvoice { + use CommonSubtotal; + const TRIGGER_PREFIX = 'BILLREC'; /** * @var string ID to identify managed object @@ -493,6 +496,12 @@ class FactureRec extends CommonInvoice if ($result < 0) { $error++; } + + $objectline->extraparams = $facline->extraparams; + $result = $objectline->setExtraParameters(); + if ($result < 0) { + $error++; + } } elseif ($result2 < 0) { $this->errors[] = $objectline->error; $error++; diff --git a/htdocs/compta/facture/class/facture.class.php b/htdocs/compta/facture/class/facture.class.php index 9aeb6dfd272..866c7155556 100644 --- a/htdocs/compta/facture/class/facture.class.php +++ b/htdocs/compta/facture/class/facture.class.php @@ -50,6 +50,7 @@ require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; require_once DOL_DOCUMENT_ROOT.'/societe/class/client.class.php'; require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php'; require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php'; +require_once DOL_DOCUMENT_ROOT.'/subtotals/class/commonsubtotal.class.php'; if (isModEnabled('accounting')) { require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php'; @@ -63,6 +64,8 @@ if (isModEnabled('accounting')) { */ class Facture extends CommonInvoice { + use CommonSubtotal; + /** * @var string ID to identify managed object */ @@ -1103,6 +1106,13 @@ class Facture extends CommonInvoice 1 ); + foreach ($this->lines as $line) { + if ($line->id == $result_insert) { + $line->extraparams = $_facrec->lines[$i]->extraparams; + $line->setExtraParameters(); + } + } + // Defined the new fk_parent_line if ($result_insert > 0 && $_facrec->lines[$i]->product_type == 9) { $fk_parent_line = $result_insert; @@ -4423,6 +4433,13 @@ class Facture extends CommonInvoice } $this->lines[] = $this->line; + } else { + foreach ($this->lines as $line) { + if ($line->id == $origin_id) { + $this->line->extraparams = $line->extraparams; + $this->line->setExtraParameters(); + } + } } if ($result > 0) { diff --git a/htdocs/compta/facture/class/factureligne.class.php b/htdocs/compta/facture/class/factureligne.class.php index e95fa0e8781..8e785f086df 100644 --- a/htdocs/compta/facture/class/factureligne.class.php +++ b/htdocs/compta/facture/class/factureligne.class.php @@ -185,7 +185,6 @@ class FactureLigne extends CommonInvoiceLine */ public $packaging; - /** * Constructor * diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index 6d524c73594..ef92c84817e 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -10994,7 +10994,7 @@ class Form } $out .= ' }); $(".' . $cssclass . '").change(function() { - $(this).closest("tr").toggleClass("highlight", this.checked); + $(this).closest("tr").toggleClass(this.checked); }); }); '; diff --git a/htdocs/core/lib/sendings.lib.php b/htdocs/core/lib/sendings.lib.php index 2d787cda10e..a308b33d814 100644 --- a/htdocs/core/lib/sendings.lib.php +++ b/htdocs/core/lib/sendings.lib.php @@ -242,7 +242,7 @@ function show_list_sending_receive($origin, $origin_id, $filter = '') $expedition = new Expedition($db); $warehousestatic = new Entrepot($db); - $sql = "SELECT obj.rowid, obj.fk_product, obj.label, obj.description, obj.product_type as fk_product_type, obj.qty as qty_asked, obj.date_start, obj.date_end,"; + $sql = "SELECT obj.rowid, obj.fk_product, obj.label, obj.description, obj.product_type as fk_product_type, obj.qty as qty_asked, obj.date_start, obj.date_end, obj.special_code,"; $sql .= " ed.rowid as edrowid, ed.qty as qty_shipped, ed.fk_expedition as expedition_id, ed.fk_elementdet, ed.fk_entrepot as warehouse_id,"; $sql .= " e.rowid as sendingid, e.ref as exp_ref, e.date_creation, e.date_delivery, e.date_expedition, e.billed, e.fk_statut as status, e.signed_status,"; $sql .= ' p.label as product_label, p.ref, p.fk_product_type, p.rowid as prodid, p.tobatch as product_tobatch,'; @@ -255,6 +255,9 @@ function show_list_sending_receive($origin, $origin_id, $filter = '') $sql .= " WHERE e.entity IN (".getEntity('expedition').")"; $sql .= " AND obj.fk_".$origin." = ".((int) $origin_id); $sql .= " AND obj.rowid = ed.fk_elementdet"; + if (isModEnabled('subtotals')) { + $sql .= " AND obj.special_code <> ".SUBTOTALS_SPECIAL_CODE; + } $sql .= " AND ed.fk_expedition = e.rowid"; if ($filter) { $sql .= $filter; diff --git a/htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php b/htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php index 993325c6f0b..10f54c67915 100644 --- a/htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php +++ b/htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php @@ -41,7 +41,6 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php'; - /** * Class to generate PDF orders with template Eratosthene */ @@ -556,10 +555,44 @@ class pdf_eratosthene extends ModelePDFCommandes // Loop on each lines $pageposbeforeprintlines = $pdf->getPage(); $pagenb = $pageposbeforeprintlines; + + $pdf_sub_options = array(); + $pdf_sub_options['titleshowuponpdf'] = 1; + $pdf_sub_options['titleshowtotalexludingvatonpdf'] = 1; + for ($i = 0; $i < $nblines; $i++) { $linePosition = $i + 1; $curY = $nexY; + $sub_options = $object->lines[$i]->extraparams["subtotal"] ?? array(); + + if ($object->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE) { + $level = $object->lines[$i]->qty; + if ($sub_options) { + if (isset($sub_options['titleshowuponpdf'])) { + $pdf_sub_options['titleshowuponpdf'] = isset($pdf_sub_options['titleshowuponpdf']) && $pdf_sub_options['titleshowuponpdf'] < $level ? $pdf_sub_options['titleshowuponpdf'] : $level; + } elseif (isset($pdf_sub_options['titleshowuponpdf']) && abs($level) <= $pdf_sub_options['titleshowuponpdf']) { + unset($pdf_sub_options['titleshowuponpdf']); + } + if (isset($sub_options['titleshowtotalexludingvatonpdf'])) { + $pdf_sub_options['titleshowtotalexludingvatonpdf'] = isset($pdf_sub_options['titleshowtotalexludingvatonpdf']) && $pdf_sub_options['titleshowtotalexludingvatonpdf'] < $level ? $pdf_sub_options['titleshowtotalexludingvatonpdf'] : $level; + } elseif (isset($pdf_sub_options['titleshowtotalexludingvatonpdf']) && abs($level) <= $pdf_sub_options['titleshowtotalexludingvatonpdf']) { + unset($pdf_sub_options['titleshowtotalexludingvatonpdf']); + } + } else { + if (isset($pdf_sub_options['titleshowuponpdf']) && abs($level) <= $pdf_sub_options['titleshowuponpdf']) { + unset($pdf_sub_options['titleshowuponpdf']); + } + if (isset($pdf_sub_options['titleshowtotalexludingvatonpdf']) && abs($level) <= $pdf_sub_options['titleshowtotalexludingvatonpdf']) { + unset($pdf_sub_options['titleshowtotalexludingvatonpdf']); + } + } + } + + if (($curY + 6) > ($this->page_hauteur - $heightforfooter) || isset($sub_options['titleforcepagebreak']) && !($pdf->getNumPages() == 1 && $curY == $tab_top + $this->tabTitleHeight)) { + $object->lines[$i]->pagebreak = true; + } + // in First Check line page break and add page if needed if (isset($object->lines[$i]->pagebreak) && $object->lines[$i]->pagebreak) { // New page @@ -569,7 +602,7 @@ class pdf_eratosthene extends ModelePDFCommandes } $pdf->setPage($pdf->getNumPages()); - $nexY = $tab_top_newpage; + $nexY = $curY = $tab_top_newpage; } $this->resetAfterColsLinePositionsData($nexY, $pdf->getPage()); @@ -624,8 +657,29 @@ class pdf_eratosthene extends ModelePDFCommandes // Description of product line if ($this->getColumnStatus('desc')) { - $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); - $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); + if ($object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); + $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); + } else { + $bg_color = colorStringToArray(getDolGlobalString("SUBTOTAL_BACK_COLOR_LEVEL_".abs($object->lines[$i]->qty))); + $pdf->SetFillColor($bg_color[0], $bg_color[1], $bg_color[2]); + $pdf->SetXY($pdf->GetX() + 1, $curY); + $pdf->MultiCell($this->page_largeur - $this->marge_droite - $this->marge_gauche - 2, 6, '', 0, '', true); + $previous_align = array(); + $previous_align['align'] = $this->cols['desc']['content']['align']; + if ($object->lines[$i]->qty < 0) { + $langs->load("subtotals"); + $object->lines[$i]->desc = $langs->trans("SubtotalOf", $object->lines[$i]->desc); + if ($previous_align['align'] == 'L') { + $this->cols['desc']['content']['align'] = 'R'; + } elseif ($previous_align['align'] == 'R') { + $this->cols['desc']['content']['align'] = 'L'; + } + } + $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); + $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); + $this->cols['desc']['content']['align'] = $previous_align['align']; // Re align if we printed a subtotal ligne + } } $afterPosData = $this->getMaxAfterColsLinePositionsData(); @@ -647,41 +701,50 @@ class pdf_eratosthene extends ModelePDFCommandes } // VAT Rate - if ($this->getColumnStatus('vat')) { + if ($this->getColumnStatus('vat') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $vat_rate = pdf_getlinevatrate($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'vat', $vat_rate); } // Unit price before discount - if ($this->getColumnStatus('subprice')) { + if ($this->getColumnStatus('subprice') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && isset($pdf_sub_options['titleshowuponpdf'])) { $up_excl_tax = pdf_getlineupexcltax($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'subprice', $up_excl_tax); } // Quantity // Enough for 6 chars - if ($this->getColumnStatus('qty')) { + if ($this->getColumnStatus('qty') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $qty = pdf_getlineqty($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'qty', $qty); } // Unit - if ($this->getColumnStatus('unit')) { + if ($this->getColumnStatus('unit') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $unit = pdf_getlineunit($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'unit', $unit); } // Discount on line - if ($this->getColumnStatus('discount') && $object->lines[$i]->remise_percent) { + if ($this->getColumnStatus('discount') && $object->lines[$i]->remise_percent && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $remise_percent = pdf_getlineremisepercent($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'discount', $remise_percent); } // Total excl tax line (HT) if ($this->getColumnStatus('totalexcltax')) { - $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails); - $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); + if ($object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && isset($pdf_sub_options['titleshowtotalexludingvatonpdf'])) { + $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); + } elseif ($object->lines[$i]->qty < 0 && isset($sub_options['subtotalshowtotalexludingvatonpdf'])) { + if (isModEnabled('multicurrency') && $object->multicurrency_code != $conf->currency) { + $total_excl_tax = $object->getSubtotalLineMulticurrencyAmount($object->lines[$i]); + } else { + $total_excl_tax = $object->getSubtotalLineAmount($object->lines[$i]); + } + $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); + } } // Total with tax line (TTC) diff --git a/htdocs/core/modules/expedition/doc/pdf_espadon.modules.php b/htdocs/core/modules/expedition/doc/pdf_espadon.modules.php index 125c901dbdb..a72b83cea76 100644 --- a/htdocs/core/modules/expedition/doc/pdf_espadon.modules.php +++ b/htdocs/core/modules/expedition/doc/pdf_espadon.modules.php @@ -572,6 +572,19 @@ class pdf_espadon extends ModelePdfExpedition $pagenb = $pageposbeforeprintlines; for ($i = 0; $i < $nblines; $i++) { $curY = $nexY; + + $sub_options = $object->lines[$i]->extraparams["subtotal"] ?? array(); + + if (($curY + 6) > ($this->page_hauteur - $heightforfooter) || isset($sub_options['titleforcepagebreak']) && !($pdf->getNumPages() == 1 && $curY == $tab_top + $this->tabTitleHeight)) { + $pdf->AddPage(); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + + $pdf->setPage($pdf->getNumPages()); + $nexY = $curY = $tab_top_newpage; + } + $pdf->SetFont('', '', $default_font_size - 1); // Into loop to work with multipage $pdf->SetTextColor(0, 0, 0); @@ -620,41 +633,62 @@ class pdf_espadon extends ModelePdfExpedition // Description of product line if ($this->getColumnStatus('desc')) { - $pdf->startTransaction(); - - $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); - - $pageposafter = $pdf->getPage(); - if ($pageposafter > $pageposbefore) { // There is a pagebreak - $pdf->rollbackTransaction(true); + if ($object->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE) { + $bg_color = colorStringToArray(getDolGlobalString("SUBTOTAL_BACK_COLOR_LEVEL_".abs($object->lines[$i]->qty))); + $pdf->SetFillColor($bg_color[0], $bg_color[1], $bg_color[2]); + $pdf->SetXY($pdf->GetX() + 1, $curY); + $pdf->MultiCell($this->page_largeur - $this->marge_droite - $this->marge_gauche - 2, 6, '', 0, '', true); + $previous_align = array(); + $previous_align['align'] = $this->cols['desc']['content']['align']; + if ($object->lines[$i]->qty < 0) { + $langs->load("subtotals"); + $object->lines[$i]->desc = $langs->trans("SubtotalOf", $object->lines[$i]->desc); + if ($previous_align['align'] == 'L') { + $this->cols['desc']['content']['align'] = 'R'; + } elseif ($previous_align['align'] == 'R') { + $this->cols['desc']['content']['align'] = 'L'; + } + } + $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); + $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); + $this->cols['desc']['content']['align'] = $previous_align['align']; // Re align if we printed a subtotal ligne + } else { + $pdf->startTransaction(); $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); $pageposafter = $pdf->getPage(); - $posyafter = $pdf->GetY(); - //var_dump($posyafter); var_dump(($this->page_hauteur - ($heightforfooter+$heightforfreetext+$heightforinfotot))); exit; - if ($posyafter > ($this->page_hauteur - ($heightforfooter + $heightforfreetext + $heightforsignature + $heightforinfotot))) { // There is no space left for total+free text - if ($i == ($nblines - 1)) { // No more lines, and no space left to show total, so we create a new page - $pdf->AddPage('', '', true); - if (!empty($tplidx)) { - $pdf->useTemplate($tplidx); + if ($pageposafter > $pageposbefore) { // There is a pagebreak + $pdf->rollbackTransaction(true); + + $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); + + $pageposafter = $pdf->getPage(); + $posyafter = $pdf->GetY(); + //var_dump($posyafter); var_dump(($this->page_hauteur - ($heightforfooter+$heightforfreetext+$heightforinfotot))); exit; + if ($posyafter > ($this->page_hauteur - ($heightforfooter + $heightforfreetext + $heightforsignature + $heightforinfotot))) { // There is no space left for total+free text + if ($i == ($nblines - 1)) { // No more lines, and no space left to show total, so we create a new page + $pdf->AddPage('', '', true); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + //if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD')) $this->_pagehead($pdf, $object, 0, $outputlangs); + $pdf->setPage($pageposafter + 1); } - //if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD')) $this->_pagehead($pdf, $object, 0, $outputlangs); - $pdf->setPage($pageposafter + 1); - } - } else { - // We found a page break - // Allows data in the first page if description is long enough to break in multiples pages - if (getDolGlobalString('MAIN_PDF_DATA_ON_FIRST_PAGE')) { - $showpricebeforepagebreak = 1; } else { - $showpricebeforepagebreak = 0; + // We found a page break + // Allows data in the first page if description is long enough to break in multiples pages + if (getDolGlobalString('MAIN_PDF_DATA_ON_FIRST_PAGE')) { + $showpricebeforepagebreak = 1; + } else { + $showpricebeforepagebreak = 0; + } } + } else { // No pagebreak + $pdf->commitTransaction(); } - } else { // No pagebreak - $pdf->commitTransaction(); + $posYAfterDescription = $pdf->GetY(); } - $posYAfterDescription = $pdf->GetY(); } $nexY = max($pdf->GetY(), $posYAfterImage); @@ -685,42 +719,42 @@ class pdf_espadon extends ModelePdfExpedition // weight $weighttxt = ''; - if (empty($object->lines[$i]->fk_product_type) && $object->lines[$i]->weight) { + if (empty($object->lines[$i]->fk_product_type) && $object->lines[$i]->weight && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $weighttxt = round($object->lines[$i]->weight * $object->lines[$i]->qty_shipped, getDolGlobalInt('SHIPMENT_ROUND_WEIGHT_ON_PDF', 5)).' '.measuringUnitString(0, "weight", $object->lines[$i]->weight_units, 1); } $voltxt = ''; - if (empty($object->lines[$i]->fk_product_type) && $object->lines[$i]->volume && !getDolGlobalString('SHIPPING_PDF_HIDE_VOLUME')) { + if (empty($object->lines[$i]->fk_product_type) && $object->lines[$i]->volume && !getDolGlobalString('SHIPPING_PDF_HIDE_VOLUME') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $voltxt = round($object->lines[$i]->volume * $object->lines[$i]->qty_shipped, getDolGlobalInt('SHIPMENT_ROUND_VOLUME_ON_PDF', 5)).' '.measuringUnitString(0, "volume", $object->lines[$i]->volume_units ? $object->lines[$i]->volume_units : 0, 1); } // weight and volume - if ($this->getColumnStatus('weight')) { + if ($this->getColumnStatus('weight') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $this->printStdColumnContent($pdf, $curY, 'weight', $weighttxt.(($weighttxt && $voltxt) ? '
' : '').$voltxt); $nexY = max($pdf->GetY(), $nexY); } - if ($this->getColumnStatus('qty_asked')) { + if ($this->getColumnStatus('qty_asked') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $this->printStdColumnContent($pdf, $curY, 'qty_asked', (string) $object->lines[$i]->qty_asked); $nexY = max($pdf->GetY(), $nexY); } - if ($this->getColumnStatus('unit_order')) { + if ($this->getColumnStatus('unit_order') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $this->printStdColumnContent($pdf, $curY, 'unit_order', measuringUnitString((int) $object->lines[$i]->fk_unit)); $nexY = max($pdf->GetY(), $nexY); } - if ($this->getColumnStatus('qty_shipped')) { + if ($this->getColumnStatus('qty_shipped') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $this->printStdColumnContent($pdf, $curY, 'qty_shipped', (string) $object->lines[$i]->qty_shipped); $nexY = max($pdf->GetY(), $nexY); } - if ($this->getColumnStatus('subprice')) { + if ($this->getColumnStatus('subprice') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $this->printStdColumnContent($pdf, $curY, 'subprice', price($object->lines[$i]->subprice, 0, $outputlangs)); $nexY = max($pdf->GetY(), $nexY); } // Extrafields - if (!empty($object->lines[$i]->array_options)) { + if (!empty($object->lines[$i]->array_options) && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { foreach ($object->lines[$i]->array_options as $extrafieldColKey => $extrafieldValue) { if ($this->getColumnStatus($extrafieldColKey)) { $extrafieldValue = $this->getExtrafieldContent($object->lines[$i], $extrafieldColKey, $outputlangs); diff --git a/htdocs/core/modules/facture/doc/pdf_sponge.modules.php b/htdocs/core/modules/facture/doc/pdf_sponge.modules.php index 9eb2577b223..dc7a49730a8 100644 --- a/htdocs/core/modules/facture/doc/pdf_sponge.modules.php +++ b/htdocs/core/modules/facture/doc/pdf_sponge.modules.php @@ -42,7 +42,6 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php'; - /** * Class to manage PDF invoice template sponge */ @@ -702,10 +701,44 @@ class pdf_sponge extends ModelePDFFactures // Loop on each lines $pageposbeforeprintlines = $pdf->getPage(); $pagenb = $pageposbeforeprintlines; + + $pdf_sub_options = array(); + $pdf_sub_options['titleshowuponpdf'] = 1; + $pdf_sub_options['titleshowtotalexludingvatonpdf'] = 1; + for ($i = 0; $i < $nblines; $i++) { $linePosition = $i + 1; $curY = $nexY; + $sub_options = $object->lines[$i]->extraparams["subtotal"] ?? array(); + + if ($object->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE) { + $level = $object->lines[$i]->qty; + if ($sub_options) { + if (isset($sub_options['titleshowuponpdf'])) { + $pdf_sub_options['titleshowuponpdf'] = isset($pdf_sub_options['titleshowuponpdf']) && $pdf_sub_options['titleshowuponpdf'] < $level ? $pdf_sub_options['titleshowuponpdf'] : $level; + } elseif (isset($pdf_sub_options['titleshowuponpdf']) && abs($level) <= $pdf_sub_options['titleshowuponpdf']) { + unset($pdf_sub_options['titleshowuponpdf']); + } + if (isset($sub_options['titleshowtotalexludingvatonpdf'])) { + $pdf_sub_options['titleshowtotalexludingvatonpdf'] = isset($pdf_sub_options['titleshowtotalexludingvatonpdf']) && $pdf_sub_options['titleshowtotalexludingvatonpdf'] < $level ? $pdf_sub_options['titleshowtotalexludingvatonpdf'] : $level; + } elseif (isset($pdf_sub_options['titleshowtotalexludingvatonpdf']) && abs($level) <= $pdf_sub_options['titleshowtotalexludingvatonpdf']) { + unset($pdf_sub_options['titleshowtotalexludingvatonpdf']); + } + } else { + if (isset($pdf_sub_options['titleshowuponpdf']) && abs($level) <= $pdf_sub_options['titleshowuponpdf']) { + unset($pdf_sub_options['titleshowuponpdf']); + } + if (isset($pdf_sub_options['titleshowtotalexludingvatonpdf']) && abs($level) <= $pdf_sub_options['titleshowtotalexludingvatonpdf']) { + unset($pdf_sub_options['titleshowtotalexludingvatonpdf']); + } + } + } + + if (($curY + 6) > ($this->page_hauteur - $this->heightforfooter) || isset($sub_options['titleforcepagebreak']) && !($pdf->getNumPages() == 1 && $curY == $this->tab_top + $this->tabTitleHeight)) { + $object->lines[$i]->pagebreak = true; + } + // in First Check line page break and add page if needed if (isset($object->lines[$i]->pagebreak) && $object->lines[$i]->pagebreak) { // New page @@ -715,7 +748,7 @@ class pdf_sponge extends ModelePDFFactures } $pdf->setPage($pdf->getNumPages()); - $nexY = $this->tab_top_newpage; + $nexY = $curY = $this->tab_top_newpage; } $this->resetAfterColsLinePositionsData($nexY, $pdf->getPage()); @@ -767,8 +800,29 @@ class pdf_sponge extends ModelePDFFactures // Description of product line if ($this->getColumnStatus('desc')) { - $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); - $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); + if ($object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); + $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); + } else { + $bg_color = colorStringToArray(getDolGlobalString("SUBTOTAL_BACK_COLOR_LEVEL_".abs($object->lines[$i]->qty))); + $pdf->SetFillColor($bg_color[0], $bg_color[1], $bg_color[2]); + $pdf->SetXY($pdf->GetX() + 1, $curY); + $pdf->MultiCell($this->page_largeur - $this->marge_droite - $this->marge_gauche - 2, 6, '', 0, '', true); + $previous_align = array(); + $previous_align['align'] = $this->cols['desc']['content']['align']; + if ($object->lines[$i]->qty < 0) { + $langs->load("subtotals"); + $object->lines[$i]->desc = $langs->trans("SubtotalOf", $object->lines[$i]->desc); + if ($previous_align['align'] == 'L') { + $this->cols['desc']['content']['align'] = 'R'; + } elseif ($previous_align['align'] == 'R') { + $this->cols['desc']['content']['align'] = 'L'; + } + } + $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); + $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); + $this->cols['desc']['content']['align'] = $previous_align['align']; // Re align if we printed a subtotal ligne + } } @@ -792,46 +846,55 @@ class pdf_sponge extends ModelePDFFactures } // VAT Rate - if ($this->getColumnStatus('vat')) { + if ($this->getColumnStatus('vat') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $vat_rate = pdf_getlinevatrate($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'vat', $vat_rate); } // Unit price before discount - if ($this->getColumnStatus('subprice')) { + if ($this->getColumnStatus('subprice') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && isset($pdf_sub_options['titleshowuponpdf'])) { $up_excl_tax = pdf_getlineupexcltax($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'subprice', $up_excl_tax); } // Quantity // Enough for 6 chars - if ($this->getColumnStatus('qty')) { + if ($this->getColumnStatus('qty') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $qty = pdf_getlineqty($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'qty', $qty); } // Situation progress - if ($this->getColumnStatus('progress')) { + if ($this->getColumnStatus('progress') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $progress = pdf_getlineprogress($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'progress', $progress); } // Unit - if ($this->getColumnStatus('unit')) { + if ($this->getColumnStatus('unit') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $unit = pdf_getlineunit($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'unit', $unit); } // Discount on line - if ($this->getColumnStatus('discount') && $object->lines[$i]->remise_percent) { + if ($this->getColumnStatus('discount') && $object->lines[$i]->remise_percent && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $remise_percent = pdf_getlineremisepercent($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'discount', $remise_percent); } // Total excl tax line (HT) if ($this->getColumnStatus('totalexcltax')) { - $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails); - $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); + if ($object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && isset($pdf_sub_options['titleshowtotalexludingvatonpdf'])) { + $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); + } elseif ($object->lines[$i]->qty < 0 && isset($sub_options['subtotalshowtotalexludingvatonpdf'])) { + if (isModEnabled('multicurrency') && $object->multicurrency_code != $conf->currency) { + $total_excl_tax = $object->getSubtotalLineMulticurrencyAmount($object->lines[$i]); + } else { + $total_excl_tax = $object->getSubtotalLineAmount($object->lines[$i]); + } + $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); + } } // Total with tax line (TTC) diff --git a/htdocs/core/modules/modSubtotals.class.php b/htdocs/core/modules/modSubtotals.class.php new file mode 100644 index 00000000000..1330bd0d695 --- /dev/null +++ b/htdocs/core/modules/modSubtotals.class.php @@ -0,0 +1,168 @@ + + * Copyright (C) 2004-2011 Laurent Destailleur + * Copyright (C) 2005-2010 Regis Houssin + * Copyright (C) 2011 Dimitri Mouillard + * Copyright (C) 2013 Juanjo Menent + * Copyright (C) 2018 Charlene Benke + * Copyright (C) 2024 MDW + * + * 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/ + */ + +/** + * \defgroup subtotals Module subtotals + * \brief Module for subtotal lines management + * + * \file htdocs/core/modules/modsubtotal.class.php + * \ingroup subtotals + * \brief Description and activation file for the module subtotals + */ +include_once DOL_DOCUMENT_ROOT."/core/modules/DolibarrModules.class.php"; +require_once DOL_DOCUMENT_ROOT.'/subtotals/class/commonsubtotal.class.php'; + +/** + * Description and activation class for module subtotals + */ +class modSubtotals extends DolibarrModules +{ + /** + * Constructor. Define names, constants, directories, boxes, permissions + * + * @param DoliDB $db Database handler + */ + public function __construct($db) + { + global $conf, $user; // Required by some include code + + $this->db = $db; + + // Id for module (must be unique). + // Use here a free id (See in Home -> System information -> Dolibarr for list of used modules id). + $this->numero = SUBTOTALS_SPECIAL_CODE; + // Key text used to identify module (for permissions, menus, etc...) + $this->rights_class = 'subtotals'; + + // Family can be 'crm','financial','hr','projects','products','ecm','technic','other' + // It is used to group modules in module setup page + $this->family = "technic"; + $this->module_position = '42'; + // Module label (no space allowed), used if translation string 'ModuleXXXName' not found (where XXX is value of numeric property 'numero' of module) + $this->name = preg_replace('/^mod/i', '', get_class($this)); + // Module description, used if translation string 'ModuleXXXDesc' not found (where XXX is value of numeric property 'numero' of module) + $this->description = "Subtotal and title lines for certain documents"; + // Possible values for version are: 'development', 'experimental', 'dolibarr' or version + $this->version = 'dolibarr'; + // Key used in llx_const table to save module status enabled/disabled (where MYMODULE is value of property name of module in uppercase) + $this->const_name = 'MAIN_MODULE_'.strtoupper($this->name); + // Name of image file used for this module. + // If file is in theme/yourtheme/img directory under name object_pictovalue.png, use this->picto='pictovalue' + // If file is in module/img directory under name object_pictovalue.png, use this->picto='pictovalue@module' + $this->picto = 'donation'; + + // Data directories to create when module is enabled. + // Example: this->dirs = array("/mymodule/temp"); + $this->dirs = array("/subtotals/temp"); + $r = 0; + + // Config pages + $this->config_page_url = array("subtotals.php"); + + // Dependencies + $this->hidden = false; // A condition to hide module + $this->depends = array(); // List of module class names as string that must be enabled if this module is enabled + $this->requiredby = array(); // List of module ids to disable if this one is disabled + $this->conflictwith = array(); // List of module class names as string this module is in conflict with + $this->phpmin = array(7, 0); // Minimum version of PHP required by module + $this->need_dolibarr_version = array(3, 0); // Minimum version of Dolibarr required by module + $this->langfiles = array("subtotals"); + + // Constants + // Example: $this->const=array(0=>array('MYMODULE_MYNEWCONST1','chaine','myvalue','This is a constant to add',0), + // 1=>array('MYMODULE_MYNEWCONST2','chaine','myvalue','This is another constant to add',0) ); + $this->const = array(); // List of particular constants to add when module is enabled (key, 'chaine', value, desc, visible, 0 or 'allentities') + $r = 0; + + // Array to add new pages in new tabs + //$this->tabs[] = array('data'=>'user:+paidholidays:CPTitreMenu:holiday:$user->rights->holiday->read:/holiday/list.php?mainmenu=hrm&id=__ID__'); // We avoid to get one tab for each module. RH data are already in RH tab. + $this->tabs[] = array(); // To add a new tab identified by code tabname1 + + // Boxes + $this->boxes = array(); // List of boxes + $r = 0; + + // Add here list of php file(s) stored in includes/boxes that contains class to show a box. + // Example: + //$this->boxes[$r][1] = "myboxa.php"; + //$r++; + //$this->boxes[$r][1] = "myboxb.php"; + //$r++; + + // Permissions + $this->rights = array(); // Permission array used by this module + $r = 0; + + // Menus + //------- + $this->menu = 1; // This module add menu entries. They are coded into menu manager. + + $this->module_parts = array('substitutions' => 1); + + // Exports + $r = 0; + } + + /** + * Function called when module is enabled. + * The init function add constants, boxes, permissions and menus (defined in constructor) into Dolibarr database. + * It also creates data directories + * + * @param string $options Options when enabling module ('', 'newboxdefonly', 'noboxes') + * @return int 1 if OK, 0 if KO + */ + public function init($options = '') + { + global $conf; + + // Permissions + $this->remove($options); + + //ODT template + /*$src=DOL_DOCUMENT_ROOT.'/install/doctemplates/holiday/template_holiday.odt'; + $dirodt=DOL_DATA_ROOT.'/doctemplates/holiday'; + $dest=$dirodt.'/template_order.odt'; + + if (file_exists($src) && ! file_exists($dest)) + { + require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; + dol_mkdir($dirodt); + $result=dol_copy($src, $dest, 0, 0); + if ($result < 0) + { + $langs->load("errors"); + $this->error=$langs->trans('ErrorFailToCopyFile', $src, $dest); + return 0; + } + } + */ + + $sql = array( + // "DELETE FROM ".MAIN_DB_PREFIX."document_model WHERE nom = '".$this->db->escape($this->const[0][2])."' AND type = 'holiday' AND entity = ".((int) $conf->entity), + // "INSERT INTO ".MAIN_DB_PREFIX."document_model (nom, type, entity) VALUES('".$this->db->escape($this->const[0][2])."','holiday',".((int) $conf->entity).")" + ); + + return $this->_init($sql, $options); + } +} diff --git a/htdocs/core/modules/propale/doc/pdf_cyan.modules.php b/htdocs/core/modules/propale/doc/pdf_cyan.modules.php index 0f090cf26a3..17f7abe524f 100644 --- a/htdocs/core/modules/propale/doc/pdf_cyan.modules.php +++ b/htdocs/core/modules/propale/doc/pdf_cyan.modules.php @@ -39,7 +39,6 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php'; - /** * Class to generate PDF proposal Cyan */ @@ -559,10 +558,43 @@ class pdf_cyan extends ModelePDFPropales $pageposbeforeprintlines = $pdf->getPage(); $pagenb = $pageposbeforeprintlines; + $pdf_sub_options = array(); + $pdf_sub_options['titleshowuponpdf'] = 1; + $pdf_sub_options['titleshowtotalexludingvatonpdf'] = 1; + for ($i = 0; $i < $nblines; $i++) { $linePosition = $i + 1; $curY = $nexY; + $sub_options = $object->lines[$i]->extraparams["subtotal"] ?? array(); + + if ($object->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE) { + $level = $object->lines[$i]->qty; + if ($sub_options) { + if (isset($sub_options['titleshowuponpdf'])) { + $pdf_sub_options['titleshowuponpdf'] = isset($pdf_sub_options['titleshowuponpdf']) && $pdf_sub_options['titleshowuponpdf'] < $level ? $pdf_sub_options['titleshowuponpdf'] : $level; + } elseif (isset($pdf_sub_options['titleshowuponpdf']) && abs($level) <= $pdf_sub_options['titleshowuponpdf']) { + unset($pdf_sub_options['titleshowuponpdf']); + } + if (isset($sub_options['titleshowtotalexludingvatonpdf'])) { + $pdf_sub_options['titleshowtotalexludingvatonpdf'] = isset($pdf_sub_options['titleshowtotalexludingvatonpdf']) && $pdf_sub_options['titleshowtotalexludingvatonpdf'] < $level ? $pdf_sub_options['titleshowtotalexludingvatonpdf'] : $level; + } elseif (isset($pdf_sub_options['titleshowtotalexludingvatonpdf']) && abs($level) <= $pdf_sub_options['titleshowtotalexludingvatonpdf']) { + unset($pdf_sub_options['titleshowtotalexludingvatonpdf']); + } + } else { + if (isset($pdf_sub_options['titleshowuponpdf']) && abs($level) <= $pdf_sub_options['titleshowuponpdf']) { + unset($pdf_sub_options['titleshowuponpdf']); + } + if (isset($pdf_sub_options['titleshowtotalexludingvatonpdf']) && abs($level) <= $pdf_sub_options['titleshowtotalexludingvatonpdf']) { + unset($pdf_sub_options['titleshowtotalexludingvatonpdf']); + } + } + } + + if (($curY + 6) > ($this->page_hauteur - $heightforfooter) || isset($sub_options['titleforcepagebreak']) && !($pdf->getNumPages() == 1 && $curY == $tab_top + $this->tabTitleHeight)) { + $object->lines[$i]->pagebreak = true; + } + // in First Check line page break and add page if needed if (isset($object->lines[$i]->pagebreak) && $object->lines[$i]->pagebreak) { // New page @@ -572,7 +604,7 @@ class pdf_cyan extends ModelePDFPropales } $pdf->setPage($pdf->getNumPages()); - $nexY = $tab_top_newpage; + $nexY = $curY = $tab_top_newpage; } $this->resetAfterColsLinePositionsData($nexY, $pdf->getPage()); @@ -625,8 +657,29 @@ class pdf_cyan extends ModelePDFPropales $pdf->setPageOrientation('', true, $heightforfooter); // The only function to edit the bottom margin of current page to set it. if ($this->getColumnStatus('desc')) { - $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); - $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); + if ($object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); + $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); + } else { + $bg_color = colorStringToArray(getDolGlobalString("SUBTOTAL_BACK_COLOR_LEVEL_".abs($object->lines[$i]->qty))); + $pdf->SetFillColor($bg_color[0], $bg_color[1], $bg_color[2]); + $pdf->SetXY($pdf->GetX() + 1, $curY + 1); + $pdf->MultiCell($this->page_largeur - $this->marge_droite - $this->marge_gauche - 2, 5, '', 0, '', true); + $previous_align = array(); + $previous_align['align'] = $this->cols['desc']['content']['align']; + if ($object->lines[$i]->qty < 0) { + $langs->load("subtotals"); + $object->lines[$i]->desc = $langs->trans("SubtotalOf", $object->lines[$i]->desc); + if ($previous_align['align'] == 'L') { + $this->cols['desc']['content']['align'] = 'R'; + } elseif ($previous_align['align'] == 'R') { + $this->cols['desc']['content']['align'] = 'L'; + } + } + $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); + $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); + $this->cols['desc']['content']['align'] = $previous_align['align']; // Re align if we printed a subtotal ligne + } } $afterPosData = $this->getMaxAfterColsLinePositionsData(); @@ -651,41 +704,50 @@ class pdf_cyan extends ModelePDFPropales } // VAT Rate - if ($this->getColumnStatus('vat')) { + if ($this->getColumnStatus('vat') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $vat_rate = pdf_getlinevatrate($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'vat', $vat_rate); } // Unit price before discount - if ($this->getColumnStatus('subprice')) { + if ($this->getColumnStatus('subprice') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && isset($pdf_sub_options['titleshowuponpdf'])) { $up_excl_tax = pdf_getlineupexcltax($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'subprice', $up_excl_tax); } // Quantity // Enough for 6 chars - if ($this->getColumnStatus('qty')) { + if ($this->getColumnStatus('qty') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $qty = pdf_getlineqty($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'qty', $qty); } // Unit - if ($this->getColumnStatus('unit')) { + if ($this->getColumnStatus('unit') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { $unit = pdf_getlineunit($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'unit', $unit); } // Discount on line - if ($this->getColumnStatus('discount') && $object->lines[$i]->remise_percent) { + if ($this->getColumnStatus('discount') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && $object->lines[$i]->remise_percent) { $remise_percent = pdf_getlineremisepercent($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'discount', $remise_percent); } // Total excl tax line (HT) if ($this->getColumnStatus('totalexcltax')) { - $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails); - $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); + if ($object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && isset($pdf_sub_options['titleshowtotalexludingvatonpdf'])) { + $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); + } elseif ($object->lines[$i]->qty < 0 && isset($sub_options['subtotalshowtotalexludingvatonpdf'])) { + if (isModEnabled('multicurrency') && $object->multicurrency_code != $conf->currency) { + $total_excl_tax = $object->getSubtotalLineMulticurrencyAmount($object->lines[$i]); + } else { + $total_excl_tax = $object->getSubtotalLineAmount($object->lines[$i]); + } + $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); + } } // Total with tax line (TTC) diff --git a/htdocs/core/tpl/objectline_edit.tpl.php b/htdocs/core/tpl/objectline_edit.tpl.php index f20c6a1d79a..01c8d9c4a33 100644 --- a/htdocs/core/tpl/objectline_edit.tpl.php +++ b/htdocs/core/tpl/objectline_edit.tpl.php @@ -61,6 +61,11 @@ if (empty($object) || !is_object($object)) { @phan-var-force string $var '; +// Handle subtotals line edit +if (defined('SUBTOTALS_SPECIAL_CODE') && $line->special_code == SUBTOTALS_SPECIAL_CODE) { + return require DOL_DOCUMENT_ROOT.'/core/tpl/subtotal_edit.tpl.php'; +} + $usemargins = 0; if (isModEnabled('margin') && !empty($object->element) && in_array($object->element, array('facture', 'facturerec', 'propal', 'commande'))) { $usemargins = 1; diff --git a/htdocs/core/tpl/objectline_view.tpl.php b/htdocs/core/tpl/objectline_view.tpl.php index 07f1464fd92..666d89b9478 100644 --- a/htdocs/core/tpl/objectline_view.tpl.php +++ b/htdocs/core/tpl/objectline_view.tpl.php @@ -78,6 +78,11 @@ if (empty($object) || !is_object($object)) { @phan-var-force Object $objp '; +// Handle subtotals line view +if (defined('SUBTOTALS_SPECIAL_CODE') && $line->special_code == SUBTOTALS_SPECIAL_CODE) { + return require DOL_DOCUMENT_ROOT.'/core/tpl/subtotal_view.tpl.php'; +} + global $mysoc; global $forceall, $senderissupplier, $inputalsopricewithtax, $outputalsopricetotalwithtax; diff --git a/htdocs/core/tpl/originproductline.tpl.php b/htdocs/core/tpl/originproductline.tpl.php index ff8902fc24d..f4029abe9f0 100644 --- a/htdocs/core/tpl/originproductline.tpl.php +++ b/htdocs/core/tpl/originproductline.tpl.php @@ -20,6 +20,7 @@ /** * @var CommonObject $this * @var Conf $conf + * @var CommonObjectLine $line */ // Protection to avoid direct call of template if (empty($conf) || !is_object($conf)) { @@ -31,7 +32,25 @@ if (empty($conf) || !is_object($conf)) { element == 'shipping') { + $classname = ucfirst($line->element_type); + $objectsrc = new $classname($this->db); + $objectsrc_line = new $objectsrc->class_element_line($this->db); + '@phan-var-force CommonObjectLine $objectsrc_line'; + $objectsrc_line->fetch($line->origin_line_id); + $shipping_use_tpl = ($objectsrc_line->special_code == SUBTOTALS_SPECIAL_CODE); +} + +// Handle subtotals line edit +if (defined('SUBTOTALS_SPECIAL_CODE') && $line->special_code == SUBTOTALS_SPECIAL_CODE || isset($shipping_use_tpl)) { + return require DOL_DOCUMENT_ROOT.'/core/tpl/originsubtotalline.tpl.php'; +} + print ''; print ''.$this->tpl['label'].''; print ''.$this->tpl['description'].''; diff --git a/htdocs/core/tpl/originsubtotalline.tpl.php b/htdocs/core/tpl/originsubtotalline.tpl.php new file mode 100644 index 00000000000..21dc6c80591 --- /dev/null +++ b/htdocs/core/tpl/originsubtotalline.tpl.php @@ -0,0 +1,70 @@ + + * Copyright (C) 2017 Charlie Benke + * Copyright (C) 2022 Gauthier VERDOL + * Copyright (C) 2024 Frédéric France + * + * 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 . + */ +/** + * @var CommonObject $this + * @var Conf $conf + * @var CommonObjectLine $line + */ +// Protection to avoid direct call of template +if (empty($conf) || !is_object($conf)) { + print "Error, template page can't be called as URL"; + exit(1); +} +?> + + +tpl['id'].'" class="'.(empty($this->tpl['strike']) ? '' : ' strikefordisabled').'" style="background:#'.$this->getSubtotalColors($line->qty).'">'; +print ''.$this->tpl['description'].''; +print ''; +print ''; +print ''; +if (isModEnabled("multicurrency")) { + print ''; +} + +print ''; +if (getDolGlobalString('PRODUCT_USE_UNITS')) { + print ''; +} + +print ''; +if ($this->tpl['qty'] < 0) { + print ''.$this->getSubtotalLineAmount($line).''; +} else { + print ''; +} + +$selected = 1; +if (!empty($selectedLines) && !in_array($this->tpl['id'], $selectedLines)) { + $selected = 0; +} +print ''; +print ''; +print ''; +print ''."\n"; +?> + diff --git a/htdocs/core/tpl/subtotal_ajaxrow.tpl.php b/htdocs/core/tpl/subtotal_ajaxrow.tpl.php new file mode 100644 index 00000000000..5021a71bfdd --- /dev/null +++ b/htdocs/core/tpl/subtotal_ajaxrow.tpl.php @@ -0,0 +1,299 @@ + + * Copyright (C) 2024 MDW + * Copyright (C) 2024 Frédéric France + * + * 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/ + */ + +/** + * Javascript code to activate the drag and drop on lines + * while using subtotal module + * You can use this if you want to be able to drag and drop rows of a HTML table. + * You must add id="tablelines" on table level tag + * $object and $object->id must be defined + * $object->fk_element or $fk_element must be defined + * you must have ($nboflines or count($object->lines) or count($taskarray) > 0) + * you must have $table_element_line = 'tablename' or $object->table_element_line with line to move + * + */ +/** + * @var Conf $conf + * @var CommonObject $object + * @var Translate $langs + * + * @var ?string $filepath + * @var ?string $fk_element + * @var ?int $nboflines + * @var ?string $tagidfortablednd + */ +// Protection to avoid direct call of template +if (empty($object) || !is_object($object)) { + print "Error, template page ".basename(__FILE__)." can't be called with no object defined."; + exit; +} +' +@phan-var-force ?string $fk_element +@phan-var-force ?Task[] $tasksarray +'; + +?> + + +id; +$fk_element = empty($object->fk_element) ? $fk_element : $object->fk_element; +$table_element_line = (empty($table_element_line) ? $object->table_element_line : $table_element_line); +$nboflines = count($object->lines); +$forcereloadpage = !getDolGlobalString('MAIN_FORCE_RELOAD_PAGE') ? 0 : 1; +$tagidfortablednd = (empty($tagidfortablednd) ? 'tablelines' : $tagidfortablednd); +$filepath = (empty($filepath) ? '' : $filepath); +$langs->load("subtotals"); + +if (GETPOST('action', 'aZ09') != 'editline' && $nboflines > 1 && $conf->browser->layout != 'phone') { ?> + + + + + + diff --git a/htdocs/core/tpl/subtotal_create.tpl.php b/htdocs/core/tpl/subtotal_create.tpl.php new file mode 100644 index 00000000000..9074724ca25 --- /dev/null +++ b/htdocs/core/tpl/subtotal_create.tpl.php @@ -0,0 +1,74 @@ + + * Copyright (C) 2024 MDW + * Copyright (C) 2024 Frédéric France + * + * 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/ + */ + +/** + * @var CommonObject $this + * @var CommonObject $object + * @var CommonObjectLine $line + * @var Form $form + * @var HookManager $hookmanager + * @var Translate $langs + * @var User $user + * + * @var array $depth_array + * @var array $titles + * @var string $type + */ + +' +@phan-var-force CommonObject $this +'; + +$depth_array = $depth_array ?? array(); +$titles = $titles ?? array(); + +if ($type == 'subtotal' && empty($titles)) { + setEventMessages("NoTitleError", null, 'errors'); + return; +} + +$formquestion = array(); + +if ($type == 'title') { + $formquestion = array( + array('type' => 'text', 'name' => 'subtotallinedesc', 'label' => $langs->trans("SubtotalLineDesc"), 'moreattr' => 'placeholder="' . $langs->trans("Description") . '"'), + array('type' => 'select', 'name' => 'subtotallinelevel', 'label' => $langs->trans("SubtotalLineLevel"), 'values' => $depth_array, 'default' => 1, 'select_show_empty' => 0), + array('type' => 'checkbox', 'value' => false, 'name' => 'titleshowuponpdf', 'label' => $langs->trans("ShowUPOnPDF")), + array('type' => 'checkbox', 'value' => false, 'name' => 'titleshowtotalexludingvatonpdf', 'label' => $langs->trans("ShowTotalExludingVATOnPDF")), + array('type' => 'checkbox', 'value' => false, 'name' => 'titleforcepagebreak', 'label' => $langs->trans("ForcePageBreak")), + ); +} elseif ($type == 'subtotal') { + $formquestion = array( + array('type' => 'select', 'name' => 'subtotaltitleline', 'label' => $langs->trans("CorrespondingTitleLine"), 'values' => $titles, 'select_show_empty' => 0), + array('type' => 'checkbox', 'value' => false, 'name' => 'subtotalshowtotalexludingvatonpdf', 'label' => $langs->trans("ShowTotalExludingVATOnPDF")), + ); +} + +$page = $_SERVER["PHP_SELF"]; + +if ($object->element == 'facture') { + $page .= '?facid=' . $object->id; +} elseif (in_array($object->element, array('propal', 'commande', 'facturerec', 'shipping'))) { + $page .= '?id=' . $object->id; +} + +$form_title = $type == 'title' ? $langs->trans('AddTitleLine') : $langs->trans('AddSubtotalLine'); + +print $form->formconfirm($page, $form_title, '', 'confirm_add' . $type . 'line', $formquestion, 'yes', 1); diff --git a/htdocs/core/tpl/subtotal_edit.tpl.php b/htdocs/core/tpl/subtotal_edit.tpl.php new file mode 100644 index 00000000000..aa57f7fa068 --- /dev/null +++ b/htdocs/core/tpl/subtotal_edit.tpl.php @@ -0,0 +1,165 @@ + + * Copyright (C) 2024 MDW + * Copyright (C) 2024 Frédéric France + * + * 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/ + */ + +/** + * @var CommonObject $this + * @var CommonObject $object + * @var CommonObjectLine $line + * @var Form $form + * @var Translate $langs + * @var User $user + * @var Conf $conf + * @var int $i + */ + +' +@phan-var-force Propal|Contrat|Commande|Facture|Expedition|Delivery|FactureFournisseur|FactureFournisseur|SupplierProposal $object +@phan-var-force CommonObjectLine|CommonInvoiceLine|CommonOrderLine|ExpeditionLigne|PropaleLigne $line +'; + +// Options for subtotal +$sub_options = $line->extraparams["subtotal"] ?? array(); + +$titleshowuponpdf = !empty($sub_options['titleshowuponpdf']); +$titleshowtotalexludingvatonpdf = !empty($sub_options['titleshowtotalexludingvatonpdf']); +$titleforcepagebreak = !empty($sub_options['titleforcepagebreak']); +$subtotalshowtotalexludingvatonpdf = !empty($sub_options['subtotalshowtotalexludingvatonpdf']); + +$line_options = array( + 'titleshowuponpdf' => array('type' => array('title'), 'value' => 'on', 'checked' => $titleshowuponpdf, 'trans_key' => 'ShowUPOnPDF'), + 'titleshowtotalexludingvatonpdf' => array('type' => array('title'), 'value' => 'on', 'checked' => $titleshowtotalexludingvatonpdf, 'trans_key' => 'ShowTotalExludingVATOnPDF'), + 'titleforcepagebreak' => array('type' => array('title'), 'value' => 'on', 'checked' => $titleforcepagebreak, 'trans_key' => 'ForcePageBreak'), + 'subtotalshowtotalexludingvatonpdf' => array('type' => array('subtotal'), 'value' => 'on', 'checked' => $subtotalshowtotalexludingvatonpdf, 'trans_key' => 'ShowTotalExludingVATOnPDF'), +); + +// Line type +$line_type = $line->qty > 0 ? 'title' : 'subtotal'; + +print "\n"; + +echo ''; + +if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER')) { + echo '' . ($i + 1) . ''; +} + +// Base colspan if there is no module activated to display line correctly +$colspan = 4; + +// Handling colspan if margin module is enabled +if (!empty($object->element) && in_array($object->element, array('facture', 'facturerec', 'propal', 'commande')) && isModEnabled('margin') && empty($user->socid)) { + if ($user->hasRight('margins', 'creer')) { + $colspan += 1; + } + if (getDolGlobalString('DISPLAY_MARGIN_RATES') && $user->hasRight('margins', 'liretous')) { + $colspan += 1; + } + if (getDolGlobalString('DISPLAY_MARK_RATES') && $user->hasRight('margins', 'liretous')) { + $colspan += 1; + } +} + +// Handling colspan if multicurrency module is enabled +if (isModEnabled('multicurrency') && $object->multicurrency_code != $conf->currency) { + $colspan += 1; +} + +// Handling colspan if MAIN_NO_INPUT_PRICE_WITH_TAX conf is enabled +if (!getDolGlobalInt('MAIN_NO_INPUT_PRICE_WITH_TAX')) { + $colspan += 1; +} + +// Handling colspan if PRODUCT_USE_UNITS conf is enabled +if (getDolGlobalString('PRODUCT_USE_UNITS')) { + $colspan += 1; +} + +?> + + +
+ + + + + + + + fk_prev_id != null && in_array($object->element, array('facture', 'facturedet'))) { + /** @var CommonInvoice $object */ + // @phan-suppress-next-line PhanUndeclaredConstantOfClass + if ($object->type == $object::TYPE_SITUATION) { // The constant TYPE_SITUATION exists only for object invoice + // Set constant to disallow editing during a situation cycle + $situationinvoicelinewithparent = 1; + } + } + + // Do not allow editing during a situation cycle + // but in some situations that is required (update legal information for example) + if (getDolGlobalString('INVOICE_SITUATION_CAN_FORCE_UPDATE_DESCRIPTION')) { + $situationinvoicelinewithparent = 0; + } + + $langs->load('subtotals'); + + + if (!$situationinvoicelinewithparent) { + print ''; + $depth_array = $this->getPossibleLevels($langs); + print $form->selectarray('line_depth', $depth_array, abs($line->qty), 0, 0, 0, '', 0, 0, $disabled); + if ($disabled) { + print ''; + } + print '
    '; + foreach ($line_options as $key => $value) { + if (in_array($line_type, $value['type'])) { + print '
  • '; + print '
  • '; + } + } + print '
'; + print ''; + } else { + print ''; + } + ?> + + + + ">
+ "> + + + + diff --git a/htdocs/core/tpl/subtotal_expedition_view.tpl.php b/htdocs/core/tpl/subtotal_expedition_view.tpl.php new file mode 100644 index 00000000000..370405351e5 --- /dev/null +++ b/htdocs/core/tpl/subtotal_expedition_view.tpl.php @@ -0,0 +1,92 @@ +origin_line_id)) { + print ''; // id of order line + $id = $line->id; + $element = $line->element; + $desc = $line->desc; + $line_options = $line->extraparams["subtotal"] ?? array(); + $buttons = $object->status == Expedition::STATUS_DRAFT; +} else { + print ''; // id of order line + $id = $line->rowid; + $element = "commande"; + $desc = $line->description; + $extraparams = (array) json_decode($line->extraparams, true); + $line_options = $extraparams["subtotal"] ?? array(); +} + +$langs->load('subtotals'); + +$line_color = $object->getSubtotalColors((int) $line->qty); +$colspan = 7; + +if (isModEnabled('productbatch')) { + $colspan++; +} +if (isModEnabled('stock')) { + $colspan++; +} + +print ''; + +if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER')) { + print '' . ($i + 1) . ''; +} + +if ($line->qty > 0) { ?> + >qty - 1) * 8); ?> + trans("ShowUPOnPDF"), 'invoicing'); + } + if (array_key_exists('titleshowtotalexludingvatonpdf', $line_options)) { + echo '  %'; + } + if (array_key_exists('titleforcepagebreak', $line_options)) { + echo ' ' . img_picto($langs->trans("ForcePageBreak"), 'file'); + } + ?> + +qty < 0) { ?> + colspan=""> + trans("ShowTotalExludingVATOnPDF") . '">%'; + } + ?> + +'; + echo ''; + if (!colorIsLight($line_color)) { + echo img_delete('default', 'class="pictodelete" style="color: white"'); + } else { + echo img_delete('default', 'class="pictodelete" style="color: #666"'); + } + echo ' '; +} + +print ""; diff --git a/htdocs/core/tpl/subtotal_view.tpl.php b/htdocs/core/tpl/subtotal_view.tpl.php new file mode 100644 index 00000000000..cf1fe6fd56e --- /dev/null +++ b/htdocs/core/tpl/subtotal_view.tpl.php @@ -0,0 +1,290 @@ + + * Copyright (C) 2024 MDW + * Copyright (C) 2024 Frédéric France + * + * 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/ + */ + +/** + * @var CommonObject $object + * @var CommonObject $this + * @var CommonObjectLine $line + * @var Conf $conf + * @var Form $form + * @var Societe $mysoc + * @var Translate $langs + * @var User $user + * @var string $action + * + * @var int $num + * @var int $i + */ + +' +@phan-var-force CommonObjectLine|CommonInvoiceLine|CommonOrderLine|ExpeditionLigne|PropaleLigne $line +@phan-var-force CommonObject $this +@phan-var-force Propal|Contrat|Commande|Facture|Expedition|Delivery|FactureFournisseur|FactureFournisseur|SupplierProposal $object +@phan-var-force int $num +'; + +echo "\n"; + +$langs->load('subtotals'); + +$line_options = $line->extraparams["subtotal"] ?? array(); + +$line_color = $this->getSubtotalColors($line->qty); + +echo ''; + +// Showing line number if conf is enabled +if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER')) { + echo '' . ($i + 1) . ''; +} + +if ($line->qty > 0) { ?> + >qty - 1) * 8); ?> + desc; + if (array_key_exists('titleshowuponpdf', $line_options)) { + echo ' ' . img_picto($langs->trans("ShowUPOnPDF"), 'invoicing'); + } + if (array_key_exists('titleshowtotalexludingvatonpdf', $line_options)) { + echo '  %'; + } + if (array_key_exists('titleforcepagebreak', $line_options)) { + echo ' ' . img_picto($langs->trans("ForcePageBreak"), 'file'); + } + ?> + + + status == 0 && $object->element != 'facturerec') { + if (GETPOST('mode', 'aZ09') == 'vatforblocklines' && GETPOSTINT('lineid') == $line->id) { + $type_tva = $type_tva ?? 0; + print '
'; + print $form->load_tva('vatforblocklines', '', $mysoc, $object->thirdparty, 0, $line->info_bits, $line->product_type, false, 1, $type_tva); + print ''; + print ''; + print '
'; + } else { + print 'id . '">'; + if (!colorIsLight($line_color)) { + echo img_edit($langs->trans("ApplyVATForBlock"), 0, 'style="color: white"'); + } else { + echo img_edit($langs->trans("ApplyVATForBlock"), 0, 'style="color: #666"'); + } + echo ''; + } + } + ?> + + + multicurrency_code != $conf->currency) { + print ''; + } + // Handling colspan if MAIN_NO_INPUT_PRICE_WITH_TAX conf is enabled + if (!getDolGlobalInt('MAIN_NO_INPUT_PRICE_WITH_TAX') && $object->element != 'facturerec') { + print ''; + } + + print ''; + + // Handling colspan if PRODUCT_USE_UNITS conf is enabled + if (getDolGlobalString('PRODUCT_USE_UNITS')) { + print ''; + } + ?> + + status == 0 && $object->element != 'facturerec') { + if (GETPOST('mode', 'aZ09') == 'discountforblocklines' && GETPOSTINT('lineid') == $line->id) { + print '
'; + print '%'; + print ''; + print ''; + print '
'; + } else { + print 'id . '">'; + if (!colorIsLight($line_color)) { + echo img_edit($langs->trans("ApplyDiscountForBlock"), 0, 'style="color: white"'); + } else { + echo img_edit($langs->trans("ApplyDiscountForBlock"), 0, 'style="color: #666"'); + } + echo ''; + } + } + ?> + + situation_cycle_ref) && $this->situation_cycle_ref) { + print ''; + if (getDolGlobalInt('INVOICE_USE_SITUATION') == 2) { + print ''; + } + print ''; + } + + // Handling colspan if margin module is enabled + if (!empty($object->element) && in_array($object->element, array('facture', 'facturerec', 'propal', 'commande')) && isModEnabled('margin') && empty($user->socid)) { + if ($user->hasRight('margins', 'creer')) { + print ''; + } + if (getDolGlobalString('DISPLAY_MARGIN_RATES') && $user->hasRight('margins', 'liretous')) { + print ''; + } + if (getDolGlobalString('DISPLAY_MARK_RATES') && $user->hasRight('margins', 'liretous')) { + print ''; + } + } + ?> + + multicurrency_code != $conf->currency) { ?> + + +qty < 0) { + // Base colspan if there is no module activated to display line correctly + $colspan = 3; + + if (isset($this->situation_cycle_ref) && $this->situation_cycle_ref) { + $colspan += 2; + if (getDolGlobalInt('INVOICE_USE_SITUATION') == 2) { + $colspan += 1; + } + } + + // Handling colspan if margin module is enabled + if (!empty($object->element) && in_array($object->element, array('facture', 'facturerec', 'propal', 'commande')) && isModEnabled('margin') && empty($user->socid)) { + if ($user->hasRight('margins', 'creer')) { + $colspan += 1; + } + if (getDolGlobalString('DISPLAY_MARGIN_RATES') && $user->hasRight('margins', 'liretous')) { + $colspan += 1; + } + if (getDolGlobalString('DISPLAY_MARK_RATES') && $user->hasRight('margins', 'liretous')) { + $colspan += 1; + } + } + + // Handling colspan if multicurrency module is enabled + if (isModEnabled('multicurrency') && $object->multicurrency_code != $conf->currency) { + $colspan += 1; + } + + // Handling colspan if MAIN_NO_INPUT_PRICE_WITH_TAX conf is enabled + if (!getDolGlobalInt('MAIN_NO_INPUT_PRICE_WITH_TAX') && $object->element != 'facturerec') { + $colspan += 1; + } + + // Handling colspan if PRODUCT_USE_UNITS conf is enabled + if (getDolGlobalString('PRODUCT_USE_UNITS')) { + $colspan += 1; + } + ?> + colspan=""> + desc; + if (array_key_exists('subtotalshowtotalexludingvatonpdf', $line_options)) { + echo '  %'; + } + echo ' :'; + ?> + + > + getSubtotalLineAmount($line); + ?> + + multicurrency_code != $conf->currency) { + echo ''; + echo $this->getSubtotalLineMulticurrencyAmount($line); + echo ''; + } + ?> +status == 0) { + // Edit picto + echo ''; + echo ''; + if (!colorIsLight($line_color)) { + echo img_edit('default', 0, 'style="color: white"'); + } else { + echo img_edit('default', 0, 'style="color: #666"'); + } + echo ' '; + + // Delete picto + echo ''; + echo ''; + if (!colorIsLight($line_color)) { + echo img_delete('default', 'class="pictodelete" style="color: white"'); + } else { + echo img_delete('default', 'class="pictodelete" style="color: #666"'); + } + echo ' '; + + // Move up-down picto + if ($num > 1 && $conf->browser->layout != 'phone' && ((property_exists($this, 'situation_counter') && $this->situation_counter == 1) || empty($this->situation_cycle_ref)) && empty($disablemove)) { + echo ''; + if ($i > 0) { + echo ''; + echo img_up('default', 0, 'imgupforline'); + echo ''; + } + if ($i < $num - 1) { + echo ''; + echo img_down('default', 0, 'imgdownforline'); + echo ''; + } + echo ''; + } else { + echo 'browser->layout != 'phone' && empty($disablemove)) ? ' class="linecolmove tdlineupdown center"' : ' class="linecolmove center"') . '>'; + } +} else { + $colspan = 3; + if (isModEnabled('asset') && $object->element == 'invoice_supplier') { + $colspan++; + } + print ''; +} + +if ($action == 'selectlines') { ?> + +'; +echo ''; +?> diff --git a/htdocs/core/tpl/subtotalline_select.tpl.php b/htdocs/core/tpl/subtotalline_select.tpl.php new file mode 100644 index 00000000000..d3c0c5312a9 --- /dev/null +++ b/htdocs/core/tpl/subtotalline_select.tpl.php @@ -0,0 +1,24 @@ +getSubtotalColors($line->qty); + +print ''."\n"; +print ''."\n"; + + +$selected = 1; +if (!empty($selectedLines) && !in_array($this->tpl['id'], $selectedLines)) { + $selected = 0; +} +print ""; +print ''; +print $line->desc . "\n"; + + +print ''; diff --git a/htdocs/expedition/card.php b/htdocs/expedition/card.php index 6543705f137..565e20ced65 100644 --- a/htdocs/expedition/card.php +++ b/htdocs/expedition/card.php @@ -400,6 +400,7 @@ if (empty($reshook)) { } if (($totalqty > 0 || getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS')) && !$error) { // There is at least one thing to ship and no error + $selected_subtotal_lines = GETPOST('subtotal_toselect', 'array'); for ($i = 0; $i < $num; $i++) { $qty = "qtyl".$i; @@ -435,6 +436,9 @@ if (empty($reshook)) { $error++; } } + if (isModEnabled('subtotals') && $objectsrc->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE && in_array($objectsrc->lines[$i]->id, $selected_subtotal_lines)) { + $object->addSubtotalLine($langs, $objectsrc->lines[$i]->desc, (int) $objectsrc->lines[$i]->qty, $objectsrc->lines[$i]->extraparams, $objectsrc->lines[$i]->id); + } } } else { // batch mode @@ -478,6 +482,14 @@ if (empty($reshook)) { if (!$error) { $db->commit(); + $object->fetch_lines(); + foreach ($object->lines as $line) { + $objectsrc_line = new $objectsrc->class_element_line($db); + '@phan-var-force CommonObjectLine $objectsrc_line'; + $objectsrc_line->fetch($line->origin_line_id); + $line->extraparams = $objectsrc_line->extraparams; + $line->setExtraParameters(); + } header("Location: card.php?id=".$object->id); exit; } else { @@ -559,6 +571,34 @@ if (empty($reshook)) { // setEventMessages($object->error, $object->errors, 'errors'); // } //} + } elseif ($action == 'confirm_delete_subtotalline' && $confirm == 'yes' && $permissiontoadd) { + $result = $object->deleteSubtotalLine($langs, GETPOSTINT('lineid'), (bool) GETPOST('deletecorrespondingsubtotalline'), $user); + if ($result > 0) { + // reorder lines + $object->line_order(true, 'ASC', false); + // Define output language + $outputlangs = $langs; + $newlang = ''; + if (getDolGlobalInt('MAIN_MULTILANGS') /* && empty($newlang) */ && GETPOST('lang_id', 'aZ09')) { + $newlang = GETPOST('lang_id', 'aZ09'); + } + if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) { + $newlang = $object->thirdparty->default_lang; + } + if (!empty($newlang)) { + $outputlangs = new Translate("", $conf); + $outputlangs->setDefaultLang($newlang); + } + if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) { + $ret = $object->fetch($object->id); // Reload to get new records + $object->generateDocument($object->model_pdf, $outputlangs, $hidedetails, $hidedesc, $hideref); + } + + header('Location: '.$_SERVER["PHP_SELF"].'?id='.$object->id); + exit; + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } } elseif ($action == 'setdate_livraison' && $user->hasRight('expedition', 'creer')) { $datedelivery = dol_mktime(GETPOSTINT('liv_hour'), GETPOSTINT('liv_min'), 0, GETPOSTINT('liv_month'), GETPOSTINT('liv_day'), GETPOSTINT('liv_year')); @@ -1196,9 +1236,27 @@ if ($action == 'create') { $alreadyQtyBatchSetted = $alreadyQtySetted = array(); + $title_lines_to_disable = array(); + if ($numAsked) { + if (isModEnabled('subtotals')) { + if (!(getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES') || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) { + $title_lines_to_disable = $object->getDisabledShippmentSubtotalLines(); + } + foreach ($object->lines as $line) { + if ($line->special_code == SUBTOTALS_SPECIAL_CODE) { + $show_check_add_buttons = true; + break; + } + } + } print ''; - print ''.$langs->trans("Description").''; + print ''; + if (isset($show_check_add_buttons)) { + print $form->showCheckAddButtons('checkforselect'); + } + print $langs->trans("Description"); + print ''; print ''.$langs->trans("QtyOrdered").''; print ''.$langs->trans("QtyShipped").''; print ''.$langs->trans("QtyToShip"); @@ -1244,7 +1302,7 @@ if ($action == 'create') { setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); } - if (empty($reshook)) { + if (empty($reshook) && $line->special_code != SUBTOTALS_SPECIAL_CODE) { // Show product and description $type = $line->product_type ? $line->product_type : $line->fk_product_type; // Try to enhance type detection using date_start and date_end for free lines where type @@ -1895,6 +1953,8 @@ if ($action == 'create') { print $expLine->showOptionals($extrafields, 'edit', array('style' => 'class="drag drop oddeven"', 'colspan' => $colspan), (string) $indiceAsked, '', '1'); } + } elseif (empty($reshook) && $line->special_code == SUBTOTALS_SPECIAL_CODE && !in_array($line->id, $title_lines_to_disable)) { + require dol_buildpath('/core/tpl/subtotalline_select.tpl.php'); } $indiceAsked++; @@ -1965,6 +2025,20 @@ if ($action == 'create') { ); } + // Confirmation de la suppression d'une ligne subtotal + if ($action == 'ask_subtotal_deleteline') { + $lineid = GETPOSTINT('lineid'); + $langs->load("subtotals"); + $title = "DeleteSubtotalLine"; + $question = "ConfirmDeleteSubtotalLine"; + if (GETPOST('type') == 'title') { + $formconfirm = array(array('type' => 'checkbox', 'name' => 'deletecorrespondingsubtotalline', 'label' => $langs->trans("DeleteCorrespondingSubtotalLine"), 'value' => 0)); + $title = "DeleteTitleLine"; + $question = "ConfirmDeleteTitleLine"; + } + $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id.'&lineid='.$lineid, $langs->trans($title), $langs->trans($question), 'confirm_delete_subtotalline', $formconfirm, 'no', 1); + } + // Confirmation validation if ($action == 'valid') { $objectref = substr($object->ref, 1, 4); @@ -2403,7 +2477,7 @@ if ($action == 'create') { // Get list of products already sent for same source object into $alreadysent $alreadysent = array(); if ($origin && $origin_id > 0) { - $sql = "SELECT obj.rowid, obj.fk_product, obj.label, obj.description, obj.product_type as fk_product_type, obj.qty as qty_asked, obj.fk_unit, obj.date_start, obj.date_end"; + $sql = "SELECT obj.rowid, obj.fk_product, obj.label, obj.description, obj.product_type as fk_product_type, obj.qty as qty_asked, obj.fk_unit, obj.date_start, obj.date_end, obj.special_code"; $sql .= ", ed.rowid as shipmentline_id, ed.qty as qty_shipped, ed.fk_expedition as expedition_id, ed.fk_elementdet, ed.fk_entrepot"; $sql .= ", e.rowid as shipment_id, e.ref as shipment_ref, e.date_creation, e.date_valid, e.date_delivery, e.date_expedition"; //if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) $sql .= ", l.rowid as livraison_id, l.ref as livraison_ref, l.date_delivery, ld.qty as qty_received"; @@ -2454,7 +2528,7 @@ if ($action == 'create') { setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); } - if (empty($reshook)) { + if (empty($reshook) && $lines[$i]->product_type != "9") { print ''; // id of order line print ''; @@ -2852,6 +2926,13 @@ if ($action == 'create') { print $lines[$i]->showOptionals($extrafields, 'view', array('colspan' => $colspan), !empty($indiceAsked) ? $indiceAsked : '', '', '', 'card'); } } + } elseif (empty($reshook) && $lines[$i]->product_type == "9") { + $objectsrc = new OrderLine($db); + $objectsrc->fetch($lines[$i]->origin_line_id); + if ($objectsrc->special_code == SUBTOTALS_SPECIAL_CODE) { + $line = $lines[$i]; + require dol_buildpath('/core/tpl/subtotal_expedition_view.tpl.php'); + } } } diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index 6132be45b79..4b52d425025 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -47,7 +47,7 @@ if (isModEnabled('order')) { } require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionlinebatch.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/commonsignedobject.class.php'; - +require_once DOL_DOCUMENT_ROOT.'/subtotals/class/commonsubtotal.class.php'; /** * Class to manage shipments @@ -58,6 +58,7 @@ class Expedition extends CommonObject { use CommonIncoterm; use CommonSignedObject; + use CommonSubtotal; /** * @var string ID to identify managed object @@ -500,6 +501,14 @@ class Expedition extends CommonObject $kits_list = array(); if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) { for ($i = 0; $i < $num; $i++) { + $objectsrc = new OrderLine($this->db); + $objectsrc->fetch($this->lines[$i]->origin_line_id); + if ($this->lines[$i]->product_type == "9" && $objectsrc->special_code == SUBTOTALS_SPECIAL_CODE) { + if ($this->create_line($this->lines[$i]->entrepot_id, $this->lines[$i]->origin_line_id, $this->lines[$i]->qty, $this->lines[$i]->rang, $this->lines[$i]->array_options) <= 0) { + $error++; + } + continue; + } if (empty($this->lines[$i]->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) { // virtual products $line = $this->lines[$i]; @@ -1917,7 +1926,7 @@ class Expedition extends CommonObject $sql .= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva"; $sql .= ", cd.fk_remise_except, cd.fk_product_fournisseur_price as fk_fournprice"; $sql .= ", cd.vat_src_code, cd.tva_tx, cd.localtax1_tx, cd.localtax2_tx, cd.localtax1_type, cd.localtax2_type, cd.info_bits, cd.price, cd.subprice, cd.remise_percent,cd.buy_price_ht as pa_ht"; - $sql .= ", cd.fk_multicurrency, cd.multicurrency_code, cd.multicurrency_subprice, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc, cd.rang, cd.date_start, cd.date_end"; + $sql .= ", cd.fk_multicurrency, cd.multicurrency_code, cd.multicurrency_subprice, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc, cd.rang, cd.date_start, cd.date_end, cd.special_code"; $sql .= ", ed.rowid as line_id, ed.qty as qty_shipped, ed.fk_element, ed.fk_elementdet, ed.element_type, ed.fk_entrepot, ed.extraparams"; $sql .= ", p.ref as product_ref, p.label as product_label, p.fk_product_type, p.barcode as product_barcode"; $sql .= ", p.weight, p.weight_units, p.length, p.length_units, p.width, p.width_units, p.height, p.height_units"; @@ -2055,6 +2064,8 @@ class Expedition extends CommonObject $line->date_start = $this->db->jdate($obj->date_start); $line->date_end = $this->db->jdate($obj->date_end); + $line->special_code = $obj->special_code; + // Multicurrency $this->fk_multicurrency = $obj->fk_multicurrency; $this->multicurrency_code = $obj->multicurrency_code; diff --git a/htdocs/expedition/class/expeditionligne.class.php b/htdocs/expedition/class/expeditionligne.class.php index ec409c4f6e6..5879f516270 100644 --- a/htdocs/expedition/class/expeditionligne.class.php +++ b/htdocs/expedition/class/expeditionligne.class.php @@ -372,11 +372,23 @@ class ExpeditionLigne extends CommonObjectLine global $langs; $error = 0; + $skip_check_parameters = false; + + // Handling parent line subtotal line + if (!empty($this->element_type) + && !empty($this->fk_elementdet) + && $this->element_type == 'commande') { + $objectsrc_line = new OrderLine($this->db); + $objectsrc_line->fetch($this->fk_elementdet); + $skip_check_parameters = $objectsrc_line->special_code == SUBTOTALS_SPECIAL_CODE; + } + // Check parameters - if (empty($this->fk_expedition) + if ((empty($this->fk_expedition) || empty($this->fk_product) // product id is mandatory || (empty($this->fk_elementdet) && empty($this->fk_parent)) // at least origin line id of parent line id is set - || !is_numeric($this->qty)) { + || !is_numeric($this->qty)) + && !$skip_check_parameters) { $langs->load('errors'); $this->errors[] = $langs->trans('ErrorMandatoryParametersNotProvided'); return -1; diff --git a/htdocs/expedition/shipment.php b/htdocs/expedition/shipment.php index 3c012575de4..4dd45ab12a9 100644 --- a/htdocs/expedition/shipment.php +++ b/htdocs/expedition/shipment.php @@ -614,7 +614,7 @@ if ($order_id > 0 || !empty($ref)) { $sql .= " cd.qty, cd.fk_unit, cd.rang,"; $sql .= ' cd.date_start,'; $sql .= ' cd.date_end,'; - $sql .= ' cd.special_code,'; + $sql .= ' cd.special_code, cd.extraparams,'; $sql .= ' p.rowid as prodid, p.label as product_label, p.entity, p.ref, p.fk_product_type as product_type, p.description as product_desc,'; $sql .= ' p.weight, p.weight_units, p.length, p.length_units, p.width, p.width_units, p.height, p.height_units,'; $sql .= ' p.surface, p.surface_units, p.volume, p.volume_units'; @@ -661,7 +661,7 @@ if ($order_id > 0 || !empty($ref)) { setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); } - if (empty($reshook)) { + if (empty($reshook) && $objp->special_code != SUBTOTALS_SPECIAL_CODE) { // Show product and description $type = isset($objp->type) ? $objp->type : $objp->product_type; @@ -840,6 +840,9 @@ if ($order_id > 0 || !empty($ref)) { } } } + } elseif (empty($reshook) && $objp->special_code == SUBTOTALS_SPECIAL_CODE) { + $line = $objp; + require dol_buildpath('/core/tpl/subtotal_expedition_view.tpl.php'); } $i++; } diff --git a/htdocs/install/doctemplates/invoices/template_invoice.odt b/htdocs/install/doctemplates/invoices/template_invoice.odt index 5d44e1a606c..9d1c0e68d4f 100644 Binary files a/htdocs/install/doctemplates/invoices/template_invoice.odt and b/htdocs/install/doctemplates/invoices/template_invoice.odt differ diff --git a/htdocs/install/doctemplates/orders/template_order.odt b/htdocs/install/doctemplates/orders/template_order.odt index b9f5162316d..8ef7773663c 100644 Binary files a/htdocs/install/doctemplates/orders/template_order.odt and b/htdocs/install/doctemplates/orders/template_order.odt differ diff --git a/htdocs/install/doctemplates/proposals/template_proposal.odt b/htdocs/install/doctemplates/proposals/template_proposal.odt index 31981d8c120..81fdb947ff5 100644 Binary files a/htdocs/install/doctemplates/proposals/template_proposal.odt and b/htdocs/install/doctemplates/proposals/template_proposal.odt differ diff --git a/htdocs/install/doctemplates/shipments/template_shipment.odt b/htdocs/install/doctemplates/shipments/template_shipment.odt index 100afe5dac4..b1d369f8f2c 100644 Binary files a/htdocs/install/doctemplates/shipments/template_shipment.odt and b/htdocs/install/doctemplates/shipments/template_shipment.odt differ diff --git a/htdocs/langs/en_US/subtotals.lang b/htdocs/langs/en_US/subtotals.lang new file mode 100644 index 00000000000..bd6868b0a3d --- /dev/null +++ b/htdocs/langs/en_US/subtotals.lang @@ -0,0 +1,54 @@ +# Dolibarr language file - Source file is en_US - subtotal + +# +# Global +# +Subtotal=Subtotals + +# +# Admin +# +SubtotalSetup=Subtotals module setup +MaxSubtotalLevel=Maximum level +SubtotalLineBackColor=Level %s lines background color +NotSupportedByAllPDF=Will work with newer PDF but not with old ones %s + +# +# Card +# +AddTitleLine=Add title line +AddSubtotalLine=Add subtotal line +TitleNeedDesc=You must enter a valid description to create a title. +TitleAddedLevelTooHigh=Added title level too high (added with level %s instead). +TitleEditedLevelTooHigh=Edited title level too high (edited with level %s instead). +DeleteSubtotalLine=Delete subtotal line +ConfirmDeleteSubtotalLine=Are you sure you want to delete this subtotal line? +DeleteTitleLine=Delete title line +ConfirmDeleteTitleLine=Are you sure you want to delete this title line? +DeleteCorrespondingSubtotalLine=Delete the corresponding subtotal line ? +Level=Level %s +SubtotalLineDesc=Line description +SubtotalLineLevel=Line level +ShowUPOnPDF=Show unit price on PDF +ShowTotalExludingVATOnPDF=Show the total excluding VAT on the PDF +ForcePageBreak=Force page break before title +NoTitleError=You have no title line or they already all have a corresponding subtotal line +CorrespondingTitleLine=Corresponding title line +SelectVATRate=Select VAT rate +EnterRemisePercent=Enter discount percent +ApplyVATForBlock=Apply VAT for this block +ApplyDiscountForBlock=Apply discount for this block +TitleUnderSameLevelSTLine=Title of the same level must be under the previous title's subtotal line. +TitleUnderSameLevelOrGreater=Title must be under a title of the same level or greater. +TitleAfterStLineOfSameLevelTitle=Title must be after the subtotal line of the previous title. +PreviousTitleLevelTooHigh=Previous title is two level or more greater than moved line. +STLineUnderCorrespondingTitleDesc=Subtotal line must be under a title with the same level that has the same description. +STLineUnderCorrespondingTitle=Subtotal line must be under a title of the same level and the same description. +STLineUnderTitle=Subtotal line must be under its title +UnsupportedModuleError=You can't add a subtotals line in unsupported module +CorrespondingTitleNotFound=The corresponding title was not found + +# +# PDF +# +SubtotalOf=Subtotal of %s: diff --git a/htdocs/subtotals/class/commonsubtotal.class.php b/htdocs/subtotals/class/commonsubtotal.class.php new file mode 100644 index 00000000000..3c03c14053e --- /dev/null +++ b/htdocs/subtotals/class/commonsubtotal.class.php @@ -0,0 +1,717 @@ + + * Copyright (C) 2024 MDW + * Copyright (C) 2024 Frédéric France + * + * 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/ + */ + +if (!defined('SUBTOTALS_SPECIAL_CODE')) { + define('SUBTOTALS_SPECIAL_CODE', 81); +} + +/** + * + * Trait CommonSubtotal + * + * Add subtotals lines + */ +trait CommonSubtotal +{ + /** + * @var int + * Type for subtotals module lines + */ + public static $PRODUCT_TYPE = 9; + + /** + * @var array + * Options for subtotals module title lines + */ + public static $TITLE_OPTIONS = ['titleshowuponpdf', 'titleshowtotalexludingvatonpdf', 'titleforcepagebreak']; + + /** + * @var array + * Options for subtotals module subtotal lines + */ + public static $SUBTOTAL_OPTIONS = ['subtotalshowtotalexludingvatonpdf']; + + /** + * Adds a subtotals line to a document. + * This function inserts a subtotal line based on the given parameters. + * + * @param Translate $langs Translation. + * @param string $desc Description of the line. + * @param int $depth Level of the line (>0 for title lines, <0 for subtotal lines) + * @param array|string $options Subtotal options for pdf view + * @param int $parent_line ID of the parent line for shipments + * @return int ID of the added line if successful, 0 on warning, -1 on error + * + * @phan-suppress PhanUndeclaredMethod + * @phan-suppress PhanUndeclaredProperty + */ + public function addSubtotalLine($langs, $desc, $depth, $options = array(), $parent_line = 0) + { + if (empty($desc)) { + if (isset($this->errors)) { + $this->errors[] = $langs->trans("TitleNeedDesc"); + } + return -1; + } + $current_module = $this->element; + // Ensure the object is one of the supported types + $allowed_types = array('propal', 'commande', 'facture', 'facturerec', 'shipping'); + if (!in_array($current_module, $allowed_types)) { + if (isset($this->errors)) { + $this->errors[] = $langs->trans("UnsupportedModuleError"); + } + return -1; // Unsupported type + } + $error = 0; + $desc = dol_html_entity_decode($desc, ENT_QUOTES); + $rang = -1; + $next_line = false; + $result = 0; + + if ($depth < 0 && $current_module != 'shipping') { + foreach ($this->lines as $line) { + if (!$next_line && $line->desc == $desc && $line->qty == -$depth) { + $next_line = true; + continue; + } + if ($next_line && $line->desc == $desc && $line->qty == $depth) { + $next_line = false; + continue; + } + if ($next_line && $line->special_code == SUBTOTALS_SPECIAL_CODE && abs($line->qty) <= abs($depth)) { + $rang = $line->rang; + break; + } + } + } + + if ($depth > 0 && $current_module != 'shipping') { + $max_existing_level = 0; + + foreach ($this->lines as $line) { + if ($line->special_code == SUBTOTALS_SPECIAL_CODE && $line->qty > $max_existing_level) { + $max_existing_level = $line->qty; + } + } + + if ($max_existing_level+1 < $depth) { + $depth = $max_existing_level+1; + if (isset($this->errors)) { + $this->errors[] = $langs->trans("TitleAddedLevelTooHigh", $depth); + } + + $error ++; + } + } + + // Add the line calling the right module + if ($current_module == 'facture') { + $result = $this->addline( // @phpstan-ignore-line + $desc, // Description @phpstan-ignore-line + 0, // Unit price @phpstan-ignore-line + $depth, // Quantity @phpstan-ignore-line + 0, // VAT rate @phpstan-ignore-line + 0, // Local tax 1 @phpstan-ignore-line + 0, // Local tax 2 @phpstan-ignore-line + 0, // FK product @phpstan-ignore-line + 0, // Discount percentage @phpstan-ignore-line + '', // Date start @phpstan-ignore-line + '', // Date end @phpstan-ignore-line + 0, // FK code ventilation @phpstan-ignore-line + 0, // Info bits @phpstan-ignore-line + 0, // FK remise except @phpstan-ignore-line + '', // Price base type @phpstan-ignore-line + 0, // PU ttc @phpstan-ignore-line + self::$PRODUCT_TYPE, // Type @phpstan-ignore-line + $rang, // Rang @phpstan-ignore-line + SUBTOTALS_SPECIAL_CODE // Special code @phpstan-ignore-line + ); + } elseif ($current_module == 'propal') { + $result = $this->addline( // @phpstan-ignore-line + $desc, // Description @phpstan-ignore-line + 0, // Unit price @phpstan-ignore-line + $depth, // Quantity @phpstan-ignore-line + 0, // VAT rate @phpstan-ignore-line + 0, // Local tax 1 @phpstan-ignore-line + 0, // Local tax 2 @phpstan-ignore-line + 0, // FK product @phpstan-ignore-line + 0, // Discount percentage @phpstan-ignore-line + '', // Price base type @phpstan-ignore-line + 0, // PU ttc @phpstan-ignore-line + 0, // Info bits @phpstan-ignore-line + self::$PRODUCT_TYPE, // Type @phpstan-ignore-line + $rang, // Rang @phpstan-ignore-line + SUBTOTALS_SPECIAL_CODE // Special code @phpstan-ignore-line + ); + } elseif ($current_module == 'commande') { + $result = $this->addline( // @phpstan-ignore-line + $desc, // Description @phpstan-ignore-line + 0, // Unit price @phpstan-ignore-line + $depth, // Quantity @phpstan-ignore-line + 0, // VAT rate @phpstan-ignore-line + 0, // Local tax 1 @phpstan-ignore-line + 0, // Local tax 2 @phpstan-ignore-line + 0, // FK product @phpstan-ignore-line + 0, // Discount percentage @phpstan-ignore-line + 0, // Info bits @phpstan-ignore-line + 0, // FK remise except @phpstan-ignore-line + '', // Price base type @phpstan-ignore-line + 0, // PU ttc @phpstan-ignore-line + '', // Date start @phpstan-ignore-line + '', // Date end @phpstan-ignore-line + self::$PRODUCT_TYPE, // Type @phpstan-ignore-line + $rang, // Rang @phpstan-ignore-line + SUBTOTALS_SPECIAL_CODE // Special code @phpstan-ignore-line + ); + } elseif ($current_module == 'shipping') { + $result = $this->addline( // @phpstan-ignore-line + '', // Warehouse ID @phpstan-ignore-line + (int) $parent_line, // Source line @phpstan-ignore-line + $depth // Quantity @phpstan-ignore-line + ); + } elseif ($current_module == 'facturerec') { + $rang = $rang == -1 ? $rang : $rang-1; + $result = $this->addline( // @phpstan-ignore-line + $desc, // Description @phpstan-ignore-line + 0, // Unit price @phpstan-ignore-line + $depth, // Quantity @phpstan-ignore-line + 0, // VAT rate @phpstan-ignore-line + 0, // Local tax 1 @phpstan-ignore-line + 0, // Local tax 2 @phpstan-ignore-line + 0, // FK product @phpstan-ignore-line + 0, // Discount percentage @phpstan-ignore-line + '', // Price base type @phpstan-ignore-line + 0, // Info bits @phpstan-ignore-line + 0, // FK remise except @phpstan-ignore-line + 0, // PU ttc @phpstan-ignore-line + self::$PRODUCT_TYPE, // Type @phpstan-ignore-line + $rang, // Rang @phpstan-ignore-line + SUBTOTALS_SPECIAL_CODE // Special code @phpstan-ignore-line + ); + $this->fetch_lines(); + } + + if ($current_module != 'shipping') { + foreach ($this->lines as $line) { + '@phan-var-force CommonObjectLine $line'; + if ($line->id == $result) { + $line->extraparams["subtotal"] = $options; + $line->setExtraParameters(); + } + } + } + + if ($result < 0) { + return $result; + } + + return $error > 0 ? 0 : $result; + } + + /** + * Deletes a subtotal or a title line from a document. + * If the corresponding subtotal line exists and second parameter true, it will also be deleted. + * + * @param Translate $langs Translation. + * @param int $id ID of the line to delete + * @param boolean $correspondingstline If true, also deletes the corresponding subtotal line + * @param User $user performing the deletion (used for permissions in some modules) + * @return int ID of deleted line if successful, -1 on error + * + * @phan-suppress PhanUndeclaredMethod + * @phan-suppress PhanUndeclaredProperty + */ + public function deleteSubtotalLine($langs, $id, $correspondingstline = false, $user = null) + { + $current_module = $this->element; + // Ensure the object is one of the supported types + $allowed_types = array('propal', 'commande', 'facture', 'facturerec', 'shipping'); + if (!in_array($current_module, $allowed_types)) { + if (isset($this->errors)) { + $this->errors[] = $langs->trans("UnsupportedModuleError"); + } + return -1; // Unsupported type + } + + $result = 0; + + if ($correspondingstline) { + $oldDesc = ""; + $oldDepth = 0; + foreach ($this->lines as $line) { + if ($line->id == $id) { + $oldDesc = $line->desc; + $oldDepth = $line->qty; + } + if ($line->special_code == SUBTOTALS_SPECIAL_CODE && $line->qty == -$oldDepth && $line->desc == $oldDesc) { + $this->deleteSubtotalLine($langs, $line->id, false, $user); + break; + } + } + } + + // Add the line calling the right module + if ($current_module == 'facture') { + $result = $this->deleteLine($id); // @phpstan-ignore-line + } elseif ($current_module == 'propal') { + $result = $this->deleteLine($id); // @phpstan-ignore-line + } elseif ($current_module == 'commande') { + $result = $this->deleteLine($user, $id); // @phpstan-ignore-line + } elseif ($current_module == 'facturerec') { + $line = new FactureLigneRec($this->db); + $line->id = $id; + $result = $line->delete($user); // @phpstan-ignore-line + } elseif ($current_module == 'shipping') { + $line = new ExpeditionLigne($this->db); + $line->id = $id; + $result = $line->delete($user); // @phpstan-ignore-line + } + + return $result >= 0 ? $result : -1; // Return line ID or false + } + + /** + * Updates a subtotal line of a document. + * This function updates a subtotals line based on its id and the given parameters. + * Updating a title line updates the corresponding subtotal line except options. + * + * @param Translate $langs Translation. + * @param int $lineid ID of the line to update. + * @param string $desc Description of the line. + * @param int $depth Level of the line (>0 for title lines, <0 for subtotal lines) + * @param array|string $options Subtotal options for pdf view + * @return int ID of the added line if successful, 0 on warning, -1 on error + * + * @phan-suppress PhanUndeclaredMethod + * @phan-suppress PhanUndeclaredProperty + */ + public function updateSubtotalLine($langs, $lineid, $desc, $depth, $options) // @phpstan-ignore-line + { + $current_module = $this->element; + // Ensure the object is one of the supported types + $allowed_types = array('propal', 'commande', 'facture', 'facturerec', 'shipping'); + if (!in_array($current_module, $allowed_types)) { + if (isset($this->errors)) { + $this->errors[] = $langs->trans("UnsupportedModuleError"); + } + return -1; // Unsupported type + } + + $result = 0; + $error = 0; + + $max_existing_level = 0; + + if ($depth>0) { + foreach ($this->lines as $line) { + if ($line->special_code == SUBTOTALS_SPECIAL_CODE && $line->qty > $max_existing_level && $line->id != $lineid) { + $max_existing_level = $line->qty; + } + } + } + + if ($max_existing_level+1 < $depth) { + $depth = $max_existing_level+1; + if (isset($this->errors)) { + $this->errors[] = $langs->trans("TitleEditedLevelTooHigh"); + } + $error ++; + } + + if ($depth>0) { + $oldDesc = ""; + $oldDepth = 0; + foreach ($this->lines as $line) { + if ($line->id == $lineid) { + $oldDesc = $line->desc; + $oldDepth = $line->qty; + } + if ($line->special_code == SUBTOTALS_SPECIAL_CODE && $line->qty == -$oldDepth && $line->desc == $oldDesc) { + $this->updateSubtotalLine($langs, $line->id, $desc, -$depth, !empty($line->extraparams["subtotal"]) ? $line->extraparams["subtotal"] : array()); + break; + } + } + } + + // Update the line calling the right module + if ($current_module == 'facture') { + $result = $this->updateline( // @phpstan-ignore-line + $lineid, // ID of line to change @phpstan-ignore-line + $desc, // Description @phpstan-ignore-line + 0, // Unit price @phpstan-ignore-line + $depth, // Quantity @phpstan-ignore-line + 0, // Discount percentage @phpstan-ignore-line + '', // Date start @phpstan-ignore-line + '', // Date end @phpstan-ignore-line + 0, // VAT rate @phpstan-ignore-line + 0, // Local tax 1 @phpstan-ignore-line + 0, // Local tax 2 @phpstan-ignore-line + '', // Price base type @phpstan-ignore-line + 0, // Info bits @phpstan-ignore-line + self::$PRODUCT_TYPE, // Type @phpstan-ignore-line + 0, // FK parent line @phpstan-ignore-line + 0, // Skip update total @phpstan-ignore-line + 0, // FK fournprice @phpstan-ignore-line + 0, // PA ht @phpstan-ignore-line + '', // Label @phpstan-ignore-line + SUBTOTALS_SPECIAL_CODE // Special code @phpstan-ignore-line + ); + } elseif ($current_module == 'propal') { + $result = $this->updateline( // @phpstan-ignore-line + $lineid, // ID of line to change @phpstan-ignore-line + 0, // Unit price @phpstan-ignore-line + $depth, // Quantity @phpstan-ignore-line + 0, // Discount percentage @phpstan-ignore-line + 0, // VAT rate @phpstan-ignore-line + 0, // Local tax 1 @phpstan-ignore-line + 0, // Local tax 2 @phpstan-ignore-line + $desc, // Description @phpstan-ignore-line + '', // Price base type @phpstan-ignore-line + 0, // Info bits @phpstan-ignore-line + SUBTOTALS_SPECIAL_CODE, // Special code @phpstan-ignore-line + 0, // FK parent line @phpstan-ignore-line + 0, // Skip update total @phpstan-ignore-line + 0, // FK fournprice @phpstan-ignore-line + 0, // PA ht @phpstan-ignore-line + '', // Label @phpstan-ignore-line + self::$PRODUCT_TYPE // Type @phpstan-ignore-line + ); + } elseif ($current_module == 'commande') { + $result = $this->updateline( // @phpstan-ignore-line + $lineid, // ID of line to change @phpstan-ignore-line + $desc, // Description @phpstan-ignore-line + 0, // Unit price @phpstan-ignore-line + $depth, // Quantity @phpstan-ignore-line + 0, // Discount percentage @phpstan-ignore-line + 0, // VAT rate @phpstan-ignore-line + 0, // Local tax 1 @phpstan-ignore-line + 0, // Local tax 2 @phpstan-ignore-line + '', // Price base type @phpstan-ignore-line + 0, // Info bits @phpstan-ignore-line + '', // Date start @phpstan-ignore-line + '', // Date end @phpstan-ignore-line + self::$PRODUCT_TYPE, // Type @phpstan-ignore-line + 0, // FK parent line @phpstan-ignore-line + 0, // Skip update total @phpstan-ignore-line + 0, // FK fournprice @phpstan-ignore-line + 0, // PA ht @phpstan-ignore-line + '', // Label @phpstan-ignore-line + SUBTOTALS_SPECIAL_CODE // Special code @phpstan-ignore-line + ); + } elseif ($current_module == 'facturerec') { + $objectline = new FactureLigneRec($this->db); + $objectline->fetch($lineid); + $line_rang = $objectline->rang; + $result = $this->updateline( // @phpstan-ignore-line + $lineid, // ID of line to change @phpstan-ignore-line + $desc, // Description @phpstan-ignore-line + 0, // Unit price @phpstan-ignore-line + $depth, // Quantity @phpstan-ignore-line + 0, // VAT rate @phpstan-ignore-line + 0, // Local tax 1 @phpstan-ignore-line + 0, // Local tax 2 @phpstan-ignore-line + 0, // FK parent line @phpstan-ignore-line + 0, // Discount percentage @phpstan-ignore-line + '', // Price base type @phpstan-ignore-line + 0, // Info bits @phpstan-ignore-line + 0, // FK parent line @phpstan-ignore-line + 0, // PU ttc @phpstan-ignore-line + self::$PRODUCT_TYPE, // Type @phpstan-ignore-line + $line_rang, // Rang @phpstan-ignore-line + SUBTOTALS_SPECIAL_CODE // Special code @phpstan-ignore-line + ); + } + + foreach ($this->lines as $line) { + '@phan-var-force CommonObjectLine $line'; + if ($line->id == $lineid) { + $line->extraparams["subtotal"] = $options; + $line->setExtraParameters(); + } + } + + if ($result < 0) { + return $result; + } + + return $error > 0 ? 0 : $result; + } + + /** + * Updates a block of lines of a document. + * + * @param Translate $langs Translation. + * @param int $linerang Rang of the line to start from. + * @param string $mode Column to change (discount or vat). + * @param int $value Value of the change. + * @return int Return integer < 0 if KO, 1 if OK + * + * @phan-suppress PhanUndeclaredMethod + * @phan-suppress PhanUndeclaredProperty + */ + public function updateSubtotalLineBlockLines($langs, $linerang, $mode, $value) // @phpstan-ignore-line + { + $current_module = $this->element; + // Ensure the object is one of the supported types + $allowed_types = array('propal', 'commande', 'facture', 'facturerec', 'shipping'); + if (!in_array($current_module, $allowed_types)) { + if (isset($this->errors)) { + $this->errors[] = $langs->trans("UnsupportedModuleError"); + } + return -1; // Unsupported type + } + + $result = 0; + $linerang -= 1; + + $nb_lines = count($this->lines)+1; + + for ($i = $linerang+1; $i < $nb_lines; $i++) { + if ($this->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE) { + if (abs($this->lines[$i]->qty) <= (int) $this->lines[$linerang]->qty) { + return 1; + } + } else { + if ($current_module == 'facture') { + $result = $this->updateline( // @phpstan-ignore-line + $this->lines[$i]->id, // @phpstan-ignore-line + $this->lines[$i]->desc, // @phpstan-ignore-line + $this->lines[$i]->subprice, // @phpstan-ignore-line + $this->lines[$i]->qty, // @phpstan-ignore-line + $mode == 'discount' ? $value : $this->lines[$i]->remise_percent, // @phpstan-ignore-line + $this->lines[$i]->date_start, // @phpstan-ignore-line + $this->lines[$i]->date_end, // @phpstan-ignore-line + $mode == 'tva' ? $value : $this->lines[$i]->tva_tx, // @phpstan-ignore-line + $this->lines[$i]->localtax1_tx, // @phpstan-ignore-line + $this->lines[$i]->localtax2_tx, // @phpstan-ignore-line + 'HT', // @phpstan-ignore-line + $this->lines[$i]->info_bits, // @phpstan-ignore-line + $this->lines[$i]->product_type, // @phpstan-ignore-line + $this->lines[$i]->fk_parent_line, 0, // @phpstan-ignore-line + $this->lines[$i]->fk_fournprice, // @phpstan-ignore-line + $this->lines[$i]->pa_ht, // @phpstan-ignore-line + $this->lines[$i]->label, // @phpstan-ignore-line + $this->lines[$i]->special_code, // @phpstan-ignore-line + $this->lines[$i]->array_options, // @phpstan-ignore-line + $this->lines[$i]->situation_percent, // @phpstan-ignore-line + $this->lines[$i]->fk_unit, // @phpstan-ignore-line + $this->lines[$i]->multicurrency_subprice // @phpstan-ignore-line + ); + } elseif ($current_module == 'commande') { + $result = $this->updateline( // @phpstan-ignore-line + $this->lines[$i]->id, // @phpstan-ignore-line + $this->lines[$i]->desc, // @phpstan-ignore-line + $this->lines[$i]->subprice, // @phpstan-ignore-line + $this->lines[$i]->qty, // @phpstan-ignore-line + $mode == 'discount' ? $value : $this->lines[$i]->remise_percent, // @phpstan-ignore-line + $mode == 'tva' ? $value : $this->lines[$i]->tva_tx, // @phpstan-ignore-line + $this->lines[$i]->localtax1_rate, // @phpstan-ignore-line + $this->lines[$i]->localtax2_rate, // @phpstan-ignore-line + 'HT', // @phpstan-ignore-line + $this->lines[$i]->info_bits, // @phpstan-ignore-line + $this->lines[$i]->date_start, // @phpstan-ignore-line + $this->lines[$i]->date_end, // @phpstan-ignore-line + $this->lines[$i]->product_type, // @phpstan-ignore-line + $this->lines[$i]->fk_parent_line, 0, // @phpstan-ignore-line + $this->lines[$i]->fk_fournprice, // @phpstan-ignore-line + $this->lines[$i]->pa_ht, // @phpstan-ignore-line + $this->lines[$i]->label, // @phpstan-ignore-line + $this->lines[$i]->special_code, // @phpstan-ignore-line + $this->lines[$i]->array_options, // @phpstan-ignore-line + $this->lines[$i]->fk_unit, // @phpstan-ignore-line + $this->lines[$i]->multicurrency_subprice // @phpstan-ignore-line + ); + } elseif ($current_module == 'propal') { + $result = $this->updateline( // @phpstan-ignore-line + $this->lines[$i]->id, // @phpstan-ignore-line + $this->lines[$i]->subprice, // @phpstan-ignore-line + $this->lines[$i]->qty, // @phpstan-ignore-line + $mode == 'discount' ? $value : $this->lines[$i]->remise_percent, // @phpstan-ignore-line + $mode == 'tva' ? $value : $this->lines[$i]->tva_tx, // @phpstan-ignore-line + $this->lines[$i]->localtax1_rate, // @phpstan-ignore-line + $this->lines[$i]->localtax2_rate, // @phpstan-ignore-line + $this->lines[$i]->desc, // @phpstan-ignore-line + 'HT', // @phpstan-ignore-line + $this->lines[$i]->info_bits, // @phpstan-ignore-line + $this->lines[$i]->special_code, // @phpstan-ignore-line + $this->lines[$i]->fk_parent_line, 0, // @phpstan-ignore-line + $this->lines[$i]->fk_fournprice, // @phpstan-ignore-line + $this->lines[$i]->pa_ht, // @phpstan-ignore-line + $this->lines[$i]->label, // @phpstan-ignore-line + $this->lines[$i]->product_type, // @phpstan-ignore-line + $this->lines[$i]->date_start, // @phpstan-ignore-line + $this->lines[$i]->date_end, // @phpstan-ignore-line + $this->lines[$i]->array_options, // @phpstan-ignore-line + $this->lines[$i]->fk_unit, // @phpstan-ignore-line + $this->lines[$i]->multicurrency_subprice // @phpstan-ignore-line + ); + } + if ($result < 0) { + return $result; + } + } + } + return 1; + } + + /** + * Return the total_ht of lines that are above the current line (excluded) and that are not a subtotal line + * until a title line of the same level is found + * + * @param object $line Line that needs the subtotal amount. + * @return string $total_ht + * + * @phan-suppress PhanUndeclaredProperty + */ + public function getSubtotalLineAmount($line) + { + $final_amount = 0; + for ($i = $line->rang-1; $i > 0; $i--) { + if (is_null($this->lines[$i-1]) || $this->lines[$i-1]->rang >= $line->rang) { + continue; + } + if ($this->lines[$i-1]->special_code == SUBTOTALS_SPECIAL_CODE && $this->lines[$i-1]->qty > 0) { + if ($this->lines[$i-1]->qty <= abs($line->qty)) { + return price($final_amount); + } + } else { + $final_amount += $this->lines[$i-1]->total_ht; + } + } + return price($final_amount); + } + + /** + * Return the multicurrency_total_ht of lines that are above the current line (excluded) and that are not a subtotal line + * until a title line of the same level is found + * + * @param object $line Line that needs the subtotal amount with multicurrency mod activated. + * @return string $total_ht + * + * @phan-suppress PhanUndeclaredProperty + */ + public function getSubtotalLineMulticurrencyAmount($line) + { + $final_amount = 0; + for ($i = $line->rang-1; $i > 0; $i--) { + if (is_null($this->lines[$i-1]) || $this->lines[$i-1]->rang >= $line->rang) { + continue; + } + if ($this->lines[$i-1]->special_code == SUBTOTALS_SPECIAL_CODE && $this->lines[$i-1]->qty>0) { + if ($this->lines[$i-1]->qty <= abs($line->qty)) { + return price($final_amount); + } + } else { + $final_amount += $this->lines[$i-1]->multicurrency_total_ht; + } + } + return price($final_amount); + } + + /** + * Retrieve the background color associated with a specific subtotal level. + * + * @param int|float $level The level of the subtotal for which the color is requested. + * @return string|null The background color in hexadecimal format or null if not set. + */ + public function getSubtotalColors($level) + { + return getDolGlobalString('SUBTOTAL_BACK_COLOR_LEVEL_'.abs($level)); + } + + /** + * Retrieve current object possible titles to choose from + * + * @return array The set of titles, empty if no title line set. + * + * @phan-suppress PhanUndeclaredProperty + */ + public function getPossibleTitles() + { + $titles = array(); + foreach ($this->lines as $line) { + if ($line->special_code == SUBTOTALS_SPECIAL_CODE && $line->qty > 0) { + $titles[$line->desc] = $line->desc; + } + if ($line->special_code == SUBTOTALS_SPECIAL_CODE && $line->qty < 0) { + unset($titles[$line->desc]); + } + } + return $titles; + } + + /** + * Retrieve the current object possible levels (defined in admin page) + * + * @param Translate $langs Translations. + * @return array The set of possible levels, empty if not defined correctly. + * + * @phan-suppress PhanUndeclaredProperty + */ + public function getPossibleLevels($langs) + { + $depth_array = array(); + $max_depth = getDolGlobalString('SUBTOTAL_'.strtoupper($this->element).'_MAX_DEPTH', 2); + for ($i = 0; $i < $max_depth; $i++) { + $depth_array[$i + 1] = $langs->trans("Level", $i + 1); + } + return $depth_array; + } + + /** + * Returns an array with the IDs of the line that we don't need to show to avoid empty blocks + * + * @return array $total_ht + * + * @phan-suppress PhanUndeclaredProperty + */ + public function getDisabledShippmentSubtotalLines() + { + $toDisableLines = array(); + $toDisable = true; + $oldDesc = ""; + $oldDepth = 0; + + foreach ($this->lines as $titleLine) { + if ($titleLine->special_code != SUBTOTALS_SPECIAL_CODE || $titleLine->qty <= 0) { + continue; + } + foreach ($this->lines as $line) { + if ($line->id == $titleLine->id) { + $oldDesc = $line->desc; + $oldDepth = $line->qty; + } + if ($line->special_code != SUBTOTALS_SPECIAL_CODE && $line->fk_product_type == 0 && !empty($oldDesc) && !empty($oldDepth)) { + $toDisable = false; + } + if ($line->special_code == SUBTOTALS_SPECIAL_CODE && $line->qty == -$oldDepth && $line->desc == $oldDesc) { + if ($toDisable) { + $toDisableLines = array_merge($toDisableLines, array($titleLine->id, $line->id)); + } + $oldDesc = ""; + $oldDepth = 0; + $toDisable = true; + break; + } + } + } + return $toDisableLines; + } +} diff --git a/htdocs/subtotals/core/substitutions/functions_subtotals.lib.php b/htdocs/subtotals/core/substitutions/functions_subtotals.lib.php new file mode 100644 index 00000000000..a3ddd90b43a --- /dev/null +++ b/htdocs/subtotals/core/substitutions/functions_subtotals.lib.php @@ -0,0 +1,28 @@ + $substitutionarray Array with substitution key=>val +* @param Translate $langs Output langs +* @param Object $object Object to use to get values +* @param object $line Line to use to get values +* @return void The entry parameter $substitutionarray is modified +*/ +function subtotals_completesubstitutionarray_lines(&$substitutionarray, $langs, $object, $line) +{ + global $conf, $db; + + $substitutionarray['is_subtotals_line'] = $line->special_code == SUBTOTALS_SPECIAL_CODE; + $substitutionarray['is_not_subtotals_line'] = !$substitutionarray['is_subtotals_line']; + $substitutionarray['is_subtotals_title'] = $line->special_code == SUBTOTALS_SPECIAL_CODE && $line->qty > 0; + $substitutionarray['is_subtotals_subtotal'] = $line->special_code == SUBTOTALS_SPECIAL_CODE && $line->qty < 0; + $subtotal_total = 0; + if (isModEnabled('multicurrency') && $object->multicurrency_code != $conf->currency) { + $subtotal_total = $object->getSubtotalLineMulticurrencyAmount($line); // @phan-suppress-current-line PhanPluginUnknownObjectMethodCall + } else { + $subtotal_total = $object->getSubtotalLineAmount($line); // @phan-suppress-current-line PhanPluginUnknownObjectMethodCall + } + $substitutionarray['subtotals_total'] = $subtotal_total == 0 ? "" : $subtotal_total; + $substitutionarray['subtotals_level'] = abs($line->qty); +} diff --git a/test/phpunit/CommonClassTest.class.php b/test/phpunit/CommonClassTest.class.php index 27722930f95..b2070bbcbfa 100644 --- a/test/phpunit/CommonClassTest.class.php +++ b/test/phpunit/CommonClassTest.class.php @@ -453,6 +453,7 @@ abstract class CommonClassTest extends TestCase 'stock' => 'Stock', 'stocktransfer' => 'StockTransfer', 'stripe' => 'Stripe', + 'subtotals' => 'Subtotals', 'supplier_invoice' => null, // Special case, uses invoice 'supplier_order' => null, // Special case, uses invoice 'supplier_proposal' => 'SupplierProposal',