From 5b337881e5bd00f0bd9bf47aad76602cdbcbb508 Mon Sep 17 00:00:00 2001 From: sparkyx Date: Sat, 8 Nov 2025 15:41:15 +0100 Subject: [PATCH] Supplementary documents for an operation, an operation can have several attached documents --- html/js/acc_ledger.js | 168 ++++++++++++++++++ .../XMLDocument/xmlinvoice_reader.class.php | 4 +- include/ajax/ajax_ledger.php | 87 +++++++++ include/export/export_suppl-document.php | 151 ++++++++++++++++ include/template/ajax_ledger+input_file.php | 68 +++++++ include/template/ledger_detail_bottom.php | 11 +- include/template/ledger_detail_sup_files.php | 91 ++++++++++ sql/upgrade.sql | 11 +- 8 files changed, 586 insertions(+), 5 deletions(-) create mode 100644 include/export/export_suppl-document.php create mode 100644 include/template/ajax_ledger+input_file.php create mode 100644 include/template/ledger_detail_sup_files.php diff --git a/html/js/acc_ledger.js b/html/js/acc_ledger.js index 57fb48df1..8f6abcd9f 100644 --- a/html/js/acc_ledger.js +++ b/html/js/acc_ledger.js @@ -2050,3 +2050,171 @@ var operation_exercice = { } } } + + +var Supplement_Document={ + +}; +/** + * see ledger_detail_sup_files.php + * $rowid=sprintf("row_js_%s_%s",$div,$item->js_id); + * @param {int} nDossier + * @param {string} sDiv + * @param {int} nJS_ID + * @returns {void} + */ +Supplement_Document.delete_document=function (nDossier,sDiv,nJS_ID,nJR_ID) +{ + confirm_box(null + ,content[47] + ,function (){ + try + { + var queryString = { + gDossier:nDossier, + div:sDiv, + jr_id:nJR_ID, + js_id:nJS_ID, + op:"ledger", + act:"rmsup" + }; + var action = new Ajax.Request( + "ajax_misc.php", + { + method: 'POST', + parameters: queryString, + onSuccess: function (req) { + remove_waiting_box(); + if (req.responseText == 'NOCONX') { + reconnect(); + return; + } + id$("row_js_"+sDiv+"_"+nJS_ID).remove(); + + } + } + ); + } catch (e) + { + alert_box(e.message); + } + } + ); + +} +Supplement_Document.input_file=function(nDossier,sDiv,nJR_ID) +{ + try + { + var dgbox = "sup_doc_input_file"+sDiv; + waiting_box(); + removeDiv(dgbox); + var queryString = { + gDossier:nDossier, + div:sDiv, + jr_id:nJR_ID, + op:"ledger", + act:"input_file", + dgbox:dgbox + }; + var action = new Ajax.Request( + "ajax_misc.php", + { + method: 'GET', + parameters: queryString, + onFailure: ajax_misc_failure, + onSuccess: function (req) { + remove_waiting_box(); + if (req.responseText == 'NOCONX') { + reconnect(); + return; + } + var y = calcy(15); + var div_style = "position:absolute;" + ";top:" + y + "px"+";z-index:"+get_next_layer(); + add_div({id: dgbox, cssclass: 'inner_box2', html: loading(), style: div_style, drag: true}); + $(dgbox).innerHTML = req.responseText; + + } + } + ); + } catch (e) + { + alert_box(e.message); + } + +} +/** + * + * @param {string} FORM ID + * @returns {Boolean} + */ +Supplement_Document.save_file=function(form_dom_id) +{ + try + { + waiting_box(); + var form_data=$(form_dom_id).serialize(); + var xhr = new XMLHttpRequest(); + var div=id$(form_dom_id).elements["div"].value; + // check size + var total_size=0; + var file_to_upload=id$("doc_sup"); + var max_size=id$(form_dom_id).elements["MAX_FILE_SIZE"].value; + var post_max_size=id$(form_dom_id).elements["post_max_size"].value; + var feedback_div=id$('feedback'+div); + + for (var e=0;e max_size) { + // if size > accepted size , push filename with error in an array feedback, + feedback_div.innerHTML += '

' + file_to_upload.files[e].name + +content[78]+ "

"; + remove_waiting_box(); + return false; + } else if ( total_size+file_to_upload.files[e].size >= post_max_size ) + { + feedback_div.innerHTML += '

limite '+content[78]+"

"; + remove_waiting_box(); + return false; + } + else { + total_size+=file_to_upload.files[e].size; + } + } + feedback_div.innerHTML="loading...."; + document.getElementById("progress_upload1b").setAttribute("value", 0); + + xhr.upload.onprogress = function (e) { + document.getElementById("progress_upload1b").setAttribute("max", e.total) + "
"; + document.getElementById("progress_upload1b").setAttribute("value", e.loaded) + "
"; + + } + + xhr.onreadystatechange = function (event) { + if (this.readyState == XMLHttpRequest.DONE) + { + remove_waiting_box(); + + if (this.status === 200) { + document.getElementById("supplement_div_list"+div).innerHTML += this.responseText ; + let dgbox=id$(form_dom_id).elements["dgbox"].value; + removeDiv(dgbox); + + } else { + document.getElementById("supplement_div_list"+div).innerHTML += "status" + this.statusText + "
"; + } + } + } + + xhr.open("POST", "ajax_misc.php?"+form_data, true); + // works xhr.send(new FormData(input.parentElement)); + var formData = new FormData(document.getElementById(form_dom_id)); + xhr.send(formData); + }catch(e) + { + console.error("Supplement_Document.save_file "+e.message) + } + remove_waiting_box(); + return false; +} diff --git a/include/XMLDocument/xmlinvoice_reader.class.php b/include/XMLDocument/xmlinvoice_reader.class.php index d7b714777..47c96a81e 100644 --- a/include/XMLDocument/xmlinvoice_reader.class.php +++ b/include/XMLDocument/xmlinvoice_reader.class.php @@ -29,7 +29,7 @@ namespace Noalyss\XMLDocument; */ /** - * @file + * @class * @brief Get information from an XML * Exception code : * - 55 : XML Invalid @@ -139,7 +139,7 @@ class XMLInvoice_Reader /** * @brief returns the embedded document in an array (keys : filecontent (BYTES),mimecode , filename) * or false if there is no document - * @return bool|array of array : filecontent (BYTES),mimecode , filename) + * @return bool|array of Document_Reference * @throws \Exception if there are several documents * */ diff --git a/include/ajax/ajax_ledger.php b/include/ajax/ajax_ledger.php index 93549280e..515c4dd99 100644 --- a/include/ajax/ajax_ledger.php +++ b/include/ajax/ajax_ledger.php @@ -566,6 +566,93 @@ switch ($action) { $acc_operation_note= Acc_Operation_Note::build_jrn_id($jr_id); echo substr($acc_operation_note->getNote()??"",0,120); + return; + case "rmsup": + //------------------------------------------------ + // Remove a document from JRN_SUP_DOCUMENT + //------------------------------------------------ + $js_id=$http->post("js_id","number"); + $jrn_sup= new Jrn_Sup_Document_SQL($cn,$js_id); + $jrn_sup->delete(); + // no answer, we stop here + return; + case 'input_file': + //------------------------------------------------ + // Display form for adding file , directly HTML + // + //------------------------------------------------ + require_once NOALYSS_TEMPLATE."/ajax_ledger+input_file.php"; + return; + case 'save_file': + //------------------------------------------------ + // Add a document to the operation + //------------------------------------------------ + if (sizeof($_FILES)==0) { + return; + } + $nb=count($_FILES['document_supplemental']["name"]); + $cn->start(); + for ($i=0;$i<$nb;$i++) + { + $file= tempnam($_ENV["TMP"], "sup_file"); + if ( move_uploaded_file($_FILES['document_supplemental']['tmp_name'][$i],$file)) + { + if ( ($oid=$cn->lo_import($file)) != false ) + { + $jrn_sup=new Jrn_Sup_Document_SQL($cn); + $jrn_sup->jr_id=$jr_id; + $jrn_sup->js_lob=$oid; + $jrn_sup->js_mimetype=$_FILES['document_supplemental']['type'][$i]; + $jrn_sup->js_filename=$_FILES['document_supplemental']['name'][$i]; + $jrn_sup->insert(); + $rowid=sprintf("row_js_%s_%s",$div,$jrn_sup->js_id); + // @var $download (url) to send file + $download="export.php?". http_build_query( + [ + "act"=>"RAW:suppl-document" + ,"js_id"=>$jrn_sup->js_id + ,"gDossier"=>$gDossier + ]); + + + $script_remove="Supplement_Document.delete_document('$gDossier','$div','{$jrn_sup->js_id}','$jr_id')"; + $icon_remove=\Icon_Action::trash(uniqid("sdd"),$script_remove); + echo << + +
+ {$jrn_sup->js_description} +
+
+ {$icon_remove} +
+ +EOF; + } + else + { + // failed + print '
'; + echo_warning(_("1 Echec ").$_FILES["name"]); + print '
'; + } + + } + else + { + //failed + // failed + print '
'; + echo_warning(_("2 Echec ").$_FILES["name"]); + print '
'; + } + } + + $cn->commit(); return; } $html = escape_xml($html); diff --git a/include/export/export_suppl-document.php b/include/export/export_suppl-document.php new file mode 100644 index 000000000..763614375 --- /dev/null +++ b/include/export/export_suppl-document.php @@ -0,0 +1,151 @@ +get('js_id',"number"); + $operation_id=$http->get("operation_id",'number',0); +} +catch (Exception $exc) +{ + record_log($exc); + return; +} +$cn=Dossier::connect(); + +//------------------------------------------------ +// Download only one document +//------------------------------------------------ +if ( $js_id > 0) +{ + $jrn_def_id=$cn->get_value("select jr_def_id from jrn_sup_document join jrn using (jr_id) where js_id=$1",array($js_id)); + + global $g_user; + if ($jrn_def_id =="" || $g_user->check_jrn($jrn_def_id) == 'X' ) + { + /* Cannot Access */ + NoAccess(); + exit -1; + } + + $jrn=new Jrn_Sup_Document_SQL($cn,$js_id); + $cn->start(); + ini_set('zlib.output_compression','Off'); + header("Pragma: public"); + header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); + header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); + header("Cache-Control: must-revalidate"); + header('Content-type: '.$jrn->js_mimetype); + header('Content-Disposition: attachment;filename="'.$jrn->js_filename.'"',FALSE); + header("Accept-Ranges: bytes"); + echo $cn->lo_read($jrn->js_lob); + $cn->commit(); + return; +} +//------------------------------------------------ +// Download all documents +//------------------------------------------------ +if ( $operation_id > 0) +{ + $jrn_def_id=$cn->get_value("select jr_def_id from jrn where jr_id=$1",array($operation_id)); + + global $g_user; + if ($jrn_def_id =="" || $g_user->check_jrn($jrn_def_id) == 'X' ) + { + /* Cannot Access */ + NoAccess(); + exit -1; + } + $jr_internal=$cn->get_value("select jr_internal from jrn where jr_id=$1",array($operation_id)); + + try { + $cn->start(); + // find all the documents for this operation + $a_file=$cn->get_array("select js_lob,js_filename from jrn_sup_document where jr_id=$1", + [$operation_id]); + // save them into a temp folder + if ( count ($a_file) == 0 ) { + // @TODO send an empty file + return; + } + $a_file_dwn=array(); + $store = tempnam($_ENV['TMP'], 'pdf_'); + unlink($store); + mkdir($store); + $nb_file=count($a_file); + for($i=0;$i < $nb_file;$i++) + { + $filename=sprintf("%s".DIRECTORY_SEPARATOR."%s", + $store, + $a_file[$i]['js_filename']); + + // avoid to overwrite a previous file with same name + if (file_exists($filename)) { + $dup=1; + do { + $filename=sprintf("%s".DIRECTORY_SEPARATOR."%s-%s", + $store, + $dup, + $a_file[$i]['js_filename']); + $dup++; + }while ( file_exists($filename)); + + } + $cn->lo_export($a_file[$i]['js_lob'], $filename); + $a_file_dwn[]=$filename; + + } + $cn->commit(); + // zip the folder + $zip = new Zip_Extended(); + $zip_file=sprintf("%s".DIRECTORY_SEPARATOR."%s.zip" + ,$store + ,$jr_internal); + + chdir($store); + $res=$zip->open($zip_file, ZipArchive::CREATE); + foreach ($a_file_dwn as $item) + { + $zip->addFile(basename($item)); + } + $zip->close(); + // send the folder to browser + header('Content-Type: application/zip'); + header('Content-Disposition: attachment; filename="document-'.$jr_internal.'.zip"'); + header('Cache-Control: private, max-age=0, must-revalidate'); + header('Pragma: public'); + echo file_get_contents($zip_file); + + } catch (Exception $exc) { + record_log($exc); + } + +} \ No newline at end of file diff --git a/include/template/ajax_ledger+input_file.php b/include/template/ajax_ledger+input_file.php new file mode 100644 index 000000000..5bcd27a7a --- /dev/null +++ b/include/template/ajax_ledger+input_file.php @@ -0,0 +1,68 @@ + list of files to update after uploading + */ + +$dgbox=$http->request("dgbox"); +$form_id="save_file{$div}"; +$progress="progress{$div}"; +$max_post_size=convert_ini_unit(ini_get("post_max_size")); + +echo \HtmlInput::title_box(_("Ajout de fichier"), $dgbox); +?> +
+ + +

+ +

+
+ + + + + + + + + +
+
    +
  • + " onclick="return Supplement_Document.save_file('');"> +
  • +
  • + +
  • +
+
+ +
\ No newline at end of file diff --git a/include/template/ledger_detail_bottom.php b/include/template/ledger_detail_bottom.php index cdea88b46..ce803a7bc 100644 --- a/include/template/ledger_detail_bottom.php +++ b/include/template/ledger_detail_bottom.php @@ -44,6 +44,7 @@ $a_tab['linked_operation_div']=array('id'=>'linked_operation_div'.$div,'label'=> $a_tab['document_operation_div']=array('id'=>'document_operation_div'.$div,'label'=>_('Document').'('.$nb_document.')','display'=>'block'); $a_tab['linked_action_div']=array('id'=>'linked_action_div'.$div,'label'=>_('Actions Gestion').'('.count($a_followup).')','display'=>'none'); $a_tab['analytic_div']=array('id'=>'analytic_div'.$div,'label'=>_('Comptabilité Analytique'),'display'=>'none'); +$a_tab['supplemental_doc_div']=array('id'=>'supplemental_doc_div'.$div,'label'=>_('Documents supplémentaires'),'display'=>'none'); //var $g_parameter \Noalyss_Parameter_Folder global $g_parameter; @@ -293,9 +294,17 @@ echo ''; ?> +
diff --git a/include/template/ledger_detail_sup_files.php b/include/template/ledger_detail_sup_files.php new file mode 100644 index 000000000..1506c1590 --- /dev/null +++ b/include/template/ledger_detail_sup_files.php @@ -0,0 +1,91 @@ + +
+
+collect_objects(" where jr_id=$1 order by js_cbc_id", [$jr_id]); +$javascript=sprintf("Supplement_Document.input_file('%s','%s','%s')", + $gDossier, + $div, + $jr_id); +if ( count($a_row) > 0) +{ + foreach ($a_row as $item) { + $export="export.php?"; + $script="Supplement_Document.delete_document('$gDossier','$div','{$item->js_id}','$jr_id')"; + $rowid=sprintf("row_js_%s_%s",$div,$item->js_id); + // @var $download (url) to send file + $download="export.php?". http_build_query( + [ + "act"=>"RAW:suppl-document" + ,"js_id"=>$item->js_id + ,"gDossier"=>$gDossier + ]); + ?> +
+ +
+ js_description?> +
+
+ +
+
+ "RAW:suppl-document" + ,"js_id"=>0 + ,"gDossier"=>$gDossier + ,'operation_id'=>$jr_id + ]); + +?> + + +
+
    +
  • + +
  • +
+
\ No newline at end of file diff --git a/sql/upgrade.sql b/sql/upgrade.sql index ccc8c7fe5..8b45c75cc 100644 --- a/sql/upgrade.sql +++ b/sql/upgrade.sql @@ -303,7 +303,7 @@ comment on sequence seq_doc_type_stdinv is 'Sequence for standard invoice'; INSERT INTO public.menu_ref (me_code, me_menu, me_file, me_url, me_description, me_parameter, me_javascript, me_type, me_description_etendue) -VALUES('RAW:xml-invoice', 'Exporte la facture XML', 'export_xml-invoice.php', NULL, 'export la facture électronique en XML', NULL, NULL, 'PR', NULL); +VALUES('RAW:xml-invoice', 'Exporte la facture XML', 'export_xml-invoice.php', NULL, 'exporte la facture électronique en XML', NULL, NULL, 'PR', NULL); insert into profile_menu (me_code,p_id, p_type_display) select 'RAW:xml-invoice',p_id,'P' from profile; @@ -491,4 +491,11 @@ COMMENT ON COLUMN public.jrn_sup_document.js_mimetype IS 'Mimetype'; COMMENT ON COLUMN public.jrn_sup_document.js_lob IS 'OID'; COMMENT ON COLUMN public.jrn_sup_document.jr_id IS 'FK to jrn'; COMMENT ON COLUMN public.jrn_sup_document.js_description IS 'Description of document'; -COMMENT ON COLUMN public.jrn_sup_document.js_cbc_id IS 'ID in XML'; \ No newline at end of file +COMMENT ON COLUMN public.jrn_sup_document.js_cbc_id IS 'ID in XML'; + + + +INSERT INTO public.menu_ref (me_code, me_menu, me_file, me_url, me_description, me_parameter, me_javascript, me_type, me_description_etendue) +VALUES('RAW:suppl-document', 'Exporte Document supplementaire', 'export_suppl-document.php', NULL, 'télécharge le document supp', NULL, NULL, 'PR', NULL); + +insert into profile_menu (me_code,p_id, p_type_display) select 'RAW:suppl-document',p_id,'P' from profile;