Browse Source

Merge branch 'develop' into completeJSProductDisplay

Laurent Destailleur 3 years ago
parent
commit
81b30d584b
100 changed files with 2513 additions and 2140 deletions
  1. 6 3
      ChangeLog
  2. 12 12
      build/makepack-howto.txt
  3. 8 8
      dev/resources/iso-normes/accountancy/accountancy_rules.txt
  4. 0 0
      dev/resources/iso-normes/banking/banknumber_format.txt
  5. 0 0
      dev/resources/iso-normes/banking/iban_iso-13616_fr.txt
  6. 5 0
      dev/resources/iso-normes/barcodes/QR code for invoices.txt
  7. 49 49
      dev/resources/iso-normes/barcodes/barcode_EAN13.txt
  8. 100 8
      htdocs/accountancy/admin/productaccount.php
  9. 33 30
      htdocs/accountancy/bookkeeping/card.php
  10. 237 124
      htdocs/accountancy/bookkeeping/list.php
  11. 322 127
      htdocs/accountancy/bookkeeping/listbyaccount.php
  12. 0 979
      htdocs/accountancy/bookkeeping/listbysubaccount.php
  13. 10 0
      htdocs/accountancy/class/accountancyexport.class.php
  14. 3 1
      htdocs/accountancy/class/bookkeeping.class.php
  15. 453 10
      htdocs/accountancy/class/lettering.class.php
  16. 10 0
      htdocs/accountancy/journal/bankjournal.php
  17. 6 0
      htdocs/accountancy/journal/purchasesjournal.php
  18. 6 0
      htdocs/accountancy/journal/sellsjournal.php
  19. 2 2
      htdocs/adherents/cartes/carte.php
  20. 2 2
      htdocs/adherents/list.php
  21. 1 1
      htdocs/adherents/subscription/list.php
  22. 3 2
      htdocs/admin/commande.php
  23. 9 7
      htdocs/admin/debugbar.php
  24. 29 3
      htdocs/admin/emailcollector_list.php
  25. 1 1
      htdocs/admin/eventorganization.php
  26. 12 19
      htdocs/admin/ihm.php
  27. 2 1
      htdocs/admin/system/dolibarr.php
  28. 15 11
      htdocs/admin/ticket.php
  29. 10 4
      htdocs/admin/translation.php
  30. 3 3
      htdocs/admin/website.php
  31. 11 1
      htdocs/api/class/api_documents.class.php
  32. 13 7
      htdocs/comm/action/class/ical.class.php
  33. 13 13
      htdocs/comm/action/index.php
  34. 16 12
      htdocs/comm/action/pertype.php
  35. 13 9
      htdocs/comm/action/peruser.php
  36. 6 6
      htdocs/comm/propal/card.php
  37. 5 5
      htdocs/comm/propal/list.php
  38. 3 3
      htdocs/commande/card.php
  39. 347 1
      htdocs/commande/list.php
  40. 5 4
      htdocs/compta/bank/card.php
  41. 8 4
      htdocs/compta/bank/list.php
  42. 18 16
      htdocs/compta/facture/card-rec.php
  43. 29 9
      htdocs/compta/facture/card.php
  44. 2 2
      htdocs/compta/facture/class/api_invoices.class.php
  45. 8 1
      htdocs/compta/facture/class/facture-rec.class.php
  46. 36 15
      htdocs/compta/facture/class/facture.class.php
  47. 51 4
      htdocs/compta/facture/list.php
  48. 1 2
      htdocs/compta/index.php
  49. 14 8
      htdocs/compta/localtax/card.php
  50. 4 1
      htdocs/compta/localtax/list.php
  51. 4 2
      htdocs/compta/paiement/class/paiement.class.php
  52. 11 8
      htdocs/compta/paiement_vat.php
  53. 2 2
      htdocs/compta/prelevement/class/bonprelevement.class.php
  54. 4 2
      htdocs/compta/prelevement/create.php
  55. 3 3
      htdocs/compta/prelevement/fiche-rejet.php
  56. 3 3
      htdocs/compta/prelevement/fiche-stat.php
  57. 2 2
      htdocs/compta/resultat/index.php
  58. 5 4
      htdocs/compta/tva/card.php
  59. 1 1
      htdocs/contrat/list.php
  60. 1 1
      htdocs/contrat/services_list.php
  61. 9 1
      htdocs/core/actions_extrafields.inc.php
  62. 55 355
      htdocs/core/actions_massactions.inc.php
  63. 6 0
      htdocs/core/class/commondocgenerator.class.php
  64. 16 10
      htdocs/core/class/commonobject.class.php
  65. 117 18
      htdocs/core/class/html.form.class.php
  66. 6 4
      htdocs/core/class/html.formfile.class.php
  67. 10 3
      htdocs/core/class/html.formmail.class.php
  68. 8 3
      htdocs/core/class/html.formother.class.php
  69. 27 23
      htdocs/core/class/html.formprojet.class.php
  70. 10 3
      htdocs/core/class/rssparser.class.php
  71. 1 1
      htdocs/core/extrafieldsinimport.inc.php
  72. 1 1
      htdocs/core/filemanagerdol/connectors/php/config.php
  73. 2 0
      htdocs/core/js/lib_head.js.php
  74. 12 9
      htdocs/core/lib/functions.lib.php
  75. 3 0
      htdocs/core/lib/modulebuilder.lib.php
  76. 5 2
      htdocs/core/lib/usergroups.lib.php
  77. 2 2
      htdocs/core/lib/website.lib.php
  78. 24 15
      htdocs/core/menus/standard/auguria.lib.php
  79. 16 10
      htdocs/core/menus/standard/eldy.lib.php
  80. 23 10
      htdocs/core/menus/standard/empty.php
  81. 3 1
      htdocs/core/modules/DolibarrModules.class.php
  82. 1 1
      htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php
  83. 6 2
      htdocs/core/modules/commande/mod_commande_saphir.php
  84. 1 1
      htdocs/core/modules/expedition/doc/pdf_rouget.modules.php
  85. 1 1
      htdocs/core/modules/facture/doc/pdf_sponge.modules.php
  86. 17 1
      htdocs/core/modules/mailings/contacts1.modules.php
  87. 15 0
      htdocs/core/modules/mailings/thirdparties.modules.php
  88. 21 24
      htdocs/core/modules/modEmailCollector.class.php
  89. 1 1
      htdocs/core/modules/modIntracommreport.class.php
  90. 10 8
      htdocs/core/modules/modPartnership.class.php
  91. 2 2
      htdocs/core/modules/modProjet.class.php
  92. 7 1
      htdocs/core/modules/modTicket.class.php
  93. 3 3
      htdocs/core/modules/project/mod_project_simple.php
  94. 5 2
      htdocs/core/modules/project/mod_project_universal.php
  95. 1 1
      htdocs/core/modules/propale/doc/pdf_cyan.modules.php
  96. 71 59
      htdocs/core/modules/stock/doc/pdf_standard.modules.php
  97. 1 1
      htdocs/core/modules/supplier_proposal/doc/pdf_aurore.modules.php
  98. 1 1
      htdocs/core/modules/ticket/mod_ticket_simple.php
  99. 4 1
      htdocs/core/modules/ticket/mod_ticket_universal.php
  100. 1 1
      htdocs/core/tpl/admin_extrafields_add.tpl.php

+ 6 - 3
ChangeLog

@@ -25,15 +25,18 @@ For developers:
 NEW: A lot of addition of hooks.
 
 
- 
+
 Following changes may create regressions for some external modules, but were necessary to make Dolibarr better:
 * The default value for MAIN_SECURITY_CSRF_WITH_TOKEN has been set to 2. It means any POST and any GET request that contains the "action" or "massaction"
   with a value of a sensitive action must also a valid token parameter (With previous value 1, only POST was concerned). Note: With value 3, any URL
   with parameter "action" or "massaction" need the token, whatever is the value of the action.
 * verifCond('stringtoevaluate') now return false when string contains a bad syntax content instead of true. 
 * The deprecated method thirdparty_doc_create() has been removed. You can use the generateDocument() instead.
-* All triggers with a name XXX_UPDATE have been rename with name XXX_MODIFY for code consistency purpose.
-* Rename build_path_from_id_categ() into buildPathFromId() and set method to private
+* All triggers with a name XXX_UPDATE have been renamed with name XXX_MODIFY for code consistency purpose.
+* Rename build_path_from_id_categ() into buildPathFromId() and set method to private.
+* Move massaction 'confirm_createbills' from actions_massactions.inc.php to commande/list.php
+* Method fetch_all_resources(), fetch_all_used(), fetch_all_available() of DolResource has been removed (they were not used by core code).
+* Method fetch_all of DolResource has been renamed into fetchAll() to match naming conventions.
 
 
 ***** ChangeLog for 15.0.1 compared to 15.0.0 *****

+ 12 - 12
build/makepack-howto.txt

@@ -8,12 +8,13 @@ This files describe steps made by Dolibarr packaging team to make a
 beta version of Dolibarr, step by step.
 
 - Check all files are commited.
-- Update version/info in ChangeLog. 
-To generate a changelog of a major new version x.y.0 (from develop repo), you can do "cd ~/git/dolibarr; git log `diff -u <(git rev-list --first-parent x.(y-1).0)  <(git rev-list --first-parent develop) | sed -ne 's/^ //p' | head -1`.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e '^FIX\|NEW' | sort -u | sed 's/FIXED:/FIX:/g' | sed 's/FIXED :/FIX:/g' | sed 's/FIX :/FIX:/g' | sed 's/FIX /FIX: /g' | sed 's/NEW :/NEW:/g' | sed 's/NEW /NEW: /g' > /tmp/aaa"
-To generate a changelog of a major new version x.y.0 (from x.y repo), you can do "cd ~/git/dolibarr_x.y; git log `diff -u <(git rev-list --first-parent x.(y-1).0)  <(git rev-list --first-parent x.y.0) | sed -ne 's/^ //p' | head -1`.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e '^FIX\|NEW' | sort -u | sed 's/FIXED:/FIX:/g' | sed 's/FIXED :/FIX:/g' | sed 's/FIX :/FIX:/g' | sed 's/FIX /FIX: /g' | sed 's/NEW :/NEW:/g' | sed 's/NEW /NEW: /g' > /tmp/aaa"
+- Update version/info in ChangeLog, for this you can: 
+To generate a changelog of a major new version x.y.0 (from a repo on branch develop), you can do "cd ~/git/dolibarr; git log `diff -u <(git rev-list --first-parent x.(y-1).0)  <(git rev-list --first-parent develop) | sed -ne 's/^ //p' | head -1`.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e '^FIX\|NEW' | sort -u | sed 's/FIXED:/FIX:/g' | sed 's/FIXED :/FIX:/g' | sed 's/FIX :/FIX:/g' | sed 's/FIX /FIX: /g' | sed 's/NEW :/NEW:/g' | sed 's/NEW /NEW: /g' > /tmp/aaa"
+To generate a changelog of a major new version x.y.0 (from a repo on branch x.y repo), you can do "cd ~/git/dolibarr_x.y; git log `diff -u <(git rev-list --first-parent x.(y-1).0)  <(git rev-list --first-parent x.y.0) | sed -ne 's/^ //p' | head -1`.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e '^FIX\|NEW' | sort -u | sed 's/FIXED:/FIX:/g' | sed 's/FIXED :/FIX:/g' | sed 's/FIX :/FIX:/g' | sed 's/FIX /FIX: /g' | sed 's/NEW :/NEW:/g' | sed 's/NEW /NEW: /g' > /tmp/aaa"
 To generate a changelog of a maintenance version x.y.z, you can do "cd ~/git/dolibarr_x.y; git log x.y.z-1.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e '^FIX\|NEW' | sort -u | sed 's/FIXED:/FIX:/g' | sed 's/FIXED :/FIX:/g' | sed 's/FIX :/FIX:/g' | sed 's/FIX /FIX: /g' | sed 's/NEW :/NEW:/g' | sed 's/NEW /NEW: /g' > /tmp/aaa"
-- To know number of lines changes: git diff --shortstat A B
-- Update version number with x.y.z-w in htdocs/filefunc.inc.php
+Recopy the content of the output file into the file ChangeLog.
+- Note: To know number of lines changes: git diff --shortstat A B
+- Update version number with x.y.z-w in file htdocs/filefunc.inc.php
 - Commit all changes.
 
 - Run makepack-dolibarr.pl to generate all packages.
@@ -24,7 +25,6 @@ To generate a changelog of a maintenance version x.y.z, you can do "cd ~/git/dol
   (/home/dolibarr/wwwroot/files/lastbuild).
 
 - Post a news on dolibarr.org/dolibarr.fr + social networks
-- Send mail on mailings-list
 
 
 ***** Actions to do a RELEASE *****
@@ -32,12 +32,13 @@ This files describe steps made by Dolibarr packaging team to make a
 complete release of Dolibarr, step by step.
 
 - Check all files are commited.
-- Update version/info in ChangeLog. 
-To generate a changelog of a major new version x.y.0 (from develop repo), you can do "cd ~/git/dolibarr; git log `diff -u <(git rev-list --first-parent x.(y-1).0)  <(git rev-list --first-parent develop) | sed -ne 's/^ //p' | head -1`.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e '^FIX\|NEW' | sort -u | sed 's/FIXED:/FIX:/g' | sed 's/FIXED :/FIX:/g' | sed 's/FIX :/FIX:/g' | sed 's/FIX /FIX: /g' | sed 's/NEW :/NEW:/g' | sed 's/NEW /NEW: /g' > /tmp/aaa"
-To generate a changelog of a major new version x.y.0 (from x.y repo), you can do "cd ~/git/dolibarr_x.y; git log `diff -u <(git rev-list --first-parent x.(y-1).0)  <(git rev-list --first-parent x.y.0) | sed -ne 's/^ //p' | head -1`.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e '^FIX\|NEW' | sort -u | sed 's/FIXED:/FIX:/g' | sed 's/FIXED :/FIX:/g' | sed 's/FIX :/FIX:/g' | sed 's/FIX /FIX: /g' | sed 's/NEW :/NEW:/g' | sed 's/NEW /NEW: /g' > /tmp/aaa"
+- Update version/info in ChangeLog, for this you can:
+To generate a changelog of a major new version x.y.0 (from a repo on branch develop), you can do "cd ~/git/dolibarr; git log `diff -u <(git rev-list --first-parent x.(y-1).0)  <(git rev-list --first-parent develop) | sed -ne 's/^ //p' | head -1`.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e '^FIX\|NEW' | sort -u | sed 's/FIXED:/FIX:/g' | sed 's/FIXED :/FIX:/g' | sed 's/FIX :/FIX:/g' | sed 's/FIX /FIX: /g' | sed 's/NEW :/NEW:/g' | sed 's/NEW /NEW: /g' > /tmp/aaa"
+To generate a changelog of a major new version x.y.0 (from a repo pn branch x.y), you can do "cd ~/git/dolibarr_x.y; git log `diff -u <(git rev-list --first-parent x.(y-1).0)  <(git rev-list --first-parent x.y.0) | sed -ne 's/^ //p' | head -1`.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e '^FIX\|NEW' | sort -u | sed 's/FIXED:/FIX:/g' | sed 's/FIXED :/FIX:/g' | sed 's/FIX :/FIX:/g' | sed 's/FIX /FIX: /g' | sed 's/NEW :/NEW:/g' | sed 's/NEW /NEW: /g' > /tmp/aaa"
 To generate a changelog of a maintenance version x.y.z, you can do "cd ~/git/dolibarr_x.y; git log x.y.z-1.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e '^FIX\|NEW' | sort -u | sed 's/FIXED:/FIX:/g' | sed 's/FIXED :/FIX:/g' | sed 's/FIX :/FIX:/g' | sed 's/FIX /FIX: /g' | sed 's/NEW :/NEW:/g' | sed 's/NEW /NEW: /g' > /tmp/aaa"
-- To know number of lines changes: git diff --shortstat A B
-- Update version number with x.y.z in htdocs/filefunc.inc.php
+Recopy the content of the output file into the file ChangeLog.
+- Note: To know the number of lines changes: git diff --shortstat A B
+- Update version number with x.y.z in file htdocs/filefunc.inc.php
 - Commit all changes.
 
 - Run makepack-dolibarr.pl to generate all packages.
@@ -52,4 +53,3 @@ To generate a changelog of a maintenance version x.y.z, you can do "cd ~/git/dol
   on server to point to new files (used by some web sites).
 
 - Post a news on dolibarr.org/dolibarr.fr + social networks
-- Send mail on mailings-list

+ 8 - 8
dev/resources/iso-normes/accountancy_rules.txt → dev/resources/iso-normes/accountancy/accountancy_rules.txt

@@ -2,13 +2,13 @@
 Gestion escompte:
 
 Sur une facture de 120 € TTC :
-707xxx                 100 € HT
-44571x                 20 € TVA
-411xxx    120 € TTC
- 
+707xxx      100 € HT
+44571x       20 € TVA
+411xxx      120 € TTC
+
 Le client règle rapidement et on lui accorde un escompte de 3% (120 € * 3% = 3.6 € TTC), on aura donc :
-665000     3,00 € HT
-44571x     0,60 € TVA
-411xxx                 3.60 € TVA
- 
+665000      3,00 € HT
+44571x      0,60 € TVA
+411xxx      3,60 € TTC
+
 Et ça marche à l’inverse avec un fournisseur sauf que l’on est en 775000 au lieu de 665000 pour escompte obtenus.

+ 0 - 0
dev/resources/iso-normes/banknumber_format.txt → dev/resources/iso-normes/banking/banknumber_format.txt


+ 0 - 0
dev/resources/iso-normes/iban_iso-13616.txt → dev/resources/iso-normes/banking/iban_iso-13616_fr.txt


+ 5 - 0
dev/resources/iso-normes/QR code for invoices.txt → dev/resources/iso-normes/barcodes/QR code for invoices.txt

@@ -1,3 +1,8 @@
+QR-Code = Quick Response Code   -  is a two-dimensional / 2D- / Matrix-Barcode
+
+ISO/IEC 18004
+
+
 List of QR Code format we found on some invoices
 ------------------------------------------------
 

+ 49 - 49
dev/resources/iso-normes/barcode_EAN13.txt → dev/resources/iso-normes/barcodes/barcode_EAN13.txt

@@ -31,60 +31,60 @@ Here is the list of country codes or system:
 List
 ====
 
-00 - 13  UCC (U.S.A / États-Unis & Canada)
+00 - 13  UCC  (U.S.A / États-Unis & Canada)
 20 - 29  Flag for internal numbering / Codification interne en magasin
 30 - 37  GENCOD-EAN  France
 380      BCCI  (Bulgaria)
 383      SANA  (Slovenia)
 385      CRO-EAN  (Croatia)
 387      EAN-BIH  (Bosnia-Herzegovina)
-400-440  CCG  (Allemagne/Germany)
+400-440  CCG  (DE/Germany/Allemagne)
 45 + 49  Distribution Code Center - DCC  (Japan)
 460-469  UNISCAN - EAN Russia  (Federation de Russie)
 471      CAN  Taiwan
-474      EAN Estonia
-475      EAN Latvia
-476      EAN Azerbaijan
-477      EAN Lithuania
-478      EAN Uzbekistan
-479      EAN Sri Lanka
+474      EAN  Estonia
+475      EAN  Latvia
+476      EAN  Azerbaijan
+477      EAN  Lithuania
+478      EAN  Uzbekistan
+479      EAN  Sri Lanka
 480      PANC Philippines
-481      EAN Belarus
-482      EAN Ukraine
-484      EAN Moldova 
-485      EAN Armenia
-486      EAN Georgia
-487      EAN Kazakhstan
+481      EAN  Belarus
+482      EAN  Ukraine
+484      EAN  Moldova 
+485      EAN  Armenia
+486      EAN  Georgia
+487      EAN  Kazakhstan
 489      HKANA Hong Kong
 50       E Centre UK - United Kingdom 
-520 HELLCAN-EAN HELLAS (Grece)
-528 EAN Liban
-529 EAN Chypre
-531 EAN-MAC (FYR Macedonie)
-535 EAN Malte
-539 EAN Irlande
-54  ICODIF/EAN Belgique. Luxembourg
-560 CODIPOR (Portugal)
-569 EAN Islande
-57  EAN Danemark
-590 EAN Pologne
-594 EAN Roumanie
-599 H.A.P.M.H. (Hongrie)
-600 - 601 EAN Afrique du Sud
-609 EAN Ile Maurice
-611 EAN Maroc
-613 EAN Algerie
-619 Tunicode (Tunisie)
-621 EAN Syrie
-622 EAN Egypte
-625 EAN Jordanie
-626 EAN Iran
-628 EAN Arabie Saoudite
-64 EAN Finlande
-690 - 693 Article Numbering Centre of China - ANCC (Chine)
-70 EAN Norge (Norvege)
-729 Israeli Bar Code Association  EAN Israel
-73 EAN Suede
+520      HELLCAN-EAN HELLAS - Greece
+528      EAN  Lebanon
+529      EAN  Cyprus
+531      EAN-MAC (FYR Macedonia)
+535      EAN  Malta
+539      EAN  Ireland
+54       ICODIF/EAN  Belgium & Luxembourg
+560      CODIPOR (Portugal)
+569      EAN  Iceland/Islande
+57       EAN  Denmark
+590      EAN  Poland
+594      EAN  Romania
+599      H.A.P.M.H. (Hungary)
+600-601  EAN  South Africa
+609      EAN  Mauritius Island
+611      EAN  Morocco
+613      EAN  Algeria
+619      Tunicode (Tunisia)
+621      EAN  Syria
+622      EAN  Egypt
+625      EAN  Jordan/Jordanie
+626      EAN  Iran
+628      EAN  Saudi Arabia
+64       EAN  Finland
+690-693  ANCC - Article Numbering Centre of China
+70  EAN Norge (Norvege)
+729 Israeli Bar Code Association - EAN Israel
+73  EAN Suede
 740 EAN Guatemala
 741 EAN El Salvador
 742 ICCC (Honduras)
@@ -93,7 +93,7 @@ List
 746 746 EAN Republique Dominicaine
 750 AMECE (Mexique)
 759 EAN Venezuela
-76 EAN (Schweiz, Suisse, Svizzera)
+76  EAN (Schweiz, Suisse, Svizzera)
 770 IAC (Colombie)
 773 EAN Uruguay
 775 APC - EAN Peru (Perou)
@@ -103,15 +103,15 @@ List
 784 EAN Paraguay
 786 ECOP (Equateur)
 789 EAN Bresil
-80 � 83 INDICOD (Italie)
-84 AECOC (Espagne)
+80 - 83 INDICOD (Italy)
+84  AECOC (Espagne)
 850 Camera de Comercio de la Republica de Cuba (Cuba)
 858 EAN Slovaquie
 859 EAN Republique Tcheque
 860 EAN YU (Yougoslavie)
 867 EAN DPR Korea (Coree du Nord)
 869 Union of Chambers of Commerce of Turkey (Turquie)
-87 EAN Nederland (Hollande)
+87  EAN Nederland (Hollande)
 880 EAN Korea (Coree du Sud)
 885 EAN Thailande
 888 SANC (Singapour)
@@ -119,11 +119,11 @@ List
 893 EAN Vietnam
 899 EAN Indonesie
 90 - 91 EAN Autriche
-93 EAN Australie
-94 EAN Nouvelle Zelande
+93  EAN Australie
+94  EAN Nouvelle Zelande
 955 Malaysian Article Numbering Council (MANC) - Malaisie
 977 Publications sirielles (ISSN)
 978 - 979 Livres (ISBN)
 980 Refus de remboursement
 981 - 982 Coupons (monnaie courante)
-99 Coupons
+99  Coupons

+ 100 - 8
htdocs/accountancy/admin/productaccount.php

@@ -1,10 +1,10 @@
 <?php
 /* Copyright (C) 2013-2014 Olivier Geffroy      <jeff@jeffinfo.com>
- * Copyright (C) 2013-2021 Alexandre Spangaro   <aspangaro@open-dsi.fr>
- * Copyright (C) 2014 	   Florian Henry		<florian.henry@open-concept.pro>
- * Copyright (C) 2014 	   Juanjo Menent		<jmenent@2byte.es>
- * Copyright (C) 2015      Ari Elbaz (elarifr)	<github@accedinfo.com>
- * Copyright (C) 2021      Gauthier VERDOL	<gauthier.verdol@atm-consulting.fr>
+ * Copyright (C) 2013-2022 Alexandre Spangaro   <aspangaro@open-dsi.fr>
+ * Copyright (C) 2014      Florian Henry        <florian.henry@open-concept.pro>
+ * Copyright (C) 2014      Juanjo Menent        <jmenent@2byte.es>
+ * Copyright (C) 2015      Ari Elbaz (elarifr)  <github@accedinfo.com>
+ * Copyright (C) 2021      Gauthier VERDOL      <gauthier.verdol@atm-consulting.fr>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -34,6 +34,9 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
 require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
+if (!empty($conf->categorie->enabled)) {
+	require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
+}
 
 // Load translation files required by the page
 $langs->loadLangs(array("companies", "compta", "accountancy", "products"));
@@ -59,6 +62,8 @@ $account_number_sell = GETPOST('account_number_sell');
 $changeaccount = GETPOST('changeaccount', 'array');
 $changeaccount_buy = GETPOST('changeaccount_buy', 'array');
 $changeaccount_sell = GETPOST('changeaccount_sell', 'array');
+$searchCategoryProductOperator = (GETPOST('search_category_product_operator', 'int') ? GETPOST('search_category_product_operator', 'int') : 0);
+$searchCategoryProductList = GETPOST('search_category_product_list', 'array');
 $search_ref = GETPOST('search_ref', 'alpha');
 $search_label = GETPOST('search_label', 'alpha');
 $search_desc = GETPOST('search_desc', 'alpha');
@@ -144,6 +149,8 @@ if ($reshook < 0) {
 
 // Purge search criteria
 if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All test are required to be compatible with all browsers
+	$searchCategoryProductOperator = 0;
+	$searchCategoryProductList = array();
 	$search_ref = '';
 	$search_label = '';
 	$search_desc = '';
@@ -283,7 +290,16 @@ $aacompta_prodsell          = getDolGlobalString('ACCOUNTING_PRODUCT_SOLD_ACCOUN
 $aacompta_prodsell_intra    = getDolGlobalString('ACCOUNTING_PRODUCT_SOLD_INTRA_ACCOUNT', $langs->trans("CodeNotDef"));
 $aacompta_prodsell_export   = getDolGlobalString('ACCOUNTING_PRODUCT_SOLD_EXPORT_ACCOUNT', $langs->trans("CodeNotDef"));
 
-llxHeader('', $langs->trans("ProductsBinding"));
+
+$title = $langs->trans("ProductsBinding");
+$helpurl = '';
+
+$paramsCat = '';
+foreach ($searchCategoryProductList as $searchCategoryProduct) {
+	$paramsCat .= "&search_category_product_list[]=".urlencode($searchCategoryProduct);
+}
+
+llxHeader('', $title, $helpurl, '', 0, 0, array(), array(), $paramsCat, '');
 
 $pcgverid = getDolGlobalString('CHARTOFACCOUNTS');
 $pcgvercode = dol_getIdFromCode($db, $pcgverid, 'accounting_system', 'rowid', 'pcg_version');
@@ -308,6 +324,9 @@ if (!empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
 } else {
 	$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "accounting_account as aa ON aa.account_number = p." . $accountancy_field_name . " AND aa.fk_pcg_version = '" . $db->escape($pcgvercode) . "'";
 }
+if (!empty($searchCategoryProductList)) {
+	$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX."categorie_product as cp ON p.rowid = cp.fk_product"; // We'll need this table joined to the select in order to filter by categ
+}
 $sql .= ' WHERE p.entity IN ('.getEntity('product').')';
 if (strlen(trim($search_current_account))) {
 	$sql .= natural_search((empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED) ? "p." : "ppe.") . $accountancy_field_name, $search_current_account);
@@ -318,6 +337,30 @@ if ($search_current_account_valid == 'withoutvalidaccount') {
 if ($search_current_account_valid == 'withvalidaccount') {
 	$sql .= " AND aa.account_number IS NOT NULL";
 }
+$searchCategoryProductSqlList = array();
+if ($searchCategoryProductOperator == 1) {
+	foreach ($searchCategoryProductList as $searchCategoryProduct) {
+		if (intval($searchCategoryProduct) == -2) {
+			$searchCategoryProductSqlList[] = "cp.fk_categorie IS NULL";
+		} elseif (intval($searchCategoryProduct) > 0) {
+			$searchCategoryProductSqlList[] = "cp.fk_categorie = ".$db->escape($searchCategoryProduct);
+		}
+	}
+	if (!empty($searchCategoryProductSqlList)) {
+		$sql .= " AND (".implode(' OR ', $searchCategoryProductSqlList).")";
+	}
+} else {
+	foreach ($searchCategoryProductList as $searchCategoryProduct) {
+		if (intval($searchCategoryProduct) == -2) {
+			$searchCategoryProductSqlList[] = "cp.fk_categorie IS NULL";
+		} elseif (intval($searchCategoryProduct) > 0) {
+			$searchCategoryProductSqlList[] = "p.rowid IN (SELECT fk_product FROM ".MAIN_DB_PREFIX."categorie_product WHERE fk_categorie = ".((int) $searchCategoryProduct).")";
+		}
+	}
+	if (!empty($searchCategoryProductSqlList)) {
+		$sql .= " AND (".implode(' AND ', $searchCategoryProductSqlList).")";
+	}
+}
 // Add search filter like
 if (strlen(trim($search_ref))) {
 	$sql .= natural_search("p.ref", $search_ref);
@@ -338,6 +381,15 @@ if ($search_onpurchase != '' && $search_onpurchase != '-1') {
 	$sql .= natural_search('p.tobuy', $search_onpurchase, 1);
 }
 
+$sql .= " GROUP BY p.rowid, p.ref, p.label, p.description, p.tosell, p.tobuy, p.tva_tx,";
+$sql .= " p.fk_product_type,";
+$sql .= ' p.tms,';
+if (empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
+	$sql .= " p.accountancy_code_sell, p.accountancy_code_sell_intra, p.accountancy_code_sell_export, p.accountancy_code_buy, p.accountancy_code_buy_intra, p.accountancy_code_buy_export";
+} else {
+	$sql .= " ppe.accountancy_code_sell, ppe.accountancy_code_sell_intra, ppe.accountancy_code_sell_export, ppe.accountancy_code_buy, ppe.accountancy_code_buy_intra, ppe.accountancy_code_buy_export";
+}
+
 $sql .= $db->order($sortfield, $sortorder);
 
 $nbtotalofrecords = '';
@@ -365,11 +417,17 @@ if ($result) {
 	if ($limit > 0 && $limit != $conf->liste_limit) {
 		$param .= '&limit='.urlencode($limit);
 	}
+	if ($searchCategoryProductOperator == 1) {
+		$param .= "&search_category_product_operator=".urlencode($searchCategoryProductOperator);
+	}
+	foreach ($searchCategoryProductList as $searchCategoryProduct) {
+		$param .= "&search_category_product_list[]=".urlencode($searchCategoryProduct);
+	}
 	if ($search_ref > 0) {
-		$param .= "&search_desc=".urlencode($search_ref);
+		$param .= "&search_ref=".urlencode($search_ref);
 	}
 	if ($search_label > 0) {
-		$param .= "&search_desc=".urlencode($search_label);
+		$param .= "&search_label=".urlencode($search_label);
 	}
 	if ($search_desc > 0) {
 		$param .= "&search_desc=".urlencode($search_desc);
@@ -461,6 +519,40 @@ if ($result) {
 		print $form->formconfirm($_SERVER["PHP_SELF"], $langs->trans("ConfirmPreselectAccount"), $langs->trans("ConfirmPreselectAccountQuestion", count($chk_prod)), "confirm_set_default_account", $formquestion, 1, 0, 200, 500, 1);
 	}
 
+	// Filter on categories
+	$moreforfilter = '';
+	if (!empty($conf->categorie->enabled) && $user->rights->categorie->lire) {
+		$moreforfilter .= '<div class="divsearchfield">';
+		$moreforfilter .= img_picto($langs->trans('Categories'), 'category', 'class="pictofixedwidth"');
+		$categoriesProductArr = $form->select_all_categories(Categorie::TYPE_PRODUCT, '', '', 64, 0, 1);
+		$categoriesProductArr[-2] = '- '.$langs->trans('NotCategorized').' -';
+		$moreforfilter .= Form::multiselectarray('search_category_product_list', $categoriesProductArr, $searchCategoryProductList, 0, 0, 'minwidth300');
+		$moreforfilter .= ' <input type="checkbox" class="valignmiddle" name="search_category_product_operator" value="1"'.($searchCategoryProductOperator == 1 ? ' checked="checked"' : '').'/> <span class="none">'.$langs->trans('UseOrOperatorForCategories').'</span>';
+		$moreforfilter .= '</div>';
+	}
+
+	//Show/hide child products. Hidden by default
+	if (!empty($conf->variants->enabled) && !empty($conf->global->PRODUIT_ATTRIBUTES_HIDECHILD)) {
+		$moreforfilter .= '<div class="divsearchfield">';
+		$moreforfilter .= '<input type="checkbox" id="search_show_childproducts" name="search_show_childproducts"'.($show_childproducts ? 'checked="checked"' : '').'>';
+		$moreforfilter .= ' <label for="search_show_childproducts">'.$langs->trans('ShowChildProducts').'</label>';
+		$moreforfilter .= '</div>';
+	}
+
+	$parameters = array();
+	$reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters); // Note that $action and $object may have been modified by hook
+	if (empty($reshook)) {
+		$moreforfilter .= $hookmanager->resPrint;
+	} else {
+		$moreforfilter = $hookmanager->resPrint;
+	}
+
+	if ($moreforfilter) {
+		print '<div class="liste_titre liste_titre_bydiv centpercent">';
+		print $moreforfilter;
+		print '</div>';
+	}
+
 	print '<div class="div-table-responsive">';
 	print '<table class="liste '.($moreforfilter ? "listwithfilterbefore" : "").'">';
 

+ 33 - 30
htdocs/accountancy/bookkeeping/card.php

@@ -267,7 +267,7 @@ if ($action == "confirm_update") {
 			if ($mode != '_tmp') {
 				setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
 			}
-			$action = 'update';
+			$action = '';
 			$id = $object->id;
 			$piece_num = $object->piece_num;
 		}
@@ -647,6 +647,12 @@ if ($action == 'create') {
 
 				print "</tr>\n";
 
+				// Empty line is the first line of $object->linesmvt
+				// So we must get the first line (the empty one) and put it at the end of the array
+				// in order to display it correctly to the user
+				$empty_line = array_shift($object->linesmvt);
+				$object->linesmvt[]= $empty_line;
+
 				foreach ($object->linesmvt as $line) {
 					print '<tr class="oddeven">';
 					$total_debit += $line->debit;
@@ -677,7 +683,33 @@ if ($action == 'create') {
 						print '<input type="hidden" name="id" value="'.$line->id.'">'."\n";
 						print '<input type="submit" class="button" name="update" value="'.$langs->trans("Update").'">';
 						print '</td>';
+					} elseif (empty($line->numero_compte) || (empty($line->debit) && empty($line->credit))) {
+						if ($action == "" || $action == 'add') {
+							print '<!-- td columns in add mode -->';
+							print '<td>';
+							print $formaccounting->select_account('', 'accountingaccount_number', 1, array(), 1, 1, '');
+							print '</td>';
+							print '<td>';
+							// TODO For the moment we keep a free input text instead of a combo. The select_auxaccount has problem because:
+							// It does not use the setup of "key pressed" to select a thirdparty and this hang browser on large databases.
+							// Also, it is not possible to use a value that is not in the list.
+							// Also, the label is not automatically filled when a value is selected.
+							if (!empty($conf->global->ACCOUNTANCY_COMBO_FOR_AUX)) {
+								print $formaccounting->select_auxaccount('', 'subledger_account', 1, 'maxwidth250', '', 'subledger_label');
+							} else {
+								print '<input type="text" class="maxwidth150" name="subledger_account" value="" placeholder="' . dol_escape_htmltag($langs->trans("SubledgerAccount")) . '">';
+							}
+							print '<br><input type="text" class="maxwidth150" name="subledger_label" value="" placeholder="' . dol_escape_htmltag($langs->trans("SubledgerAccountLabel")) . '">';
+							print '</td>';
+							print '<td><input type="text" class="minwidth200" name="label_operation" value="' . $label_operation . '"/></td>';
+							print '<td class="right"><input type="text" size="6" class="right" name="debit" value=""/></td>';
+							print '<td class="right"><input type="text" size="6" class="right" name="credit" value=""/></td>';
+							print '<td>';
+							print '<input type="submit" class="button" name="save" value="' . $langs->trans("Add") . '">';
+							print '</td>';
+						}
 					} else {
+						print '<!-- td columns in display mode -->';
 						$resultfetch = $accountingaccount->fetch(null, $line->numero_compte, true);
 						print '<td>';
 						if ($resultfetch > 0) {
@@ -733,35 +765,6 @@ if ($action == 'create') {
 					setEventMessages(null, array($langs->trans('MvtNotCorrectlyBalanced', $total_debit, $total_credit)), 'warnings');
 				}
 
-				if (empty($object->date_export) && empty($object->date_validation)) {
-					if ($action == "" || $action == 'add') {
-						print '<tr class="oddeven">';
-						print '<!-- td columns in add mode -->';
-						print '<td>';
-						print $formaccounting->select_account('', 'accountingaccount_number', 1, array(), 1, 1, '');
-						print '</td>';
-						print '<td>';
-						// TODO For the moment we keep a free input text instead of a combo. The select_auxaccount has problem because:
-						// It does not use the setup of "key pressed" to select a thirdparty and this hang browser on large databases.
-						// Also, it is not possible to use a value that is not in the list.
-						// Also, the label is not automatically filled when a value is selected.
-						if (!empty($conf->global->ACCOUNTANCY_COMBO_FOR_AUX)) {
-							print $formaccounting->select_auxaccount('', 'subledger_account', 1, 'maxwidth250', '', 'subledger_label');
-						} else {
-							print '<input type="text" class="maxwidth150" name="subledger_account" value="" placeholder="' . dol_escape_htmltag($langs->trans("SubledgerAccount")) . '">';
-						}
-						print '<br><input type="text" class="maxwidth150" name="subledger_label" value="" placeholder="' . dol_escape_htmltag($langs->trans("SubledgerAccountLabel")) . '">';
-						print '</td>';
-						print '<td><input type="text" class="minwidth200" name="label_operation" value="' . $label_operation . '"/></td>';
-						print '<td class="right"><input type="text" size="6" class="right" name="debit" value=""/></td>';
-						print '<td class="right"><input type="text" size="6" class="right" name="credit" value=""/></td>';
-						print '<td>';
-						print '<input type="submit" class="button" name="save" value="' . $langs->trans("Add") . '">';
-						print '</td>';
-						print '</tr>';
-					}
-				}
-
 				print '</table>';
 				print '</div>';
 

+ 237 - 124
htdocs/accountancy/bookkeeping/list.php

@@ -28,6 +28,7 @@
 require '../../main.inc.php';
 require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountancyexport.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/accountancy/class/lettering.class.php';
 require_once DOL_DOCUMENT_ROOT.'/accountancy/class/bookkeeping.class.php';
 require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingjournal.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
@@ -42,6 +43,10 @@ $langs->loadLangs(array("accountancy", "compta"));
 $socid = GETPOST('socid', 'int');
 
 $action = GETPOST('action', 'aZ09');
+$massaction = GETPOST('massaction', 'alpha');
+$confirm = GETPOST('confirm', 'alpha');
+$toselect = GETPOST('toselect', 'array');
+$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'bookkeepinglist';
 $search_mvt_num = GETPOST('search_mvt_num', 'int');
 $search_doc_type = GETPOST("search_doc_type", 'alpha');
 $search_doc_ref = GETPOST("search_doc_ref", 'alpha');
@@ -86,6 +91,7 @@ $search_date_validation_endmonth =  GETPOST('search_date_validation_endmonth', '
 $search_date_validation_endday =  GETPOST('search_date_validation_endday', 'int');
 $search_date_validation_start = dol_mktime(0, 0, 0, $search_date_validation_startmonth, $search_date_validation_startday, $search_date_validation_startyear);
 $search_date_validation_end = dol_mktime(23, 59, 59, $search_date_validation_endmonth, $search_date_validation_endday, $search_date_validation_endyear);
+$search_import_key = GETPOST("search_import_key", 'alpha');
 
 //var_dump($search_date_start);exit;
 if (GETPOST("button_delmvt_x") || GETPOST("button_delmvt.x") || GETPOST("button_delmvt")) {
@@ -191,6 +197,7 @@ $arrayfields = array(
 	't.tms'=>array('label'=>$langs->trans("DateModification"), 'checked'=>0),
 	't.date_export'=>array('label'=>$langs->trans("DateExport"), 'checked'=>1),
 	't.date_validated'=>array('label'=>$langs->trans("DateValidationAndLock"), 'checked'=>1),
+	't.import_key'=>array('label'=>$langs->trans("ImportId"), 'checked'=>0, 'position'=>1100),
 );
 
 if (empty($conf->global->ACCOUNTING_ENABLE_LETTERING)) {
@@ -220,10 +227,12 @@ if (empty($user->rights->accounting->mouvements->lire)) {
  * Actions
  */
 
+$param = '';
+
 if (GETPOST('cancel', 'alpha')) {
 	$action = 'list'; $massaction = '';
 }
-if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend') {
+if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'preunlettering' && $massaction != 'predeletebookkeepingwriting') {
 	$massaction = '';
 }
 
@@ -294,10 +303,11 @@ if (empty($reshook)) {
 		$search_credit = '';
 		$search_lettering_code = '';
 		$search_not_reconciled = '';
+		$search_import_key = '';
+		$toselect = '';
 	}
 
 	// Must be after the remove filter action, before the export.
-	$param = '';
 	$filter = array();
 	if (!empty($search_date_start)) {
 		$filter['t.doc_date>='] = $search_date_start;
@@ -416,77 +426,143 @@ if (empty($reshook)) {
 		$filter['t.reconciled_option'] = $search_not_reconciled;
 		$param .= '&search_not_reconciled='.urlencode($search_not_reconciled);
 	}
-}
-
-if ($action == 'delbookkeeping' && $user->rights->accounting->mouvements->supprimer) {
-	$import_key = GETPOST('importkey', 'alpha');
+	if (!empty($search_import_key)) {
+		$filter['t.import_key'] = $search_import_key;
+		$param .= '&search_import_key='.urlencode($search_import_key);
+	}
 
-	if (!empty($import_key)) {
-		$result = $object->deleteByImportkey($import_key);
-		if ($result < 0) {
-			setEventMessages($object->error, $object->errors, 'errors');
+	//if ($action == 'delbookkeepingyearconfirm' && $user->rights->accounting->mouvements->supprimer_tous) {
+	//	$delmonth = GETPOST('delmonth', 'int');
+	//	$delyear = GETPOST('delyear', 'int');
+	//	if ($delyear == -1) {
+	//		$delyear = 0;
+	//	}
+	//	$deljournal = GETPOST('deljournal', 'alpha');
+	//	if ($deljournal == -1) {
+	//		$deljournal = 0;
+	//	}
+	//
+	//	if (!empty($delmonth) || !empty($delyear) || !empty($deljournal)) {
+	//		$result = $object->deleteByYearAndJournal($delyear, $deljournal, '', ($delmonth > 0 ? $delmonth : 0));
+	//		if ($result < 0) {
+	//			setEventMessages($object->error, $object->errors, 'errors');
+	//		} else {
+	//			setEventMessages("RecordDeleted", null, 'mesgs');
+	//		}
+	//
+	//		// Make a redirect to avoid to launch the delete later after a back button
+	//		header("Location: list.php".($param ? '?'.$param : ''));
+	//		exit;
+	//	} else {
+	//		setEventMessages("NoRecordDeleted", null, 'warnings');
+	//	}
+	//}
+	if ($action == 'setreexport') {
+		$setreexport = GETPOST('value', 'int');
+		if (!dolibarr_set_const($db, "ACCOUNTING_REEXPORT", $setreexport, 'yesno', 0, '', $conf->entity)) {
+			$error++;
 		}
 
-		// Make a redirect to avoid to launch the delete later after a back button
-		header("Location: list.php".($param ? '?'.$param : ''));
-		exit;
-	}
-}
-if ($action == 'delbookkeepingyearconfirm' && $user->rights->accounting->mouvements->supprimer_tous) {
-	$delmonth = GETPOST('delmonth', 'int');
-	$delyear = GETPOST('delyear', 'int');
-	if ($delyear == -1) {
-		$delyear = 0;
-	}
-	$deljournal = GETPOST('deljournal', 'alpha');
-	if ($deljournal == -1) {
-		$deljournal = 0;
-	}
-
-	if (!empty($delmonth) || !empty($delyear) || !empty($deljournal)) {
-		$result = $object->deleteByYearAndJournal($delyear, $deljournal, '', ($delmonth > 0 ? $delmonth : 0));
-		if ($result < 0) {
-			setEventMessages($object->error, $object->errors, 'errors');
+		if (!$error) {
+			if ($conf->global->ACCOUNTING_REEXPORT == 1) {
+				setEventMessages($langs->trans("ExportOfPiecesAlreadyExportedIsEnable"), null, 'mesgs');
+			} else {
+				setEventMessages($langs->trans("ExportOfPiecesAlreadyExportedIsDisable"), null, 'mesgs');
+			}
 		} else {
-			setEventMessages("RecordDeleted", null, 'mesgs');
+			setEventMessages($langs->trans("Error"), null, 'errors');
 		}
-
-		// Make a redirect to avoid to launch the delete later after a back button
-		header("Location: list.php".($param ? '?'.$param : ''));
-		exit;
-	} else {
-		setEventMessages("NoRecordDeleted", null, 'warnings');
 	}
-}
-if ($action == 'delmouvconfirm' && $user->rights->accounting->mouvements->supprimer) {
-	$mvt_num = GETPOST('mvt_num', 'int');
 
-	if (!empty($mvt_num)) {
-		$result = $object->deleteMvtNum($mvt_num);
-		if ($result < 0) {
-			setEventMessages($object->error, $object->errors, 'errors');
-		} else {
-			setEventMessages($langs->trans("RecordDeleted"), null, 'mesgs');
+	// Mass actions
+	$objectclass = 'Bookkeeping';
+	$objectlabel = 'Bookkeeping';
+	$permissiontoread = $user->rights->societe->lire;
+	$permissiontodelete = $user->rights->societe->supprimer;
+	$permissiontoadd = $user->rights->societe->creer;
+	$uploaddir = $conf->societe->dir_output;
+	include DOL_DOCUMENT_ROOT.'/core/actions_massactions.inc.php';
+
+	if (!$error && $action == 'deletebookkeepingwriting' && $confirm == "yes" && $user->rights->accounting->mouvements->supprimer) {
+		$nbok = 0;
+		foreach ($toselect as $toselectid) {
+			$result = $object->fetch($toselectid);
+			if ($result > 0 && (!isset($object->date_validation) || $object->date_validation === '')) {
+				$result = $object->deleteMvtNum($object->piece_num);
+				if ($result > 0) {
+					$nbok++;
+				} else {
+					setEventMessages($object->error, $object->errors, 'errors');
+					$error++;
+					break;
+				}
+			} elseif ($result < 0) {
+				setEventMessages($object->error, $object->errors, 'errors');
+				$error++;
+				break;
+			}
 		}
 
-		header("Location: list.php?noreset=1".($param ? '&'.$param : ''));
-		exit;
-	}
-}
-if ($action == 'setreexport') {
-	$setreexport = GETPOST('value', 'int');
-	if (!dolibarr_set_const($db, "ACCOUNTING_REEXPORT", $setreexport, 'yesno', 0, '', $conf->entity)) {
-		$error++;
+		// Message for elements well deleted
+		if ($nbok > 1) {
+			setEventMessages($langs->trans("RecordsDeleted", $nbok), null, 'mesgs');
+		} elseif ($nbok > 0) {
+			setEventMessages($langs->trans("RecordDeleted", $nbok), null, 'mesgs');
+		} elseif (!$error) {
+			setEventMessages($langs->trans("NoRecordDeleted"), null, 'mesgs');
+		}
+
+		if (!$error) {
+			header("Location: ".$_SERVER["PHP_SELF"]."?noreset=1".($param ? '&'.$param : ''));
+			exit;
+		}
 	}
 
-	if (!$error) {
-		if ($conf->global->ACCOUNTING_REEXPORT == 1) {
-			setEventMessages($langs->trans("ExportOfPiecesAlreadyExportedIsEnable"), null, 'mesgs');
-		} else {
-			setEventMessages($langs->trans("ExportOfPiecesAlreadyExportedIsDisable"), null, 'mesgs');
+	// others mass actions
+	if (!$error && getDolGlobalInt('ACCOUNTING_ENABLE_LETTERING') && $user->rights->accounting->mouvements->creer) {
+		if ($massaction == 'lettering') {
+			$lettering = new Lettering($db);
+			$nb_lettering = $lettering->bookkeepingLetteringAll($toselect);
+			if ($nb_lettering < 0) {
+				setEventMessages('', $lettering->errors, 'errors');
+				$error++;
+				$nb_lettering = max(0, abs($nb_lettering) - 2);
+			} elseif ($nb_lettering == 0) {
+				$nb_lettering = 0;
+				setEventMessages($langs->trans('AccountancyNoLetteringModified'), array(), 'mesgs');
+			}
+			if ($nb_lettering == 1) {
+				setEventMessages($langs->trans('AccountancyOneLetteringModifiedSuccessfully'), array(), 'mesgs');
+			} elseif ($nb_lettering > 1) {
+				setEventMessages($langs->trans('AccountancyLetteringModifiedSuccessfully', $nb_lettering), array(), 'mesgs');
+			}
+
+			if (!$error) {
+				header('Location: ' . $_SERVER['PHP_SELF'] . '?noreset=1' . $param);
+				exit();
+			}
+		} elseif ($action == 'unlettering' && $confirm == "yes") {
+			$lettering = new Lettering($db);
+			$nb_lettering = $lettering->bookkeepingLetteringAll($toselect, true);
+			if ($nb_lettering < 0) {
+				setEventMessages('', $lettering->errors, 'errors');
+				$error++;
+				$nb_lettering = max(0, abs($nb_lettering) - 2);
+			} elseif ($nb_lettering == 0) {
+				$nb_lettering = 0;
+				setEventMessages($langs->trans('AccountancyNoUnletteringModified'), array(), 'mesgs');
+			}
+			if ($nb_lettering == 1) {
+				setEventMessages($langs->trans('AccountancyOneUnletteringModifiedSuccessfully'), array(), 'mesgs');
+			} elseif ($nb_lettering > 1) {
+				setEventMessages($langs->trans('AccountancyUnletteringModifiedSuccessfully', $nb_lettering), array(), 'mesgs');
+			}
+
+			if (!$error) {
+				header('Location: ' . $_SERVER['PHP_SELF'] . '?noreset=1' . $param);
+				exit();
+			}
 		}
-	} else {
-		setEventMessages($langs->trans("Error"), null, 'errors');
 	}
 }
 
@@ -520,7 +596,8 @@ $sql .= " t.piece_num,";
 $sql .= " t.date_creation,";
 $sql .= " t.tms as date_modification,";
 $sql .= " t.date_export,";
-$sql .= " t.date_validated as date_validation";
+$sql .= " t.date_validated as date_validation,";
+$sql .= " t.import_key";
 $sql .= ' FROM '.MAIN_DB_PREFIX.$object->table_element.' as t';
 // Manage filter
 $sqlwhere = array();
@@ -667,6 +744,7 @@ if (is_numeric($nbtotalofrecords) && $limit > $nbtotalofrecords) {
 	$num = $db->num_rows($resql);
 }
 
+$arrayofselected = is_array($toselect) ? $toselect : array();
 
 // Output page
 // --------------------------------------------------------------------
@@ -684,7 +762,7 @@ if ($action == 'export_file') {
 		'name' => 'notifiedexportdate',
 		'type' => 'checkbox',
 		'label' => $langs->trans('NotifiedExportDate'),
-		'value' => $checked,
+		'value' => (!empty($conf->global->ACCOUNTING_DEFAULT_NOT_NOTIFIED_EXPORT_DATE) ? 'false' : 'true'),
 	);
 
 	$form_question['separator'] = array('name'=>'separator', 'type'=>'separator');
@@ -703,50 +781,46 @@ if ($action == 'export_file') {
 	$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans("ExportFilteredList").' ('.$listofformat[$formatexportset].')', $langs->trans('ConfirmExportFile'), 'export_fileconfirm', $form_question, '', 1, 300, 600);
 }
 
-if ($action == 'delmouv') {
-	$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?mvt_num='.urlencode(GETPOST('mvt_num')).$param, $langs->trans('DeleteMvt'), $langs->trans('ConfirmDeleteMvtPartial'), 'delmouvconfirm', '', 0, 1);
-}
-
-if ($action == 'delbookkeepingyear') {
-	$form_question = array();
-	$delyear = GETPOST('delyear', 'int');
-	$deljournal = GETPOST('deljournal', 'alpha');
-
-	if (empty($delyear)) {
-		$delyear = dol_print_date(dol_now(), '%Y');
-	}
-	$month_array = array();
-	for ($i = 1; $i <= 12; $i++) {
-		$month_array[$i] = $langs->trans("Month".sprintf("%02d", $i));
-	}
-	$year_array = $formaccounting->selectyear_accountancy_bookkepping($delyear, 'delyear', 0, 'array');
-	$journal_array = $formaccounting->select_journal($deljournal, 'deljournal', '', 1, 1, 1, '', 0, 1);
-
-	$form_question['delmonth'] = array(
-		'name' => 'delmonth',
-		'type' => 'select',
-		'label' => $langs->trans('DelMonth'),
-		'values' => $month_array,
-		'morecss' => 'minwidth150',
-		'default' => ''
-	);
-	$form_question['delyear'] = array(
-			'name' => 'delyear',
-			'type' => 'select',
-			'label' => $langs->trans('DelYear'),
-			'values' => $year_array,
-			'default' => $delyear
-	);
-	$form_question['deljournal'] = array(
-			'name' => 'deljournal',
-			'type' => 'other', // We don't use select here, the journal_array is already a select html component
-			'label' => $langs->trans('DelJournal'),
-			'value' => $journal_array,
-			'default' => $deljournal
-	);
-
-	$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans('DeleteMvt'), $langs->trans('ConfirmDeleteMvt', $langs->transnoentitiesnoconv("RegistrationInAccounting")), 'delbookkeepingyearconfirm', $form_question, '', 1, 320);
-}
+//if ($action == 'delbookkeepingyear') {
+//	$form_question = array();
+//	$delyear = GETPOST('delyear', 'int');
+//	$deljournal = GETPOST('deljournal', 'alpha');
+//
+//	if (empty($delyear)) {
+//		$delyear = dol_print_date(dol_now(), '%Y');
+//	}
+//	$month_array = array();
+//	for ($i = 1; $i <= 12; $i++) {
+//		$month_array[$i] = $langs->trans("Month".sprintf("%02d", $i));
+//	}
+//	$year_array = $formaccounting->selectyear_accountancy_bookkepping($delyear, 'delyear', 0, 'array');
+//	$journal_array = $formaccounting->select_journal($deljournal, 'deljournal', '', 1, 1, 1, '', 0, 1);
+//
+//	$form_question['delmonth'] = array(
+//		'name' => 'delmonth',
+//		'type' => 'select',
+//		'label' => $langs->trans('DelMonth'),
+//		'values' => $month_array,
+//		'morecss' => 'minwidth150',
+//		'default' => ''
+//	);
+//	$form_question['delyear'] = array(
+//			'name' => 'delyear',
+//			'type' => 'select',
+//			'label' => $langs->trans('DelYear'),
+//			'values' => $year_array,
+//			'default' => $delyear
+//	);
+//	$form_question['deljournal'] = array(
+//			'name' => 'deljournal',
+//			'type' => 'other', // We don't use select here, the journal_array is already a select html component
+//			'label' => $langs->trans('DelJournal'),
+//			'value' => $journal_array,
+//			'default' => $deljournal
+//	);
+//
+//	$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans('DeleteMvt'), $langs->trans('ConfirmDeleteMvt', $langs->transnoentitiesnoconv("RegistrationInAccounting")), 'delbookkeepingyearconfirm', $form_question, '', 1, 320);
+//}
 
 // Print form confirm
 print $formconfirm;
@@ -759,6 +833,22 @@ if ($limit > 0 && $limit != $conf->liste_limit) {
 	$param .= '&limit='.urlencode($limit);
 }
 
+// List of mass actions available
+$arrayofmassactions = array();
+/*
+if (getDolGlobalInt('ACCOUNTING_ENABLE_LETTERING') && $user->rights->accounting->mouvements->creer) {
+	$arrayofmassactions['lettering'] = img_picto('', 'check', 'class="pictofixedwidth"') . $langs->trans('Lettering');
+	$arrayofmassactions['preunlettering'] = img_picto('', 'uncheck', 'class="pictofixedwidth"') . $langs->trans('Unlettering');
+}
+*/
+if ($user->rights->accounting->mouvements->supprimer) {
+	$arrayofmassactions['predeletebookkeepingwriting'] = img_picto('', 'delete', 'class="pictofixedwidth"').$langs->trans("Delete");
+}
+if (GETPOST('nomassaction', 'int') || in_array($massaction, array('preunlettering', 'predeletebookkeepingwriting'))) {
+	$arrayofmassactions = array();
+}
+$massactionbutton = $form->selectMassAction($massaction, $arrayofmassactions);
+
 print '<form method="POST" id="searchFormList" action="'.$_SERVER["PHP_SELF"].'">';
 print '<input type="hidden" name="token" value="'.newToken().'">';
 print '<input type="hidden" name="action" value="list">';
@@ -768,8 +858,7 @@ if ($optioncss != '') {
 print '<input type="hidden" name="formfilteraction" id="formfilteraction" value="list">';
 print '<input type="hidden" name="sortfield" value="'.urlencode($sortfield).'">';
 print '<input type="hidden" name="sortorder" value="'.urlencode($sortorder).'">';
-
-$massactionbutton = '';
+print '<input type="hidden" name="contextpage" value="'.$contextpage.'">';
 
 if (count($filter)) {
 	$buttonLabel = $langs->trans("ExportFilteredList");
@@ -794,7 +883,7 @@ if (empty($reshook)) {
 
 	$newcardbutton .= dolGetButtonTitle($langs->trans('ViewFlatList'), '', 'fa fa-list paddingleft imgforviewmode', DOL_URL_ROOT.'/accountancy/bookkeeping/list.php?'.$param, '', 1, array('morecss' => 'marginleftonly btnTitleSelected'));
 	$newcardbutton .= dolGetButtonTitle($langs->trans('GroupByAccountAccounting'), '', 'fa fa-stream paddingleft imgforviewmode', DOL_URL_ROOT.'/accountancy/bookkeeping/listbyaccount.php?'.$param, '', 1, array('morecss' => 'marginleftonly'));
-	$newcardbutton .= dolGetButtonTitle($langs->trans('GroupBySubAccountAccounting'), '', 'fa fa-align-left vmirror paddingleft imgforviewmode', DOL_URL_ROOT.'/accountancy/bookkeeping/listbysubaccount.php?'.$param, '', 1, array('morecss' => 'marginleftonly'));
+	$newcardbutton .= dolGetButtonTitle($langs->trans('GroupBySubAccountAccounting'), '', 'fa fa-align-left vmirror paddingleft imgforviewmode', DOL_URL_ROOT.'/accountancy/bookkeeping/listbyaccount.php?type=sub'.$param, '', 1, array('morecss' => 'marginleftonly'));
 
 	$url = './card.php?action=create';
 	if (!empty($socid)) {
@@ -805,9 +894,21 @@ if (empty($reshook)) {
 
 print_barre_liste($title_page, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, 'title_accountancy', 0, $newcardbutton, '', $limit, 0, 0, 1);
 
+if ($massaction == 'preunlettering') {
+	print $form->formconfirm($_SERVER["PHP_SELF"], $langs->trans("ConfirmMassUnlettering"), $langs->trans("ConfirmMassUnletteringQuestion", count($toselect)), "unlettering", null, '', 0, 200, 500, 1);
+} elseif ($massaction == 'predeletebookkeepingwriting') {
+	print $form->formconfirm($_SERVER["PHP_SELF"], $langs->trans("ConfirmMassDeleteBookkeepingWriting"), $langs->trans("ConfirmMassDeleteBookkeepingWritingQuestion", count($toselect)), "deletebookkeepingwriting", null, '', 0, 200, 500, 1);
+}
+
+//$topicmail = "Information";
+//$modelmail = "accountingbookkeeping";
+//$objecttmp = new BookKeeping($db);
+//$trackid = 'bk'.$object->id;
+include DOL_DOCUMENT_ROOT.'/core/tpl/massactions_pre.tpl.php';
+
 $varpage = empty($contextpage) ? $_SERVER["PHP_SELF"] : $contextpage;
 $selectedfields = $form->multiSelectArrayWithCheckbox('selectedfields', $arrayfields, $varpage); // This also change content of $arrayfields
-if ($massactionbutton) {
+if ($massactionbutton && $contextpage != 'poslist') {
 	$selectedfields .= $form->showCheckAddButtons('checkforselect', 1);
 }
 
@@ -954,6 +1055,11 @@ if (!empty($arrayfields['t.date_validated']['checked'])) {
 	print '</div>';
 	print '</td>';
 }
+if (!empty($arrayfields['t.import_key']['checked'])) {
+	print '<td class="liste_titre center">';
+	print '<input class="flat searchstring maxwidth50" type="text" name="search_import_key" value="'.dol_escape_htmltag($search_import_key).'">';
+	print '</td>';
+}
 // Action column
 print '<td class="liste_titre center">';
 $searchpicto = $form->showFilterButtons();
@@ -1008,6 +1114,9 @@ if (!empty($arrayfields['t.date_export']['checked'])) {
 if (!empty($arrayfields['t.date_validated']['checked'])) {
 	print_liste_field_titre($arrayfields['t.date_validated']['label'], $_SERVER['PHP_SELF'], "t.date_validated", "", $param, '', $sortfield, $sortorder, 'center ');
 }
+if (!empty($arrayfields['t.import_key']['checked'])) {
+	print_liste_field_titre($arrayfields['t.import_key']['label'], $_SERVER["PHP_SELF"], "t.import_key", "", $param, '', $sortfield, $sortorder, 'center ');
+}
 print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ');
 print "</tr>\n";
 
@@ -1252,17 +1361,21 @@ while ($i < min($num, $limit)) {
 		}
 	}
 
-	// Action column
-	print '<td class="nowraponall center">';
-	if (empty($line->date_export) && empty($line->date_validation)) {
-		if ($user->rights->accounting->mouvements->creer) {
-			print '<a class="editfielda paddingleft marginrightonly" href="' . DOL_URL_ROOT . '/accountancy/bookkeeping/card.php?piece_num=' . $line->piece_num . $param . '&page=' . $page . ($sortfield ? '&sortfield=' . $sortfield : '') . ($sortorder ? '&sortorder=' . $sortorder : '') . '">' . img_edit() . '</a>';
+	if (!empty($arrayfields['t.import_key']['checked'])) {
+		print '<td class="tdoverflowmax100">'.$obj->import_key."</td>\n";
+		if (!$i) {
+			$totalarray['nbfield']++;
 		}
 	}
-	if (empty($line->date_validation)) {
-		if ($user->rights->accounting->mouvements->supprimer) {
-			print '<a class="reposition paddingleft marginrightonly" href="'.$_SERVER['PHP_SELF'].'?action=delmouv&token='.newToken().'&mvt_num='.$line->piece_num.$param.'&page='.$page.($sortfield ? '&sortfield='.$sortfield : '').($sortorder ? '&sortorder='.$sortorder : '').'">'.img_delete().'</a>';
+
+	// Action column
+	print '<td class="nowraponall center">';
+	if (($massactionbutton || $massaction) && $contextpage != 'poslist') {   // If we are in select mode (massactionbutton defined) or if we have already selected and sent an action ($massaction) defined
+		$selected = 0;
+		if (in_array($line->id, $arrayofselected)) {
+			$selected = 1;
 		}
+		print '<input id="cb'.$line->id.'" class="flat checkforselect" type="checkbox" name="toselect[]" value="'.$line->id.'"'.($selected ? ' checked="checked"' : '').' />';
 	}
 	print '</td>';
 
@@ -1283,11 +1396,11 @@ print "</table>";
 print '</div>';
 
 // TODO Replace this with mass delete action
-if ($user->rights->accounting->mouvements->supprimer_tous) {
-	print '<div class="tabsAction tabsActionNoBottom">'."\n";
-	print '<a class="butActionDelete" name="button_delmvt" href="'.$_SERVER["PHP_SELF"].'?action=delbookkeepingyear&token='.newToken().($param ? '&'.$param : '').'">'.$langs->trans("DeleteMvt").'</a>';
-	print '</div>';
-}
+//if ($user->rights->accounting->mouvements->supprimer_tous) {
+//	print '<div class="tabsAction tabsActionNoBottom">'."\n";
+//	print '<a class="butActionDelete" name="button_delmvt" href="'.$_SERVER["PHP_SELF"].'?action=delbookkeepingyear&token='.newToken().($param ? '&'.$param : '').'">'.$langs->trans("DeleteMvt").'</a>';
+//	print '</div>';
+//}
 
 print '</form>';
 

+ 322 - 127
htdocs/accountancy/bookkeeping/listbyaccount.php

@@ -28,6 +28,7 @@
 require '../../main.inc.php';
 
 require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/accountancy/class/lettering.class.php';
 require_once DOL_DOCUMENT_ROOT.'/accountancy/class/bookkeeping.class.php';
 require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingjournal.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
@@ -39,6 +40,16 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
 $langs->loadLangs(array("accountancy", "compta"));
 
 $action = GETPOST('action', 'aZ09');
+$massaction = GETPOST('massaction', 'alpha');
+$confirm = GETPOST('confirm', 'alpha');
+$toselect = GETPOST('toselect', 'array');
+$type = GETPOST('type', 'alpha');
+if ($type == 'sub') {
+	$context_default = 'bookkeepingbysubaccountlist';
+} else {
+	$context_default = 'bookkeepingbyaccountlist';
+}
+$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : $context_default;
 $search_date_startyear =  GETPOST('search_date_startyear', 'int');
 $search_date_startmonth =  GETPOST('search_date_startmonth', 'int');
 $search_date_startday =  GETPOST('search_date_startday', 'int');
@@ -64,6 +75,7 @@ $search_date_validation_endmonth =  GETPOST('search_date_validation_endmonth', '
 $search_date_validation_endday =  GETPOST('search_date_validation_endday', 'int');
 $search_date_validation_start = dol_mktime(0, 0, 0, $search_date_validation_startmonth, $search_date_validation_startday, $search_date_validation_startyear);
 $search_date_validation_end = dol_mktime(23, 59, 59, $search_date_validation_endmonth, $search_date_validation_endday, $search_date_validation_endyear);
+$search_import_key = GETPOST("search_import_key", 'alpha');
 
 $search_accountancy_code = GETPOST("search_accountancy_code");
 $search_accountancy_code_start = GETPOST('search_accountancy_code_start', 'alpha');
@@ -109,7 +121,7 @@ if ($sortfield == "") {
 // Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context
 $object = new BookKeeping($db);
 $formfile = new FormFile($db);
-$hookmanager->initHooks(array('bookkeepingbyaccountlist'));
+$hookmanager->initHooks(array($context_default));
 
 $formaccounting = new FormAccounting($db);
 $form = new Form($db);
@@ -153,6 +165,7 @@ $arrayfields = array(
 	't.lettering_code'=>array('label'=>$langs->trans("LetteringCode"), 'checked'=>1),
 	't.date_export'=>array('label'=>$langs->trans("DateExport"), 'checked'=>1),
 	't.date_validated'=>array('label'=>$langs->trans("DateValidation"), 'checked'=>1),
+	't.import_key'=>array('label'=>$langs->trans("ImportId"), 'checked'=>0, 'position'=>1100),
 );
 
 if (empty($conf->global->ACCOUNTING_ENABLE_LETTERING)) {
@@ -187,10 +200,13 @@ if (empty($user->rights->accounting->mouvements->lire)) {
  * Action
  */
 
+$param = '';
+
 if (GETPOST('cancel', 'alpha')) {
-	$action = 'list'; $massaction = '';
+	$action = 'list';
+	$massaction = '';
 }
-if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend') {
+if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'preunlettering' && $massaction != 'predeletebookkeepingwriting') {
 	$massaction = '';
 }
 
@@ -242,10 +258,11 @@ if (empty($reshook)) {
 		$search_credit = '';
 		$search_lettering_code = '';
 		$search_not_reconciled = '';
+		$search_import_key = '';
+		$toselect = '';
 	}
 
 	// Must be after the remove filter action, before the export.
-	$param = '';
 	$filter = array();
 
 	if (!empty($search_date_start)) {
@@ -261,12 +278,20 @@ if (empty($reshook)) {
 		$param .= '&doc_datemonth='.GETPOST('doc_datemonth', 'int').'&doc_dateday='.GETPOST('doc_dateday', 'int').'&doc_dateyear='.GETPOST('doc_dateyear', 'int');
 	}
 	if (!empty($search_accountancy_code_start)) {
-		$filter['t.numero_compte>='] = $search_accountancy_code_start;
-		$param .= '&search_accountancy_code_start='.urlencode($search_accountancy_code_start);
+		if ($type == 'sub') {
+			$filter['t.subledger_account>='] = $search_accountancy_code_start;
+		} else {
+			$filter['t.numero_compte>='] = $search_accountancy_code_start;
+		}
+		$param .= '&search_accountancy_code_start=' . urlencode($search_accountancy_code_start);
 	}
 	if (!empty($search_accountancy_code_end)) {
-		$filter['t.numero_compte<='] = $search_accountancy_code_end;
-		$param .= '&search_accountancy_code_end='.urlencode($search_accountancy_code_end);
+		if ($type == 'sub') {
+			$filter['t.subledger_account<='] = $search_accountancy_code_end;
+		} else {
+			$filter['t.numero_compte<='] = $search_accountancy_code_end;
+		}
+		$param .= '&search_accountancy_code_end=' . urlencode($search_accountancy_code_end);
 	}
 	if (!empty($search_label_account)) {
 		$filter['t.label_compte'] = $search_label_account;
@@ -326,61 +351,133 @@ if (empty($reshook)) {
 		$filter['t.date_validated<='] = $search_date_validation_end;
 		$param .= '&search_date_validation_endmonth='.$search_date_validation_endmonth.'&search_date_validation_endday='.$search_date_validation_endday.'&search_date_validation_endyear='.$search_date_validation_endyear;
 	}
-}
+	if (!empty($search_import_key)) {
+		$filter['t.import_key'] = $search_import_key;
+		$param .= '&search_import_key='.urlencode($search_import_key);
+	}
 
-if ($action == 'delbookkeeping' && $user->rights->accounting->mouvements->supprimer) {
-	$import_key = GETPOST('importkey', 'alpha');
+	// param with type of list
+	$url_param = substr($param, 1); // remove first "&"
+	if (!empty($type)) {
+		$param = '&type='.$type.$param;
+	}
 
-	if (!empty($import_key)) {
-		$result = $object->deleteByImportkey($import_key);
-		if ($result < 0) {
-			setEventMessages($object->error, $object->errors, 'errors');
+	//if ($action == 'delbookkeepingyearconfirm' && $user->rights->accounting->mouvements->supprimer_tous) {
+	//	$delmonth = GETPOST('delmonth', 'int');
+	//	$delyear = GETPOST('delyear', 'int');
+	//	if ($delyear == -1) {
+	//		$delyear = 0;
+	//	}
+	//	$deljournal = GETPOST('deljournal', 'alpha');
+	//	if ($deljournal == -1) {
+	//		$deljournal = 0;
+	//	}
+	//
+	//	if (!empty($delmonth) || !empty($delyear) || !empty($deljournal)) {
+	//		$result = $object->deleteByYearAndJournal($delyear, $deljournal, '', ($delmonth > 0 ? $delmonth : 0));
+	//		if ($result < 0) {
+	//			setEventMessages($object->error, $object->errors, 'errors');
+	//		} else {
+	//			setEventMessages("RecordDeleted", null, 'mesgs');
+	//		}
+	//
+	//		// Make a redirect to avoid to launch the delete later after a back button
+	//		header("Location: ".$_SERVER["PHP_SELF"].($param ? '?'.$param : ''));
+	//		exit;
+	//	} else {
+	//		setEventMessages("NoRecordDeleted", null, 'warnings');
+	//	}
+	//}
+
+	// Mass actions
+	$objectclass = 'Bookkeeping';
+	$objectlabel = 'Bookkeeping';
+	$permissiontoread = $user->rights->societe->lire;
+	$permissiontodelete = $user->rights->societe->supprimer;
+	$permissiontoadd = $user->rights->societe->creer;
+	$uploaddir = $conf->societe->dir_output;
+	include DOL_DOCUMENT_ROOT.'/core/actions_massactions.inc.php';
+
+	if (!$error && $action == 'deletebookkeepingwriting' && $confirm == "yes" && $user->rights->accounting->mouvements->supprimer) {
+		$nbok = 0;
+		foreach ($toselect as $toselectid) {
+			$result = $object->fetch($toselectid);
+			if ($result > 0 && (!isset($object->date_validation) || $object->date_validation === '')) {
+				$result = $object->deleteMvtNum($object->piece_num);
+				if ($result > 0) {
+					$nbok++;
+				} else {
+					setEventMessages($object->error, $object->errors, 'errors');
+					$error++;
+					break;
+				}
+			} elseif ($result < 0) {
+				setEventMessages($object->error, $object->errors, 'errors');
+				$error++;
+				break;
+			}
 		}
 
-		// Make a redirect to avoid to launch the delete later after a back button
-		header("Location: ".$_SERVER["PHP_SELF"].($param ? '?'.$param : ''));
-		exit;
-	}
-}
-if ($action == 'delbookkeepingyearconfirm' && $user->rights->accounting->mouvements->supprimer_tous) {
-	$delmonth = GETPOST('delmonth', 'int');
-	$delyear = GETPOST('delyear', 'int');
-	if ($delyear == -1) {
-		$delyear = 0;
-	}
-	$deljournal = GETPOST('deljournal', 'alpha');
-	if ($deljournal == -1) {
-		$deljournal = 0;
-	}
-
-	if (!empty($delmonth) || !empty($delyear) || !empty($deljournal)) {
-		$result = $object->deleteByYearAndJournal($delyear, $deljournal, '', ($delmonth > 0 ? $delmonth : 0));
-		if ($result < 0) {
-			setEventMessages($object->error, $object->errors, 'errors');
-		} else {
-			setEventMessages("RecordDeleted", null, 'mesgs');
+		// Message for elements well deleted
+		if ($nbok > 1) {
+			setEventMessages($langs->trans("RecordsDeleted", $nbok), null, 'mesgs');
+		} elseif ($nbok > 0) {
+			setEventMessages($langs->trans("RecordDeleted", $nbok), null, 'mesgs');
+		} elseif (!$error) {
+			setEventMessages($langs->trans("NoRecordDeleted"), null, 'mesgs');
 		}
 
-		// Make a redirect to avoid to launch the delete later after a back button
-		header("Location: ".$_SERVER["PHP_SELF"].($param ? '?'.$param : ''));
-		exit;
-	} else {
-		setEventMessages("NoRecordDeleted", null, 'warnings');
+		if (!$error) {
+			header("Location: ".$_SERVER["PHP_SELF"]."?noreset=1".($param ? '&'.$param : ''));
+			exit;
+		}
 	}
-}
-if ($action == 'delmouvconfirm' && $user->rights->accounting->mouvements->supprimer) {
-	$mvt_num = GETPOST('mvt_num', 'int');
 
-	if (!empty($mvt_num)) {
-		$result = $object->deleteMvtNum($mvt_num);
-		if ($result < 0) {
-			setEventMessages($object->error, $object->errors, 'errors');
-		} else {
-			setEventMessages($langs->trans("RecordDeleted"), null, 'mesgs');
-		}
+	// others mass actions
+	if (!$error && getDolGlobalInt('ACCOUNTING_ENABLE_LETTERING') && $user->rights->accounting->mouvements->creer) {
+		if ($massaction == 'lettering') {
+			$lettering = new Lettering($db);
+			$nb_lettering = $lettering->bookkeepingLetteringAll($toselect);
+			if ($nb_lettering < 0) {
+				setEventMessages('', $lettering->errors, 'errors');
+				$error++;
+				$nb_lettering = max(0, abs($nb_lettering) - 2);
+			} elseif ($nb_lettering == 0) {
+				$nb_lettering = 0;
+				setEventMessages($langs->trans('AccountancyNoLetteringModified'), array(), 'mesgs');
+			}
+			if ($nb_lettering == 1) {
+				setEventMessages($langs->trans('AccountancyOneLetteringModifiedSuccessfully'), array(), 'mesgs');
+			} elseif ($nb_lettering > 1) {
+				setEventMessages($langs->trans('AccountancyLetteringModifiedSuccessfully', $nb_lettering), array(), 'mesgs');
+			}
+
+			if (!$error) {
+				header('Location: ' . $_SERVER['PHP_SELF'] . '?noreset=1' . $param);
+				exit();
+			}
+		} elseif ($action == 'unlettering' && $confirm == "yes") {
+			$lettering = new Lettering($db);
+			$nb_lettering = $lettering->bookkeepingLetteringAll($toselect, true);
+			if ($nb_lettering < 0) {
+				setEventMessages('', $lettering->errors, 'errors');
+				$error++;
+				$nb_lettering = max(0, abs($nb_lettering) - 2);
+			} elseif ($nb_lettering == 0) {
+				$nb_lettering = 0;
+				setEventMessages($langs->trans('AccountancyNoUnletteringModified'), array(), 'mesgs');
+			}
+			if ($nb_lettering == 1) {
+				setEventMessages($langs->trans('AccountancyOneUnletteringModifiedSuccessfully'), array(), 'mesgs');
+			} elseif ($nb_lettering > 1) {
+				setEventMessages($langs->trans('AccountancyUnletteringModifiedSuccessfully', $nb_lettering), array(), 'mesgs');
+			}
 
-		header("Location: ".$_SERVER["PHP_SELF"]."?noreset=1".($param ? '&'.$param : ''));
-		exit;
+			if (!$error) {
+				header('Location: ' . $_SERVER['PHP_SELF'] . '?noreset=1' . $param);
+				exit();
+			}
+		}
 	}
 }
 
@@ -394,73 +491,101 @@ $formfile = new FormFile($db);
 $formother = new FormOther($db);
 $form = new Form($db);
 
-$title_page = $langs->trans("Operations").' - '.$langs->trans("VueByAccountAccounting").' ('.$langs->trans("Bookkeeping").')';
+$title_page = $langs->trans("Operations").' - '.$langs->trans("VueByAccountAccounting").' (';
+if ($type == 'sub') {
+	$title_page .= $langs->trans("BookkeepingSubAccount");
+} else {
+	$title_page .= $langs->trans("Bookkeeping");
+}
+$title_page .= ')';
 
 llxHeader('', $title_page);
 
 // List
 $nbtotalofrecords = '';
 if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
-	$nbtotalofrecords = $object->fetchAllByAccount($sortorder, $sortfield, 0, 0, $filter);
+	if ($type == 'sub') {
+		$nbtotalofrecords = $object->fetchAllByAccount($sortorder, $sortfield, 0, 0, $filter, 'AND', 1);
+	} else {
+		$nbtotalofrecords = $object->fetchAllByAccount($sortorder, $sortfield, 0, 0, $filter);
+	}
+
 	if ($nbtotalofrecords < 0) {
 		setEventMessages($object->error, $object->errors, 'errors');
 	}
 }
 
-$result = $object->fetchAllByAccount($sortorder, $sortfield, $limit, $offset, $filter);
+if ($type == 'sub') {
+	$result = $object->fetchAllByAccount($sortorder, $sortfield, $limit, $offset, $filter, 'AND', 1);
+} else {
+	$result = $object->fetchAllByAccount($sortorder, $sortfield, $limit, $offset, $filter);
+}
 
 if ($result < 0) {
 	setEventMessages($object->error, $object->errors, 'errors');
 }
 
+$arrayofselected = is_array($toselect) ? $toselect : array();
+
 $num = count($object->lines);
 
 
-if ($action == 'delmouv') {
-	$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?mvt_num='.GETPOST('mvt_num'), $langs->trans('DeleteMvt'), $langs->trans('ConfirmDeleteMvtPartial'), 'delmouvconfirm', '', 0, 1);
-	print $formconfirm;
-}
-if ($action == 'delbookkeepingyear') {
-	$form_question = array();
-	$delyear = GETPOST('delyear', 'int');
-	$deljournal = GETPOST('deljournal', 'alpha');
-
-	if (empty($delyear)) {
-		$delyear = dol_print_date(dol_now(), '%Y');
-	}
-	$month_array = array();
-	for ($i = 1; $i <= 12; $i++) {
-		$month_array[$i] = $langs->trans("Month".sprintf("%02d", $i));
-	}
-	$year_array = $formaccounting->selectyear_accountancy_bookkepping($delyear, 'delyear', 0, 'array');
-	$journal_array = $formaccounting->select_journal($deljournal, 'deljournal', '', 1, 1, 1, '', 0, 1);
-
-	$form_question['delmonth'] = array(
-		'name' => 'delmonth',
-		'type' => 'select',
-		'label' => $langs->trans('DelMonth'),
-		'values' => $month_array,
-		'default' => ''
-	);
-	$form_question['delyear'] = array(
-		'name' => 'delyear',
-		'type' => 'select',
-		'label' => $langs->trans('DelYear'),
-		'values' => $year_array,
-		'default' => $delyear
-	);
-	$form_question['deljournal'] = array(
-		'name' => 'deljournal',
-		'type' => 'other', // We don't use select here, the journal_array is already a select html component
-		'label' => $langs->trans('DelJournal'),
-		'value' => $journal_array,
-		'default' => $deljournal
-	);
-
-	$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans('DeleteMvt'), $langs->trans('ConfirmDeleteMvt', $langs->transnoentitiesnoconv("RegistrationInAccounting")), 'delbookkeepingyearconfirm', $form_question, '', 1, 300);
-	print $formconfirm;
+///if ($action == 'delbookkeepingyear') {
+//	$form_question = array();
+//	$delyear = GETPOST('delyear', 'int');
+//	$deljournal = GETPOST('deljournal', 'alpha');
+//
+//	if (empty($delyear)) {
+//		$delyear = dol_print_date(dol_now(), '%Y');
+//	}
+//	$month_array = array();
+//	for ($i = 1; $i <= 12; $i++) {
+//		$month_array[$i] = $langs->trans("Month".sprintf("%02d", $i));
+//	}
+//	$year_array = $formaccounting->selectyear_accountancy_bookkepping($delyear, 'delyear', 0, 'array');
+//	$journal_array = $formaccounting->select_journal($deljournal, 'deljournal', '', 1, 1, 1, '', 0, 1);
+//
+//	$form_question['delmonth'] = array(
+//		'name' => 'delmonth',
+//		'type' => 'select',
+//		'label' => $langs->trans('DelMonth'),
+//		'values' => $month_array,
+//		'default' => ''
+//	);
+//	$form_question['delyear'] = array(
+//		'name' => 'delyear',
+//		'type' => 'select',
+//		'label' => $langs->trans('DelYear'),
+//		'values' => $year_array,
+//		'default' => $delyear
+//	);
+//	$form_question['deljournal'] = array(
+//		'name' => 'deljournal',
+//		'type' => 'other', // We don't use select here, the journal_array is already a select html component
+//		'label' => $langs->trans('DelJournal'),
+//		'value' => $journal_array,
+//		'default' => $deljournal
+//	);
+//
+//	$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans('DeleteMvt'), $langs->trans('ConfirmDeleteMvt', $langs->transnoentitiesnoconv("RegistrationInAccounting")), 'delbookkeepingyearconfirm', $form_question, '', 1, 300);
+//}
+
+// Print form confirm
+print $formconfirm;
+
+// List of mass actions available
+$arrayofmassactions = array();
+if (getDolGlobalInt('ACCOUNTING_ENABLE_LETTERING') && $user->rights->accounting->mouvements->creer) {
+	$arrayofmassactions['lettering'] = img_picto('', 'check', 'class="pictofixedwidth"') . $langs->trans('Lettering');
+	$arrayofmassactions['preunlettering'] = img_picto('', 'uncheck', 'class="pictofixedwidth"') . $langs->trans('Unlettering');
 }
-
+if ($user->rights->accounting->mouvements->supprimer) {
+	$arrayofmassactions['predeletebookkeepingwriting'] = img_picto('', 'delete', 'class="pictofixedwidth"').$langs->trans("Delete");
+}
+if (GETPOST('nomassaction', 'int') || in_array($massaction, array('preunlettering', 'predeletebookkeepingwriting'))) {
+	$arrayofmassactions = array();
+}
+$massactionbutton = $form->selectMassAction($massaction, $arrayofmassactions);
 
 print '<form method="POST" id="searchFormList" action="'.$_SERVER["PHP_SELF"].'">';
 print '<input type="hidden" name="token" value="'.newToken().'">';
@@ -469,15 +594,22 @@ if ($optioncss != '') {
 	print '<input type="hidden" name="optioncss" value="'.$optioncss.'">';
 }
 print '<input type="hidden" name="formfilteraction" id="formfilteraction" value="list">';
+print '<input type="hidden" name="type" value="'.$type.'">';
 print '<input type="hidden" name="sortfield" value="'.$sortfield.'">';
 print '<input type="hidden" name="sortorder" value="'.$sortorder.'">';
+print '<input type="hidden" name="contextpage" value="'.$contextpage.'">';
 
 $parameters = array();
 $reshook = $hookmanager->executeHooks('addMoreActionsButtonsList', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 if (empty($reshook)) {
 	$newcardbutton = dolGetButtonTitle($langs->trans('ViewFlatList'), '', 'fa fa-list paddingleft imgforviewmode', DOL_URL_ROOT.'/accountancy/bookkeeping/list.php?'.$param);
-	$newcardbutton .= dolGetButtonTitle($langs->trans('GroupByAccountAccounting'), '', 'fa fa-stream paddingleft imgforviewmode', DOL_URL_ROOT.'/accountancy/bookkeeping/listbyaccount.php?'.$param, '', 1, array('morecss' => 'marginleftonly btnTitleSelected'));
-	$newcardbutton .= dolGetButtonTitle($langs->trans('GroupBySubAccountAccounting'), '', 'fa fa-align-left vmirror paddingleft imgforviewmode', DOL_URL_ROOT.'/accountancy/bookkeeping/listbysubaccount.php?'.$param, '', 1, array('morecss' => 'marginleftonly'));
+	if ($type == 'sub') {
+		$newcardbutton .= dolGetButtonTitle($langs->trans('GroupByAccountAccounting'), '', 'fa fa-stream paddingleft imgforviewmode', DOL_URL_ROOT . '/accountancy/bookkeeping/listbyaccount.php?' . $url_param, '', 1, array('morecss' => 'marginleftonly'));
+		$newcardbutton .= dolGetButtonTitle($langs->trans('GroupBySubAccountAccounting'), '', 'fa fa-align-left vmirror paddingleft imgforviewmode', DOL_URL_ROOT . '/accountancy/bookkeeping/listbyaccount.php?type=sub&' . $url_param, '', 1, array('morecss' => 'marginleftonly btnTitleSelected'));
+	} else {
+		$newcardbutton .= dolGetButtonTitle($langs->trans('GroupByAccountAccounting'), '', 'fa fa-stream paddingleft imgforviewmode', DOL_URL_ROOT . '/accountancy/bookkeeping/listbyaccount.php?' . $url_param, '', 1, array('morecss' => 'marginleftonly btnTitleSelected'));
+		$newcardbutton .= dolGetButtonTitle($langs->trans('GroupBySubAccountAccounting'), '', 'fa fa-align-left vmirror paddingleft imgforviewmode', DOL_URL_ROOT . '/accountancy/bookkeeping/listbyaccount.php?type=sub&' . $url_param, '', 1, array('morecss' => 'marginleftonly'));
+	}
 	$newcardbutton .= dolGetButtonTitle($langs->trans('NewAccountingMvt'), '', 'fa fa-plus-circle paddingleft', DOL_URL_ROOT.'/accountancy/bookkeeping/card.php?action=create');
 }
 
@@ -488,11 +620,29 @@ if ($limit > 0 && $limit != $conf->liste_limit) {
 	$param .= '&limit='.urlencode($limit);
 }
 
-print_barre_liste($title_page, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $result, $nbtotalofrecords, 'title_accountancy', 0, $newcardbutton, '', $limit, 0, 0, 1);
+print_barre_liste($title_page, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $result, $nbtotalofrecords, 'title_accountancy', 0, $newcardbutton, '', $limit, 0, 0, 1);
+
+if ($massaction == 'preunlettering') {
+	print $form->formconfirm($_SERVER["PHP_SELF"], $langs->trans("ConfirmMassUnlettering"), $langs->trans("ConfirmMassUnletteringQuestion", count($toselect)), "unlettering", null, '', 0, 200, 500, 1);
+} elseif ($massaction == 'predeletebookkeepingwriting') {
+	print $form->formconfirm($_SERVER["PHP_SELF"], $langs->trans("ConfirmMassDeleteBookkeepingWriting"), $langs->trans("ConfirmMassDeleteBookkeepingWritingQuestion", count($toselect)), "deletebookkeepingwriting", null, '', 0, 200, 500, 1);
+}
+//DeleteMvt=Supprimer des lignes d'opérations de la comptabilité
+//DelMonth=Mois à effacer
+//DelYear=Année à supprimer
+//DelJournal=Journal à supprimer
+//ConfirmDeleteMvt=Cette action supprime les lignes des opérations pour l'année/mois et/ou pour le journal sélectionné (au moins un critère est requis). Vous devrez utiliser de nouveau la fonctionnalité '%s' pour retrouver vos écritures dans la comptabilité.
+//ConfirmDeleteMvtPartial=Cette action supprime l'écriture de la comptabilité (toutes les lignes opérations liées à une même écriture seront effacées).
+
+//$topicmail = "Information";
+//$modelmail = "accountingbookkeeping";
+//$objecttmp = new BookKeeping($db);
+//$trackid = 'bk'.$object->id;
+include DOL_DOCUMENT_ROOT.'/core/tpl/massactions_pre.tpl.php';
 
 $varpage = empty($contextpage) ? $_SERVER["PHP_SELF"] : $contextpage;
 $selectedfields = $form->multiSelectArrayWithCheckbox('selectedfields', $arrayfields, $varpage); // This also change content of $arrayfields
-if ($massactionbutton) {
+if ($massactionbutton && $contextpage != 'poslist') {
 	$selectedfields .= $form->showCheckAddButtons('checkforselect', 1);
 }
 
@@ -509,9 +659,17 @@ $moreforfilter = '';
 $moreforfilter .= '<div class="divsearchfield">';
 $moreforfilter .= $langs->trans('AccountAccounting').': ';
 $moreforfilter .= '<div class="nowrap inline-block">';
-$moreforfilter .= $formaccounting->select_account($search_accountancy_code_start, 'search_accountancy_code_start', $langs->trans('From'), array(), 1, 1, 'maxwidth200');
+if ($type == 'sub') {
+	$moreforfilter .= $formaccounting->select_auxaccount($search_accountancy_code_start, 'search_accountancy_code_start', $langs->trans('From'), 'maxwidth200');
+} else {
+	$moreforfilter .= $formaccounting->select_account($search_accountancy_code_start, 'search_accountancy_code_start', $langs->trans('From'), array(), 1, 1, 'maxwidth200');
+}
 $moreforfilter .= ' ';
-$moreforfilter .= $formaccounting->select_account($search_accountancy_code_end, 'search_accountancy_code_end', $langs->trans('to'), array(), 1, 1, 'maxwidth200');
+if ($type == 'sub') {
+	$moreforfilter .= $formaccounting->select_auxaccount($search_accountancy_code_end, 'search_accountancy_code_end', $langs->trans('to'), 'maxwidth200');
+} else {
+	$moreforfilter .= $formaccounting->select_account($search_accountancy_code_end, 'search_accountancy_code_end', $langs->trans('to'), array(), 1, 1, 'maxwidth200');
+}
 $moreforfilter .= '</div>';
 $moreforfilter .= '</div>';
 
@@ -599,6 +757,11 @@ if (!empty($arrayfields['t.date_validated']['checked'])) {
 	print '</div>';
 	print '</td>';
 }
+if (!empty($arrayfields['t.import_key']['checked'])) {
+	print '<td class="liste_titre center">';
+	print '<input class="flat searchstring maxwidth50" type="text" name="search_import_key" value="'.dol_escape_htmltag($search_import_key).'">';
+	print '</td>';
+}
 
 // Fields from hook
 $parameters = array('arrayfields'=>$arrayfields);
@@ -643,6 +806,9 @@ if (!empty($arrayfields['t.date_export']['checked'])) {
 if (!empty($arrayfields['t.date_validated']['checked'])) {
 	print_liste_field_titre($arrayfields['t.date_validated']['label'], $_SERVER['PHP_SELF'], "t.date_validated", "", $param, '', $sortfield, $sortorder, 'center ');
 }
+if (!empty($arrayfields['t.import_key']['checked'])) {
+	print_liste_field_titre($arrayfields['t.import_key']['label'], $_SERVER["PHP_SELF"], "t.import_key", "", $param, '', $sortfield, $sortorder, 'center ');
+}
 // Hook fields
 $parameters = array('arrayfields'=>$arrayfields, 'param'=>$param, 'sortfield'=>$sortfield, 'sortorder'=>$sortorder);
 $reshook = $hookmanager->executeHooks('printFieldListTitle', $parameters); // Note that $action and $object may have been modified by hook
@@ -667,7 +833,11 @@ while ($i < min($num, $limit)) {
 	$total_debit += $line->debit;
 	$total_credit += $line->credit;
 
-	$accountg = length_accountg($line->numero_compte);
+	if ($type == 'sub') {
+		$accountg = length_accounta($line->subledger_account);
+	} else {
+		$accountg = length_accountg($line->numero_compte);
+	}
 	//if (empty($accountg)) $accountg = '-';
 
 	$colspan = 0;			// colspan before field 'label of operation'
@@ -686,7 +856,11 @@ while ($i < min($num, $limit)) {
 		// Show a subtotal by accounting account
 		if (isset($displayed_account_number)) {
 			print '<tr class="liste_total">';
-			print '<td class="right" colspan="'.$colspan.'">'.$langs->trans("TotalForAccount").' '.length_accountg($displayed_account_number).':</td>';
+			if ($type == 'sub') {
+				print '<td class="right" colspan="' . $colspan . '">' . $langs->trans("TotalForAccount") . ' ' . length_accounta($displayed_account_number) . ':</td>';
+			} else {
+				print '<td class="right" colspan="' . $colspan . '">' . $langs->trans("TotalForAccount") . ' ' . length_accountg($displayed_account_number) . ':</td>';
+			}
 			print '<td class="nowrap right">'.price($sous_total_debit).'</td>';
 			print '<td class="nowrap right">'.price($sous_total_credit).'</td>';
 			print '<td colspan="'.$colspanend.'"></td>';
@@ -712,11 +886,28 @@ while ($i < min($num, $limit)) {
 
 		// Show the break account
 		print '<tr class="trforbreak">';
-		print '<td colspan="'.($totalarray['nbfield'] ? $totalarray['nbfield'] : 10).'" class="tdforbreak">';
-		if ($line->numero_compte != "" && $line->numero_compte != '-1') {
-			print length_accountg($line->numero_compte).' : '.$object->get_compte_desc($line->numero_compte);
+		print '<td colspan="'.($totalarray['nbfield'] ? $totalarray['nbfield'] : count($arrayfields)+1).'" class="tdforbreak">';
+		if ($type == 'sub') {
+			if ($line->subledger_account != "" && $line->subledger_account != '-1') {
+				print $line->subledger_label . ' : ' . length_accounta($line->subledger_account);
+			} else {
+				// Should not happen: subledger account must be null or a non empty value
+				print '<span class="error">' . $langs->trans("Unknown");
+				if ($line->subledger_label) {
+					print ' (' . $line->subledger_label . ')';
+					$htmltext = 'EmptyStringForSubledgerAccountButSubledgerLabelDefined';
+				} else {
+					$htmltext = 'EmptyStringForSubledgerAccountAndSubledgerLabel';
+				}
+				print $form->textwithpicto('', $htmltext);
+				print '</span>';
+			}
 		} else {
-			print '<span class="error">'.$langs->trans("Unknown").'</span>';
+			if ($line->numero_compte != "" && $line->numero_compte != '-1') {
+				print length_accountg($line->numero_compte) . ' : ' . $object->get_compte_desc($line->numero_compte);
+			} else {
+				print '<span class="error">' . $langs->trans("Unknown") . '</span>';
+			}
 		}
 		print '</td>';
 		print '</tr>';
@@ -890,22 +1081,26 @@ while ($i < min($num, $limit)) {
 		}
 	}
 
+	if (!empty($arrayfields['t.import_key']['checked'])) {
+		print '<td class="tdoverflowmax100">'.$line->import_key."</td>\n";
+		if (!$i) {
+			$totalarray['nbfield']++;
+		}
+	}
+
 	// Fields from hook
-	$parameters = array('arrayfields'=>$arrayfields, 'obj'=>$obj);
+	$parameters = array('arrayfields'=>$arrayfields, 'obj'=>$line);
 	$reshook = $hookmanager->executeHooks('printFieldListValue', $parameters); // Note that $action and $object may have been modified by hook
 	print $hookmanager->resPrint;
 
 	// Action column
 	print '<td class="nowraponall center">';
-	if (empty($line->date_export) && empty($line->date_validation)) {
-		if ($user->rights->accounting->mouvements->creer) {
-			print '<a class="editfielda paddingleft marginrightonly" href="' . DOL_URL_ROOT . '/accountancy/bookkeeping/card.php?piece_num=' . $line->piece_num . $param . '&page=' . $page . ($sortfield ? '&sortfield=' . $sortfield : '') . ($sortorder ? '&sortorder=' . $sortorder : '') . '">' . img_edit() . '</a>';
-		}
-	}
-	if (empty($line->date_validation)) {
-		if ($user->rights->accounting->mouvements->supprimer) {
-			print '<a class="reposition paddingleft marginrightonly" href="'.$_SERVER['PHP_SELF'].'?action=delmouv&token='.newToken().'&mvt_num='.$line->piece_num.$param.'&page='.$page.($sortfield ? '&sortfield='.$sortfield : '').($sortorder ? '&sortorder='.$sortorder : '').'">'.img_delete().'</a>';
+	if (($massactionbutton || $massaction) && $contextpage != 'poslist') {   // If we are in select mode (massactionbutton defined) or if we have already selected and sent an action ($massaction) defined
+		$selected = 0;
+		if (in_array($line->id, $arrayofselected)) {
+			$selected = 1;
 		}
+		print '<input id="cb' . $line->id . '" class="flat checkforselect" type="checkbox" name="toselect[]" value="' . $line->id . '"' . ($selected ? ' checked="checked"' : '') . ' />';
 	}
 	print '</td>';
 	if (!$i) {
@@ -955,11 +1150,11 @@ print "</table>";
 print '</div>';
 
 // TODO Replace this with mass delete action
-if ($user->rights->accounting->mouvements->supprimer_tous) {
-	print '<div class="tabsAction tabsActionNoBottom">'."\n";
-	print '<a class="butActionDelete" name="button_delmvt" href="'.$_SERVER["PHP_SELF"].'?action=delbookkeepingyear&token='.newToken().($param ? '&'.$param : '').'">'.$langs->trans("DeleteMvt").'</a>';
-	print '</div>';
-}
+//if ($user->rights->accounting->mouvements->supprimer_tous) {
+//	print '<div class="tabsAction tabsActionNoBottom">'."\n";
+//	print '<a class="butActionDelete" name="button_delmvt" href="'.$_SERVER["PHP_SELF"].'?action=delbookkeepingyear&token='.newToken().($param ? '&'.$param : '').'">'.$langs->trans("DeleteMvt").'</a>';
+//	print '</div>';
+//}
 
 print '</form>';
 

+ 0 - 979
htdocs/accountancy/bookkeeping/listbysubaccount.php

@@ -1,979 +0,0 @@
-<?php
-/* Copyright (C) 2016       Neil Orley          <neil.orley@oeris.fr>
- * Copyright (C) 2013-2016  Olivier Geffroy     <jeff@jeffinfo.com>
- * Copyright (C) 2013-2020  Florian Henry       <florian.henry@open-concept.pro>
- * Copyright (C) 2013-2021  Alexandre Spangaro  <aspangaro@open-dsi.fr>
- * Copyright (C) 2018-2020  Frédéric France		<frederic.france@netlogic.fr>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
-
-/**
- * \file 		htdocs/accountancy/bookkeeping/listbysubaccount.php
- * \ingroup 	Accountancy (Double entries)
- * \brief 		List operation of ledger ordered by subaccount number
- */
-
-require '../../main.inc.php';
-
-require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
-require_once DOL_DOCUMENT_ROOT.'/accountancy/class/bookkeeping.class.php';
-require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingjournal.class.php';
-require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
-require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
-require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
-require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
-
-// Load translation files required by the page
-$langs->loadLangs(array("accountancy", "compta"));
-
-$action = GETPOST('action', 'aZ09');
-$search_date_startyear = GETPOST('search_date_startyear', 'int');
-$search_date_startmonth = GETPOST('search_date_startmonth', 'int');
-$search_date_startday = GETPOST('search_date_startday', 'int');
-$search_date_endyear = GETPOST('search_date_endyear', 'int');
-$search_date_endmonth = GETPOST('search_date_endmonth', 'int');
-$search_date_endday = GETPOST('search_date_endday', 'int');
-$search_date_start = dol_mktime(0, 0, 0, $search_date_startmonth, $search_date_startday, $search_date_startyear);
-$search_date_end = dol_mktime(23, 59, 59, $search_date_endmonth, $search_date_endday, $search_date_endyear);
-$search_doc_date = dol_mktime(0, 0, 0, GETPOST('doc_datemonth', 'int'), GETPOST('doc_dateday', 'int'), GETPOST('doc_dateyear', 'int'));
-$search_date_export_startyear =  GETPOST('search_date_export_startyear', 'int');
-$search_date_export_startmonth =  GETPOST('search_date_export_startmonth', 'int');
-$search_date_export_startday =  GETPOST('search_date_export_startday', 'int');
-$search_date_export_endyear =  GETPOST('search_date_export_endyear', 'int');
-$search_date_export_endmonth =  GETPOST('search_date_export_endmonth', 'int');
-$search_date_export_endday =  GETPOST('search_date_export_endday', 'int');
-$search_date_export_start = dol_mktime(0, 0, 0, $search_date_export_startmonth, $search_date_export_startday, $search_date_export_startyear);
-$search_date_export_end = dol_mktime(23, 59, 59, $search_date_export_endmonth, $search_date_export_endday, $search_date_export_endyear);
-$search_date_validation_startyear =  GETPOST('search_date_validation_startyear', 'int');
-$search_date_validation_startmonth =  GETPOST('search_date_validation_startmonth', 'int');
-$search_date_validation_startday =  GETPOST('search_date_validation_startday', 'int');
-$search_date_validation_endyear =  GETPOST('search_date_validation_endyear', 'int');
-$search_date_validation_endmonth =  GETPOST('search_date_validation_endmonth', 'int');
-$search_date_validation_endday =  GETPOST('search_date_validation_endday', 'int');
-$search_date_validation_start = dol_mktime(0, 0, 0, $search_date_validation_startmonth, $search_date_validation_startday, $search_date_validation_startyear);
-$search_date_validation_end = dol_mktime(23, 59, 59, $search_date_validation_endmonth, $search_date_validation_endday, $search_date_validation_endyear);
-
-$search_accountancy_code = GETPOST("search_accountancy_code");
-$search_accountancy_code_start = GETPOST('search_accountancy_code_start', 'alpha');
-if ($search_accountancy_code_start == - 1) {
-	$search_accountancy_code_start = '';
-}
-$search_accountancy_code_end = GETPOST('search_accountancy_code_end', 'alpha');
-if ($search_accountancy_code_end == - 1) {
-	$search_accountancy_code_end = '';
-}
-$search_doc_ref = GETPOST('search_doc_ref', 'alpha');
-$search_label_operation = GETPOST('search_label_operation', 'alpha');
-$search_mvt_num = GETPOST('search_mvt_num', 'int');
-$search_direction = GETPOST('search_direction', 'alpha');
-$search_ledger_code = GETPOST('search_ledger_code', 'array');
-$search_debit = GETPOST('search_debit', 'alpha');
-$search_credit = GETPOST('search_credit', 'alpha');
-$search_lettering_code = GETPOST('search_lettering_code', 'alpha');
-$search_not_reconciled = GETPOST('search_not_reconciled', 'alpha');
-
-if (GETPOST("button_delmvt_x") || GETPOST("button_delmvt.x") || GETPOST("button_delmvt")) {
-	$action = 'delbookkeepingyear';
-}
-
-// Load variable for pagination
-$limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : (empty($conf->global->ACCOUNTING_LIMIT_LIST_VENTILATION) ? $conf->liste_limit : $conf->global->ACCOUNTING_LIMIT_LIST_VENTILATION);
-$sortfield = GETPOST('sortfield', 'aZ09comma');
-$sortorder = GETPOST('sortorder', 'aZ09comma');
-$page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int');
-if (empty($page) || $page < 0) {
-	$page = 0;
-}
-$offset = $limit * $page;
-$pageprev = $page - 1;
-$pagenext = $page + 1;
-if ($sortorder == "") {
-	$sortorder = "ASC";
-}
-if ($sortfield == "") {
-	$sortfield = "t.doc_date,t.rowid";
-}
-
-// Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context
-$object = new BookKeeping($db);
-$formfile = new FormFile($db);
-$hookmanager->initHooks(array('bookkeepingbysubaccountlist'));
-
-$formaccounting = new FormAccounting($db);
-$form = new Form($db);
-
-if (empty($search_date_start) && empty($search_date_end) && !GETPOSTISSET('search_date_startday') && !GETPOSTISSET('search_date_startmonth') && !GETPOSTISSET('search_date_starthour')) {
-	$sql = "SELECT date_start, date_end from ".MAIN_DB_PREFIX."accounting_fiscalyear ";
-	$sql .= " where date_start < '".$db->idate(dol_now())."' and date_end > '".$db->idate(dol_now())."'";
-	$sql .= $db->plimit(1);
-	$res = $db->query($sql);
-
-	if ($res->num_rows > 0) {
-		$fiscalYear = $db->fetch_object($res);
-		$search_date_start = strtotime($fiscalYear->date_start);
-		$search_date_end = strtotime($fiscalYear->date_end);
-	} else {
-		$month_start = ($conf->global->SOCIETE_FISCAL_MONTH_START ? ($conf->global->SOCIETE_FISCAL_MONTH_START) : 1);
-		$year_start = dol_print_date(dol_now(), '%Y');
-		if (dol_print_date(dol_now(), '%m') < $month_start) {
-			$year_start--; // If current month is lower that starting fiscal month, we start last year
-		}
-		$year_end = $year_start + 1;
-		$month_end = $month_start - 1;
-		if ($month_end < 1) {
-			$month_end = 12;
-			$year_end--;
-		}
-		$search_date_start = dol_mktime(0, 0, 0, $month_start, 1, $year_start);
-		$search_date_end = dol_get_last_day($year_end, $month_end);
-	}
-}
-
-$arrayfields = array(
-	// 't.subledger_account'=>array('label'=>$langs->trans("SubledgerAccount"), 'checked'=>1),
-	't.piece_num'=>array('label'=>$langs->trans("TransactionNumShort"), 'checked'=>1),
-	't.code_journal'=>array('label'=>$langs->trans("Codejournal"), 'checked'=>1),
-	't.doc_date'=>array('label'=>$langs->trans("Docdate"), 'checked'=>1),
-	't.doc_ref'=>array('label'=>$langs->trans("Piece"), 'checked'=>1),
-	't.label_operation'=>array('label'=>$langs->trans("Label"), 'checked'=>1),
-	't.debit'=>array('label'=>$langs->trans("Debit"), 'checked'=>1),
-	't.credit'=>array('label'=>$langs->trans("Credit"), 'checked'=>1),
-	't.lettering_code'=>array('label'=>$langs->trans("LetteringCode"), 'checked'=>1),
-	't.date_export'=>array('label'=>$langs->trans("DateExport"), 'checked'=>1),
-	't.date_validated'=>array('label'=>$langs->trans("DateValidation"), 'checked'=>1),
-);
-
-if (empty($conf->global->ACCOUNTING_ENABLE_LETTERING)) {
-	unset($arrayfields['t.lettering_code']);
-}
-
-if ($search_date_start && empty($search_date_startyear)) {
-	$tmparray = dol_getdate($search_date_start);
-	$search_date_startyear = $tmparray['year'];
-	$search_date_startmonth = $tmparray['mon'];
-	$search_date_startday = $tmparray['mday'];
-}
-if ($search_date_end && empty($search_date_endyear)) {
-	$tmparray = dol_getdate($search_date_end);
-	$search_date_endyear = $tmparray['year'];
-	$search_date_endmonth = $tmparray['mon'];
-	$search_date_endday = $tmparray['mday'];
-}
-
-if (empty($conf->accounting->enabled)) {
-	accessforbidden();
-}
-if ($user->socid > 0) {
-	accessforbidden();
-}
-if (empty($user->rights->accounting->mouvements->lire)) {
-	accessforbidden();
-}
-
-
-/*
- * Action
- */
-
-if (GETPOST('cancel', 'alpha')) {
-	$action = 'list'; $massaction = '';
-}
-if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend') {
-	$massaction = '';
-}
-
-$parameters = array('socid'=>$socid);
-$reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
-if ($reshook < 0) {
-	setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
-}
-
-if (empty($reshook)) {
-	include DOL_DOCUMENT_ROOT.'/core/actions_changeselectedfields.inc.php';
-
-	if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All tests are required to be compatible with all browsers
-		$search_doc_date = '';
-		$search_accountancy_code = '';
-		$search_accountancy_code_start = '';
-		$search_accountancy_code_end = '';
-		$search_label_account = '';
-		$search_doc_ref = '';
-		$search_label_operation = '';
-		$search_mvt_num = '';
-		$search_direction = '';
-		$search_ledger_code = array();
-		$search_date_start = '';
-		$search_date_end = '';
-		$search_date_startyear = '';
-		$search_date_startmonth = '';
-		$search_date_startday = '';
-		$search_date_endyear = '';
-		$search_date_endmonth = '';
-		$search_date_endday = '';
-		$search_date_export_start = '';
-		$search_date_export_end = '';
-		$search_date_export_startyear = '';
-		$search_date_export_startmonth = '';
-		$search_date_export_startday = '';
-		$search_date_export_endyear = '';
-		$search_date_export_endmonth = '';
-		$search_date_export_endday = '';
-		$search_date_validation_start = '';
-		$search_date_validation_end = '';
-		$search_date_validation_startyear = '';
-		$search_date_validation_startmonth = '';
-		$search_date_validation_startday = '';
-		$search_date_validation_endyear = '';
-		$search_date_validation_endmonth = '';
-		$search_date_validation_endday = '';
-		$search_debit = '';
-		$search_credit = '';
-		$search_lettering_code = '';
-		$search_not_reconciled = '';
-	}
-
-	// Must be after the remove filter action, before the export.
-	$param = '';
-	$filter = array();
-
-	if (!empty($search_date_start)) {
-		$filter['t.doc_date>='] = $search_date_start;
-		$param .= '&search_date_startmonth='.$search_date_startmonth.'&search_date_startday='.$search_date_startday.'&search_date_startyear='.$search_date_startyear;
-	}
-	if (!empty($search_date_end)) {
-		$filter['t.doc_date<='] = $search_date_end;
-		$param .= '&search_date_endmonth='.$search_date_endmonth.'&search_date_endday='.$search_date_endday.'&search_date_endyear='.$search_date_endyear;
-	}
-	if (!empty($search_doc_date)) {
-		$filter['t.doc_date'] = $search_doc_date;
-		$param .= '&doc_datemonth='.GETPOST('doc_datemonth', 'int').'&doc_dateday='.GETPOST('doc_dateday', 'int').'&doc_dateyear='.GETPOST('doc_dateyear', 'int');
-	}
-	if (!empty($search_accountancy_code_start)) {
-		$filter['t.subledger_account>='] = $search_accountancy_code_start;
-		$param .= '&search_accountancy_code_start='.urlencode($search_accountancy_code_start);
-	}
-	if (!empty($search_accountancy_code_end)) {
-		$filter['t.subledger_account<='] = $search_accountancy_code_end;
-		$param .= '&search_accountancy_code_end='.urlencode($search_accountancy_code_end);
-	}
-	if (!empty($search_label_account)) {
-		$filter['t.label_compte'] = $search_label_account;
-		$param .= '&search_label_compte='.urlencode($search_label_account);
-	}
-	if (!empty($search_mvt_num)) {
-		$filter['t.piece_num'] = $search_mvt_num;
-		$param .= '&search_mvt_num='.urlencode($search_mvt_num);
-	}
-	if (!empty($search_doc_ref)) {
-		$filter['t.doc_ref'] = $search_doc_ref;
-		$param .= '&search_doc_ref='.urlencode($search_doc_ref);
-	}
-	if (!empty($search_label_operation)) {
-		$filter['t.label_operation'] = $search_label_operation;
-		$param .= '&search_label_operation='.urlencode($search_label_operation);
-	}
-	if (!empty($search_direction)) {
-		$filter['t.sens'] = $search_direction;
-		$param .= '&search_direction='.urlencode($search_direction);
-	}
-	if (!empty($search_ledger_code)) {
-		$filter['t.code_journal'] = $search_ledger_code;
-		foreach ($search_ledger_code as $code) {
-			$param .= '&search_ledger_code[]='.urlencode($code);
-		}
-	}
-	if (!empty($search_debit)) {
-		$filter['t.debit'] = $search_debit;
-		$param .= '&search_debit='.urlencode($search_debit);
-	}
-	if (!empty($search_credit)) {
-		$filter['t.credit'] = $search_credit;
-		$param .= '&search_credit='.urlencode($search_credit);
-	}
-	if (!empty($search_lettering_code)) {
-		$filter['t.lettering_code'] = $search_lettering_code;
-		$param .= '&search_lettering_code='.urlencode($search_lettering_code);
-	}
-	if (!empty($search_not_reconciled)) {
-		$filter['t.reconciled_option'] = $search_not_reconciled;
-		$param .= '&search_not_reconciled='.urlencode($search_not_reconciled);
-	}
-	if (!empty($search_date_export_start)) {
-		$filter['t.date_export>='] = $search_date_export_start;
-		$param .= '&search_date_export_startmonth='.$search_date_export_startmonth.'&search_date_export_startday='.$search_date_export_startday.'&search_date_export_startyear='.$search_date_export_startyear;
-	}
-	if (!empty($search_date_export_end)) {
-		$filter['t.date_export<='] = $search_date_export_end;
-		$param .= '&search_date_export_endmonth='.$search_date_export_endmonth.'&search_date_export_endday='.$search_date_export_endday.'&search_date_export_endyear='.$search_date_export_endyear;
-	}
-	if (!empty($search_date_validation_start)) {
-		$filter['t.date_validated>='] = $search_date_validation_start;
-		$param .= '&search_date_validation_startmonth='.$search_date_validation_startmonth.'&search_date_validation_startday='.$search_date_validation_startday.'&search_date_validation_startyear='.$search_date_validation_startyear;
-	}
-	if (!empty($search_date_validation_end)) {
-		$filter['t.date_validated<='] = $search_date_validation_end;
-		$param .= '&search_date_validation_endmonth='.$search_date_validation_endmonth.'&search_date_validation_endday='.$search_date_validation_endday.'&search_date_validation_endyear='.$search_date_validation_endyear;
-	}
-}
-
-if ($action == 'delbookkeeping' && $user->rights->accounting->mouvements->supprimer) {
-	$import_key = GETPOST('importkey', 'alpha');
-
-	if (!empty($import_key)) {
-		$result = $object->deleteByImportkey($import_key);
-		if ($result < 0) {
-			setEventMessages($object->error, $object->errors, 'errors');
-		}
-
-		// Make a redirect to avoid to launch the delete later after a back button
-		header("Location: ".$_SERVER["PHP_SELF"].($param ? '?'.$param : ''));
-		exit;
-	}
-}
-if ($action == 'delbookkeepingyearconfirm' && $user->rights->accounting->mouvements->supprimer_tous) {
-	$delmonth = GETPOST('delmonth', 'int');
-	$delyear = GETPOST('delyear', 'int');
-	if ($delyear == -1) {
-		$delyear = 0;
-	}
-	$deljournal = GETPOST('deljournal', 'alpha');
-	if ($deljournal == -1) {
-		$deljournal = 0;
-	}
-
-	if (!empty($delmonth) || !empty($delyear) || !empty($deljournal)) {
-		$result = $object->deleteByYearAndJournal($delyear, $deljournal, '', ($delmonth > 0 ? $delmonth : 0));
-		if ($result < 0) {
-			setEventMessages($object->error, $object->errors, 'errors');
-		} else {
-			setEventMessages("RecordDeleted", null, 'mesgs');
-		}
-
-		// Make a redirect to avoid to launch the delete later after a back button
-		header("Location: ".$_SERVER["PHP_SELF"].($param ? '?'.$param : ''));
-		exit;
-	} else {
-		setEventMessages("NoRecordDeleted", null, 'warnings');
-	}
-}
-if ($action == 'delmouvconfirm' && $user->rights->accounting->mouvements->supprimer) {
-	$mvt_num = GETPOST('mvt_num', 'int');
-
-	if (!empty($mvt_num)) {
-		$result = $object->deleteMvtNum($mvt_num);
-		if ($result < 0) {
-			setEventMessages($object->error, $object->errors, 'errors');
-		} else {
-			setEventMessages($langs->trans("RecordDeleted"), null, 'mesgs');
-		}
-
-		header("Location: ".$_SERVER["PHP_SELF"]."?noreset=1".($param ? '&'.$param : ''));
-		exit;
-	}
-}
-
-
-/*
- * View
- */
-
-$formaccounting = new FormAccounting($db);
-$formfile = new FormFile($db);
-$formother = new FormOther($db);
-$form = new Form($db);
-
-$title_page = $langs->trans("Operations").' - '.$langs->trans("VueByAccountAccounting").' ('.$langs->trans("BookkeepingSubAccount").')';
-
-llxHeader('', $title_page);
-
-// List
-$nbtotalofrecords = '';
-if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
-	$nbtotalofrecords = $object->fetchAllByAccount($sortorder, $sortfield, 0, 0, $filter, 'AND', 1);
-	if ($nbtotalofrecords < 0) {
-		setEventMessages($object->error, $object->errors, 'errors');
-	}
-}
-
-$result = $object->fetchAllByAccount($sortorder, $sortfield, $limit, $offset, $filter, 'AND', 1);
-
-if ($result < 0) {
-	setEventMessages($object->error, $object->errors, 'errors');
-}
-
-$num = count($object->lines);
-
-
-if ($action == 'delmouv') {
-	$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?mvt_num='.GETPOST('mvt_num'), $langs->trans('DeleteMvt'), $langs->trans('ConfirmDeleteMvtPartial'), 'delmouvconfirm', '', 0, 1);
-	print $formconfirm;
-}
-if ($action == 'delbookkeepingyear') {
-	$form_question = array();
-	$delyear = GETPOST('delyear', 'int');
-	$deljournal = GETPOST('deljournal', 'alpha');
-
-	if (empty($delyear)) {
-		$delyear = dol_print_date(dol_now(), '%Y');
-	}
-	$month_array = array();
-	for ($i = 1; $i <= 12; $i++) {
-		$month_array[$i] = $langs->trans("Month".sprintf("%02d", $i));
-	}
-	$year_array = $formaccounting->selectyear_accountancy_bookkepping($delyear, 'delyear', 0, 'array');
-	$journal_array = $formaccounting->select_journal($deljournal, 'deljournal', '', 1, 1, 1, '', 0, 1);
-
-	$form_question['delmonth'] = array(
-		'name' => 'delmonth',
-		'type' => 'select',
-		'label' => $langs->trans('DelMonth'),
-		'values' => $month_array,
-		'default' => ''
-	);
-	$form_question['delyear'] = array(
-		'name' => 'delyear',
-		'type' => 'select',
-		'label' => $langs->trans('DelYear'),
-		'values' => $year_array,
-		'default' => $delyear
-	);
-	$form_question['deljournal'] = array(
-		'name' => 'deljournal',
-		'type' => 'other', // We don't use select here, the journal_array is already a select html component
-		'label' => $langs->trans('DelJournal'),
-		'value' => $journal_array,
-		'default' => $deljournal
-	);
-
-	$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans('DeleteMvt'), $langs->trans('ConfirmDeleteMvt', $langs->transnoentitiesnoconv("RegistrationInAccounting")), 'delbookkeepingyearconfirm', $form_question, '', 1, 300);
-	print $formconfirm;
-}
-
-
-print '<form method="POST" id="searchFormList" action="'.$_SERVER["PHP_SELF"].'">';
-print '<input type="hidden" name="token" value="'.newToken().'">';
-print '<input type="hidden" name="action" value="list">';
-if ($optioncss != '') {
-	print '<input type="hidden" name="optioncss" value="'.$optioncss.'">';
-}
-print '<input type="hidden" name="formfilteraction" id="formfilteraction" value="list">';
-print '<input type="hidden" name="sortfield" value="'.$sortfield.'">';
-print '<input type="hidden" name="sortorder" value="'.$sortorder.'">';
-
-$parameters = array();
-$reshook = $hookmanager->executeHooks('addMoreActionsButtonsList', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
-if (empty($reshook)) {
-	$newcardbutton = dolGetButtonTitle($langs->trans('ViewFlatList'), '', 'fa fa-list paddingleft imgforviewmode', DOL_URL_ROOT.'/accountancy/bookkeeping/list.php?'.$param);
-	$newcardbutton .= dolGetButtonTitle($langs->trans('GroupByAccountAccounting'), '', 'fa fa-stream paddingleft imgforviewmode', DOL_URL_ROOT.'/accountancy/bookkeeping/listbyaccount.php?'.$param, '', 1, array('morecss' => 'marginleftonly'));
-	$newcardbutton .= dolGetButtonTitle($langs->trans('GroupBySubAccountAccounting'), '', 'fa fa-align-left vmirror paddingleft imgforviewmode', DOL_URL_ROOT.'/accountancy/bookkeeping/listbysubaccount.php?'.$param, '', 1, array('morecss' => 'marginleftonly btnTitleSelected'));
-	$newcardbutton .= dolGetButtonTitle($langs->trans('NewAccountingMvt'), '', 'fa fa-plus-circle paddingleft', DOL_URL_ROOT.'/accountancy/bookkeeping/card.php?action=create');
-}
-
-if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) {
-	$param .= '&contextpage='.urlencode($contextpage);
-}
-if ($limit > 0 && $limit != $conf->liste_limit) {
-	$param .= '&limit='.urlencode($limit);
-}
-
-print_barre_liste($title_page, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $result, $nbtotalofrecords, 'title_accountancy', 0, $newcardbutton, '', $limit, 0, 0, 1);
-
-print info_admin($langs->trans("WarningRecordWithoutSubledgerAreExcluded"));
-
-$varpage = empty($contextpage) ? $_SERVER["PHP_SELF"] : $contextpage;
-$selectedfields = $form->multiSelectArrayWithCheckbox('selectedfields', $arrayfields, $varpage); // This also change content of $arrayfields
-if ($massactionbutton) {
-	$selectedfields .= $form->showCheckAddButtons('checkforselect', 1);
-}
-
-// Reverse sort order
-if (preg_match('/^asc/i', $sortorder)) {
-	$sortorder = "asc";
-} else {
-	$sortorder = "desc";
-}
-
-$moreforfilter = '';
-
-// Accountancy account
-$moreforfilter .= '<div class="divsearchfield">';
-$moreforfilter .= $langs->trans('AccountAccounting').': ';
-$moreforfilter .= '<div class="nowrap inline-block">';
-$moreforfilter .= $formaccounting->select_auxaccount($search_accountancy_code_start, 'search_accountancy_code_start', $langs->trans('From'), 'maxwidth200');
-$moreforfilter .= ' ';
-$moreforfilter .= $formaccounting->select_auxaccount($search_accountancy_code_end, 'search_accountancy_code_end', $langs->trans('to'), 'maxwidth200');
-$moreforfilter .= '</div>';
-$moreforfilter .= '</div>';
-
-$parameters = array();
-$reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters); // Note that $action and $object may have been modified by hook
-if (empty($reshook)) {
-	$moreforfilter .= $hookmanager->resPrint;
-} else {
-	$moreforfilter = $hookmanager->resPrint;
-}
-
-print '<div class="liste_titre liste_titre_bydiv centpercent">';
-print $moreforfilter;
-print '</div>';
-
-print '<div class="div-table-responsive">';
-print '<table class="tagtable liste centpercent">';
-
-// Filters lines
-print '<tr class="liste_titre_filter">';
-
-// Movement number
-if (!empty($arrayfields['t.piece_num']['checked'])) {
-	print '<td class="liste_titre"><input type="text" name="search_mvt_num" size="6" value="'.dol_escape_htmltag($search_mvt_num).'"></td>';
-}
-// Code journal
-if (!empty($arrayfields['t.code_journal']['checked'])) {
-	print '<td class="liste_titre center">';
-	print $formaccounting->multi_select_journal($search_ledger_code, 'search_ledger_code', 0, 1, 1, 1);
-	print '</td>';
-}
-// Date document
-if (!empty($arrayfields['t.doc_date']['checked'])) {
-	print '<td class="liste_titre center">';
-	print '<div class="nowrap">';
-	print $form->selectDate($search_date_start, 'search_date_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("From"));
-	print '</div>';
-	print '<div class="nowrap">';
-	print $form->selectDate($search_date_end, 'search_date_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("to"));
-	print '</div>';
-	print '</td>';
-}
-// Ref document
-if (!empty($arrayfields['t.doc_ref']['checked'])) {
-	print '<td class="liste_titre"><input type="text" size="7" class="flat" name="search_doc_ref" value="'.dol_escape_htmltag($search_doc_ref).'"/></td>';
-}
-// Label operation
-if (!empty($arrayfields['t.label_operation']['checked'])) {
-	print '<td class="liste_titre"><input type="text" size="7" class="flat" name="search_label_operation" value="'.dol_escape_htmltag($search_label_operation).'"/></td>';
-}
-// Debit
-if (!empty($arrayfields['t.debit']['checked'])) {
-	print '<td class="liste_titre right"><input type="text" class="flat" name="search_debit" size="4" value="'.dol_escape_htmltag($search_debit).'"></td>';
-}
-// Credit
-if (!empty($arrayfields['t.credit']['checked'])) {
-	print '<td class="liste_titre right"><input type="text" class="flat" name="search_credit" size="4" value="'.dol_escape_htmltag($search_credit).'"></td>';
-}
-// Lettering code
-if (!empty($arrayfields['t.lettering_code']['checked'])) {
-	print '<td class="liste_titre center">';
-	print '<input type="text" size="3" class="flat" name="search_lettering_code" value="'.$search_lettering_code.'"/>';
-	print '<br><span class="nowrap"><input type="checkbox" name="search_not_reconciled" value="notreconciled"'.($search_not_reconciled == 'notreconciled' ? ' checked' : '').'>'.$langs->trans("NotReconciled").'</span>';
-	print '</td>';
-}
-// Date export
-if (!empty($arrayfields['t.date_export']['checked'])) {
-	print '<td class="liste_titre center">';
-	print '<div class="nowrap">';
-	print $form->selectDate($search_date_export_start, 'search_date_export_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("From"));
-	print '</div>';
-	print '<div class="nowrap">';
-	print $form->selectDate($search_date_export_end, 'search_date_export_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("to"));
-	print '</div>';
-	print '</td>';
-}
-// Date validation
-if (!empty($arrayfields['t.date_validated']['checked'])) {
-	print '<td class="liste_titre center">';
-	print '<div class="nowrap">';
-	print $form->selectDate($search_date_validation_start, 'search_date_validation_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("From"));
-	print '</div>';
-	print '<div class="nowrap">';
-	print $form->selectDate($search_date_validation_end, 'search_date_validation_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("to"));
-	print '</div>';
-	print '</td>';
-}
-
-// Fields from hook
-$parameters = array('arrayfields'=>$arrayfields);
-$reshook = $hookmanager->executeHooks('printFieldListOption', $parameters); // Note that $action and $object may have been modified by hook
-print $hookmanager->resPrint;
-
-// Action column
-print '<td class="liste_titre center">';
-$searchpicto = $form->showFilterButtons();
-print $searchpicto;
-print '</td>';
-print "</tr>\n";
-
-print '<tr class="liste_titre">';
-if (!empty($arrayfields['t.piece_num']['checked'])) {
-	print_liste_field_titre($arrayfields['t.piece_num']['label'], $_SERVER['PHP_SELF'], "t.piece_num", "", $param, '', $sortfield, $sortorder);
-}
-if (!empty($arrayfields['t.code_journal']['checked'])) {
-	print_liste_field_titre($arrayfields['t.code_journal']['label'], $_SERVER['PHP_SELF'], "t.code_journal", "", $param, '', $sortfield, $sortorder, 'center ');
-}
-if (!empty($arrayfields['t.doc_date']['checked'])) {
-	print_liste_field_titre($arrayfields['t.doc_date']['label'], $_SERVER['PHP_SELF'], "t.doc_date", "", $param, '', $sortfield, $sortorder, 'center ');
-}
-if (!empty($arrayfields['t.doc_ref']['checked'])) {
-	print_liste_field_titre($arrayfields['t.doc_ref']['label'], $_SERVER['PHP_SELF'], "t.doc_ref", "", $param, "", $sortfield, $sortorder);
-}
-if (!empty($arrayfields['t.label_operation']['checked'])) {
-	print_liste_field_titre($arrayfields['t.label_operation']['label'], $_SERVER['PHP_SELF'], "t.label_operation", "", $param, "", $sortfield, $sortorder);
-}
-if (!empty($arrayfields['t.debit']['checked'])) {
-	print_liste_field_titre($arrayfields['t.debit']['label'], $_SERVER['PHP_SELF'], "t.debit", "", $param, '', $sortfield, $sortorder, 'right ');
-}
-if (!empty($arrayfields['t.credit']['checked'])) {
-	print_liste_field_titre($arrayfields['t.credit']['label'], $_SERVER['PHP_SELF'], "t.credit", "", $param, '', $sortfield, $sortorder, 'right ');
-}
-if (!empty($arrayfields['t.lettering_code']['checked'])) {
-	print_liste_field_titre($arrayfields['t.lettering_code']['label'], $_SERVER['PHP_SELF'], "t.lettering_code", "", $param, '', $sortfield, $sortorder, 'center ');
-}
-if (!empty($arrayfields['t.date_export']['checked'])) {
-	print_liste_field_titre($arrayfields['t.date_export']['label'], $_SERVER['PHP_SELF'], "t.date_export", "", $param, '', $sortfield, $sortorder, 'center ');
-}
-if (!empty($arrayfields['t.date_validated']['checked'])) {
-	print_liste_field_titre($arrayfields['t.date_validated']['label'], $_SERVER['PHP_SELF'], "t.date_validated", "", $param, '', $sortfield, $sortorder, 'center ');
-}
-// Hook fields
-$parameters = array('arrayfields'=>$arrayfields, 'param'=>$param, 'sortfield'=>$sortfield, 'sortorder'=>$sortorder);
-$reshook = $hookmanager->executeHooks('printFieldListTitle', $parameters); // Note that $action and $object may have been modified by hook
-print $hookmanager->resPrint;
-print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ');
-print "</tr>\n";
-
-
-$total_debit = 0;
-$total_credit = 0;
-$sous_total_debit = 0;
-$sous_total_credit = 0;
-$displayed_account_number = null; // Start with undefined to be able to distinguish with empty
-
-// Loop on record
-// --------------------------------------------------------------------
-$i = 0;
-$totalarray = array();
-while ($i < min($num, $limit)) {
-	$line = $object->lines[$i];
-
-	$total_debit += $line->debit;
-	$total_credit += $line->credit;
-
-	$accountg = length_accounta($line->subledger_account);
-	//if (empty($accountg)) $accountg = '-';
-
-	$colspan = 0;			// colspan before field 'label of operation'
-	$colspanend = 3;		// colspan after debit/credit
-	if (!empty($arrayfields['t.piece_num']['checked'])) { $colspan++; }
-	if (!empty($arrayfields['t.code_journal']['checked'])) { $colspan++; }
-	if (!empty($arrayfields['t.doc_date']['checked'])) { $colspan++; }
-	if (!empty($arrayfields['t.doc_ref']['checked'])) { $colspan++; }
-	if (!empty($arrayfields['t.label_operation']['checked'])) { $colspan++; }
-	if (!empty($arrayfields['t.date_export']['checked'])) { $colspanend++; }
-	if (!empty($arrayfields['t.date_validating']['checked'])) { $colspanend++; }
-	if (!empty($arrayfields['t.lettering_code']['checked'])) { $colspanend++; }
-
-	// Is it a break ?
-	if ($accountg != $displayed_account_number || !isset($displayed_account_number)) {
-		// Show a subtotal by accounting account
-		if (isset($displayed_account_number)) {
-			print '<tr class="liste_total">';
-			print '<td class="right" colspan="'.$colspan.'">'.$langs->trans("TotalForAccount").' '.length_accounta($displayed_account_number).':</td>';
-			print '<td class="nowrap right">'.price($sous_total_debit).'</td>';
-			print '<td class="nowrap right">'.price($sous_total_credit).'</td>';
-			print '<td colspan="'.$colspanend.'"></td>';
-			print '</tr>';
-			// Show balance of last shown account
-			$balance = $sous_total_debit - $sous_total_credit;
-			print '<tr class="liste_total">';
-			print '<td class="right" colspan="'.$colspan.'">'.$langs->trans("Balance").':</td>';
-			if ($balance > 0) {
-				print '<td class="nowraponall right">';
-				print price($sous_total_debit - $sous_total_credit);
-				print '</td>';
-				print '<td></td>';
-			} else {
-				print '<td></td>';
-				print '<td class="nowraponall right">';
-				print price($sous_total_credit - $sous_total_debit);
-				print '</td>';
-			}
-			print '<td colspan="'.$colspanend.'"></td>';
-			print '</tr>';
-		}
-
-		// Show the break account
-		print '<tr class="trforbreak">';
-		print '<td colspan="'.($totalarray['nbfield'] ? $totalarray['nbfield'] : 10).'" class="tdforbreak">';
-		if ($line->subledger_account != "" && $line->subledger_account != '-1') {
-			print $line->subledger_label.' : '.length_accounta($line->subledger_account);
-		} else {
-			// Should not happen: subledger account must be null or a non empty value
-			print '<span class="error">'.$langs->trans("Unknown");
-			if ($line->subledger_label) {
-				print ' ('.$line->subledger_label.')';
-				$htmltext = 'EmptyStringForSubledgerAccountButSubledgerLabelDefined';
-			} else {
-				$htmltext = 'EmptyStringForSubledgerAccountAndSubledgerLabel';
-			}
-			print $form->textwithpicto('', $htmltext);
-			print '</span>';
-		}
-		print '</td>';
-		print '</tr>';
-
-		$displayed_account_number = $accountg;
-		//if (empty($displayed_account_number)) $displayed_account_number='-';
-		$sous_total_debit = 0;
-		$sous_total_credit = 0;
-
-		$colspan = 0;
-	}
-
-	print '<tr class="oddeven">';
-
-	// Piece number
-	if (!empty($arrayfields['t.piece_num']['checked'])) {
-		print '<td>';
-		$object->id = $line->id;
-		$object->piece_num = $line->piece_num;
-		print $object->getNomUrl(1, '', 0, '', 1);
-		print '</td>';
-		if (!$i) {
-			$totalarray['nbfield']++;
-		}
-	}
-
-	// Journal code
-	if (!empty($arrayfields['t.code_journal']['checked'])) {
-		$accountingjournal = new AccountingJournal($db);
-		$result = $accountingjournal->fetch('', $line->code_journal);
-		$journaltoshow = (($result > 0) ? $accountingjournal->getNomUrl(0, 0, 0, '', 0) : $line->code_journal);
-		print '<td class="center">'.$journaltoshow.'</td>';
-		if (!$i) {
-			$totalarray['nbfield']++;
-		}
-	}
-
-	// Document date
-	if (!empty($arrayfields['t.doc_date']['checked'])) {
-		print '<td class="center">'.dol_print_date($line->doc_date, 'day').'</td>';
-		if (!$i) {
-			$totalarray['nbfield']++;
-		}
-	}
-
-	// Document ref
-	if (!empty($arrayfields['t.doc_ref']['checked'])) {
-		if ($line->doc_type == 'customer_invoice') {
-			$langs->loadLangs(array('bills'));
-
-			require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
-			$objectstatic = new Facture($db);
-			$objectstatic->fetch($line->fk_doc);
-			//$modulepart = 'facture';
-
-			$filename = dol_sanitizeFileName($line->doc_ref);
-			$filedir = $conf->facture->dir_output.'/'.dol_sanitizeFileName($line->doc_ref);
-			$urlsource = $_SERVER['PHP_SELF'].'?id='.$objectstatic->id;
-			$documentlink = $formfile->getDocumentsLink($objectstatic->element, $filename, $filedir);
-		} elseif ($line->doc_type == 'supplier_invoice') {
-			$langs->loadLangs(array('bills'));
-
-			require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
-			$objectstatic = new FactureFournisseur($db);
-			$objectstatic->fetch($line->fk_doc);
-			//$modulepart = 'invoice_supplier';
-
-			$filename = dol_sanitizeFileName($line->doc_ref);
-			$filedir = $conf->fournisseur->facture->dir_output.'/'.get_exdir($line->fk_doc, 2, 0, 0, $objectstatic, $modulepart).dol_sanitizeFileName($line->doc_ref);
-			$subdir = get_exdir($objectstatic->id, 2, 0, 0, $objectstatic, $modulepart).dol_sanitizeFileName($line->doc_ref);
-			$documentlink = $formfile->getDocumentsLink($objectstatic->element, $subdir, $filedir);
-		} elseif ($line->doc_type == 'expense_report') {
-			$langs->loadLangs(array('trips'));
-
-			require_once DOL_DOCUMENT_ROOT.'/expensereport/class/expensereport.class.php';
-			$objectstatic = new ExpenseReport($db);
-			$objectstatic->fetch($line->fk_doc);
-			//$modulepart = 'expensereport';
-
-			$filename = dol_sanitizeFileName($line->doc_ref);
-			$filedir = $conf->expensereport->dir_output.'/'.dol_sanitizeFileName($line->doc_ref);
-			$urlsource = $_SERVER['PHP_SELF'].'?id='.$objectstatic->id;
-			$documentlink = $formfile->getDocumentsLink($objectstatic->element, $filename, $filedir);
-		} elseif ($line->doc_type == 'bank') {
-			require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
-			$objectstatic = new AccountLine($db);
-			$objectstatic->fetch($line->fk_doc);
-		} else {
-			// Other type
-		}
-
-		print '<td class="maxwidth400">';
-
-		print '<table class="nobordernopadding"><tr class="nocellnopadd">';
-		// Picto + Ref
-		print '<td class="nobordernopadding">';
-
-		if ($line->doc_type == 'customer_invoice' || $line->doc_type == 'supplier_invoice' || $line->doc_type == 'expense_report') {
-			print $objectstatic->getNomUrl(1, '', 0, 0, '', 0, -1, 1);
-			print $documentlink;
-		} elseif ($line->doc_type == 'bank') {
-			print $objectstatic->getNomUrl(1);
-			$bank_ref = strstr($line->doc_ref, '-');
-			print " " . $bank_ref;
-		} else {
-			print $line->doc_ref;
-		}
-		print '</td></tr></table>';
-
-		print "</td>\n";
-		if (!$i) {
-			$totalarray['nbfield']++;
-		}
-	}
-
-	// Label operation
-	if (!empty($arrayfields['t.label_operation']['checked'])) {
-		// Affiche un lien vers la facture client/fournisseur
-		$doc_ref = preg_replace('/\(.*\)/', '', $line->doc_ref);
-		print strlen(length_accounta($line->subledger_account)) == 0 ? '<td>'.$line->label_operation.'</td>' : '<td>'.$line->label_operation.'<br><span style="font-size:0.8em">('.length_accounta($line->subledger_account).')</span></td>';
-		if (!$i) {
-			$totalarray['nbfield']++;
-		}
-	}
-
-	// Amount debit
-	if (!empty($arrayfields['t.debit']['checked'])) {
-		print '<td class="right nowraponall amount">'.($line->debit ? price($line->debit) : '').'</td>';
-		if (!$i) {
-			$totalarray['nbfield']++;
-		}
-		if (!$i) {
-			$totalarray['pos'][$totalarray['nbfield']] = 'totaldebit';
-		}
-		$totalarray['val']['totaldebit'] += $line->debit;
-	}
-
-	// Amount credit
-	if (!empty($arrayfields['t.credit']['checked'])) {
-		print '<td class="right nowraponall amount">'.($line->credit ? price($line->credit) : '').'</td>';
-		if (!$i) {
-			$totalarray['nbfield']++;
-		}
-		if (!$i) {
-			$totalarray['pos'][$totalarray['nbfield']] = 'totalcredit';
-		}
-		$totalarray['val']['totalcredit'] += $line->credit;
-	}
-
-	// Lettering code
-	if (!empty($arrayfields['t.lettering_code']['checked'])) {
-		print '<td class="center">'.$line->lettering_code.'</td>';
-		if (!$i) {
-			$totalarray['nbfield']++;
-		}
-	}
-
-	// Exported operation date
-	if (!empty($arrayfields['t.date_export']['checked'])) {
-		print '<td class="center">'.dol_print_date($line->date_export, 'dayhour').'</td>';
-		if (!$i) {
-			$totalarray['nbfield']++;
-		}
-	}
-
-	// Validated operation date
-	if (!empty($arrayfields['t.date_validated']['checked'])) {
-		print '<td class="center">'.dol_print_date($line->date_validation, 'dayhour').'</td>';
-		if (!$i) {
-			$totalarray['nbfield']++;
-		}
-	}
-
-	// Fields from hook
-	$parameters = array('arrayfields'=>$arrayfields, 'obj'=>$obj);
-	$reshook = $hookmanager->executeHooks('printFieldListValue', $parameters); // Note that $action and $object may have been modified by hook
-	print $hookmanager->resPrint;
-
-	// Action column
-	print '<td class="nowraponall center">';
-	if (empty($line->date_export) && empty($line->date_validation)) {
-		if ($user->rights->accounting->mouvements->creer) {
-			print '<a class="editfielda paddingleft marginrightonly" href="' . DOL_URL_ROOT . '/accountancy/bookkeeping/card.php?piece_num=' . $line->piece_num . $param . '&page=' . $page . ($sortfield ? '&sortfield=' . $sortfield : '') . ($sortorder ? '&sortorder=' . $sortorder : '') . '">' . img_edit() . '</a>';
-		}
-	}
-	if (empty($line->date_validation)) {
-		if ($user->rights->accounting->mouvements->supprimer) {
-			print '<a class="reposition paddingleft marginrightonly" href="'.$_SERVER['PHP_SELF'].'?action=delmouv&token='.newToken().'&mvt_num='.$line->piece_num.$param.'&page='.$page.($sortfield ? '&sortfield='.$sortfield : '').($sortorder ? '&sortorder='.$sortorder : '').'">'.img_delete().'</a>';
-		}
-	}
-	print '</td>';
-	if (!$i) {
-		$totalarray['nbfield']++;
-	}
-
-	// Comptabilise le sous-total
-	$sous_total_debit += $line->debit;
-	$sous_total_credit += $line->credit;
-
-	print "</tr>\n";
-
-	$i++;
-}
-
-if ($num > 0 && $colspan > 0) {
-	print '<tr class="liste_total">';
-	print '<td class="right" colspan="'.$colspan.'">'.$langs->trans("TotalForAccount").' '.$accountg.':</td>';
-	print '<td class="nowrap right">'.price($sous_total_debit).'</td>';
-	print '<td class="nowrap right">'.price($sous_total_credit).'</td>';
-	print '<td colspan="'.$colspanend.'"></td>';
-	print '</tr>';
-	// Show balance of last shown account
-	$balance = $sous_total_debit - $sous_total_credit;
-	print '<tr class="liste_total">';
-	print '<td class="right" colspan="'.$colspan.'">'.$langs->trans("Balance").':</td>';
-	if ($balance > 0) {
-		print '<td class="nowraponall right">';
-		print price($sous_total_debit - $sous_total_credit);
-		print '</td>';
-		print '<td></td>';
-	} else {
-		print '<td></td>';
-		print '<td class="nowraponall right">';
-		print price($sous_total_credit - $sous_total_debit);
-		print '</td>';
-	}
-	print '<td colspan="'.$colspanend.'"></td>';
-	print '</tr>';
-}
-
-// Show total line
-include DOL_DOCUMENT_ROOT.'/core/tpl/list_print_total.tpl.php';
-
-
-print "</table>";
-print '</div>';
-
-// TODO Replace this with mass delete action
-if ($user->rights->accounting->mouvements->supprimer_tous) {
-	print '<div class="tabsAction tabsActionNoBottom">'."\n";
-	print '<a class="butActionDelete" name="button_delmvt" href="'.$_SERVER["PHP_SELF"].'?action=delbookkeepingyear&token='.newToken().($param ? '&'.$param : '').'">'.$langs->trans("DeleteMvt").'</a>';
-	print '</div>';
-}
-
-print '</form>';
-
-// End of page
-llxFooter();
-$db->close();

+ 10 - 0
htdocs/accountancy/class/accountancyexport.class.php

@@ -980,6 +980,8 @@ class AccountancyExport
 				print dol_string_unaccent($date_creation) . $separator;
 
 				// FEC:EcritureLib
+				// Clean label operation to prevent problem on export with tab separator & other character
+				$line->label_operation = str_replace(array("\t", "\n", "\r"), " ", $line->label_operation);
 				print dol_string_unaccent($line->label_operation) . $separator;
 
 				// FEC:Debit
@@ -1007,6 +1009,8 @@ class AccountancyExport
 				print $date_limit_payment . $separator;
 
 				// FEC_suppl:NumFacture
+				// Clean ref invoice to prevent problem on export with tab separator & other character
+				$refInvoice = str_replace(array("\t", "\n", "\r"), " ", $refInvoice);
 				print dol_trunc(self::toAnsi($refInvoice), 17, 'right', 'UTF-8', 1);
 
 				print $end_line;
@@ -1107,6 +1111,8 @@ class AccountancyExport
 				print $date_document . $separator;
 
 				// FEC:EcritureLib
+				// Clean label operation to prevent problem on export with tab separator & other character
+				$line->label_operation = str_replace(array("\t", "\n", "\r"), " ", $line->label_operation);
 				print dol_string_unaccent($line->label_operation) . $separator;
 
 				// FEC:Debit
@@ -1134,6 +1140,8 @@ class AccountancyExport
 				print $date_limit_payment . $separator;
 
 				// FEC_suppl:NumFacture
+				// Clean ref invoice to prevent problem on export with tab separator & other character
+				$refInvoice = str_replace(array("\t", "\n", "\r"), " ", $refInvoice);
 				print dol_trunc(self::toAnsi($refInvoice), 17, 'right', 'UTF-8', 1);
 
 
@@ -1712,6 +1720,8 @@ class AccountancyExport
 
 			print self::trunc($line->label_compte, 60).$separator; //Account label
 			print self::trunc($line->doc_ref, 20).$separator; //Piece
+			// Clean label operation to prevent problem on export with tab separator & other character
+			$line->label_operation = str_replace(array("\t", "\n", "\r"), " ", $line->label_operation);
 			print self::trunc($line->label_operation, 60).$separator; //Operation label
 			print price(abs($line->debit - $line->credit)).$separator; //Amount
 			print $line->sens.$separator; //Direction

+ 3 - 1
htdocs/accountancy/class/bookkeeping.class.php

@@ -852,7 +852,8 @@ class BookKeeping extends CommonObject
 		$sql .= " t.piece_num,";
 		$sql .= " t.date_creation,";
 		$sql .= " t.date_export,";
-		$sql .= " t.date_validated as date_validation";
+		$sql .= " t.date_validated as date_validation,";
+		$sql .= " t.import_key";
 		// Manage filter
 		$sqlwhere = array();
 		if (count($filter) > 0) {
@@ -947,6 +948,7 @@ class BookKeeping extends CommonObject
 				$line->date_creation = $this->db->jdate($obj->date_creation);
 				$line->date_export = $this->db->jdate($obj->date_export);
 				$line->date_validation = $this->db->jdate($obj->date_validation);
+				$line->import_key = $obj->import_key;
 
 				$this->lines[] = $line;
 

+ 453 - 10
htdocs/accountancy/class/lettering.class.php

@@ -33,6 +33,12 @@ include_once DOL_DOCUMENT_ROOT."/core/lib/date.lib.php";
  */
 class Lettering extends BookKeeping
 {
+	/**
+	 * @var BookKeeping[] 	Bookkeeping cached
+	 */
+	public static $bookkeeping_cached = array();
+
+
 	/**
 	 * letteringThirdparty
 	 *
@@ -119,6 +125,7 @@ class Lettering extends BookKeeping
 							$ids[$obj2->rowid] = $obj2->rowid;
 							$ids_fact[] = $obj2->fact_id;
 						}
+						$this->db->free($resql2);
 					} else {
 						$this->errors[] = $this->db->lasterror;
 						return -1;
@@ -146,6 +153,7 @@ class Lettering extends BookKeeping
 							while ($obj2 = $this->db->fetch_object($resql2)) {
 								$ids[$obj2->rowid] = $obj2->rowid;
 							}
+							$this->db->free($resql2);
 						} else {
 							$this->errors[] = $this->db->lasterror;
 							return -1;
@@ -205,6 +213,7 @@ class Lettering extends BookKeeping
 							while ($obj2 = $this->db->fetch_object($resql2)) {
 								$ids[$obj2->rowid] = $obj2->rowid;
 							}
+							$this->db->free($resql2);
 						} else {
 							$this->errors[] = $this->db->lasterror;
 							return -1;
@@ -216,6 +225,7 @@ class Lettering extends BookKeeping
 					$result = $this->updateLettering($ids);
 				}
 			}
+			$this->db->free($resql);
 		}
 		if ($error) {
 			foreach ($this->errors as $errmsg) {
@@ -230,17 +240,31 @@ class Lettering extends BookKeeping
 
 	/**
 	 *
-	 * @param array $ids ids array
-	 * @param boolean $notrigger no trigger
-	 * @return number
+	 * @param	array		$ids			ids array
+	 * @param	boolean		$notrigger		no trigger
+	 * @return	int
 	 */
 	public function updateLettering($ids = array(), $notrigger = false)
 	{
 		$error = 0;
 		$lettre = 'AAA';
 
-		$sql = "SELECT DISTINCT lettering_code FROM ".MAIN_DB_PREFIX."accounting_bookkeeping WHERE ";
-		$sql .= " lettering_code != '' ORDER BY lettering_code DESC limit 1";
+		$sql = "SELECT DISTINCT ab2.lettering_code";
+		$sql .=	" FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping As ab";
+		$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "bank_url AS bu ON bu.fk_bank = ab.fk_doc";
+		$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "bank_url AS bu2 ON bu2.url_id = bu.url_id";
+		$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "accounting_bookkeeping AS ab2 ON ab2.fk_doc = bu2.fk_bank";
+		$sql .= " WHERE ab.rowid IN (" . $this->db->sanitize(implode(',', $ids)) . ")";
+		$sql .= " AND ab.doc_type = 'bank'";
+		$sql .= " AND ab2.doc_type = 'bank'";
+		$sql .= " AND bu.type = 'company'";
+		$sql .= " AND bu2.type = 'company'";
+		$sql .= " AND ab.subledger_account != ''";
+		$sql .= " AND ab2.subledger_account != ''";
+		$sql .= " AND ab.lettering_code IS NULL";
+		$sql .= " AND ab2.lettering_code != ''";
+		$sql .= " ORDER BY ab2.lettering_code DESC";
+		$sql .= " LIMIT 1 ";
 
 		$result = $this->db->query($sql);
 		if ($result) {
@@ -249,13 +273,14 @@ class Lettering extends BookKeeping
 			if (!empty($obj->lettering_code)) {
 				$lettre++;
 			}
+			$this->db->free($result);
 		} else {
 			$this->errors[] = 'Error'.$this->db->lasterror();
 			$error++;
 		}
 
 		$sql = "SELECT SUM(ABS(debit)) as deb, SUM(ABS(credit)) as cred FROM ".MAIN_DB_PREFIX."accounting_bookkeeping WHERE ";
-		$sql .= " rowid IN (".$this->db->sanitize(implode(',', $ids)).") AND date_validated IS NULL";
+		$sql .= " rowid IN (".$this->db->sanitize(implode(',', $ids)).") AND lettering_code IS NULL AND subledger_account != ''";
 		$result = $this->db->query($sql);
 		if ($result) {
 			$obj = $this->db->fetch_object($result);
@@ -263,6 +288,7 @@ class Lettering extends BookKeeping
 				$this->errors[] = 'Total not exacts '.round(abs($obj->deb), 2).' vs '.round(abs($obj->cred), 2);
 				$error++;
 			}
+			$this->db->free($result);
 		} else {
 			$this->errors[] = 'Erreur sql'.$this->db->lasterror();
 			$error++;
@@ -276,8 +302,7 @@ class Lettering extends BookKeeping
 			$sql = "UPDATE ".MAIN_DB_PREFIX."accounting_bookkeeping SET";
 			$sql .= " lettering_code='".$this->db->escape($lettre)."'";
 			$sql .= " , date_lettering = '".$this->db->idate($now)."'"; // todo correct date it's false
-			$sql .= "  WHERE rowid IN (".$this->db->sanitize(implode(',', $ids)).") AND date_validated IS NULL ";
-			$this->db->begin();
+			$sql .= "  WHERE rowid IN (".$this->db->sanitize(implode(',', $ids)).") AND lettering_code IS NULL AND subledger_account != ''";
 
 			dol_syslog(get_class($this)."::update", LOG_DEBUG);
 			$resql = $this->db->query($sql);
@@ -293,11 +318,429 @@ class Lettering extends BookKeeping
 				dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
 				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
 			}
-			$this->db->rollback();
 			return -1 * $error;
 		} else {
-			$this->db->commit();
 			return 1;
 		}
 	}
+
+	/**
+	 *
+	 * @param	array		$ids			ids array
+	 * @param	boolean		$notrigger		no trigger
+	 * @return	int
+	 */
+	public function deleteLettering($ids, $notrigger = false)
+	{
+		$error = 0;
+
+		$sql = "UPDATE ".MAIN_DB_PREFIX."accounting_bookkeeping SET";
+		$sql .= " lettering_code = NULL";
+		$sql .= " , date_lettering = NULL";
+		$sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $ids)).")";
+		$sql .= " AND subledger_account != ''";
+
+		dol_syslog(get_class($this)."::update", LOG_DEBUG);
+		$resql = $this->db->query($sql);
+		if (!$resql) {
+			$error++;
+			$this->errors[] = "Error ".$this->db->lasterror();
+		}
+
+		// Commit or rollback
+		if ($error) {
+			foreach ($this->errors as $errmsg) {
+				dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
+				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
+			}
+			return -1 * $error;
+		} else {
+			return 1;
+		}
+	}
+
+	/**
+	 * Lettering bookkeeping lines all types
+	 *
+	 * @param	array		$bookkeeping_ids		Lettering specific list of bookkeeping id
+	 * @param	bool		$unlettering			Do unlettering
+	 * @return	int									<0 if error (nb lettered = result -1), 0 if noting to lettering, >0 if OK (nb lettered)
+	 */
+	public function bookkeepingLetteringAll($bookkeeping_ids, $unlettering = false)
+	{
+		dol_syslog(__METHOD__ . " - ", LOG_DEBUG);
+
+		$error = 0;
+		$errors = array();
+		$nb_lettering = 0;
+
+		$result = $this->bookkeepingLettering($bookkeeping_ids, 'customer_invoice', $unlettering);
+		if ($result < 0) {
+			$error++;
+			$errors = array_merge($errors, $this->errors);
+			$nb_lettering += abs($result) - 2;
+		} else {
+			$nb_lettering += $result;
+		}
+
+		$result = $this->bookkeepingLettering($bookkeeping_ids, 'supplier_invoice', $unlettering);
+		if ($result < 0) {
+			$error++;
+			$errors = array_merge($errors, $this->errors);
+			$nb_lettering += abs($result) - 2;
+		} else {
+			$nb_lettering += $result;
+		}
+
+		if ($error) {
+			$this->errors = $errors;
+			return -2 - $nb_lettering;
+		} else {
+			return $nb_lettering;
+		}
+	}
+
+	/**
+	 * Lettering bookkeeping lines
+	 *
+	 * @param	array		$bookkeeping_ids		Lettering specific list of bookkeeping id
+	 * @param	string		$type					Type of bookkeeping type to lettering ('customer_invoice' or 'supplier_invoice')
+	 * @param	bool		$unlettering			Do unlettering
+	 * @return	int									<0 if error (nb lettered = result -1), 0 if noting to lettering, >0 if OK (nb lettered)
+	 */
+	public function bookkeepingLettering($bookkeeping_ids, $type = 'customer_invoice', $unlettering = false)
+	{
+		global $langs;
+
+		$this->errors = array();
+
+		// Clean parameters
+		$bookkeeping_ids = is_array($bookkeeping_ids) ? $bookkeeping_ids : array();
+		$type = trim($type);
+
+		$error = 0;
+		$nb_lettering = 0;
+		$grouped_lines = $this->getLinkedLines($bookkeeping_ids, $type);
+		foreach ($grouped_lines as $lines) {
+			$group_error = 0;
+			$total = 0;
+			$do_it = !$unlettering;
+			$lettering_code = null;
+			$piece_num_lines = array();
+			$bookkeeping_lines = array();
+			foreach ($lines as $line_infos) {
+				$bookkeeping_lines[$line_infos['id']] = $line_infos['id'];
+				$piece_num_lines[$line_infos['piece_num']] = $line_infos['piece_num'];
+				$total += ($line_infos['credit'] > 0 ? $line_infos['credit'] : -$line_infos['debit']);
+
+				// Check lettering code
+				if ($unlettering) {
+					if (isset($lettering_code) && $lettering_code != $line_infos['lettering_code']) {
+						$this->errors[] = $langs->trans('AccountancyErrorMismatchLetteringCode');
+						$group_error++;
+						break;
+					}
+					if (!isset($lettering_code)) $lettering_code = (string) $line_infos['lettering_code'];
+					if (!empty($line_infos['lettering_code'])) $do_it = true;
+				} elseif (!empty($line_infos['lettering_code'])) $do_it = false;
+			}
+
+			// Check balance amount
+			if (!$group_error && !$unlettering && price2num($total) != 0) {
+				$this->errors[] = $langs->trans('AccountancyErrorMismatchBalanceAmount', $total);
+				$group_error++;
+			}
+
+			// Lettering/Unlettering the group of bookkeeping lines
+			if (!$group_error && $do_it) {
+				if ($unlettering) $result = $this->deleteLettering($bookkeeping_lines);
+				else $result = $this->updateLettering($bookkeeping_lines);
+				if ($result < 0) {
+					$group_error++;
+				} else {
+					$nb_lettering++;
+				}
+			}
+
+			if ($group_error) {
+				$this->errors[] = $langs->trans('AccountancyErrorLetteringBookkeeping', implode(', ', $piece_num_lines));
+				$error++;
+			}
+		}
+
+		if ($error) {
+			return -2 - $nb_lettering;
+		} else {
+			return $nb_lettering;
+		}
+	}
+
+	/**
+	 * Lettering bookkeeping lines
+	 *
+	 * @param	array			$bookkeeping_ids		Lettering specific list of bookkeeping id
+	 * @param	string			$type					Type of bookkeeping type to lettering ('customer_invoice' or 'supplier_invoice')
+	 * @return	array|int								<0 if error otherwise all linked lines by block
+	 */
+	public function getLinkedLines($bookkeeping_ids, $type = 'customer_invoice')
+	{
+		global $conf, $langs;
+		$this->errors = array();
+
+		// Clean parameters
+		$bookkeeping_ids = is_array($bookkeeping_ids) ? $bookkeeping_ids : array();
+		$type = trim($type);
+
+		if ($type == 'customer_invoice') {
+			$doc_type = 'customer_invoice';
+			$bank_url_type = 'payment';
+			$payment_element = 'paiement_facture';
+			$fk_payment_element = 'fk_paiement';
+			$fk_element = 'fk_facture';
+			$account_number = $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER;
+		} elseif ($type == 'supplier_invoice') {
+			$doc_type = 'supplier_invoice';
+			$bank_url_type = 'payment_supplier';
+			$payment_element = 'paiementfourn_facturefourn';
+			$fk_payment_element = 'fk_paiementfourn';
+			$fk_element = 'fk_facturefourn';
+			$account_number = $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER;
+		} else {
+			$langs->load('errors');
+			$this->errors[] = $langs->trans('ErrorBadParameters');
+			return -1;
+		}
+
+		$payment_ids = array();
+
+		// Get all payment id from bank lines
+		$sql = "SELECT DISTINCT bu.url_id AS payment_id";
+		$sql .= " FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping AS ab";
+		$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "bank_url AS bu ON bu.fk_bank = ab.fk_doc";
+		$sql .= " WHERE ab.doc_type = 'bank'";
+		//	$sql .= " AND ab.subledger_account != ''";
+		//	$sql .= " AND ab.numero_compte = '" . $this->db->escape($account_number) . "'";
+		$sql .= " AND bu.type = '" . $this->db->escape($bank_url_type) . "'";
+		if (!empty($bookkeeping_ids)) $sql .= " AND ab.rowid IN (" . $this->db->sanitize(implode(',', $bookkeeping_ids)) . ")";
+
+		dol_syslog(__METHOD__ . " - Get all payment id from bank lines", LOG_DEBUG);
+		$resql = $this->db->query($sql);
+		if (!$resql) {
+			$this->errors[] = "Error " . $this->db->lasterror();
+			return -1;
+		}
+
+		while ($obj = $this->db->fetch_object($resql)) {
+			$payment_ids[$obj->payment_id] = $obj->payment_id;
+		}
+		$this->db->free($resql);
+
+		// Get all payment id from payment lines
+		$sql = "SELECT DISTINCT pe.$fk_payment_element AS payment_id";
+		$sql .= " FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping AS ab";
+		$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "$payment_element AS pe ON pe.$fk_element = ab.fk_doc";
+		$sql .= " WHERE ab.doc_type = '" . $this->db->escape($doc_type) . "'";
+		//	$sql .= " AND ab.subledger_account != ''";
+		//	$sql .= " AND ab.numero_compte = '" . $this->db->escape($account_number) . "'";
+		$sql .= " AND pe.$fk_payment_element IS NOT NULL";
+		if (!empty($bookkeeping_ids)) $sql .= " AND ab.rowid IN (" . $this->db->sanitize(implode(',', $bookkeeping_ids)) . ")";
+
+		dol_syslog(__METHOD__ . " - Get all payment id from bank lines", LOG_DEBUG);
+		$resql = $this->db->query($sql);
+		if (!$resql) {
+			$this->errors[] = "Error " . $this->db->lasterror();
+			return -1;
+		}
+
+		while ($obj = $this->db->fetch_object($resql)) {
+			$payment_ids[$obj->payment_id] = $obj->payment_id;
+		}
+		$this->db->free($resql);
+
+		if (empty($payment_ids)) {
+			return array();
+		}
+
+		// Get all payments linked by group
+		$payment_by_group = $this->getLinkedPaymentByGroup($payment_ids, $type);
+
+		$groups = array();
+		foreach ($payment_by_group as $payment_list) {
+			$lines = array();
+
+			// Get bank lines
+			$sql = "SELECT DISTINCT ab.rowid, ab.piece_num, ab.lettering_code, ab.debit, ab.credit";
+			$sql .=	" FROM " . MAIN_DB_PREFIX . "bank_url AS bu";
+			$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "accounting_bookkeeping AS ab ON ab.fk_doc = bu.fk_bank";
+			$sql .= " WHERE bu.url_id IN (" . $this->db->sanitize(implode(',', $payment_list)) . ")";
+			$sql .= " AND bu.type = '" . $this->db->escape($bank_url_type) . "'";
+			$sql .= " AND ab.doc_type = 'bank'";
+			$sql .= " AND ab.subledger_account != ''";
+			$sql .= " AND ab.numero_compte = '" . $this->db->escape($account_number) . "'";
+
+			dol_syslog(__METHOD__ . " - Get bank lines", LOG_DEBUG);
+			$resql = $this->db->query($sql);
+			if (!$resql) {
+				$this->errors[] = "Error " . $this->db->lasterror();
+				return -1;
+			}
+
+			while ($obj = $this->db->fetch_object($resql)) {
+				$lines[$obj->rowid] = array('id' => $obj->rowid, 'piece_num' => $obj->piece_num, 'lettering_code' => $obj->lettering_code, 'debit' => $obj->debit, 'credit' => $obj->credit);
+			}
+			$this->db->free($resql);
+
+			// Get payment lines
+			$sql = "SELECT DISTINCT ab.rowid, ab.piece_num, ab.lettering_code, ab.debit, ab.credit";
+			$sql .=	" FROM " . MAIN_DB_PREFIX . "$payment_element AS pe";
+			$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "accounting_bookkeeping AS ab ON ab.fk_doc = pe.$fk_element";
+			$sql .= " WHERE pe.$fk_payment_element IN (" . $this->db->sanitize(implode(',', $payment_list)) . ")";
+			$sql .= " AND ab.doc_type = '" . $this->db->escape($doc_type) . "'";
+			$sql .= " AND ab.subledger_account != ''";
+			$sql .= " AND ab.numero_compte = '" . $this->db->escape($account_number) . "'";
+
+			dol_syslog(__METHOD__ . " - Get payment lines", LOG_DEBUG);
+			$resql = $this->db->query($sql);
+			if (!$resql) {
+				$this->errors[] = "Error " . $this->db->lasterror();
+				return -1;
+			}
+
+			while ($obj = $this->db->fetch_object($resql)) {
+				$lines[$obj->rowid] = array('id' => $obj->rowid, 'piece_num' => $obj->piece_num, 'lettering_code' => $obj->lettering_code, 'debit' => $obj->debit, 'credit' => $obj->credit);
+			}
+			$this->db->free($resql);
+
+			if (!empty($lines)) {
+				$groups[] = $lines;
+			}
+		}
+
+		return $groups;
+	}
+
+	/**
+	 * Linked payment by group
+	 *
+	 * @param	array			$payment_ids			list of payment id
+	 * @param	string			$type					Type of bookkeeping type to lettering ('customer_invoice' or 'supplier_invoice')
+	 * @return	array|int								<0 if error otherwise all linked lines by block
+	 */
+	public function getLinkedPaymentByGroup($payment_ids, $type)
+	{
+		global $langs;
+
+		// Clean parameters
+		$payment_ids = is_array($payment_ids) ? $payment_ids : array();
+		$type = trim($type);
+
+		if (empty($payment_ids)) {
+			return array();
+		}
+
+		if ($type == 'customer_invoice') {
+			$payment_element = 'paiement_facture';
+			$fk_payment_element = 'fk_paiement';
+			$fk_element = 'fk_facture';
+		} elseif ($type == 'supplier_invoice') {
+			$payment_element = 'paiementfourn_facturefourn';
+			$fk_payment_element = 'fk_paiementfourn';
+			$fk_element = 'fk_facturefourn';
+		} else {
+			$langs->load('errors');
+			$this->errors[] = $langs->trans('ErrorBadParameters');
+			return -1;
+		}
+
+		// Get payment lines
+		$sql = "SELECT DISTINCT pe2.$fk_payment_element, pe2.$fk_element";
+		$sql .=	" FROM " . MAIN_DB_PREFIX . "$payment_element AS pe";
+		$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "$payment_element AS pe2 ON pe2.$fk_element = pe.$fk_element";
+		$sql .=	" WHERE pe.$fk_payment_element IN (" . $this->db->sanitize(implode(',', $payment_ids)) . ")";
+
+		dol_syslog(__METHOD__ . " - Get payment lines", LOG_DEBUG);
+		$resql = $this->db->query($sql);
+		if (!$resql) {
+			$this->errors[] = "Error " . $this->db->lasterror();
+			return -1;
+		}
+
+		$current_payment_ids = array();
+		$payment_by_element = array();
+		$element_by_payment = array();
+		while ($obj = $this->db->fetch_object($resql)) {
+			$current_payment_ids[$obj->$fk_payment_element] = $obj->$fk_payment_element;
+			$element_by_payment[$obj->$fk_payment_element][$obj->$fk_element] = $obj->$fk_element;
+			$payment_by_element[$obj->$fk_element][$obj->$fk_payment_element] = $obj->$fk_payment_element;
+		}
+		$this->db->free($resql);
+
+		if (count(array_diff($payment_ids, $current_payment_ids))) {
+			return $this->getLinkedPaymentByGroup($current_payment_ids, $type);
+		}
+
+		return $this->getGroupElements($payment_by_element, $element_by_payment);
+	}
+
+	/**
+	 * Get payment ids grouped by payment id and element id in common
+	 *
+	 * @param	array	$payment_by_element		List of payment ids by element id
+	 * @param	array	$element_by_payment		List of element ids by payment id
+	 * @param	int		$element_id				Element Id (used for recursive function)
+	 * @param	array	$current_group			Current group (used for recursive function)
+	 * @return	array							List of payment ids grouped by payment id and element id in common
+	 */
+	public function getGroupElements(&$payment_by_element, &$element_by_payment, $element_id = 0, &$current_group = array())
+	{
+		$grouped_payments = array();
+		if ($element_id > 0 && !isset($payment_by_element[$element_id])) {
+			// Return if specific element id not found
+			return $grouped_payments;
+		}
+
+		if ($element_id == 0) {
+			// Save list when is the begin of recursive function
+			$save_payment_by_element = $payment_by_element;
+			$save_element_by_payment = $element_by_payment;
+		}
+
+		do {
+			// Get current element id, get this payment id list and delete the entry
+			$current_element_id = $element_id > 0 ? $element_id : array_keys($payment_by_element)[0];
+			$payment_ids = $payment_by_element[$current_element_id];
+			unset($payment_by_element[$current_element_id]);
+
+			foreach ($payment_ids as $payment_id) {
+				// Continue if payment id in not found
+				if (!isset($element_by_payment[$payment_id])) continue;
+
+				// Set the payment in the current group
+				$current_group[$payment_id] = $payment_id;
+
+				// Get current element ids, get this payment id list and delete the entry
+				$element_ids = $element_by_payment[$payment_id];
+				unset($element_by_payment[$payment_id]);
+
+				// Set payment id on the current group for each element id of the payment
+				foreach ($element_ids as $id) {
+					$this->getGroupElements($payment_by_element, $element_by_payment, $id, $current_group);
+				}
+			}
+
+			if ($element_id == 0) {
+				// Save current group and reset the current group when is the begin of recursive function
+				$grouped_payments[] = $current_group;
+				$current_group = array();
+			}
+		} while (!empty($payment_by_element) && $element_id == 0);
+
+		if ($element_id == 0) {
+			// Restore list when is the begin of recursive function
+			$payment_by_element = $save_payment_by_element;
+			$element_by_payment = $save_element_by_payment;
+		}
+
+		return $grouped_payments;
+	}
 }

+ 10 - 0
htdocs/accountancy/journal/bankjournal.php

@@ -665,6 +665,8 @@ if (!$error && $action == 'writebookkeeping') {
 				// Line into thirdparty account
 				foreach ($tabtp[$key] as $k => $mt) {
 					if ($mt) {
+						$lettering = false;
+
 						$reflabel = '';
 						if (!empty($val['lib'])) {
 							$reflabel .= dol_string_nohtmltag($val['lib']).($val['soclib'] ? " - " : "");
@@ -693,11 +695,13 @@ if (!$error && $action == 'writebookkeeping') {
 						$bookkeeping->date_creation = $now;
 
 						if ($tabtype[$key] == 'payment') {	// If payment is payment of customer invoice, we get ref of invoice
+							$lettering = true;
 							$bookkeeping->subledger_account = $k; // For payment, the subledger account is stored as $key of $tabtp
 							$bookkeeping->subledger_label = $tabcompany[$key]['name']; // $tabcompany is defined only if we are sure there is 1 thirdparty for the bank transaction
 							$bookkeeping->numero_compte = $conf->global->ACCOUNTING_ACCOUNT_CUSTOMER;
 							$bookkeeping->label_compte = $accountingaccountcustomer->label;
 						} elseif ($tabtype[$key] == 'payment_supplier') {	// If payment is payment of supplier invoice, we get ref of invoice
+							$lettering = true;
 							$bookkeeping->subledger_account = $k; // For payment, the subledger account is stored as $key of $tabtp
 							$bookkeeping->subledger_label = $tabcompany[$key]['name']; // $tabcompany is defined only if we are sure there is 1 thirdparty for the bank transaction
 							$bookkeeping->numero_compte = $conf->global->ACCOUNTING_ACCOUNT_SUPPLIER;
@@ -780,6 +784,12 @@ if (!$error && $action == 'writebookkeeping') {
 								$errorforline++;
 								setEventMessages($bookkeeping->error, $bookkeeping->errors, 'errors');
 							}
+						} else {
+							if ($lettering && getDolGlobalInt('ACCOUNTING_ENABLE_LETTERING')) {
+								require_once DOL_DOCUMENT_ROOT . '/accountancy/class/lettering.class.php';
+								$lettering_static = new Lettering($db);
+								$nb_lettering = $lettering_static->bookkeepingLetteringAll(array($bookkeeping->id));
+							}
 						}
 					}
 				}

+ 6 - 0
htdocs/accountancy/journal/purchasesjournal.php

@@ -377,6 +377,12 @@ if ($action == 'writebookkeeping') {
 						$errorforinvoice[$key] = 'other';
 						setEventMessages($bookkeeping->error, $bookkeeping->errors, 'errors');
 					}
+				} else {
+					if (getDolGlobalInt('ACCOUNTING_ENABLE_LETTERING')) {
+						require_once DOL_DOCUMENT_ROOT . '/accountancy/class/lettering.class.php';
+						$lettering_static = new Lettering($db);
+						$nb_lettering = $lettering_static->bookkeepingLettering(array($bookkeeping->id), 'supplier_invoice');
+					}
 				}
 			}
 		}

+ 6 - 0
htdocs/accountancy/journal/sellsjournal.php

@@ -390,6 +390,12 @@ if ($action == 'writebookkeeping') {
 						$errorforinvoice[$key] = 'other';
 						setEventMessages($bookkeeping->error, $bookkeeping->errors, 'errors');
 					}
+				} else {
+					if (getDolGlobalInt('ACCOUNTING_ENABLE_LETTERING')) {
+						require_once DOL_DOCUMENT_ROOT . '/accountancy/class/lettering.class.php';
+						$lettering_static = new Lettering($db);
+						$nb_lettering = $lettering_static->bookkeepingLettering(array($bookkeeping->id), 'customer_invoice');
+					}
 				}
 			}
 		}

+ 2 - 2
htdocs/adherents/cartes/carte.php

@@ -79,7 +79,7 @@ if ((!empty($foruserid) || !empty($foruserlogin) || !empty($mode)) && !$mesg) {
 	}
 	$sql .= " FROM ".MAIN_DB_PREFIX."adherent_type as t, ".MAIN_DB_PREFIX."adherent as d";
 	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_country as c ON d.country = c.rowid";
-	if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label'])) {
+	if (isset($extrafields->attributes[$object->table_element]['label']) && is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label'])) {
 		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."adherent_extrafields as ef on (d.rowid = ef.fk_object)";
 	}
 	$sql .= " WHERE d.fk_adherent_type = t.rowid AND d.statut = 1";
@@ -110,7 +110,7 @@ if ((!empty($foruserid) || !empty($foruserlogin) || !empty($mode)) && !$mesg) {
 			$adherentstatic->firstname = $objp->firstname;
 
 			// Format extrafield so they can be parsed in function complete_substitutions_array
-			if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label'])) {
+			if (isset($extrafields->attributes[$object->table_element]['label']) && is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label'])) {
 				$adherentstatic->array_options = array();
 				foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $val) {
 					$tmpkey = 'options_'.$key;

+ 2 - 2
htdocs/adherents/list.php

@@ -172,7 +172,7 @@ $result = restrictedArea($user, 'adherent');
 if (GETPOST('cancel', 'alpha')) {
 	$action = 'list'; $massaction = '';
 }
-if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend' && $massaction != 'confirm_createbills') {
+if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend') {
 	$massaction = '';
 }
 
@@ -587,7 +587,7 @@ include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php';
 
 // List of mass actions available
 $arrayofmassactions = array(
-	//'presend'=>img_picto('', 'email', 'class="pictofixedwidth"').'&ensp;'.$langs->trans("SendByMail"),
+	//'presend'=>img_picto('', 'email', 'class="pictofixedwidth"').$langs->trans("SendByMail"),
 	//'builddoc'=>img_picto('', 'pdf', 'class="pictofixedwidth"').$langs->trans("PDFMerge"),
 );
 if ($user->rights->adherent->creer) {

+ 1 - 1
htdocs/adherents/subscription/list.php

@@ -111,7 +111,7 @@ $result = restrictedArea($user, 'adherent', '', '', 'cotisation');
 if (GETPOST('cancel', 'alpha')) {
 	$action = 'list'; $massaction = '';
 }
-if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend' && $massaction != 'confirm_createbills') {
+if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend') {
 	$massaction = '';
 }
 

+ 3 - 2
htdocs/admin/commande.php

@@ -302,6 +302,7 @@ foreach ($dirmodels as $reldir) {
 						$htmltooltip = '';
 						$htmltooltip .= ''.$langs->trans("Version").': <b>'.$module->getVersion().'</b><br>';
 						$commande->type = 0;
+
 						$nextval = $module->getNextValue($mysoc, $commande);
 						if ("$nextval" != $langs->trans("NotAvailable")) {  // Keep " on nextval
 							$htmltooltip .= ''.$langs->trans("NextValue").': ';
@@ -614,7 +615,7 @@ if (empty($conf->global->PDF_ALLOW_HTML_FOR_FREE_TEXT)) {
 	print '<textarea name="'.$variablename.'" class="flat" cols="120">'.$conf->global->$variablename.'</textarea>';
 } else {
 	include_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
-	$doleditor = new DolEditor($variablename, $conf->global->$variablename, '', 80, 'dolibarr_notes');
+	$doleditor = new DolEditor($variablename, getDolGlobalString($variablename), '', 80, 'dolibarr_notes');
 	print $doleditor->Create();
 }
 print '</td><td class="right">';
@@ -630,7 +631,7 @@ print "<input type=\"hidden\" name=\"action\" value=\"set_COMMANDE_DRAFT_WATERMA
 print '<tr class="oddeven"><td>';
 print $form->textwithpicto($langs->trans("WatermarkOnDraftOrders"), $htmltext, 1, 'help', '', 0, 2, 'watermarktooltip').'<br>';
 print '</td><td>';
-print '<input class="flat minwidth200" type="text" name="COMMANDE_DRAFT_WATERMARK" value="'.$conf->global->COMMANDE_DRAFT_WATERMARK.'">';
+print '<input class="flat minwidth200" type="text" name="COMMANDE_DRAFT_WATERMARK" value="'.getDolGlobalString('COMMANDE_DRAFT_WATERMARK').'">';
 print '</td><td class="right">';
 print '<input type="submit" class="button button-edit" value="'.$langs->trans("Modify").'">';
 print "</td></tr>\n";

+ 9 - 7
htdocs/admin/debugbar.php

@@ -93,15 +93,17 @@ print '<td>'.$langs->trans("Parameter").'</td><td>'.$langs->trans("Value").'</td
 print '<td class="right"><input type="submit" class="button button-edit" '.$option.' value="'.$langs->trans("Modify").'"></td>';
 print "</tr>\n";
 
-print '<tr class="oddeven"><td>'.$langs->trans("DEBUGBAR_LOGS_LINES_NUMBER").'</td>';
-print '<td colspan="2"><input type="text" class="flat" name="DEBUGBAR_LOGS_LINES_NUMBER" value="'.(empty($conf->global->DEBUGBAR_LOGS_LINES_NUMBER) ? 250 : $conf->global->DEBUGBAR_LOGS_LINES_NUMBER).'">'; // This slow seriously output
-print ' '.$langs->trans("WarningValueHigherSlowsDramaticalyOutput");
+print '<tr class="oddeven"><td class="nowrap">'.$langs->trans("DEBUGBAR_USE_LOG_FILE").'</td>';
+print '<td>';
+print $form->selectyesno('DEBUGBAR_USE_LOG_FILE', $conf->global->DEBUGBAR_USE_LOG_FILE, 1);
+print '</td><td>';
+print '<span class="opacitymedium"> '.$langs->trans("UsingLogFileShowAllRecordOfSubrequestButIsSlower").'</span>';
 print '</td></tr>';
 
-print '<tr class="oddeven"><td>'.$langs->trans("DEBUGBAR_USE_LOG_FILE").'</td>';
-print '<td colspan="2">';
-print $form->selectyesno('DEBUGBAR_USE_LOG_FILE', $conf->global->DEBUGBAR_USE_LOG_FILE, 1);
-print ' '.$langs->trans("UsingLogFileShowAllRecordOfSubrequestButIsSlower");
+print '<tr class="oddeven"><td class="nowrap">'.$langs->trans("DEBUGBAR_LOGS_LINES_NUMBER").'</td>';
+print '<td><input type="text" class="flat width75" name="DEBUGBAR_LOGS_LINES_NUMBER" value="'.(empty($conf->global->DEBUGBAR_LOGS_LINES_NUMBER) ? 250 : $conf->global->DEBUGBAR_LOGS_LINES_NUMBER).'">'; // This slow seriously output
+print '</td><td>';
+print '<span class="opacitymedium">'.$langs->trans("WarningValueHigherSlowsDramaticalyOutput").'</span>';
 print '</td></tr>';
 
 print '</table>';

+ 29 - 3
htdocs/admin/emailcollector_list.php

@@ -29,6 +29,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/events.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
+
 dol_include_once('/emailcollector/class/emailcollector.class.php');
 
 // Load translation files required by page
@@ -119,7 +120,7 @@ foreach ($object->fields as $key => $val) {
 	}
 }
 // Extra fields
-if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label']) > 0) {
+if (isset($extrafields->attributes[$object->table_element]['label']) && is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label']) > 0) {
 	foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $val) {
 		if (!empty($extrafields->attributes[$object->table_element]['list'][$key])) {
 			$arrayfields["ef.".$key] = array(
@@ -222,7 +223,7 @@ $reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters, $obje
 $sql .= preg_replace('/^,/', '', $hookmanager->resPrint);
 $sql = preg_replace('/,\s*$/', '', $sql);
 $sql .= " FROM ".MAIN_DB_PREFIX.$object->table_element." as t";
-if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label'])) {
+if (isset($extrafields->attributes[$object->table_element]['label']) && is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label'])) {
 	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$object->table_element."_extrafields as ef on (t.rowid = ef.fk_object)";
 }
 if ($object->ismultientitymanaged == 1) {
@@ -476,7 +477,7 @@ print '</tr>'."\n";
 
 // Detect if we need a fetch on each output line
 $needToFetchEachLine = 0;
-if (is_array($extrafields->attributes[$object->table_element]['computed']) && count($extrafields->attributes[$object->table_element]['computed']) > 0) {
+if (isset($extrafields->attributes[$object->table_element]['computed']) && is_array($extrafields->attributes[$object->table_element]['computed']) && count($extrafields->attributes[$object->table_element]['computed']) > 0) {
 	foreach ($extrafields->attributes[$object->table_element]['computed'] as $key => $val) {
 		if (preg_match('/\$object/', $val)) {
 			$needToFetchEachLine++; // There is at least one compute field that use $object
@@ -594,6 +595,31 @@ print $hookmanager->resPrint;
 print '</table>'."\n";
 print '</div>'."\n";
 
+print load_fiche_titre($langs->trans("Other"), '', '');
+print '<table class="noborder centpercent">';
+
+print '<tr class="liste_titre">';
+print '<td>'.$langs->trans("Parameter").'</td>';
+print '<td></td>';
+print '<td></td>';
+print "</tr>\n";
+
+// Hide e-mail headers from collected messages
+print '<tr class="oddeven"><td>'.$langs->trans("EmailCollectorHideMailHeaders").'</td>';
+print '<td class="left">';
+if ($conf->use_javascript_ajax) {
+	print ajax_constantonoff('MAIN_EMAILCOLLECTOR_MAIL_WITHOUT_HEADER');
+} else {
+	$arrval = array('0' => $langs->trans("No"), '1' => $langs->trans("Yes"));
+	print $form->selectarray("MAIN_EMAILCOLLECTOR_MAIL_WITHOUT_HEADER", $arrval, $conf->global->TICKET_AUTO_READ_WHEN_CREATED_FROM_BACKEND);
+}
+print '</td>';
+print '<td class="center">';
+print $form->textwithpicto('', $langs->trans("EmailCollectorHideMailHeadersHelp"), 1, 'help');
+print '</td>';
+print '</tr>';
+
+print '</table><br>';
 print '</form>'."\n";
 
 if (in_array('builddoc', $arrayofmassactions) && ($nbtotalofrecords === '' || $nbtotalofrecords)) {

+ 1 - 1
htdocs/admin/eventorganization.php

@@ -220,7 +220,7 @@ if ($action == 'edit') {
 	foreach ($arrayofparameters as $constname => $val) {
 		if ($val['enabled']==1) {
 			$setupnotempty++;
-			print '<tr class="oddeven"><td>aa';
+			print '<tr class="oddeven"><td>';
 			$tooltiphelp = (($langs->trans($constname . 'Tooltip') != $constname . 'Tooltip') ? $langs->trans($constname . 'Tooltip') : '');
 			$tooltiphelp .= (($langs->trans($constname . 'Tooltip2') && $langs->trans($constname . 'Tooltip2') != $constname . 'Tooltip2') ? '<br><br>'."\n".$langs->trans($constname . 'Tooltip2') : '');
 			print '<span id="helplink'.$constname.'" class="spanforparamtooltip">'.$form->textwithpicto($langs->trans($constname), $tooltiphelp, 1, 'info', '', 0, 3, 'tootips'.$constname).'</span>';

+ 12 - 19
htdocs/admin/ihm.php

@@ -475,6 +475,7 @@ if ($mode == 'other') {
 
 	print '<div class="center">';
 	print '<input class="button button-save reposition" type="submit" name="submit" value="' . $langs->trans("Save") . '">';
+	print '<input class="button button-cancel reposition" type="submit" name="cancel" value="' . $langs->trans("Cancel") . '">';
 	print '</div>';
 
 	print '<br>';
@@ -485,18 +486,17 @@ if ($mode == 'other') {
 	print '<table summary="otherparameters" class="noborder centpercent editmode tableforfield">';
 
 	print '<tr class="liste_titre"><td class="titlefieldmiddle">';
-	print $langs->trans("Miscelaneous");
-	print '</td><td class="titlefieldmiddle">';
-	print '</td></tr>';
+	print $langs->trans("Miscellaneous");
+	print '</td>';
+	print '<td class="titlefieldmiddle"></td>';
+	print '</tr>';
 
 	// Max size of lists
 	print '<tr class="oddeven"><td>' . $langs->trans("DefaultMaxSizeList") . '</td><td><input class="flat" name="main_size_liste_limit" size="4" value="' . $conf->global->MAIN_SIZE_LISTE_LIMIT . '"></td>';
-	print '<td width="20">&nbsp;</td>';
 	print '</tr>';
 
 	// Max size of short lists on customer card
 	print '<tr class="oddeven"><td>' . $langs->trans("DefaultMaxSizeShortList") . '</td><td><input class="flat" name="main_size_shortliste_limit" size="4" value="' . $conf->global->MAIN_SIZE_SHORTLIST_LIMIT . '"></td>';
-	print '<td width="20">&nbsp;</td>';
 	print '</tr>';
 
 	// show input border
@@ -504,7 +504,6 @@ if ($mode == 'other') {
 	 print '<tr><td>'.$langs->trans("showInputBorder").'</td><td>';
 	 print $form->selectyesno('main_showInputBorder',isset($conf->global->THEME_ELDY_SHOW_BORDER_INPUT)?$conf->global->THEME_ELDY_SHOW_BORDER_INPUT:0,1);
 	 print '</td>';
-	 print '<td width="20">&nbsp;</td>';
 	 print '</tr>';
 	 */
 
@@ -512,21 +511,18 @@ if ($mode == 'other') {
 	print '<tr class="oddeven"><td>' . $langs->trans("WeekStartOnDay") . '</td><td>';
 	print $formother->select_dayofweek((isset($conf->global->MAIN_START_WEEK) ? $conf->global->MAIN_START_WEEK : '1'), 'MAIN_START_WEEK', 0);
 	print '</td>';
-	print '<td width="20">&nbsp;</td>';
 	print '</tr>';
 
 	// DefaultWorkingDays
 	print '<tr class="oddeven"><td>' . $langs->trans("DefaultWorkingDays") . '</td><td>';
 	print '<input type="text" name="MAIN_DEFAULT_WORKING_DAYS" size="5" value="' . (isset($conf->global->MAIN_DEFAULT_WORKING_DAYS) ? $conf->global->MAIN_DEFAULT_WORKING_DAYS : '1-5') . '">';
 	print '</td>';
-	print '<td width="20">&nbsp;</td>';
 	print '</tr>';
 
 	// DefaultWorkingHours
 	print '<tr class="oddeven"><td>' . $langs->trans("DefaultWorkingHours") . '</td><td>';
 	print '<input type="text" name="MAIN_DEFAULT_WORKING_HOURS" size="5" value="' . (isset($conf->global->MAIN_DEFAULT_WORKING_HOURS) ? $conf->global->MAIN_DEFAULT_WORKING_HOURS : '9-18') . '">';
 	print '</td>';
-	print '<td width="20">&nbsp;</td>';
 	print '</tr>';
 
 	// Firstname/Name
@@ -534,7 +530,6 @@ if ($mode == 'other') {
 	$array = array(0 => $langs->trans("Firstname") . ' ' . $langs->trans("Lastname"), 1 => $langs->trans("Lastname") . ' ' . $langs->trans("Firstname"));
 	print $form->selectarray('MAIN_FIRSTNAME_NAME_POSITION', $array, (isset($conf->global->MAIN_FIRSTNAME_NAME_POSITION) ? $conf->global->MAIN_FIRSTNAME_NAME_POSITION : 0));
 	print '</td>';
-	print '<td width="20">&nbsp;</td>';
 	print '</tr>';
 
 	// Hide unauthorized menus
@@ -542,7 +537,6 @@ if ($mode == 'other') {
 	//print $form->selectyesno('MAIN_MENU_HIDE_UNAUTHORIZED', isset($conf->global->MAIN_MENU_HIDE_UNAUTHORIZED) ? $conf->global->MAIN_MENU_HIDE_UNAUTHORIZED : 0, 1);
 	print ajax_constantonoff("MAIN_MENU_HIDE_UNAUTHORIZED", array(), $conf->entity, 0, 0, 1, 0, 0, 0, '', 'other');
 	print '</td>';
-	print '<td width="20">&nbsp;</td>';
 	print '</tr>';
 
 	// Hide unauthorized button
@@ -550,7 +544,6 @@ if ($mode == 'other') {
 	//print $form->selectyesno('MAIN_BUTTON_HIDE_UNAUTHORIZED', isset($conf->global->MAIN_BUTTON_HIDE_UNAUTHORIZED) ? $conf->global->MAIN_BUTTON_HIDE_UNAUTHORIZED : 0, 1);
 	print ajax_constantonoff("MAIN_BUTTON_HIDE_UNAUTHORIZED", array(), $conf->entity, 0, 0, 1, 0, 0, 0, '', 'other');
 	print '</td>';
-	print '<td width="20">&nbsp;</td>';
 	print '</tr>';
 
 	// Hide version link
@@ -559,17 +552,21 @@ if ($mode == 'other') {
 	print '<tr><td>'.$langs->trans("HideVersionLink").'</td><td>';
 	print $form->selectyesno('MAIN_HIDE_VERSION',$conf->global->MAIN_HIDE_VERSION,1);
 	print '</td>';
-	print '<td width="20">&nbsp;</td>';
 	print '</tr>';
 	*/
 
+	// Show Quick Add link
+	print '<tr class="oddeven"><td>' . $langs->trans("ShowQuickAddLink") . '</td><td>';
+	print ajax_constantonoff("MAIN_USE_TOP_MENU_QUICKADD_DROPDOWN", array(), $conf->entity, 0, 0, 1, 0, 0, 0, '', 'other');
+	print '</td>';
+	print '</tr>';
+
 	// Show bugtrack link
 	print '<tr class="oddeven"><td>';
 	print $form->textwithpicto($langs->trans("ShowBugTrackLink", $langs->transnoentitiesnoconv("FindBug")), $langs->trans("ShowBugTrackLinkDesc"));
 	print '</td><td>';
 	print '<input type="text" name="MAIN_BUGTRACK_ENABLELINK" value="' . (empty($conf->global->MAIN_BUGTRACK_ENABLELINK) ? '' : $conf->global->MAIN_BUGTRACK_ENABLELINK) . '">';
 	print '</td>';
-	print '<td width="20">&nbsp;</td>';
 	print '</tr>';
 
 	// Hide wiki link on login page
@@ -578,15 +575,11 @@ if ($mode == 'other') {
 	print ajax_constantonoff("MAIN_HELP_DISABLELINK", array(), $conf->entity, 0, 0, 1, 0, 0, 0, '', 'other');
 	//print $form->selectyesno('MAIN_HELP_DISABLELINK', isset($conf->global->MAIN_HELP_DISABLELINK) ? $conf->global->MAIN_HELP_DISABLELINK : 0, 1);
 	print '</td>';
-	print '<td width="20">&nbsp;</td>';
 	print '</tr>';
 
 	// Disable javascript and ajax
-	print '<tr class="oddeven"><td>' . $langs->trans("DisableJavascript") . '</td><td>';
+	print '<tr class="oddeven"><td>' . $form->textwithpicto($langs->trans("DisableJavascript"), $langs->trans("DisableJavascriptNote")) . '</td><td>';
 	print ajax_constantonoff("MAIN_DISABLE_JAVASCRIPT", array(), $conf->entity, 0, 0, 1, 0, 0, 0, '', 'other');
-	print ' <span class="opacitymedium paddingleft marginleft">' . $langs->trans("DisableJavascriptNote") . '</span>';
-	print '</td>';
-	print '<td>';
 	print '</td>';
 	print '</tr>';
 

+ 2 - 1
htdocs/admin/system/dolibarr.php

@@ -435,7 +435,8 @@ foreach ($configfileparameters as $key => $value) {
 				if (empty($valuetoshow)) {
 					print img_warning("EditConfigFileToAddEntry", 'dolibarr_main_instance_unique_id');
 				}
-				print ' &nbsp; <span class="opacitymedium">('.$langs->trans("HashForPing").'='.md5('dolibarr'.$valuetoshow).')</span>';
+				print '</td></tr>';
+				print '<tr class="oddeven"><td></td><td>&nbsp; => '.$langs->trans("HashForPing").'</td><td>'.md5('dolibarr'.$valuetoshow).'</td></tr>'."\n";
 			} elseif ($newkey == 'dolibarr_main_prod') {
 				print ${$newkey};
 

+ 15 - 11
htdocs/admin/ticket.php

@@ -103,10 +103,11 @@ if ($action == 'updateMask') {
 	include_once DOL_DOCUMENT_ROOT."/core/lib/files.lib.php";
 
 	$notification_email = GETPOST('TICKET_NOTIFICATION_EMAIL_FROM', 'alpha');
+	$notification_email_description = "Sender of ticket replies sent from Dolibarr";
 	if (!empty($notification_email)) {
-		$res = dolibarr_set_const($db, 'TICKET_NOTIFICATION_EMAIL_FROM', $notification_email, 'chaine', 0, '', $conf->entity);
-	} else {
-		$res = dolibarr_set_const($db, 'TICKET_NOTIFICATION_EMAIL_FROM', '', 'chaine', 0, '', $conf->entity);
+		$res = dolibarr_set_const($db, 'TICKET_NOTIFICATION_EMAIL_FROM', $notification_email, 'chaine', 0, $notification_email_description, $conf->entity);
+	} else { // If an empty e-mail address is providen, use the global "FROM" since an empty field will cause other issues
+		$res = dolibarr_set_const($db, 'TICKET_NOTIFICATION_EMAIL_FROM', $conf->global->MAIN_MAIL_EMAIL_FROM, 'chaine', 0, $notification_email_description, $conf->entity);
 	}
 	if (!($res > 0)) {
 		$error++;
@@ -114,30 +115,33 @@ if ($action == 'updateMask') {
 
 	// altairis : differentiate notification email FROM and TO
 	$notification_email_to = GETPOST('TICKET_NOTIFICATION_EMAIL_TO', 'alpha');
+	$notification_email_to_description = "Notified e-mail for ticket replies sent from Dolibarr";
 	if (!empty($notification_email_to)) {
-		$res = dolibarr_set_const($db, 'TICKET_NOTIFICATION_EMAIL_TO', $notification_email_to, 'chaine', 0, '', $conf->entity);
+		$res = dolibarr_set_const($db, 'TICKET_NOTIFICATION_EMAIL_TO', $notification_email_to, 'chaine', 0, $notification_email_to_description, $conf->entity);
 	} else {
-		$res = dolibarr_set_const($db, 'TICKET_NOTIFICATION_EMAIL_TO', '', 'chaine', 0, '', $conf->entity);
+		$res = dolibarr_set_const($db, 'TICKET_NOTIFICATION_EMAIL_TO', '', 'chaine', 0, $notification_email_to_description, $conf->entity);
 	}
 	if (!($res > 0)) {
 		$error++;
 	}
 
 	$mail_intro = GETPOST('TICKET_MESSAGE_MAIL_INTRO', 'restricthtml');
+	$mail_intro_description = "Introduction text of ticket replies sent from Dolibarr";
 	if (!empty($mail_intro)) {
-		$res = dolibarr_set_const($db, 'TICKET_MESSAGE_MAIL_INTRO', $mail_intro, 'chaine', 0, '', $conf->entity);
+		$res = dolibarr_set_const($db, 'TICKET_MESSAGE_MAIL_INTRO', $mail_intro, 'chaine', 0, $mail_intro_description, $conf->entity);
 	} else {
-		$res = dolibarr_set_const($db, 'TICKET_MESSAGE_MAIL_INTRO', $langs->trans('TicketMessageMailIntroText'), 'chaine', 0, '', $conf->entity);
+		$res = dolibarr_set_const($db, 'TICKET_MESSAGE_MAIL_INTRO', '', 'chaine', 0, $mail_intro_description, $conf->entity);
 	}
 	if (!($res > 0)) {
 		$error++;
 	}
 
 	$mail_signature = GETPOST('TICKET_MESSAGE_MAIL_SIGNATURE', 'restricthtml');
+	$signature_description = "Signature of ticket replies sent from Dolibarr";
 	if (!empty($mail_signature)) {
-		$res = dolibarr_set_const($db, 'TICKET_MESSAGE_MAIL_SIGNATURE', $mail_signature, 'chaine', 0, '', $conf->entity);
+		$res = dolibarr_set_const($db, 'TICKET_MESSAGE_MAIL_SIGNATURE', $mail_signature, 'chaine', 0, $signature_description, $conf->entity);
 	} else {
-		$res = dolibarr_set_const($db, 'TICKET_MESSAGE_MAIL_SIGNATURE', $langs->trans('TicketMessageMailSignatureText'), 'chaine', 0, '', $conf->entity);
+		$res = dolibarr_set_const($db, 'TICKET_MESSAGE_MAIL_SIGNATURE', '', 'chaine', 0, $signature_description, $conf->entity);
 	}
 	if (!($res > 0)) {
 		$error++;
@@ -644,7 +648,7 @@ print '</td>';
 print '</tr>';
 
 // Email for notification of TICKET_CREATE
-print '<tr class="oddeven"><td>'.$langs->trans("TicketEmailNotificationTo").' ('.$langs->trans("Creation").')</td>';
+print '<tr class="oddeven"><td>'.$langs->trans("TicketEmailNotificationTo").'</td>';
 print '<td class="left">';
 print '<input type="text" name="TICKET_NOTIFICATION_EMAIL_TO" value="'.(!empty($conf->global->TICKET_NOTIFICATION_EMAIL_TO) ? $conf->global->TICKET_NOTIFICATION_EMAIL_TO : '').'"></td>';
 print '<td class="center">';
@@ -671,7 +675,7 @@ if ($conf->global->MAIN_FEATURES_LEVEL >= 2) {
 
 // Texte d'introduction
 $mail_intro = $conf->global->TICKET_MESSAGE_MAIL_INTRO ? $conf->global->TICKET_MESSAGE_MAIL_INTRO : $langs->trans('TicketMessageMailIntroText');
-print '<tr class="oddeven"><td>'.$langs->trans("TicketMessageMailIntroLabelAdmin").' ('.$langs->trans("Responses").')';
+print '<tr class="oddeven"><td>'.$langs->trans("TicketMessageMailIntroLabelAdmin");
 print '</td><td>';
 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
 $doleditor = new DolEditor('TICKET_MESSAGE_MAIL_INTRO', $mail_intro, '100%', 120, 'dolibarr_mailings', '', false, true, $conf->global->FCKEDITOR_ENABLE_MAIL, ROWS_2, 70);

+ 10 - 4
htdocs/admin/translation.php

@@ -271,7 +271,8 @@ $recordtoshow = array();
 // Search modules dirs
 $modulesdir = dolGetModulesDirs();
 
-$nbtotaloffiles = 0;
+$listoffiles = array();
+$listoffilesexternalmodules = array();
 
 // Search into dir of modules (the $modulesdir is already a list that loop on $conf->file->dol_document_root)
 $i = 0;
@@ -298,7 +299,10 @@ foreach ($modulesdir as $keydir => $tmpsearchdir) {
 		if ($result < 0) {
 			print 'Failed to load language file '.$tmpfile.'<br>'."\n";
 		} else {
-			$nbtotaloffiles++;
+			$listoffiles[$langkey] = $tmpfile;
+			if (strpos($langkey, '@') !== false) {
+				$listoffilesexternalmodules[$langkey] = $tmpfile;
+			}
 		}
 		//print 'After loading lang '.$langkey.', newlang has '.count($newlang->tab_translate).' records<br>'."\n";
 
@@ -307,6 +311,8 @@ foreach ($modulesdir as $keydir => $tmpsearchdir) {
 	$i++;
 }
 
+$nbtotaloffiles = count($listoffiles);
+$nbtotaloffilesexternal = count($listoffilesexternalmodules);
 
 if ($mode == 'overwrite') {
 	print '<input type="hidden" name="page" value="'.$page.'">';
@@ -358,7 +364,7 @@ if ($mode == 'overwrite') {
 	print '</td>';
 	print '<td class="center">';
 	print '<input type="hidden" name="entity" value="'.$conf->entity.'">';
-	print '<input type="submit" class="button"'.$disabled.' value="'.$langs->trans("Add").'" name="add" title="'.dol_escape_htmltag($langs->trans("YouMustEnabledTranslationOverwriteBefore")).'">';
+	print '<input type="submit" class="button"'.$disabled.' value="'.$langs->trans("Add").'" name="add" title="'.dol_escape_htmltag($langs->trans("YouMustEnableTranslationOverwriteBefore")).'">';
 	print "</td>\n";
 	print '</tr>';
 
@@ -477,7 +483,7 @@ if ($mode == 'searchkey') {
 	//print 'param='.$param.' $_SERVER["PHP_SELF"]='.$_SERVER["PHP_SELF"].' num='.$num.' page='.$page.' nbtotalofrecords='.$nbtotalofrecords." sortfield=".$sortfield." sortorder=".$sortorder;
 	$title = $langs->trans("Translation");
 	if ($nbtotalofrecords > 0) {
-		$title .= ' <span class="opacitymedium colorblack paddingleft">('.$nbtotalofrecords.' / '.$nbtotalofrecordswithoutfilters.' - '.$nbtotaloffiles.' '.$langs->trans("Files").')</span>';
+		$title .= ' <span class="opacitymedium colorblack paddingleft">('.$nbtotalofrecords.' / '.$nbtotalofrecordswithoutfilters.' - <span title="'.dol_escape_htmltag(($nbtotaloffiles - $nbtotaloffilesexternal).' core - '.($nbtotaloffilesexternal).' external').'">'.$nbtotaloffiles.' '.$langs->trans("Files").'</span>)</span>';
 	}
 	print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num, -1 * $nbtotalofrecords, '', 0, '', '', $limit, 0, 0, 1);
 

+ 3 - 3
htdocs/admin/website.php

@@ -113,7 +113,7 @@ $tabcond[1] = (!empty($conf->website->enabled));
 
 // List of help for fields
 $tabhelp = array();
-$tabhelp[1] = array('ref'=>$langs->trans("EnterAnyCode"), 'virtualhost'=>$langs->trans("SetHereVirtualHost", DOL_DATA_ROOT.'/website/<i>websiteref</i>'));
+$tabhelp[1] = array('ref'=>$langs->trans("EnterAnyCode"), 'virtualhost'=>$langs->trans("SetHereVirtualHost", DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/website/<i>websiteref</i>'));
 
 // List of check for fields (NOT USED YET)
 $tabfieldcheck = array();
@@ -271,8 +271,8 @@ if (GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) {
 		if ($resql) {
 			$newname = dol_sanitizeFileName(GETPOST('ref', 'aZ09'));
 			if ($newname != $website->ref) {
-				$srcfile = DOL_DATA_ROOT.'/website/'.$website->ref;
-				$destfile = DOL_DATA_ROOT.'/website/'.$newname;
+				$srcfile = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/website/'.$website->ref;
+				$destfile = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/website/'.$newname;
 
 				if (dol_is_dir($destfile)) {
 					$error++;

+ 11 - 1
htdocs/api/class/api_documents.class.php

@@ -611,6 +611,16 @@ class Documents extends DolibarrApi
 
 				require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
 				$object = new FactureFournisseur($this->db);
+			} elseif ($modulepart == 'commande' || $modulepart == 'order') {
+					$modulepart = 'commande';
+
+					require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
+					$object = new Commande($this->db);
+			} elseif ($modulepart == 'commande_fournisseur' || $modulepart == 'supplier_order') {
+					$modulepart = 'supplier_order';
+
+					require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
+					$object = new CommandeFournisseur($this->db);
 			} elseif ($modulepart == 'project') {
 				require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
 				$object = new Project($this->db);
@@ -669,7 +679,7 @@ class Documents extends DolibarrApi
 			}
 
 			// Special cases that need to use get_exdir to get real dir of object
-			// If future, all object should use this to define path of documents.
+			// In future, all object should use this to define path of documents.
 			if ($modulepart == 'supplier_invoice') {
 				$tmpreldir = get_exdir($object->id, 2, 0, 0, $object, 'invoice_supplier');
 			}

+ 13 - 7
htdocs/comm/action/class/ical.class.php

@@ -25,6 +25,7 @@
  *       \brief      File of class to parse ical calendars
  */
 require_once DOL_DOCUMENT_ROOT.'/core/lib/xcal.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
 
 
 /**
@@ -39,6 +40,7 @@ class ICal
 	public $todo_count; // Number of Todos
 	public $freebusy_count; // Number of Freebusy
 	public $last_key; //Help variable save last key (multiline string)
+	public $error;
 
 
 	/**
@@ -61,11 +63,15 @@ class ICal
 		$this->file = $file;
 		$file_text = '';
 
-		$tmparray = file($file);
-		if (is_array($tmparray)) {
-			$file_text = join("", $tmparray); //load file
-			$file_text = preg_replace("/[\r\n]{1,} /", "", $file_text);
+		$tmpresult = getURLContent($file, 'GET');
+		if ($tmpresult['http_code'] != 200) {
+			$file_text = '';
+			$this->error = 'Error: '.$tmpresult['http_code'].' '.$tmpresult['content'];
+		} else {
+			$file_text = preg_replace("/[\r\n]{1,} /", "", $tmpresult['content']);
 		}
+		//var_dump($tmpresult);
+
 		return $file_text; // return all text
 	}
 
@@ -396,19 +402,19 @@ class ICal
 	public function get_event_list()
 	{
 		// phpcs:enable
-		return (!empty($this->cal['VEVENT']) ? $this->cal['VEVENT'] : '');
+		return (empty($this->cal['VEVENT']) ? '' : $this->cal['VEVENT']);
 	}
 
 	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
 	/**
-	 * Return eventlist array (not sort eventlist array)
+	 * Return freebusy array (not sort eventlist array)
 	 *
 	 * @return array
 	 */
 	public function get_freebusy_list()
 	{
 		// phpcs:enable
-		return $this->cal['VFREEBUSY'];
+		return (empty($this->cal['VFREEBUSY']) ? '' : $this->cal['VFREEBUSY']);
 	}
 
 	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps

+ 13 - 13
htdocs/comm/action/index.php

@@ -277,12 +277,12 @@ if (empty($conf->global->AGENDA_DISABLE_EXT)) {
 		if (!empty($conf->global->$source) && !empty($conf->global->$name)) {
 			// Note: $conf->global->buggedfile can be empty or 'uselocalandtznodaylight' or 'uselocalandtzdaylight'
 			$listofextcals[] = array(
-				'src'=>$conf->global->$source,
-				'name'=>$conf->global->$name,
+				'src' => getDolGlobalString($source),
+				'name' => getDolGlobalString($name),
 				'offsettz' => (!empty($conf->global->$offsettz) ? $conf->global->$offsettz : 0),
-				'color'=>$conf->global->$color,
-				'default'=>$conf->global->$default,
-				'buggedfile'=>(isset($conf->global->buggedfile) ? $conf->global->buggedfile : 0)
+				'color' => getDolGlobalString($color),
+				'default' => getDolGlobalString($default),
+				'buggedfile' => (isset($conf->global->buggedfile) ? $conf->global->buggedfile : 0)
 			);
 		}
 	}
@@ -302,12 +302,12 @@ if (empty($user->conf->AGENDA_DISABLE_EXT)) {
 		if (!empty($user->conf->$source) && !empty($user->conf->$name)) {
 			// Note: $conf->global->buggedfile can be empty or 'uselocalandtznodaylight' or 'uselocalandtzdaylight'
 			$listofextcals[] = array(
-				'src'=>$user->conf->$source,
-				'name'=>$user->conf->$name,
+				'src' => $user->conf->$source,
+				'name' => $user->conf->$name,
 				'offsettz' => (!empty($user->conf->$offsettz) ? $user->conf->$offsettz : 0),
-				'color'=>$user->conf->$color,
-				'default'=>$user->conf->$default,
-				'buggedfile'=>(isset($user->conf->buggedfile) ? $user->conf->buggedfile : 0)
+				'color' => $user->conf->$color,
+				'default' => $user->conf->$default,
+				'buggedfile' => (isset($user->conf->buggedfile) ? $user->conf->buggedfile : 0)
 			);
 		}
 	}
@@ -397,7 +397,7 @@ if ($actioncode || GETPOSTISSET('search_actioncode')) {
 if ($resourceid > 0) {
 	$param .= "&search_resourceid=".urlencode($resourceid);
 }
-if ($status || GETPOSTISSET('status')) {
+if ($status || GETPOSTISSET('status') || GETPOSTISSET('search_status')) {
 	$param .= "&search_status=".urlencode($status);
 }
 if ($filter) {
@@ -622,7 +622,7 @@ if (!empty($conf->use_javascript_ajax)) {	// If javascript on
 	$s .= '<div class="nowrap inline-block minheight30"><input type="checkbox" id="check_birthday" name="check_birthday" class="check_birthday"><label for="check_birthday"> <span class="check_birthday_text">'.$langs->trans("AgendaShowBirthdayEvents").'</span></label> &nbsp; </div>';
 
 	// Calendars from hooks
-	$parameters = array(); $object = null;
+	$parameters = array();
 	$reshook = $hookmanager->executeHooks('addCalendarChoice', $parameters, $object, $action);
 	if (empty($reshook)) {
 		$s .= $hookmanager->resPrint;
@@ -1213,7 +1213,7 @@ if (count($listofextcals)) {
 					$addevent = true;
 				} elseif (!is_array($icalevent['DTSTART'])) { // not fullday event (DTSTART is not array. It is a value like '19700101T000000Z' for 00:00 in greenwitch)
 					$datestart = $icalevent['DTSTART'];
-					$dateend = $icalevent['DTEND'];
+					$dateend = empty($icalevent['DTEND']) ? $datestart : $icalevent['DTEND'];
 
 					$datestart += +($offsettz * 3600);
 					$dateend += +($offsettz * 3600);

+ 16 - 12
htdocs/comm/action/pertype.php

@@ -93,15 +93,15 @@ if (empty($user->rights->agenda->allactions->read) || $filter == 'mine') {  // I
 }
 
 $mode = 'show_pertype';
-$resourceid = GETPOST("search_resourceid", "int") ?GETPOST("search_resourceid", "int") : GETPOST("resourceid", "int");
-$year = GETPOST("year", "int") ?GETPOST("year", "int") : date("Y");
-$month = GETPOST("month", "int") ?GETPOST("month", "int") : date("m");
-$week = GETPOST("week", "int") ?GETPOST("week", "int") : date("W");
-$day = GETPOST("day", "int") ?GETPOST("day", "int") : date("d");
-$pid = GETPOST("search_projectid", "int", 3) ?GETPOST("search_projectid", "int", 3) : GETPOST("projectid", "int", 3);
-$status = GETPOST("search_status", 'alpha') ?GETPOST("search_status", 'alpha') : GETPOST("status", 'alpha');
-$type = GETPOST("search_type", 'alpha') ?GETPOST("search_type", 'alpha') : GETPOST("type", 'alpha');
-$maxprint = ((GETPOST("maxprint", 'int') != '') ?GETPOST("maxprint", 'int') : $conf->global->AGENDA_MAX_EVENTS_DAY_VIEW);
+$resourceid = GETPOST("search_resourceid", "int") ? GETPOST("search_resourceid", "int") : GETPOST("resourceid", "int");
+$year = GETPOST("year", "int") ? GETPOST("year", "int") : date("Y");
+$month = GETPOST("month", "int") ? GETPOST("month", "int") : date("m");
+$week = GETPOST("week", "int") ? GETPOST("week", "int") : date("W");
+$day = GETPOST("day", "int") ? GETPOST("day", "int") : date("d");
+$pid = GETPOSTISSET("search_projectid") ? GETPOST("search_projectid", "int", 3) : GETPOST("projectid", "int", 3);
+$status = GETPOSTISSET("search_status") ? GETPOST("search_status", 'alpha') : GETPOST("status", 'alpha');
+$type = GETPOSTISSET("search_type") ? GETPOST("search_type", 'alpha') : GETPOST("type", 'alpha');
+$maxprint = ((GETPOST("maxprint", 'int') != '') ? GETPOST("maxprint", 'int') : $conf->global->AGENDA_MAX_EVENTS_DAY_VIEW);
 $optioncss = GETPOST('optioncss', 'aZ'); // Option for the css output (always '' except when 'print')
 // Set actioncode (this code must be same for setting actioncode into peruser, listacton and index)
 if (GETPOST('search_actioncode', 'array')) {
@@ -144,7 +144,7 @@ $tmparray = explode('-', $tmp);
 $begin_d = 1;
 $end_d = 53;
 
-if ($status == '' && !GETPOSTISSET('status')) {
+if ($status == '' && !GETPOSTISSET('search_status')) {
 	$status = (empty($conf->global->AGENDA_DEFAULT_FILTER_STATUS) ? '' : $conf->global->AGENDA_DEFAULT_FILTER_STATUS);
 }
 if (empty($mode) && !GETPOSTISSET('mode')) {
@@ -164,6 +164,8 @@ if (GETPOST('viewyear', 'alpha') || $mode == 'show_year') {
 	$mode = 'show_year';
 }                                  // View by year
 
+$object = new ActionComm($db);
+
 // Load translation files required by the page
 $langs->loadLangs(array('users', 'agenda', 'other', 'commercial'));
 
@@ -175,6 +177,8 @@ if ($user->socid && $socid) {
 	$result = restrictedArea($user, 'societe', $socid);
 }
 
+$search_status = $status;
+
 
 /*
  * Actions
@@ -276,7 +280,7 @@ if ($actioncode || GETPOSTISSET('search_actioncode')) {
 if ($resourceid > 0) {
 	$param .= "&search_resourceid=".urlencode($resourceid);
 }
-if ($status || GETPOSTISSET('status')) {
+if ($status || GETPOSTISSET('status') || GETPOSTISSET('search_status')) {
 	$param .= "&search_status=".urlencode($status);
 }
 if ($filter) {
@@ -402,7 +406,7 @@ if ($conf->use_javascript_ajax) {
 		//$s.='<div class="nowrap float"><input type="checkbox" id="check_birthday" name="check_birthday"> '.$langs->trans("AgendaShowBirthdayEvents").' &nbsp; </div>';
 
 		// Calendars from hooks
-		$parameters = array(); $object = null;
+		$parameters = array();
 		$reshook = $hookmanager->executeHooks('addCalendarChoice', $parameters, $object, $action);
 		if (empty($reshook)) {
 			$s .= $hookmanager->resPrint;

+ 13 - 9
htdocs/comm/action/peruser.php

@@ -98,9 +98,9 @@ $year = GETPOST("year", "int") ?GETPOST("year", "int") : date("Y");
 $month = GETPOST("month", "int") ?GETPOST("month", "int") : date("m");
 $week = GETPOST("week", "int") ?GETPOST("week", "int") : date("W");
 $day = GETPOST("day", "int") ?GETPOST("day", "int") : date("d");
-$pid = GETPOST("search_projectid", "int", 3) ?GETPOST("search_projectid", "int", 3) : GETPOST("projectid", "int", 3);
-$status = GETPOST("search_status", 'alpha') ?GETPOST("search_status", 'alpha') : GETPOST("status", 'alpha');
-$type = GETPOST("search_type", 'alpha') ?GETPOST("search_type", 'alpha') : GETPOST("type", 'alpha');
+$pid = GETPOSTISSET("search_projectid") ? GETPOST("search_projectid", "int", 3) : GETPOST("projectid", "int", 3);
+$status = GETPOSTISSET("search_status") ? GETPOST("search_status", 'alpha') : GETPOST("status", 'alpha');
+$type = GETPOSTISSET("search_type") ? GETPOST("search_type", 'alpha') : GETPOST("type", 'alpha');
 $maxprint = ((GETPOST("maxprint", 'int') != '') ?GETPOST("maxprint", 'int') : $conf->global->AGENDA_MAX_EVENTS_DAY_VIEW);
 $optioncss = GETPOST('optioncss', 'aZ'); // Option for the css output (always '' except when 'print')
 // Set actioncode (this code must be same for setting actioncode into peruser, listacton and index)
@@ -153,9 +153,10 @@ if ($end_d < $begin_d) {
 	$end_d = $begin_d + 1;
 }
 
-if ($status == '' && !GETPOSTISSET('status')) {
+if ($status == '' && !GETPOSTISSET('search_status')) {
 	$status = (empty($conf->global->AGENDA_DEFAULT_FILTER_STATUS) ? '' : $conf->global->AGENDA_DEFAULT_FILTER_STATUS);
 }
+
 if (empty($mode) && !GETPOSTISSET('mode')) {
 	$mode = (empty($conf->global->AGENDA_DEFAULT_VIEW) ? 'show_month' : $conf->global->AGENDA_DEFAULT_VIEW);
 }
@@ -170,6 +171,8 @@ if (GETPOST('viewday', 'alpha') || $mode == 'show_day') {
 	$mode = 'show_day'; $day = ($day ? $day : date("d"));
 } // View by day
 
+$object = new ActionComm($db);
+
 // Load translation files required by the page
 $langs->loadLangs(array('users', 'agenda', 'other', 'commercial'));
 
@@ -181,6 +184,8 @@ if ($user->socid && $socid) {
 	$result = restrictedArea($user, 'societe', $socid);
 }
 
+$search_status = $status;
+
 
 /*
  * Actions
@@ -282,7 +287,8 @@ if ($actioncode || GETPOSTISSET('search_actioncode')) {
 if ($resourceid > 0) {
 	$param .= "&search_resourceid=".urlencode($resourceid);
 }
-if ($status || GETPOSTISSET('status')) {
+
+if ($status || GETPOSTISSET('status') || GETPOSTISSET('search_status')) {
 	$param .= "&search_status=".urlencode($status);
 }
 if ($filter) {
@@ -412,7 +418,7 @@ if ($conf->use_javascript_ajax) {
 		//$s.='<div class="nowrap float"><input type="checkbox" id="check_birthday" name="check_birthday"> '.$langs->trans("AgendaShowBirthdayEvents").' &nbsp; </div>';
 
 		// Calendars from hooks
-		$parameters = array(); $object = null;
+		$parameters = array();
 		$reshook = $hookmanager->executeHooks('addCalendarChoice', $parameters, $object, $action);
 		if (empty($reshook)) {
 			$s .= $hookmanager->resPrint;
@@ -509,9 +515,7 @@ $s = $newtitle;
 print $s;
 
 print '<div class="liste_titre liste_titre_bydiv centpercent">';
-if (empty($search_status)) {
-	$search_status = '';
-}
+
 print_actions_filter($form, $canedit, $search_status, $year, $month, $day, $showbirthday, 0, $filtert, 0, $pid, $socid, $action, -1, $actioncode, $usergroup, '', $resourceid);
 print '</div>';
 

+ 6 - 6
htdocs/comm/propal/card.php

@@ -1646,17 +1646,17 @@ if ($action == 'create') {
 	print '</td></tr>';
 
 	// Validaty duration
-	print '<tr><td class="fieldrequired">'.$langs->trans("ValidityDuration").'</td><td>'.img_picto('', 'clock').'&ensp;<input name="duree_validite" class="width50" value="'.(GETPOSTISSET('duree_validite') ? GETPOST('duree_validite', 'alphanohtml') : $conf->global->PROPALE_VALIDITY_DURATION).'"> '.$langs->trans("days").'</td></tr>';
+	print '<tr><td class="fieldrequired">'.$langs->trans("ValidityDuration").'</td><td>'.img_picto('', 'clock', 'class="paddingright"').'<input name="duree_validite" class="width50" value="'.(GETPOSTISSET('duree_validite') ? GETPOST('duree_validite', 'alphanohtml') : $conf->global->PROPALE_VALIDITY_DURATION).'"> '.$langs->trans("days").'</td></tr>';
 
 	// Terms of payment
 	print '<tr><td class="nowrap">'.$langs->trans('PaymentConditionsShort').'</td><td>';
-	print img_picto('', 'paiment');
+	print img_picto('', 'payment', 'class="pictofixedwidth"');
 	$form->select_conditions_paiements((GETPOSTISSET('cond_reglement_id') && GETPOST('cond_reglement_id') != 0) ? GETPOST('cond_reglement_id', 'int') : $soc->cond_reglement_id, 'cond_reglement_id', -1, 1);
 	print '</td></tr>';
 
 	// Mode of payment
 	print '<tr><td>'.$langs->trans('PaymentMode').'</td><td>';
-	print img_picto('', 'bank').'&ensp;';
+	print img_picto('', 'bank', 'class="pictofixedwidth"');
 	$form->select_types_paiements((GETPOSTISSET('mode_reglement_id') ? GETPOST('mode_reglement_id', 'int') : $soc->mode_reglement_id), 'mode_reglement_id', 'CRDT', 0, 1, 0, 0, 1, 'maxwidth200 widthcentpercentminusx');
 	print '</td></tr>';
 
@@ -1679,7 +1679,7 @@ if ($action == 'create') {
 		print ' ('.$langs->trans('AfterOrder').')';
 	}
 	print '</td><td>';
-	print img_picto('', 'clock').'&ensp;';
+	print img_picto('', 'clock', 'class="pictofixedwidth"');
 	$form->selectAvailabilityDelay('', 'availability_id', '', 1, 'maxwidth200 widthcentpercentminusx');
 	print '</td></tr>';
 
@@ -1741,7 +1741,7 @@ if ($action == 'create') {
 	print '<tr>';
 	print '<td>'.$langs->trans("DefaultModel").'</td>';
 	print '<td>';
-	print img_picto('', 'pdf').'&ensp;';
+	print img_picto('', 'pdf', 'class="pictofixedwidth"');
 	$liste = ModelePDFPropales::liste_modeles($db);
 	$preselected = (!empty($conf->global->PROPALE_ADDON_PDF_ODT_DEFAULT) ? $conf->global->PROPALE_ADDON_PDF_ODT_DEFAULT : getDolGlobalString("PROPALE_ADDON_PDF"));
 	print $form->selectarray('model', $liste, $preselected, 0, 0, 0, '', 0, 0, 0, '', 'maxwidth200 widthcentpercentminusx', 1);
@@ -1752,7 +1752,7 @@ if ($action == 'create') {
 		print '<tr>';
 		print '<td>'.$form->editfieldkey('Currency', 'multicurrency_code', '', $object, 0).'</td>';
 		print '<td class="maxwidthonsmartphone">';
-		print $form->selectMultiCurrency($currency_code, 'multicurrency_code', 0);
+		print img_picto('', 'currency', 'class="pictofixedwidth"').$form->selectMultiCurrency($currency_code, 'multicurrency_code', 0);
 		print '</td></tr>';
 	}
 

+ 5 - 5
htdocs/comm/propal/list.php

@@ -1043,7 +1043,7 @@ if ($resql) {
 	$moreforfilter = '';
 
 	// If the user can view prospects other than his'
-	if ($user->rights->societe->client->voir || $socid) {
+	if ($user->rights->user->user->lire) {
 		$langs->load("commercial");
 		$moreforfilter .= '<div class="divsearchfield">';
 		$tmptitle = $langs->trans('ThirdPartiesOfSaleRepresentative');
@@ -1051,9 +1051,9 @@ if ($resql) {
 		$moreforfilter .= '</div>';
 	}
 	// If the user can view prospects other than his'
-	if ($user->rights->societe->client->voir || $socid) {
+	if ($user->rights->user->user->lire) {
 		$moreforfilter .= '<div class="divsearchfield">';
-		$tmptitle =  $langs->trans('LinkedToSpecificUsers');
+		$tmptitle = $langs->trans('LinkedToSpecificUsers');
 		$moreforfilter .= img_picto($tmptitle, 'user', 'class="pictofixedwidth"').$form->select_dolusers($search_user, 'search_user', $tmptitle, '', 0, '', '', 0, 0, 0, '', 0, '', 'maxwidth250 widthcentpercentminusx');
 		$moreforfilter .= '</div>';
 	}
@@ -2097,7 +2097,7 @@ if ($resql) {
 		// Note public
 		if (!empty($arrayfields['p.note_public']['checked'])) {
 			print '<td class="center">';
-			print dol_escape_htmltag($obj->note_public);
+			print dol_string_nohtmltag($obj->note_public);
 			print '</td>';
 			if (!$i) {
 				$totalarray['nbfield']++;
@@ -2106,7 +2106,7 @@ if ($resql) {
 		// Note private
 		if (!empty($arrayfields['p.note_private']['checked'])) {
 			print '<td class="center">';
-			print dol_escape_htmltag($obj->note_private);
+			print dol_string_nohtmltag($obj->note_private);
 			print '</td>';
 			if (!$i) {
 				$totalarray['nbfield']++;

+ 3 - 3
htdocs/commande/card.php

@@ -1670,7 +1670,7 @@ if ($action == 'create' && $usercancreate) {
 
 	// Terms of the settlement
 	print '<tr><td class="nowrap">'.$langs->trans('PaymentConditionsShort').'</td><td>';
-	print img_picto('', 'paiment');
+	print img_picto('', 'payment', 'class="pictofixedwidth"');
 	$form->select_conditions_paiements($cond_reglement_id, 'cond_reglement_id', - 1, 1);
 	print '</td></tr>';
 
@@ -1759,10 +1759,10 @@ if ($action == 'create' && $usercancreate) {
 	// Template to use by default
 	print '<tr><td>'.$langs->trans('DefaultModel').'</td>';
 	print '<td>';
-	print img_picto('', 'pdf', 'class="pictofixedwidth"');
 	include_once DOL_DOCUMENT_ROOT.'/core/modules/commande/modules_commande.php';
 	$liste = ModelePDFCommandes::liste_modeles($db);
 	$preselected = $conf->global->COMMANDE_ADDON_PDF;
+	print img_picto('', 'pdf', 'class="pictofixedwidth"');
 	print $form->selectarray('model', $liste, $preselected, 0, 0, 0, '', 0, 0, 0, '', 'maxwidth200 widthcentpercentminusx', 1);
 	print "</td></tr>";
 
@@ -1771,7 +1771,7 @@ if ($action == 'create' && $usercancreate) {
 		print '<tr>';
 		print '<td>'.$form->editfieldkey("Currency", 'multicurrency_code', '', $object, 0).'</td>';
 		print '<td class="maxwidthonsmartphone">';
-		print $form->selectMultiCurrency($currency_code, 'multicurrency_code');
+		print img_picto('', 'currency', 'class="pictofixedwidth"').$form->selectMultiCurrency($currency_code, 'multicurrency_code');
 		print '</td></tr>';
 	}
 

+ 347 - 1
htdocs/commande/list.php

@@ -302,6 +302,352 @@ if (empty($reshook)) {
 	$uploaddir = $conf->commande->multidir_output[$conf->entity];
 	$triggersendname = 'ORDER_SENTBYMAIL';
 	include DOL_DOCUMENT_ROOT.'/core/actions_massactions.inc.php';
+
+	if ($massaction == 'confirm_createbills') {   // Create bills from orders.
+		$orders = GETPOST('toselect', 'array');
+		$createbills_onebythird = GETPOST('createbills_onebythird', 'int');
+		$validate_invoices = GETPOST('validate_invoices', 'int');
+
+		$errors = array();
+
+		$TFact = array();
+		$TFactThird = array();
+		$TFactThirdNbLines = array();
+
+		$nb_bills_created = 0;
+		$lastid= 0;
+		$lastref = '';
+
+		$db->begin();
+
+		foreach ($orders as $id_order) {
+			$cmd = new Commande($db);
+			if ($cmd->fetch($id_order) <= 0) {
+				continue;
+			}
+			$cmd->fetch_thirdparty();
+
+			$objecttmp = new Facture($db);
+			if (!empty($createbills_onebythird) && !empty($TFactThird[$cmd->socid])) {
+				// If option "one bill per third" is set, and an invoice for this thirdparty was already created, we re-use it.
+				$objecttmp = $TFactThird[$cmd->socid];
+			} else {
+				// If we want one invoice per order or if there is no first invoice yet for this thirdparty.
+				$objecttmp->socid = $cmd->socid;
+				$objecttmp->thirdparty = $cmd->thirdparty;
+
+				$objecttmp->type = $objecttmp::TYPE_STANDARD;
+				$objecttmp->cond_reglement_id = !empty($cmd->cond_reglement_id) ? $cmd->cond_reglement_id : $cmd->thirdparty->cond_reglement_id;
+				$objecttmp->mode_reglement_id = !empty($cmd->mode_reglement_id) ? $cmd->mode_reglement_id : $cmd->thirdparty->mode_reglement_id;
+
+				$objecttmp->fk_project = $cmd->fk_project;
+				$objecttmp->multicurrency_code = $cmd->multicurrency_code;
+				if (empty($createbills_onebythird)) {
+					$objecttmp->ref_client = $cmd->ref_client;
+				}
+
+				$datefacture = dol_mktime(12, 0, 0, GETPOST('remonth', 'int'), GETPOST('reday', 'int'), GETPOST('reyear', 'int'));
+				if (empty($datefacture)) {
+					$datefacture = dol_now();
+				}
+
+				$objecttmp->date = $datefacture;
+				$objecttmp->origin    = 'commande';
+				$objecttmp->origin_id = $id_order;
+
+				$objecttmp->array_options = $cmd->array_options; // Copy extrafields
+
+				$res = $objecttmp->create($user);
+
+				if ($res > 0) {
+					$nb_bills_created++;
+					$lastref = $objecttmp->ref;
+					$lastid = $objecttmp->id;
+
+					$TFactThird[$cmd->socid] = $objecttmp;
+					$TFactThirdNbLines[$cmd->socid] = 0; //init nblines to have lines ordered by expedition and rang
+				} else {
+					$langs->load("errors");
+					$errors[] = $cmd->ref.' : '.$langs->trans($objecttmp->errors[0]);
+					$error++;
+				}
+			}
+
+			if ($objecttmp->id > 0) {
+				$res = $objecttmp->add_object_linked($objecttmp->origin, $id_order);
+
+				if ($res == 0) {
+					$errors[] = $cmd->ref.' : '.$langs->trans($objecttmp->errors[0]);
+					$error++;
+				}
+
+				if (!$error) {
+					$lines = $cmd->lines;
+					if (empty($lines) && method_exists($cmd, 'fetch_lines')) {
+						$cmd->fetch_lines();
+						$lines = $cmd->lines;
+					}
+
+					$fk_parent_line = 0;
+					$num = count($lines);
+
+					for ($i = 0; $i < $num; $i++) {
+						$desc = ($lines[$i]->desc ? $lines[$i]->desc : '');
+						// If we build one invoice for several orders, we must put the ref of order on the invoice line
+						if (!empty($createbills_onebythird)) {
+							$desc = dol_concatdesc($desc, $langs->trans("Order").' '.$cmd->ref.' - '.dol_print_date($cmd->date, 'day'));
+						}
+
+						if ($lines[$i]->subprice < 0) {
+							// Negative line, we create a discount line
+							$discount = new DiscountAbsolute($db);
+							$discount->fk_soc = $objecttmp->socid;
+							$discount->amount_ht = abs($lines[$i]->total_ht);
+							$discount->amount_tva = abs($lines[$i]->total_tva);
+							$discount->amount_ttc = abs($lines[$i]->total_ttc);
+							$discount->tva_tx = $lines[$i]->tva_tx;
+							$discount->fk_user = $user->id;
+							$discount->description = $desc;
+							$discountid = $discount->create($user);
+							if ($discountid > 0) {
+								$result = $objecttmp->insert_discount($discountid);
+								//$result=$discount->link_to_invoice($lineid,$id);
+							} else {
+								setEventMessages($discount->error, $discount->errors, 'errors');
+								$error++;
+								break;
+							}
+						} else {
+							// Positive line
+							$product_type = ($lines[$i]->product_type ? $lines[$i]->product_type : 0);
+							// Date start
+							$date_start = false;
+							if ($lines[$i]->date_debut_prevue) {
+								$date_start = $lines[$i]->date_debut_prevue;
+							}
+							if ($lines[$i]->date_debut_reel) {
+								$date_start = $lines[$i]->date_debut_reel;
+							}
+							if ($lines[$i]->date_start) {
+								$date_start = $lines[$i]->date_start;
+							}
+							//Date end
+							$date_end = false;
+							if ($lines[$i]->date_fin_prevue) {
+								$date_end = $lines[$i]->date_fin_prevue;
+							}
+							if ($lines[$i]->date_fin_reel) {
+								$date_end = $lines[$i]->date_fin_reel;
+							}
+							if ($lines[$i]->date_end) {
+								$date_end = $lines[$i]->date_end;
+							}
+							// Reset fk_parent_line for no child products and special product
+							if (($lines[$i]->product_type != 9 && empty($lines[$i]->fk_parent_line)) || $lines[$i]->product_type == 9) {
+								$fk_parent_line = 0;
+							}
+
+							// Extrafields
+							if (method_exists($lines[$i], 'fetch_optionals')) {
+								$lines[$i]->fetch_optionals();
+								$array_options = $lines[$i]->array_options;
+							}
+
+							$objecttmp->context['createfromclone'];
+
+							$rang = $lines[$i]->rang;
+							//there may already be rows from previous orders
+							if (!empty($createbills_onebythird))
+								$rang = $TFactThirdNbLines[$cmd->socid];
+
+							$result = $objecttmp->addline(
+								$desc,
+								$lines[$i]->subprice,
+								$lines[$i]->qty,
+								$lines[$i]->tva_tx,
+								$lines[$i]->localtax1_tx,
+								$lines[$i]->localtax2_tx,
+								$lines[$i]->fk_product,
+								$lines[$i]->remise_percent,
+								$date_start,
+								$date_end,
+								0,
+								$lines[$i]->info_bits,
+								$lines[$i]->fk_remise_except,
+								'HT',
+								0,
+								$product_type,
+								$rang,
+								$lines[$i]->special_code,
+								$objecttmp->origin,
+								$lines[$i]->rowid,
+								$fk_parent_line,
+								$lines[$i]->fk_fournprice,
+								$lines[$i]->pa_ht,
+								$lines[$i]->label,
+								$array_options,
+								100,
+								0,
+								$lines[$i]->fk_unit
+							);
+							if ($result > 0) {
+								$lineid = $result;
+								if (!empty($createbills_onebythird)) //increment rang to keep order
+									$TFactThirdNbLines[$rcp->socid]++;
+							} else {
+								$lineid = 0;
+								$error++;
+								break;
+							}
+							// Defined the new fk_parent_line
+							if ($result > 0 && $lines[$i]->product_type == 9) {
+								$fk_parent_line = $result;
+							}
+						}
+					}
+				}
+			}
+
+			//$cmd->classifyBilled($user);        // Disabled. This behavior must be set or not using the workflow module.
+
+			if (!empty($createbills_onebythird) && empty($TFactThird[$cmd->socid])) {
+				$TFactThird[$cmd->socid] = $objecttmp;
+			} else {
+				$TFact[$objecttmp->id] = $objecttmp;
+			}
+		}
+
+		// Build doc with all invoices
+		$TAllFact = empty($createbills_onebythird) ? $TFact : $TFactThird;
+		$toselect = array();
+
+		if (!$error && $validate_invoices) {
+			$massaction = $action = 'builddoc';
+
+			foreach ($TAllFact as &$objecttmp) {
+				$result = $objecttmp->validate($user);
+				if ($result <= 0) {
+					$error++;
+					setEventMessages($objecttmp->error, $objecttmp->errors, 'errors');
+					break;
+				}
+
+				$id = $objecttmp->id; // For builddoc action
+
+				// Builddoc
+				$donotredirect = 1;
+				$upload_dir = $conf->facture->dir_output;
+				$permissiontoadd = $user->rights->facture->creer;
+
+				// Call action to build doc
+				$savobject = $object;
+				$object = $objecttmp;
+				include DOL_DOCUMENT_ROOT.'/core/actions_builddoc.inc.php';
+				$object = $savobject;
+			}
+
+			$massaction = $action = 'confirm_createbills';
+		}
+
+		if (!$error) {
+			$db->commit();
+
+			if ($nb_bills_created == 1) {
+				$texttoshow = $langs->trans('BillXCreated', '{s1}');
+				$texttoshow = str_replace('{s1}', '<a href="'.DOL_URL_ROOT.'/compta/facture/card.php?id='.urlencode($lastid).'">'.$lastref.'</a>', $texttoshow);
+				setEventMessages($texttoshow, null, 'mesgs');
+			} else {
+				setEventMessages($langs->trans('BillCreated', $nb_bills_created), null, 'mesgs');
+			}
+
+			// Make a redirect to avoid to bill twice if we make a refresh or back
+			$param = '';
+			if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) {
+				$param .= '&contextpage='.urlencode($contextpage);
+			}
+			if ($limit > 0 && $limit != $conf->liste_limit) {
+				$param .= '&limit='.urlencode($limit);
+			}
+			if ($sall) {
+				$param .= '&sall='.urlencode($sall);
+			}
+			if ($socid > 0) {
+				$param .= '&socid='.urlencode($socid);
+			}
+			if ($search_status != '') {
+				$param .= '&search_status='.urlencode($search_status);
+			}
+			if ($search_orderday) {
+				$param .= '&search_orderday='.urlencode($search_orderday);
+			}
+			if ($search_ordermonth) {
+				$param .= '&search_ordermonth='.urlencode($search_ordermonth);
+			}
+			if ($search_orderyear) {
+				$param .= '&search_orderyear='.urlencode($search_orderyear);
+			}
+			if ($search_deliveryday) {
+				$param .= '&search_deliveryday='.urlencode($search_deliveryday);
+			}
+			if ($search_deliverymonth) {
+				$param .= '&search_deliverymonth='.urlencode($search_deliverymonth);
+			}
+			if ($search_deliveryyear) {
+				$param .= '&search_deliveryyear='.urlencode($search_deliveryyear);
+			}
+			if ($search_ref) {
+				$param .= '&search_ref='.urlencode($search_ref);
+			}
+			if ($search_company) {
+				$param .= '&search_company='.urlencode($search_company);
+			}
+			if ($search_ref_customer) {
+				$param .= '&search_ref_customer='.urlencode($search_ref_customer);
+			}
+			if ($search_user > 0) {
+				$param .= '&search_user='.urlencode($search_user);
+			}
+			if ($search_sale > 0) {
+				$param .= '&search_sale='.urlencode($search_sale);
+			}
+			if ($search_total_ht != '') {
+				$param .= '&search_total_ht='.urlencode($search_total_ht);
+			}
+			if ($search_total_vat != '') {
+				$param .= '&search_total_vat='.urlencode($search_total_vat);
+			}
+			if ($search_total_ttc != '') {
+				$param .= '&search_total_ttc='.urlencode($search_total_ttc);
+			}
+			if ($search_project_ref >= 0) {
+				$param .= "&search_project_ref=".urlencode($search_project_ref);
+			}
+			if ($show_files) {
+				$param .= '&show_files='.urlencode($show_files);
+			}
+			if ($optioncss != '') {
+				$param .= '&optioncss='.urlencode($optioncss);
+			}
+			if ($billed != '') {
+				$param .= '&billed='.urlencode($billed);
+			}
+
+			header("Location: ".$_SERVER['PHP_SELF'].'?'.$param);
+			exit;
+		} else {
+			$db->rollback();
+
+			$action = 'create';
+			$_GET["origin"] = $_POST["origin"];
+			$_GET["originid"] = $_POST["originid"];
+			if (!empty($errors)) {
+				setEventMessages(null, $errors, 'errors');
+			} else {
+				setEventMessages("Error", null, 'errors');
+			}
+			$error++;
+		}
+	}
 }
 if ($action == 'validate' && $permissiontoadd) {
 	if (GETPOST('confirm') == 'yes') {
@@ -975,7 +1321,7 @@ if ($resql) {
 	$moreforfilter = '';
 
 	// If the user can view prospects other than his'
-	if ($user->rights->societe->client->voir || $socid) {
+	if ($user->rights->user->user->lire) {
 		$langs->load("commercial");
 		$moreforfilter .= '<div class="divsearchfield">';
 		$tmptitle = $langs->trans('ThirdPartiesOfSaleRepresentative');

+ 5 - 4
htdocs/compta/bank/card.php

@@ -778,13 +778,14 @@ if ($action == 'create') {
 			print '</td></tr>';
 
 			if ($conf->prelevement->enabled) {
-				print '<tr><td>'.$langs->trans("ICS").' ('.$langs->trans("StandingOrder").')</td>';
+				print '<tr><td>'.$form->textwithpicto($langs->trans("ICS"), $langs->trans("ICS").' ('.$langs->trans("UsedFor", $langs->transnoentitiesnoconv("StandingOrder")).')').'</td>';
 				print '<td>'.$object->ics.'</td>';
 				print '</tr>';
 			}
 
+			// TODO ICS is not used with bank transfer !
 			if ($conf->paymentbybanktransfer->enabled) {
-				print '<tr><td>'.$langs->trans("ICS").' ('.$langs->trans("BankTransfer").')</td>';
+				print '<tr><td>'.$form->textwithpicto($langs->trans("IDS"), $langs->trans("IDS").' ('.$langs->trans("UsedFor", $langs->transnoentitiesnoconv("BankTransfer")).')').'</td>';
 				print '<td>'.$object->ics_transfer.'</td>';
 				print '</tr>';
 
@@ -1082,12 +1083,12 @@ if ($action == 'create') {
 			print '<td><input class="minwidth150 maxwidth200onsmartphone" maxlength="11" type="text" class="flat" name="bic" value="'.(GETPOSTISSET('bic') ? GETPOST('bic',  'alphanohtml') : $object->bic).'"></td></tr>';
 
 			if ($conf->prelevement->enabled) {
-				print '<tr><td>'.$langs->trans("ICS").' ('.$langs->trans("StandingOrder").')</td>';
+				print '<tr><td>'.$form->textwithpicto($langs->trans("ICS"), $langs->trans("ICS").' ('.$langs->trans("UsedFor", $langs->transnoentitiesnoconv("StandingOrder")).')').'</td>';
 				print '<td><input class="minwidth150 maxwidth200onsmartphone" maxlength="32" type="text" class="flat" name="ics" value="'.(GETPOSTISSET('ics') ? GETPOST('ics', 'alphanohtml') : $object->ics).'"></td></tr>';
 			}
 
 			if ($conf->paymentbybanktransfer->enabled) {
-				print '<tr><td>'.$langs->trans("ICS").' ('.$langs->trans("BankTransfer").')</td>';
+				print '<tr><td>'.$form->textwithpicto($langs->trans("IDS"), $langs->trans("IDS").' ('.$langs->trans("UsedFor", $langs->transnoentitiesnoconv("BankTransfer")).')').'</td>';
 				print '<td><input class="minwidth150 maxwidth200onsmartphone" maxlength="32" type="text" class="flat" name="ics_transfer" value="'.(GETPOSTISSET('ics_transfer') ? GETPOST('ics_transfer', 'alphanohtml') : $object->ics_transfer).'"></td></tr>';
 
 				print '<tr><td>'.$langs->trans("SEPAXMLPlacePaymentTypeInformationInCreditTransfertransactionInformation").'</td>';

+ 8 - 4
htdocs/compta/bank/list.php

@@ -587,10 +587,14 @@ foreach ($accounts as $key => $type) {
 	// Accountancy journal
 	if (!empty($arrayfields['b.fk_accountancy_journal']['checked'])) {
 		print '<td>';
-		if (!empty($conf->accounting->enabled) && !empty($objecttmp->fk_accountancy_journal)) {
-			$accountingjournal = new AccountingJournal($db);
-			$accountingjournal->fetch($objecttmp->fk_accountancy_journal);
-			print $accountingjournal->getNomUrl(0, 1, 1, '', 1);
+		if (!empty($conf->accounting->enabled)) {
+			if (empty($objecttmp->fk_accountancy_journal)) {
+				print img_warning($langs->trans("Mandatory"));
+			} else {
+				$accountingjournal = new AccountingJournal($db);
+				$accountingjournal->fetch($objecttmp->fk_accountancy_journal);
+				print $accountingjournal->getNomUrl(0, 1, 1, '', 1);
+			}
 		} else {
 			print '';
 		}

+ 18 - 16
htdocs/compta/facture/card-rec.php

@@ -641,7 +641,7 @@ if (empty($reshook)) {
 				setEventMessages($mesg, null, 'errors');
 			} else {
 				// Insert line
-				$result = $object->addline($desc, $pu_ht, $qty, $tva_tx, $localtax1_tx, $localtax2_tx, $idprod, $remise_percent, $price_base_type, $info_bits, '', $pu_ttc, $type, - 1, $special_code, $label, $fk_unit, 0, $date_start_fill, $date_end_fill, $fournprice, $buyingprice);
+				$result = $object->addline($desc, $pu_ht, $qty, $tva_tx, $localtax1_tx, $localtax2_tx, $idprod, $remise_percent, $price_base_type, $info_bits, '', $pu_ttc, $type, -1, $special_code, $label, $fk_unit, 0, $date_start_fill, $date_end_fill, $fournprice, $buyingprice);
 
 				if ($result > 0) {
 					// Define output language and generate document
@@ -1034,32 +1034,34 @@ if ($action == 'create') {
 
 		// Payment mode
 		print "<tr><td>".$langs->trans("PaymentMode")."</td><td>";
+		print img_picto('', 'payment', 'class="pictofixedwidth"');
 		print $form->select_types_paiements(GETPOSTISSET('mode_reglement_id') ? GETPOST('mode_reglement_id', 'int') : $object->mode_reglement_id, 'mode_reglement_id', '', 0, 1, 0, 0, 1, '', 1);
 		//$form->form_modes_reglement($_SERVER['PHP_SELF'].'?id='.$object->id, $object->mode_reglement_id, 'mode_reglement_id', '', 1);
 		print "</td></tr>";
 
+		// Bank account
+		if ($object->fk_account > 0) {
+			print "<tr><td>".$langs->trans('BankAccount')."</td><td>";
+			$form->formSelectAccount($_SERVER['PHP_SELF'].'?id='.$object->id, $object->fk_account, 'none');
+			print "</td></tr>";
+		}
+
 		// Project
 		if (!empty($conf->projet->enabled) && is_object($object->thirdparty) && $object->thirdparty->id > 0) {
 			$projectid = GETPOST('projectid') ?GETPOST('projectid') : $object->fk_project;
 			$langs->load('projects');
 			print '<tr><td>'.$langs->trans('Project').'</td><td>';
-			print img_picto('', 'project');
+			print img_picto('', 'project', 'class="pictofixedwidth"');
 			$numprojet = $formproject->select_projects($object->thirdparty->id, $projectid, 'projectid', 0, 0, 1, 0, 0, 0, 0, '', 0, 0, '');
 			print ' &nbsp; <a href="'.DOL_URL_ROOT.'/projet/card.php?socid='.$object->thirdparty->id.'&action=create&status=1&backtopage='.urlencode($_SERVER["PHP_SELF"].'?action=create&socid='.$object->thirdparty->id.(!empty($id) ? '&id='.$id : '')).'">'.img_object($langs->trans("AddProject"), 'add').'</a>';
 			print '</td></tr>';
 		}
 
-		// Bank account
-		if ($object->fk_account > 0) {
-			print "<tr><td>".$langs->trans('BankAccount')."</td><td>";
-			$form->formSelectAccount($_SERVER['PHP_SELF'].'?id='.$object->id, $object->fk_account, 'none');
-			print "</td></tr>";
-		}
-
 		// Model pdf
 		print "<tr><td>".$langs->trans('Model')."</td><td>";
 		include_once DOL_DOCUMENT_ROOT.'/core/modules/facture/modules_facture.php';
 		$list = ModelePDFFactures::liste_modeles($db);
+		print img_picto('', 'generic', 'class="pictofixedwidth"');
 		print $form->selectarray('modelpdf', $list, $conf->global->FACTURE_ADDON_PDF);
 		print "</td></tr>";
 
@@ -1605,19 +1607,19 @@ if ($action == 'create') {
 
 
 		// Lines
-		print '	<form name="addproduct" id="addproduct" action="'.$_SERVER["PHP_SELF"].'?id='.$object->id.(($action != 'editline') ? '#add' : '#line_'.GETPOST('lineid', 'int')).'" method="POST">
-        	<input type="hidden" name="token" value="' . newToken().'">
-        	<input type="hidden" name="action" value="' . (($action != 'editline') ? 'addline' : 'updateline').'">
-        	<input type="hidden" name="mode" value="">
-        	<input type="hidden" name="id" value="' . $object->id.'">
-        	';
+		print '<form name="addproduct" id="addproduct" action="'.$_SERVER["PHP_SELF"].'?id='.$object->id.(($action != 'editline') ? '#add' : '#line_'.GETPOST('lineid', 'int')).'" method="POST">';
+		print '<input type="hidden" name="token" value="' . newToken().'">';
+		print '<input type="hidden" name="action" value="' . (($action != 'editline') ? 'addline' : 'updateline').'">';
+		print '<input type="hidden" name="mode" value="">';
+		print '<input type="hidden" name="id" value="' . $object->id.'">';
+		print '<input type="hidden" name="page_y" value="">';
 
 		if (!empty($conf->use_javascript_ajax) && $object->statut == 0) {
 			include DOL_DOCUMENT_ROOT.'/core/tpl/ajaxrow.tpl.php';
 		}
 
 		print '<div class="div-table-responsive-no-min">';
-		print '<table id="tablelines" class="noborder noshadow" width="100%">';
+		print '<table id="tablelines" class="noborder noshadow centpercent">';
 		// Show object lines
 		if (!empty($object->lines)) {
 			$canchangeproduct = 1;

+ 29 - 9
htdocs/compta/facture/card.php

@@ -987,6 +987,10 @@ if (empty($reshook)) {
 		}
 		$selectedLines = GETPOST('toselect', 'array');
 
+		if (GETPOST('type', 'int') === '') {
+			setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Type")), null, 'errors');
+		}
+
 		$db->begin();
 
 		$error = 0;
@@ -1571,8 +1575,15 @@ if (empty($reshook)) {
 									0,
 									0,
 									0,
-									0
-									//,$langs->trans('Deposit') //Deprecated
+									0,
+									'',
+									0,
+									100,
+									0,
+									null,
+									0,
+									'',
+									1
 								);
 							}
 
@@ -1739,7 +1750,10 @@ if (empty($reshook)) {
 											$array_options,
 											$lines[$i]->situation_percent,
 											$lines[$i]->fk_prev_id,
-											$lines[$i]->fk_unit
+											$lines[$i]->fk_unit,
+											0,
+											'',
+											1
 										);
 
 										if ($result > 0) {
@@ -1762,6 +1776,8 @@ if (empty($reshook)) {
 							}
 						}
 
+						$object->update_price(1, 'auto', 0, $mysoc);
+
 						// Now we create same links to contact than the ones found on origin object
 						/* Useless, already into the create
 						if (! empty($conf->global->MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN))
@@ -1809,9 +1825,11 @@ if (empty($reshook)) {
 							$product->fetch(GETPOST('idprod'.$i, 'int'));
 							$startday = dol_mktime(12, 0, 0, GETPOST('date_start'.$i.'month'), GETPOST('date_start'.$i.'day'), GETPOST('date_start'.$i.'year'));
 							$endday = dol_mktime(12, 0, 0, GETPOST('date_end'.$i.'month'), GETPOST('date_end'.$i.'day'), GETPOST('date_end'.$i.'year'));
-							$result = $object->addline($product->description, $product->price, price2num(GETPOST('qty'.$i), 'MS'), $product->tva_tx, $product->localtax1_tx, $product->localtax2_tx, GETPOST('idprod'.$i, 'int'), price2num(GETPOST('remise_percent'.$i), '', 2), $startday, $endday, 0, 0, '', $product->price_base_type, $product->price_ttc, $product->type, -1, 0, '', 0, 0, null, 0, '', 0, 100, '', $product->fk_unit);
+							$result = $object->addline($product->description, $product->price, price2num(GETPOST('qty'.$i), 'MS'), $product->tva_tx, $product->localtax1_tx, $product->localtax2_tx, GETPOST('idprod'.$i, 'int'), price2num(GETPOST('remise_percent'.$i), '', 2), $startday, $endday, 0, 0, '', $product->price_base_type, $product->price_ttc, $product->type, -1, 0, '', 0, 0, null, 0, '', 0, 100, '', $product->fk_unit, 0, '', 1);
 						}
 					}
+
+					$object->update_price(1, 'auto', 0, $mysoc);
 				}
 			}
 		}
@@ -3162,6 +3180,9 @@ if ($action == 'create') {
 					$i++;
 				}
 				print '</select>';
+
+				print ajax_combobox("fac_rec");
+
 				// Option to reload page to retrieve customer informations. Note, this clear other input
 				if (empty($conf->global->RELOAD_PAGE_ON_TEMPLATE_CHANGE_DISABLED)) {
 					print '<script type="text/javascript">
@@ -3189,7 +3210,7 @@ if ($action == 'create') {
 
 	// Standard invoice
 	print '<div class="tagtr listofinvoicetype"><div class="tagtd listofinvoicetype">';
-	$tmp = '<input type="radio" id="radio_standard" name="type" value="0"'.(GETPOST('type') == 0 ? ' checked' : '').'> ';
+	$tmp = '<input type="radio" id="radio_standard" name="type" value="0"'.(GETPOST('type', 'int') ? '' : ' checked').'> ';
 	$tmp  = $tmp.'<label for="radio_standard" >'.$langs->trans("InvoiceStandardAsk").'</label>';
 	$desc = $form->textwithpicto($tmp, $langs->transnoentities("InvoiceStandardDesc"), 1, 'help', '', 0, 3);
 	print '<table class="nobordernopadding"><tr>';
@@ -3383,7 +3404,6 @@ if ($action == 'create') {
 		print '</div></div>';
 	}
 
-
 	if (empty($origin)) {
 		if ($socid > 0) {
 			// Credit note
@@ -3520,7 +3540,6 @@ if ($action == 'create') {
 	}
 
 
-
 	print '</td></tr>';
 
 	if ($socid > 0) {
@@ -3608,8 +3627,8 @@ if ($action == 'create') {
 
 	// Payment mode
 	print '<tr><td>'.$langs->trans('PaymentMode').'</td><td colspan="2">';
-	print img_picto('', 'bank', 'class="pictofixedwidth"');
-	$form->select_types_paiements(GETPOSTISSET('mode_reglement_id') ? GETPOST('mode_reglement_id') : $mode_reglement_id, 'mode_reglement_id', 'CRDT', 0, 1, 0, 0, 1, 'maxwidth200 widthcentpercentminusx');
+	print img_picto('', 'payment', 'class="pictofixedwidth"');
+	print $form->select_types_paiements(GETPOSTISSET('mode_reglement_id') ? GETPOST('mode_reglement_id') : $mode_reglement_id, 'mode_reglement_id', 'CRDT', 0, 1, 0, 0, 1, 'maxwidth200 widthcentpercentminusx', 1);
 	print '</td></tr>';
 
 	// Bank Account
@@ -3682,6 +3701,7 @@ if ($action == 'create') {
 		print '<tr>';
 		print '<td>'.$form->editfieldkey('Currency', 'multicurrency_code', '', $object, 0).'</td>';
 		print '<td colspan="2" class="maxwidthonsmartphone">';
+		print img_picto('', 'currency', 'class="pictofixedwidth"');
 		print $form->selectMultiCurrency($currency_code, 'multicurrency_code');
 		print '</td></tr>';
 	}

+ 2 - 2
htdocs/compta/facture/class/api_invoices.class.php

@@ -1724,8 +1724,8 @@ class Invoices extends DolibarrApi
 	 *
 	 * Return an array with invoice informations
 	 *
-	 * @param 	int 	$id           ID of template invoice
-	 * @param   int     $contact_list 0:Return array contains all properties, 1:Return array contains just id, 1: Return array contains just id, -1: Do not return contacts/adddesses
+	 * @param 	int 	$id           	ID of template invoice
+	 * @param   int     $contact_list 	0:Return array contains all properties, 1:Return array contains just id, -1: Do not return contacts/adddesses
 	 * @return 	array|mixed data without useless information
 	 *
 	 * @url GET    templates/{id}

+ 8 - 1
htdocs/compta/facture/class/facture-rec.class.php

@@ -945,6 +945,13 @@ class FactureRec extends CommonInvoice
 				$product_type = $product->type;
 			}
 
+			// Rank to use
+			$ranktouse = $rang;
+			if ($ranktouse == -1) {
+				$rangmax = $this->line_max(0);
+				$ranktouse = $rangmax + 1;
+			}
+
 			$sql = "INSERT INTO ".MAIN_DB_PREFIX."facturedet_rec (";
 			$sql .= "fk_facture";
 			$sql .= ", label";
@@ -1003,7 +1010,7 @@ class FactureRec extends CommonInvoice
 			$sql .= ", ".($fk_fournprice > 0 ? $fk_fournprice : 'null');
 			$sql .= ", ".($pa_ht ? price2num($pa_ht) : 0);
 			$sql .= ", ".((int) $info_bits);
-			$sql .= ", ".((int) $rang);
+			$sql .= ", ".((int) $ranktouse);
 			$sql .= ", ".((int) $special_code);
 			$sql .= ", ".($fk_unit ? ((int) $fk_unit) : "null");
 			$sql .= ", ".(int) $this->fk_multicurrency;

+ 36 - 15
htdocs/compta/facture/class/facture.class.php

@@ -519,8 +519,11 @@ class Facture extends CommonInvoice
 
 			// Define some dates
 			$originaldatewhen = $_facrec->date_when;
-			$nextdatewhen = dol_time_plus_duree($originaldatewhen, $_facrec->frequency, $_facrec->unit_frequency);
-			$previousdaynextdatewhen = dol_time_plus_duree($nextdatewhen, -1, 'd');
+			$nextdatewhen = null; $previousdaynextdatewhen = null;
+			if ($originaldatewhen) {
+				$nextdatewhen = dol_time_plus_duree($originaldatewhen, $_facrec->frequency, $_facrec->unit_frequency);
+				$previousdaynextdatewhen = dol_time_plus_duree($nextdatewhen, -1, 'd');
+			}
 
 			if (!empty($_facrec->frequency)) {  // Invoice are created on same thirdparty than template when there is a recurrence, but not necessarly when there is no recurrence.
 				$this->socid = $_facrec->socid;
@@ -610,9 +613,9 @@ class Facture extends CommonInvoice
 			$substitutionarray['__INVOICE_YEAR__'] = dol_print_date($this->date, '%Y');
 			$substitutionarray['__INVOICE_NEXT_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'y'), '%Y');
 			// Only for template invoice
-			$substitutionarray['__INVOICE_DATE_NEXT_INVOICE_BEFORE_GEN__'] = dol_print_date($originaldatewhen, 'dayhour');
-			$substitutionarray['__INVOICE_DATE_NEXT_INVOICE_AFTER_GEN__'] = dol_print_date($nextdatewhen, 'dayhour');
-			$substitutionarray['__INVOICE_PREVIOUS_DATE_NEXT_INVOICE_AFTER_GEN__'] = dol_print_date($previousdaynextdatewhen, 'dayhour');
+			$substitutionarray['__INVOICE_DATE_NEXT_INVOICE_BEFORE_GEN__'] = (isset($originaldatewhen) ? dol_print_date($originaldatewhen, 'dayhour') : '');
+			$substitutionarray['__INVOICE_DATE_NEXT_INVOICE_AFTER_GEN__'] = (isset($nextdatewhen) ? dol_print_date($nextdatewhen, 'dayhour') : '');
+			$substitutionarray['__INVOICE_PREVIOUS_DATE_NEXT_INVOICE_AFTER_GEN__'] = (isset($previousdaynextdatewhen) ? dol_print_date($previousdaynextdatewhen, 'dayhour') : '');
 			$substitutionarray['__INVOICE_COUNTER_CURRENT__'] = $_facrec->nb_gen_done;
 			$substitutionarray['__INVOICE_COUNTER_MAX__'] = $_facrec->nb_gen_max;
 
@@ -854,7 +857,9 @@ class Facture extends CommonInvoice
 							$newinvoiceline->situation_percent,
 							$newinvoiceline->fk_prev_id,
 							$newinvoiceline->fk_unit,
-							$newinvoiceline->multicurrency_subprice
+							$newinvoiceline->multicurrency_subprice,
+							$newinvoiceline->ref_ext,
+							1
 						);
 
 						// Defined the new fk_parent_line
@@ -938,7 +943,8 @@ class Facture extends CommonInvoice
 							$line->fk_prev_id,
 							$line->fk_unit,
 							$line->multicurrency_subprice,
-							$line->ref_ext
+							$line->ref_ext,
+							1
 						);
 						if ($result < 0) {
 							$this->error = $this->db->lasterror();
@@ -1035,7 +1041,9 @@ class Facture extends CommonInvoice
 						$_facrec->lines[$i]->situation_percent,
 						'',
 						$_facrec->lines[$i]->fk_unit,
-						$_facrec->lines[$i]->multicurrency_subprice
+						$_facrec->lines[$i]->multicurrency_subprice,
+						$_facrec->lines[$i]->ref_ext,
+						1
 					);
 
 					if ($result_insert < 0) {
@@ -1047,7 +1055,7 @@ class Facture extends CommonInvoice
 			}
 
 			if (!$error) {
-				$result = $this->update_price(1);
+				$result = $this->update_price(1, 'auto', 0, $mysoc);
 				if ($result > 0) {
 					$action = 'create';
 
@@ -2724,7 +2732,7 @@ class Facture extends CommonInvoice
 
 		// Check for mandatory fields in thirdparty (defined into setup)
 		if (!empty($this->thirdparty) && is_object($this->thirdparty)) {
-			$array_to_check = array('IDPROF1', 'IDPROF2', 'IDPROF3', 'IDPROF4', 'IDPROF5', 'IDPROF6', 'EMAIL');
+			$array_to_check = array('IDPROF1', 'IDPROF2', 'IDPROF3', 'IDPROF4', 'IDPROF5', 'IDPROF6', 'EMAIL', 'ACCOUNTANCY_CODE_CUSTOMER');
 			foreach ($array_to_check as $key) {
 				$keymin = strtolower($key);
 				if (!property_exists($this->thirdparty, $keymin)) {
@@ -2756,6 +2764,15 @@ class Facture extends CommonInvoice
 							return -1;
 						}
 					}
+					if ($key == 'ACCOUNTANCY_CODE_CUSTOMER') {
+						// Check for mandatory
+						if (!empty($conf->global->SOCIETE_ACCOUNTANCY_CODE_CUSTOMER_INVOICE_MANDATORY) && empty($this->thirdparty->code_compta)) {
+							$langs->load("errors");
+							$this->error = $langs->trans("ErrorAccountancyCodeCustomerIsMandatory", $this->thirdparty->name).' ('.$langs->trans("ForbiddenBySetupRules").')';
+							dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
+							return -1;
+						}
+					}
 				}
 			}
 		}
@@ -3228,7 +3245,7 @@ class Facture extends CommonInvoice
 	 *  @param		string		$price_base_type	'HT' or 'TTC'
 	 *  @param    	double		$pu_ttc             Unit price with tax (> 0 even for credit note)
 	 *  @param		int			$type				Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used.
-	 *  @param      int			$rang               Position of line
+	 *  @param      int			$rang               Position of line (-1 means last value + 1)
 	 *  @param		int			$special_code		Special code (also used by externals modules!)
 	 *  @param		string		$origin				Depend on global conf MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION can be 'orderdet', 'propaldet'..., else 'order','propal,'....
 	 *  @param		int			$origin_id			Depend on global conf MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION can be Id of origin object (aka line id), else object id
@@ -3242,6 +3259,7 @@ class Facture extends CommonInvoice
 	 *  @param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
 	 *  @param		double		$pu_ht_devise		Unit price in foreign currency
 	 *  @param		string		$ref_ext		    External reference of the line
+	 *  @param		int			$noupdateafterinsertline	No update after insert of line
 	 *  @return    	int             				<0 if KO, Id of line if OK
 	 */
 	public function addline(
@@ -3274,7 +3292,8 @@ class Facture extends CommonInvoice
 		$fk_prev_id = 0,
 		$fk_unit = null,
 		$pu_ht_devise = 0,
-		$ref_ext = ''
+		$ref_ext = '',
+		$noupdateafterinsertline = 0
 	) {
 		// Deprecation warning
 		if ($label) {
@@ -3483,7 +3502,9 @@ class Facture extends CommonInvoice
 				}
 
 				// Mise a jour informations denormalisees au niveau de la facture meme
-				$result = $this->update_price(1, 'auto', 0, $mysoc); // The addline method is designed to add line from user input so total calculation with update_price must be done using 'auto' mode.
+				if (empty($noupdateafterinsertline)) {
+					$result = $this->update_price(1, 'auto', 0, $mysoc); // The addline method is designed to add line from user input so total calculation with update_price must be done using 'auto' mode.
+				}
 
 				if ($result > 0) {
 					$this->db->commit();
@@ -5200,11 +5221,11 @@ class Facture extends CommonInvoice
 							if (!empty($recipient->email)) {
 								$to = $recipient->email;
 							} else {
-								$errormesg = "Failed to send remind to thirdparty id=".$tmpinvoice->fk_soc.". No email defined for user.";
+								$errormesg = "Failed to send remind to thirdparty id=".$tmpinvoice->socid.". No email defined for user.";
 								$error++;
 							}
 						} else {
-							$errormesg = "Failed to load recipient with thirdparty id=".$tmpinvoice->fk_soc;
+							$errormesg = "Failed to load recipient with thirdparty id=".$tmpinvoice->socid;
 							$error++;
 						}
 

+ 51 - 4
htdocs/compta/facture/list.php

@@ -47,6 +47,7 @@ require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
 require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
 require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';
+require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture-rec.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/invoice.lib.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
@@ -135,6 +136,7 @@ $search_datelimit_endyear = GETPOST('search_datelimit_endyear', 'int');
 $search_datelimit_start = dol_mktime(0, 0, 0, $search_datelimit_startmonth, $search_datelimit_startday, $search_datelimit_startyear);
 $search_datelimit_end = dol_mktime(23, 59, 59, $search_datelimit_endmonth, $search_datelimit_endday, $search_datelimit_endyear);
 $search_categ_cus = GETPOST("search_categ_cus", 'int');
+$search_fac_rec_source_title = GETPOST("search_fac_rec_source_title", 'alpha');
 $search_btn = GETPOST('button_search', 'alpha');
 $search_remove_btn = GETPOST('button_removefilter', 'alpha');
 $optioncss = GETPOST('optioncss', 'alpha');
@@ -251,6 +253,7 @@ $arrayfields = array(
 	'f.tms'=>array('label'=>"DateModificationShort", 'checked'=>0, 'position'=>502),
 	'f.note_public'=>array('label'=>'NotePublic', 'checked'=>0, 'position'=>510, 'enabled'=>(empty($conf->global->MAIN_LIST_ALLOW_PUBLIC_NOTES))),
 	'f.note_private'=>array('label'=>'NotePrivate', 'checked'=>0, 'position'=>511, 'enabled'=>(empty($conf->global->MAIN_LIST_ALLOW_PRIVATE_NOTES))),
+	'f.fk_fac_rec_source'=>array('label'=>'GeneratedFromTemplate', 'checked'=>0, 'position'=>520, 'enabled'=>'1'),
 	'f.fk_statut'=>array('label'=>"Status", 'checked'=>1, 'position'=>1000),
 );
 
@@ -360,6 +363,7 @@ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter',
 	$search_datelimit_endyear = '';
 	$search_datelimit_start = '';
 	$search_datelimit_end = '';
+	$search_fac_rec_source_title = '';
 	$option = '';
 	$filter = '';
 	$toselect = '';
@@ -566,6 +570,7 @@ $sql .= ' s.rowid as socid, s.nom as name, s.name_alias as alias, s.email, s.pho
 $sql .= ' typent.code as typent_code,';
 $sql .= ' state.code_departement as state_code, state.nom as state_name,';
 $sql .= ' country.code as country_code,';
+$sql .= ' f.fk_fac_rec_source,';
 $sql .= ' p.rowid as project_id, p.ref as project_ref, p.title as project_label,';
 $sql .= ' u.login, u.lastname, u.firstname, u.email as user_email, u.statut as user_statut, u.entity, u.photo, u.office_phone, u.office_fax, u.user_mobile, u.job, u.gender';
 // We need dynamount_payed to be able to sort on status (value is surely wrong because we can count several lines several times due to other left join or link with contacts. But what we need is just 0 or > 0).
@@ -601,7 +606,7 @@ $sql .= ', '.MAIN_DB_PREFIX.'facture as f';
 if ($sortfield == "f.datef") {
 	$sql .= $db->hintindex('idx_facture_datef');
 }
-if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label'])) {
+if (isset($extrafields->attributes[$object->table_element]['label']) && is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label'])) {
 	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$object->table_element."_extrafields as ef on (f.rowid = ef.fk_object)";
 }
 
@@ -617,6 +622,10 @@ if ($sall || $search_product_category > 0) {
 if ($search_product_category > 0) {
 	$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'categorie_product as cp ON cp.fk_product=pd.fk_product';
 }
+
+if (!empty($search_fac_rec_source_title)) {
+	$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'facture_rec as facrec ON f.fk_fac_rec_source=facrec.rowid';
+}
 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet as p ON p.rowid = f.fk_projet";
 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'user AS u ON f.fk_user_author = u.rowid';
 // We'll need this table joined to the select in order to filter by sale
@@ -801,6 +810,9 @@ if ($search_sale > 0) {
 if ($search_user > 0) {
 	$sql .= " AND ec.fk_c_type_contact = tc.rowid AND tc.element='facture' AND tc.source='internal' AND ec.element_id = f.rowid AND ec.fk_socpeople = ".((int) $search_user);
 }
+if (!empty($search_fac_rec_source_title)) {
+	$sql .= natural_search('facrec.titre', $search_fac_rec_source_title);
+}
 // Add where from extra fields
 include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php';
 // Add where from hooks
@@ -1079,6 +1091,9 @@ if ($resql) {
 	if ($search_categ_cus > 0) {
 		$param .= '&search_categ_cus='.urlencode($search_categ_cus);
 	}
+	if (!empty($search_fac_rec_source_title)) {
+		$param .= '&$search_fac_rec_source_title='.urlencode($search_fac_rec_source_title);
+	}
 
 	// Add $param from extra fields
 	include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php';
@@ -1170,7 +1185,7 @@ if ($resql) {
 
 	// If the user can view prospects other than his'
 	$moreforfilter = '';
-	if ($user->rights->societe->client->voir || $socid) {
+	if ($user->rights->user->user->lire) {
 		$langs->load("commercial");
 		$moreforfilter .= '<div class="divsearchfield">';
 		$tmptitle = $langs->trans('ThirdPartiesOfSaleRepresentative');
@@ -1178,7 +1193,7 @@ if ($resql) {
 		$moreforfilter .= '</div>';
 	}
 	// If the user can view prospects other than his'
-	if ($user->rights->societe->client->voir || $socid) {
+	if ($user->rights->user->user->lire) {
 		$moreforfilter .= '<div class="divsearchfield">';
 		$tmptitle = $langs->trans('LinkedToSpecificUsers');
 		$moreforfilter .= img_picto($tmptitle, 'user', 'class="pictofixedwidth"').$form->select_dolusers($search_user, 'search_user', $tmptitle, '', 0, '', '', 0, 0, 0, '', 0, '', 'maxwidth250');
@@ -1500,6 +1515,12 @@ if ($resql) {
 		print '<td class="liste_titre">';
 		print '</td>';
 	}
+	if (!empty($arrayfields['f.fk_fac_rec_source']['checked'])) {
+		// Template Invoice
+		print '<td class="liste_titre maxwidthonsmartphone right">';
+		print '<input class="flat maxwidth50imp" type="text" name="search_fac_rec_source_title" id="search_fac_rec_source_title" value="'.dol_escape_htmltag($search_fac_rec_source_title).'">';
+		print '</td>';
+	}
 	// Status
 	if (!empty($arrayfields['f.fk_statut']['checked'])) {
 		print '<td class="liste_titre maxwidthonsmartphone right">';
@@ -1659,6 +1680,9 @@ if ($resql) {
 	if (!empty($arrayfields['f.note_private']['checked'])) {
 		print_liste_field_titre($arrayfields['f.note_private']['label'], $_SERVER["PHP_SELF"], "f.note_private", "", $param, '', $sortfield, $sortorder, 'center nowrap ');
 	}
+	if (!empty($arrayfields['f.fk_fac_rec_source']['checked'])) {
+		print_liste_field_titre($arrayfields['f.fk_fac_rec_source']['label'], $_SERVER["PHP_SELF"], "facrec.titre", "", $param, '', $sortfield, $sortorder);
+	}
 	if (!empty($arrayfields['f.fk_statut']['checked'])) {
 		print_liste_field_titre($arrayfields['f.fk_statut']['label'], $_SERVER["PHP_SELF"], "f.fk_statut,f.paye,f.type", "", $param, 'class="right"', $sortfield, $sortorder);
 	}
@@ -2188,7 +2212,13 @@ if ($resql) {
 
 			// Currency
 			if (!empty($arrayfields['f.multicurrency_code']['checked'])) {
-				print '<td class="nowraponall">'.dol_escape_htmltag($obj->multicurrency_code).' - '.$langs->trans('Currency'.$obj->multicurrency_code)."</td>\n";
+				print '<td class="nowraponall tdoverflowmax125" title="'.dol_escape_htmltag($obj->multicurrency_code.' - '.$langs->transnoentitiesnoconv('Currency'.$obj->multicurrency_code)).'">';
+				if (empty($conf->global->MAIN_SHOW_ONLY_CODE_MULTICURRENCY)) {
+					print $langs->transnoentitiesnoconv('Currency'.$obj->multicurrency_code);
+				} else {
+					print dol_escape_htmltag($obj->multicurrency_code);
+				}
+				print "</td>\n";
 				if (!$i) {
 					$totalarray['nbfield']++;
 				}
@@ -2335,6 +2365,23 @@ if ($resql) {
 					$totalarray['nbfield']++;
 				}
 			}
+			// Template Invoice
+			if (!empty($arrayfields['f.fk_fac_rec_source']['checked'])) {
+				print '<td class="center">';
+				if (!empty($obj->fk_fac_rec_source)) {
+					$facrec = new FactureRec($db);
+					$result = $facrec->fetch($obj->fk_fac_rec_source);
+					if ($result < 0) {
+						setEventMessages($facrec->error, $facrec->errors, 'errors');
+					} else {
+						print $facrec->getNomUrl();
+					}
+				}
+				print '</td>';
+				if (!$i) {
+					$totalarray['nbfield']++;
+				}
+			}
 			// Status
 			if (!empty($arrayfields['f.fk_statut']['checked'])) {
 				print '<td class="nowrap right">';

+ 1 - 2
htdocs/compta/index.php

@@ -527,9 +527,8 @@ if (!empty($conf->tax->enabled) && !empty($user->rights->tax->charges->lire)) {
 
 					if ($i >= $max) {
 						$othernb += 1;
+						$tot_ttc += $obj->amount;
 						$i++;
-						$total_ht += $obj->total_ht;
-						$total_ttc += $obj->total_ttc;
 						continue;
 					}
 

+ 14 - 8
htdocs/compta/localtax/card.php

@@ -32,8 +32,8 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/vat.lib.php';
 $langs->loadLangs(array('compta', 'banks', 'bills'));
 
 $id = GETPOST("id", 'int');
-$action = GETPOST("action", "alpha");
-$cancel = GETPOST('cancel');
+$action = GETPOST("action", "aZ09");
+$cancel = GETPOST('cancel', 'aZ09');
 
 $refund = GETPOST("refund", "int");
 if (empty($refund)) {
@@ -143,7 +143,7 @@ $form = new Form($db);
 
 $title = $langs->trans("LT".$object->ltt)." - ".$langs->trans("Card");
 $help_url = '';
-llxHeader("", $title, $helpurl);
+llxHeader('', $title, $helpurl);
 
 if ($action == 'create') {
 	print load_fiche_titre($langs->transcountry($lttype == 2 ? "newLT2Payment" : "newLT1Payment", $mysoc->country_code));
@@ -157,11 +157,13 @@ if ($action == 'create') {
 
 	print '<table class="border centpercent">';
 
+	// Date of payment
 	print "<tr>";
 	print '<td class="titlefieldcreate fieldrequired">'.$langs->trans("DatePayment").'</td><td>';
 	print $form->selectDate($datep, "datep", '', '', '', 'add', 1, 1);
 	print '</td></tr>';
 
+	// End date of period
 	print '<tr><td class="fieldrequired">'.$form->textwithpicto($langs->trans("PeriodEndDate"), $langs->trans("LastDayTaxIsRelatedTo")).'</td><td>';
 	print $form->selectDate($datev, "datev", '', '', '', 'add', 1, 1);
 	print '</td></tr>';
@@ -173,20 +175,24 @@ if ($action == 'create') {
 	print '<tr><td class="fieldrequired">'.$langs->trans("Amount").'</td><td><input name="amount" size="10" value="'.GETPOST("amount").'"></td></tr>';
 
 	if (!empty($conf->banque->enabled)) {
-		print '<tr><td class="fieldrequired">'.$langs->trans("Account").'</td><td>';
-		$form->select_comptes(GETPOST("accountid", "int"), "accountid", 0, "courant=1", 2); // Affiche liste des comptes courant
-		print '</td></tr>';
-
+		// Type payment
 		print '<tr><td class="fieldrequired">'.$langs->trans("PaymentMode").'</td><td>';
-		$form->select_types_paiements(GETPOST("paiementtype"), "paiementtype");
+		$form->select_types_paiements(GETPOST("paiementtype"), "paiementtype", '', 0, 1, 0, 0, 1, 'maxwidth500 widthcentpercentminusx');
 		print "</td>\n";
 		print "</tr>";
 
+		// Bank account
+		print '<tr><td class="fieldrequired" id="label_fk_account">'.$langs->trans("Account").'</td><td>';
+		print img_picto('', 'bank_account', 'pictofixedwidth');
+		$form->select_comptes(GETPOST("accountid", "int"), "accountid", 0, "courant=1", 2, '', 0, 'maxwidth500 widthcentpercentminusx'); // Affiche liste des comptes courant
+		print '</td></tr>';
+
 		// Number
 		print '<tr><td>'.$langs->trans('Numero');
 		print ' <em>('.$langs->trans("ChequeOrTransferNumber").')</em>';
 		print '<td><input name="num_payment" type="text" value="'.GETPOST("num_payment").'"></td></tr>'."\n";
 	}
+
 	// Other attributes
 	$parameters = array();
 	$reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $object, $action); // Note that $action and $object may have been modified by hook

+ 4 - 1
htdocs/compta/localtax/list.php

@@ -63,6 +63,7 @@ if ($result) {
 	$i = 0;
 	$total = 0;
 
+	print '<div class="div-table-responsive-no-min">'; // You can use div-table-responsive-no-min if you dont need reserved height for your table
 	print '<table class="noborder centpercent">';
 	print '<tr class="liste_titre">';
 	print '<td class="nowrap" align="left">'.$langs->trans("Ref").'</td>';
@@ -85,7 +86,7 @@ if ($result) {
 		print '<td class="left">'.dol_print_date($db->jdate($obj->datep), 'day')."</td>\n";
 		$total = $total + $obj->amount;
 
-		print "<td align=\"right\">".price($obj->amount)."</td>";
+		print '<td class="right nowraponall"><span class="amount">'.price($obj->amount).'</span></td>';
 		print "</tr>\n";
 
 		$i++;
@@ -94,6 +95,8 @@ if ($result) {
 	print '<td class="right"><span class="amount">'.price($total).'</span></td></tr>';
 
 	print "</table>";
+	print '</div>';
+
 	$db->free($result);
 } else {
 	dol_print_error($db);

+ 4 - 2
htdocs/compta/paiement/class/paiement.class.php

@@ -419,7 +419,8 @@ class Paiement extends CommonObject
 									}
 
 									if ($error) {
-										setEventMessages($discount->error, $discount->errors, 'errors');
+										$this->error = $discount->error;
+										$this->errors = $discount->errors;
 										$error++;
 									}
 								}
@@ -460,7 +461,8 @@ class Paiement extends CommonObject
 							$result = $invoice->generateDocument($invoice->model_pdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
 
 							if ($result < 0) {
-								setEventMessages($invoice->error, $invoice->errors, 'errors');
+								$this->error = $invoice->error;
+								$this->errors = $invoice->errors;
 								$error++;
 							}
 						}

+ 11 - 8
htdocs/compta/paiement_vat.php

@@ -207,14 +207,15 @@ if ($action == 'create') {
 	print '</tr>';
 
 	print '<tr><td class="fieldrequired">'.$langs->trans("PaymentMode").'</td><td>';
-	$form->select_types_paiements(GETPOSTISSET("paiementtype") ? GETPOST("paiementtype", "int") : $tva->paiementtype, "paiementtype");
+	$form->select_types_paiements(GETPOSTISSET("paiementtype") ? GETPOST("paiementtype", "int") : $tva->paiementtype, "paiementtype", '', 0, 1, 0, 0, 1, 'maxwidth500 widthcentpercentminusx');
 	print "</td>\n";
 	print '</tr>';
 
 	print '<tr>';
 	print '<td class="fieldrequired">'.$langs->trans('AccountToDebit').'</td>';
 	print '<td>';
-	$form->select_comptes(GETPOST("accountid") ? GETPOST("accountid", "int") : $tva->accountid, "accountid", 0, '', 1); // Show opend bank account list
+	print img_picto('', 'bank_account', 'pictofixedwidth');
+	$form->select_comptes(GETPOST("accountid", "int") ? GETPOST("accountid", "int") : $tva->accountid, "accountid", 0, '', 1, '', 0, 'maxwidth500 widthcentpercentminusx'); // Show opend bank account list
 	print '</td></tr>';
 
 	// Number
@@ -225,7 +226,7 @@ if ($action == 'create') {
 
 	print '<tr>';
 	print '<td class="tdtop">'.$langs->trans("Comments").'</td>';
-	print '<td class="tdtop"><textarea name="note" wrap="soft" cols="60" rows="'.ROWS_3.'"></textarea></td>';
+	print '<td class="tdtop"><textarea name="note" class="quatrevingtpercent" wrap="soft" rows="'.ROWS_3.'"></textarea></td>';
 	print '</tr>';
 
 	print '</table>';
@@ -238,6 +239,7 @@ if ($action == 'create') {
 	$num = 1;
 	$i = 0;
 
+	print '<div class="div-table-responsive-no-min">'; // You can use div-table-responsive-no-min if you dont need reserved height for your table
 	print '<table class="noborder centpercent">';
 	print '<tr class="liste_titre">';
 	//print '<td>'.$langs->trans("SocialContribution").'</td>';
@@ -259,14 +261,14 @@ if ($action == 'create') {
 		if ($objp->datev > 0) {
 			print '<td class="left">'.dol_print_date($objp->datev, 'day').'</td>'."\n";
 		} else {
-			print "<td align=\"center\"><b>!!!</b></td>\n";
+			print '<td class="center"><b>!!!</b></td>'."\n";
 		}
 
-		print '<td class="right"><span class="amount">'.price($objp->amount)."</span></td>";
+		print '<td class="right nowraponall"><span class="amount">'.price($objp->amount)."</span></td>";
 
-		print '<td class="right"><span class="amount">'.price($sumpaid)."</span></td>";
+		print '<td class="right nowraponall"><span class="amount">'.price($sumpaid)."</span></td>";
 
-		print '<td class="right"><span class="amount">'.price($objp->amount - $sumpaid)."</span></td>";
+		print '<td class="right nowraponall"><span class="amount">'.price($objp->amount - $sumpaid)."</span></td>";
 
 		print '<td class="center">';
 
@@ -279,7 +281,7 @@ if ($action == 'create') {
 			} */
 			$remaintopay = $objp->amount - $sumpaid;
 			print '<input type=hidden class="sum_remain" name="'.$nameRemain.'" value="'.$remaintopay.'">';
-			print '<input type="text" class="right width100" name="'.$namef.'" id="'.$namef.'" value="'.$remaintopay.'">';
+			print '<input type="text" class="right width75" name="'.$namef.'" id="'.$namef.'" value="'.$remaintopay.'">';
 		} else {
 			print '-';
 		}
@@ -303,6 +305,7 @@ if ($action == 'create') {
 	}
 
 	print "</table>";
+	print '</div>';
 
 	// Bouton Save payment
 	print '<br><div class="center"><input type="checkbox" checked name="closepaidvat"> '.$langs->trans("ClosePaidVATAutomatically");

+ 2 - 2
htdocs/compta/prelevement/class/bonprelevement.class.php

@@ -922,7 +922,7 @@ class BonPrelevement extends CommonObject
 
 				$sql = "SELECT substring(ref from char_length(ref) - 1)";	// To extract "YYMMXX" from "TYYMMXX"
 				$sql .= " FROM ".MAIN_DB_PREFIX."prelevement_bons";
-				$sql .= " WHERE ref LIKE '%".$this->db->escape($ref)."%'";
+				$sql .= " WHERE ref LIKE '_".$this->db->escape($ref)."%'";
 				$sql .= " AND entity = ".((int) $conf->entity);
 				$sql .= " ORDER BY ref DESC LIMIT 1";
 
@@ -933,7 +933,7 @@ class BonPrelevement extends CommonObject
 					$row = $this->db->fetch_row($resql);
 
 					// Build the new ref
-					$ref = "T".$ref.str_pad(dol_substr("00".(intval($row[0]) + 1), 0, 2), 2, "0", STR_PAD_LEFT);
+					$ref = "T".$ref.sprintf("%02d", (intval($row[0]) + 1));
 
 					// $conf->abc->dir_output may be:
 					// /home/ldestailleur/git/dolibarr_15.0/documents/abc/

+ 4 - 2
htdocs/compta/prelevement/create.php

@@ -106,9 +106,11 @@ if (empty($reshook)) {
 		require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
 		$bank = new Account($db);
 		$bank->fetch($conf->global->{$default_account});
-		if ((empty($bank->ics) && $type !== 'bank-transfer')
+		// ICS is not mandatory with payment by bank transfer
+		/*if ((empty($bank->ics) && $type !== 'bank-transfer')
 			|| (empty($bank->ics_transfer) && $type === 'bank-transfer')
-		) {
+		) {*/
+		if (empty($bank->ics) && $type !== 'bank-transfer') {
 			$errormessage = str_replace('{url}', $bank->getNomUrl(1, '', '', -1, 1), $langs->trans("ErrorICSmissing", '{url}'));
 			setEventMessages($errormessage, null, 'errors');
 			$action = '';

+ 3 - 3
htdocs/compta/prelevement/fiche-rejet.php

@@ -40,7 +40,7 @@ if ($user->socid > 0) {
 }
 
 // Get supervariables
-$prev_id = GETPOST('id', 'int');
+$id = GETPOST('id', 'int');
 $ref = GETPOST('ref', 'alpha');
 
 $type = GETPOST('type', 'aZ09');
@@ -82,8 +82,8 @@ if ($type == 'bank-transfer') {
 
 llxHeader('', $langs->trans("WithdrawalsReceipts"));
 
-if ($prev_id > 0 || $ref) {
-	if ($object->fetch($prev_id, $ref) >= 0) {
+if ($id > 0 || $ref) {
+	if ($object->fetch($id, $ref) >= 0) {
 		$head = prelevement_prepare_head($object);
 		print dol_get_fiche_head($head, 'rejects', $langs->trans("WithdrawalsReceipts"), -1, 'payment');
 

+ 3 - 3
htdocs/compta/prelevement/fiche-stat.php

@@ -33,7 +33,7 @@ require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
 $langs->loadLangs(array("banks", "categories", 'withdrawals', 'bills'));
 
 // Get supervariables
-$prev_id = GETPOST('id', 'int');
+$id = GETPOST('id', 'int');
 $ref = GETPOST('ref', 'alpha');
 
 $type = GETPOST('type', 'aZ09');
@@ -76,8 +76,8 @@ if ($type == 'bank-transfer') {
 
 llxHeader('', $langs->trans("WithdrawalsReceipts"));
 
-if ($prev_id > 0 || $ref) {
-	if ($object->fetch($prev_id, $ref) >= 0) {
+if ($id > 0 || $ref) {
+	if ($object->fetch($id, $ref) >= 0) {
 		$head = prelevement_prepare_head($object);
 		print dol_get_fiche_head($head, 'statistics', $langs->trans("WithdrawalsReceipts"), -1, 'payment');
 

+ 2 - 2
htdocs/compta/resultat/index.php

@@ -1042,7 +1042,7 @@ for ($mois = 1 + $nb_mois_decalage; $mois <= 12 + $nb_mois_decalage; $mois++) {
 		}
 		$case = strftime("%Y-%m", dol_mktime(12, 0, 0, $mois_modulo, 1, $annee_decalage));
 
-		print '<td class="right">&nbsp;';
+		print '<td class="right">';
 		if ($modecompta == 'CREANCES-DETTES' || $modecompta == 'BOOKKEEPING') {
 			if (isset($decaiss[$case]) && $decaiss[$case] != 0) {
 				print '<a href="clientfourn.php?year='.$annee_decalage.'&month='.$mois_modulo.($modecompta ? '&modecompta='.$modecompta : '').'">'.price(price2num($decaiss[$case], 'MT')).'</a>';
@@ -1062,7 +1062,7 @@ for ($mois = 1 + $nb_mois_decalage; $mois <= 12 + $nb_mois_decalage; $mois++) {
 		}
 		print "</td>";
 
-		print '<td class="borderrightlight nowrap right">&nbsp;';
+		print '<td class="borderrightlight nowrap right">';
 		if ($modecompta == 'CREANCES-DETTES' || $modecompta == 'BOOKKEEPING') {
 			if (isset($encaiss[$case])) {
 				print '<a href="clientfourn.php?year='.$annee_decalage.'&month='.$mois_modulo.($modecompta ? '&modecompta='.$modecompta : '').'">'.price(price2num($encaiss[$case], 'MT')).'</a>';

+ 5 - 4
htdocs/compta/tva/card.php

@@ -460,7 +460,7 @@ if ($action == 'create') {
 	print '</div>';
 
 	print '</td>';
-	//print "<br>\n";
+	print "</tr>\n";
 
 	// Label
 	if ($refund == 1) {
@@ -490,14 +490,15 @@ if ($action == 'create') {
 
 	// Type payment
 	print '<tr><td class="fieldrequired" id="label_type_payment">'.$langs->trans("PaymentMode").'</td><td>';
-	$form->select_types_paiements(GETPOST("type_payment"), "type_payment");
+	$form->select_types_paiements(GETPOST("type_payment", 'int'), "type_payment", '', 0, 1, 0, 0, 1, 'maxwidth500 widthcentpercentminusx');
 	print "</td>\n";
 	print "</tr>";
 
 	if (!empty($conf->banque->enabled)) {
+		// Bank account
 		print '<tr><td class="fieldrequired" id="label_fk_account">'.$langs->trans("BankAccount").'</td><td>';
 		print img_picto('', 'bank_account', 'pictofixedwidth');
-		$form->select_comptes(GETPOST("accountid", 'int'), "accountid", 0, "courant=1", 1); // List of bank account available
+		$form->select_comptes(GETPOST("accountid", 'int'), "accountid", 0, "courant=1", 1, '', 0, 'maxwidth500 widthcentpercentminusx'); // List of bank account available
 		print '</td></tr>';
 	}
 
@@ -509,7 +510,7 @@ if ($action == 'create') {
 	// Comments
 	print '<tr class="hide_if_no_auto_create_payment">';
 	print '<td class="tdtop">'.$langs->trans("Comments").'</td>';
-	print '<td class="tdtop"><textarea name="note" wrap="soft" cols="60" rows="'.ROWS_3.'">'.GETPOST('note', 'restricthtml').'</textarea></td>';
+	print '<td class="tdtop"><textarea name="note" wrap="soft" rows="'.ROWS_3.'" class="quatrevingtpercent">'.GETPOST('note', 'restricthtml').'</textarea></td>';
 	print '</tr>';
 
 	// Other attributes

+ 1 - 1
htdocs/contrat/list.php

@@ -550,7 +550,7 @@ if ($sall) {
 $moreforfilter = '';
 
 // If the user can view prospects other than his'
-if ($user->rights->societe->client->voir || $socid) {
+if ($user->rights->user->user->lire) {
 	$langs->load("commercial");
 	$moreforfilter .= '<div class="divsearchfield">';
 	$tmptitle = $langs->trans('ThirdPartiesOfSaleRepresentative');

+ 1 - 1
htdocs/contrat/services_list.php

@@ -163,7 +163,7 @@ $arrayfields = dol_sort_array($arrayfields, 'position');
 if (GETPOST('cancel', 'alpha')) {
 	$action = 'list'; $massaction = '';
 }
-if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend' && $massaction != 'confirm_createbills') {
+if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend') {
 	$massaction = '';
 }
 

+ 9 - 1
htdocs/core/actions_extrafields.inc.php

@@ -152,6 +152,7 @@ if ($action == 'add') {
 				$default_value = GETPOST('default_value', 'alpha');
 				$parameters = $param;
 				$parameters_array = explode("\r\n", $parameters);
+				$params = array();
 				//In sellist we have only one line and it can have come to do SQL expression
 				if ($type == 'sellist' || $type == 'chkbxlst') {
 					foreach ($parameters_array as $param_ligne) {
@@ -161,6 +162,9 @@ if ($action == 'add') {
 					// Else it's separated key/value and coma list
 					foreach ($parameters_array as $param_ligne) {
 						list($key, $value) = explode(',', $param_ligne);
+						if (!array_key_exists('options', $params)) {
+							$params['options'] = array();
+						}
 						$params['options'][$key] = $value;
 					}
 				}
@@ -183,7 +187,7 @@ if ($action == 'add') {
 					$default_value,
 					$params,
 					(GETPOST('alwayseditable', 'alpha') ? 1 : 0),
-					(GETPOST('perms', 'alpha') ?GETPOST('perms', 'alpha') : ''),
+					(GETPOST('perms', 'alpha') ? GETPOST('perms', 'alpha') : ''),
 					$visibility,
 					GETPOST('help', 'alpha'),
 					GETPOST('computed_value', 'alpha'),
@@ -315,6 +319,7 @@ if ($action == 'update') {
 				// Construct array for parameter (value of select list)
 				$parameters = $param;
 				$parameters_array = explode("\r\n", $parameters);
+				$params = array();
 				//In sellist we have only one line and it can have come to do SQL expression
 				if ($type == 'sellist' || $type == 'chkbxlst') {
 					foreach ($parameters_array as $param_ligne) {
@@ -324,6 +329,9 @@ if ($action == 'update') {
 					//Esle it's separated key/value and coma list
 					foreach ($parameters_array as $param_ligne) {
 						list($key, $value) = explode(',', $param_ligne);
+						if (!array_key_exists('options', $params)) {
+							$params['options'] = array();
+						}
 						$params['options'][$key] = $value;
 					}
 				}

+ 55 - 355
htdocs/core/actions_massactions.inc.php

@@ -128,6 +128,14 @@ if (!$error && $massaction == 'confirm_presend') {
 							$listofobjectcontacts[$toselectid][$data_email['id']] = $data_email['email'];
 						}
 					}
+				} elseif ($objectclass == 'CommandeFournisseur') {
+					$tmparraycontact = array();
+					$tmparraycontact = $objecttmp->liste_contact(-1, 'external', 0, 'CUSTOMER');
+					if (is_array($tmparraycontact) && count($tmparraycontact) > 0) {
+						foreach ($tmparraycontact as $data_email) {
+							$listofobjectcontacts[$toselectid][$data_email['id']] = $data_email['email'];
+						}
+					}
 				}
 
 				$listofobjectthirdparties[$thirdpartyid] = $thirdpartyid;
@@ -277,6 +285,19 @@ if (!$error && $massaction == 'confirm_presend') {
 						if (count($emails_to_sends) > 0) {
 							$sendto = implode(',', $emails_to_sends);
 						}
+					} elseif ($objectobj->element == 'order_supplier' && !empty($listofobjectcontacts[$objectid])) {
+						$emails_to_sends = array();
+						$objectobj->fetch_thirdparty();
+						$contactidtosend = array();
+						foreach ($listofobjectcontacts[$objectid] as $contactemailid => $contactemailemail) {
+							$emails_to_sends[] = $objectobj->thirdparty->contact_get_property($contactemailid, 'email');
+							if (!in_array($contactemailid, $contactidtosend)) {
+								$contactidtosend[] = $contactemailid;
+							}
+						}
+						if (count($emails_to_sends) > 0) {
+							$sendto = implode(',', $emails_to_sends);
+						}
 					} else {
 						$objectobj->fetch_thirdparty();
 						$sendto = $objectobj->thirdparty->email;
@@ -306,29 +327,51 @@ if (!$error && $massaction == 'confirm_presend') {
 					// TODO Set subdir to be compatible with multi levels dir trees
 					// $subdir = get_exdir($objectobj->id, 2, 0, 0, $objectobj, $objectobj->element)
 					$filedir = $uploaddir.'/'.$subdir.dol_sanitizeFileName($objectobj->ref);
-					$file = $filedir.'/'.$filename;
+					$filepath = $filedir.'/'.$filename;
 
 					// For supplier invoices, we use the file provided by supplier, not the one we generate
 					if ($objectobj->element == 'invoice_supplier') {
 						$fileparams = dol_most_recent_file($uploaddir.'/'.get_exdir($objectobj->id, 2, 0, 0, $objectobj, $objectobj->element).$objectobj->ref, preg_quote($objectobj->ref, '/').'([^\-])+');
-						$file = $fileparams['fullname'];
+						$filepath = $fileparams['fullname'];
 					}
 
-					$mime = dol_mimetype($file);
+					// try to find other files generated for this object (last_main_doc)
+					$filename_found = '';
+					$filepath_found = '';
+					$file_check_list = array();
+					$file_check_list[] = array(
+						'name' => $filename,
+						'path' => $filepath,
+					);
+					if (!empty($conf->global->MAIL_MASS_ACTION_ADD_LAST_IF_MAIN_DOC_NOT_FOUND) && !empty($objectobj->last_main_doc)) {
+						$file_check_list[] = array(
+							'name' => basename($objectobj->last_main_doc),
+							'path' => DOL_DATA_ROOT . '/' . $objectobj->last_main_doc,
+						);
+					}
+					foreach ($file_check_list as $file_check_arr) {
+						if (dol_is_file($file_check_arr['path'])) {
+							$filename_found = $file_check_arr['name'];
+							$filepath_found = $file_check_arr['path'];
+							break;
+						}
+					}
 
-					if (dol_is_file($file)) {
+					if ($filepath_found) {
 						// Create form object
 						$attachedfilesThirdpartyObj[$thirdpartyid][$objectid] = array(
-							'paths'=>array($file),
-							'names'=>array($filename),
-							'mimes'=>array($mime)
+							'paths'=>array($filepath_found),
+							'names'=>array($filename_found),
+							'mimes'=>array(dol_mimetype($filepath_found))
 						);
 					} else {
-							$nbignored++;
-							$langs->load("errors");
-							$resaction .= '<div class="error">'.$langs->trans('ErrorCantReadFile', $file).'</div><br>';
-							dol_syslog('Failed to read file: '.$file, LOG_WARNING);
-							continue;
+						$nbignored++;
+						$langs->load("errors");
+						foreach ($file_check_list as $file_check_arr) {
+							$resaction .= '<div class="error">'.$langs->trans('ErrorCantReadFile', $file_check_arr['path']).'</div><br>';
+							dol_syslog('Failed to read file: '.$file_check_arr['path'], LOG_WARNING);
+						}
+						continue;
 					}
 				}
 
@@ -625,349 +668,6 @@ if (!$error && $massaction == 'confirm_presend') {
 	}
 }
 
-// TODO Move this action into commande/list.php if called only by this page.
-if ($massaction == 'confirm_createbills') {   // Create bills from orders.
-	$orders = GETPOST('toselect', 'array');
-	$createbills_onebythird = GETPOST('createbills_onebythird', 'int');
-	$validate_invoices = GETPOST('validate_invoices', 'int');
-
-	$errors = array();
-
-	$TFact = array();
-	$TFactThird = array();
-	$TFactThirdNbLines = array();
-
-	$nb_bills_created = 0;
-	$lastid= 0;
-	$lastref = '';
-
-	$db->begin();
-
-	foreach ($orders as $id_order) {
-		$cmd = new Commande($db);
-		if ($cmd->fetch($id_order) <= 0) {
-			continue;
-		}
-		$cmd->fetch_thirdparty();
-
-		$objecttmp = new Facture($db);
-		if (!empty($createbills_onebythird) && !empty($TFactThird[$cmd->socid])) {
-			// If option "one bill per third" is set, and an invoice for this thirdparty was already created, we re-use it.
-			$objecttmp = $TFactThird[$cmd->socid];
-		} else {
-			// If we want one invoice per order or if there is no first invoice yet for this thirdparty.
-			$objecttmp->socid = $cmd->socid;
-			$objecttmp->thirdparty = $cmd->thirdparty;
-
-			$objecttmp->type = $objecttmp::TYPE_STANDARD;
-			$objecttmp->cond_reglement_id = !empty($cmd->cond_reglement_id) ? $cmd->cond_reglement_id : $cmd->thirdparty->cond_reglement_id;
-			$objecttmp->mode_reglement_id = !empty($cmd->mode_reglement_id) ? $cmd->mode_reglement_id : $cmd->thirdparty->mode_reglement_id;
-
-			$objecttmp->fk_project = $cmd->fk_project;
-			$objecttmp->multicurrency_code = $cmd->multicurrency_code;
-			if (empty($createbills_onebythird)) {
-				$objecttmp->ref_client = $cmd->ref_client;
-			}
-
-			$datefacture = dol_mktime(12, 0, 0, GETPOST('remonth', 'int'), GETPOST('reday', 'int'), GETPOST('reyear', 'int'));
-			if (empty($datefacture)) {
-				$datefacture = dol_now();
-			}
-
-			$objecttmp->date = $datefacture;
-			$objecttmp->origin    = 'commande';
-			$objecttmp->origin_id = $id_order;
-
-			$objecttmp->array_options = $cmd->array_options; // Copy extrafields
-
-			$res = $objecttmp->create($user);
-
-			if ($res > 0) {
-				$nb_bills_created++;
-				$lastref = $objecttmp->ref;
-				$lastid = $objecttmp->id;
-
-				$TFactThird[$cmd->socid] = $objecttmp;
-				$TFactThirdNbLines[$cmd->socid] = 0; //init nblines to have lines ordered by expedition and rang
-			} else {
-				$langs->load("errors");
-				$errors[] = $cmd->ref.' : '.$langs->trans($objecttmp->error);
-				$error++;
-			}
-		}
-
-		if ($objecttmp->id > 0) {
-			$res = $objecttmp->add_object_linked($objecttmp->origin, $id_order);
-
-			if ($res == 0) {
-				$errors[] = $objecttmp->error;
-				$error++;
-			}
-
-			if (!$error) {
-				$lines = $cmd->lines;
-				if (empty($lines) && method_exists($cmd, 'fetch_lines')) {
-					$cmd->fetch_lines();
-					$lines = $cmd->lines;
-				}
-
-				$fk_parent_line = 0;
-				$num = count($lines);
-
-				for ($i = 0; $i < $num; $i++) {
-					$desc = ($lines[$i]->desc ? $lines[$i]->desc : '');
-					// If we build one invoice for several orders, we must put the ref of order on the invoice line
-					if (!empty($createbills_onebythird)) {
-						$desc = dol_concatdesc($desc, $langs->trans("Order").' '.$cmd->ref.' - '.dol_print_date($cmd->date, 'day'));
-					}
-
-					if ($lines[$i]->subprice < 0) {
-						// Negative line, we create a discount line
-						$discount = new DiscountAbsolute($db);
-						$discount->fk_soc = $objecttmp->socid;
-						$discount->amount_ht = abs($lines[$i]->total_ht);
-						$discount->amount_tva = abs($lines[$i]->total_tva);
-						$discount->amount_ttc = abs($lines[$i]->total_ttc);
-						$discount->tva_tx = $lines[$i]->tva_tx;
-						$discount->fk_user = $user->id;
-						$discount->description = $desc;
-						$discountid = $discount->create($user);
-						if ($discountid > 0) {
-							$result = $objecttmp->insert_discount($discountid);
-							//$result=$discount->link_to_invoice($lineid,$id);
-						} else {
-							setEventMessages($discount->error, $discount->errors, 'errors');
-							$error++;
-							break;
-						}
-					} else {
-						// Positive line
-						$product_type = ($lines[$i]->product_type ? $lines[$i]->product_type : 0);
-						// Date start
-						$date_start = false;
-						if ($lines[$i]->date_debut_prevue) {
-							$date_start = $lines[$i]->date_debut_prevue;
-						}
-						if ($lines[$i]->date_debut_reel) {
-							$date_start = $lines[$i]->date_debut_reel;
-						}
-						if ($lines[$i]->date_start) {
-							$date_start = $lines[$i]->date_start;
-						}
-						//Date end
-						$date_end = false;
-						if ($lines[$i]->date_fin_prevue) {
-							$date_end = $lines[$i]->date_fin_prevue;
-						}
-						if ($lines[$i]->date_fin_reel) {
-							$date_end = $lines[$i]->date_fin_reel;
-						}
-						if ($lines[$i]->date_end) {
-							$date_end = $lines[$i]->date_end;
-						}
-						// Reset fk_parent_line for no child products and special product
-						if (($lines[$i]->product_type != 9 && empty($lines[$i]->fk_parent_line)) || $lines[$i]->product_type == 9) {
-							$fk_parent_line = 0;
-						}
-
-						// Extrafields
-						if (method_exists($lines[$i], 'fetch_optionals')) {
-							$lines[$i]->fetch_optionals();
-							$array_options = $lines[$i]->array_options;
-						}
-
-						$objecttmp->context['createfromclone'];
-
-						$rang = $lines[$i]->rang;
-						//there may already be rows from previous orders
-						if (!empty($createbills_onebythird))
-							$rang = $TFactThirdNbLines[$cmd->socid];
-
-						$result = $objecttmp->addline(
-							$desc,
-							$lines[$i]->subprice,
-							$lines[$i]->qty,
-							$lines[$i]->tva_tx,
-							$lines[$i]->localtax1_tx,
-							$lines[$i]->localtax2_tx,
-							$lines[$i]->fk_product,
-							$lines[$i]->remise_percent,
-							$date_start,
-							$date_end,
-							0,
-							$lines[$i]->info_bits,
-							$lines[$i]->fk_remise_except,
-							'HT',
-							0,
-							$product_type,
-							$rang,
-							$lines[$i]->special_code,
-							$objecttmp->origin,
-							$lines[$i]->rowid,
-							$fk_parent_line,
-							$lines[$i]->fk_fournprice,
-							$lines[$i]->pa_ht,
-							$lines[$i]->label,
-							$array_options,
-							100,
-							0,
-							$lines[$i]->fk_unit
-						);
-						if ($result > 0) {
-							$lineid = $result;
-							if (!empty($createbills_onebythird)) //increment rang to keep order
-								$TFactThirdNbLines[$rcp->socid]++;
-						} else {
-							$lineid = 0;
-							$error++;
-							break;
-						}
-						// Defined the new fk_parent_line
-						if ($result > 0 && $lines[$i]->product_type == 9) {
-							$fk_parent_line = $result;
-						}
-					}
-				}
-			}
-		}
-
-		//$cmd->classifyBilled($user);        // Disabled. This behavior must be set or not using the workflow module.
-
-		if (!empty($createbills_onebythird) && empty($TFactThird[$cmd->socid])) {
-			$TFactThird[$cmd->socid] = $objecttmp;
-		} else {
-			$TFact[$objecttmp->id] = $objecttmp;
-		}
-	}
-
-	// Build doc with all invoices
-	$TAllFact = empty($createbills_onebythird) ? $TFact : $TFactThird;
-	$toselect = array();
-
-	if (!$error && $validate_invoices) {
-		$massaction = $action = 'builddoc';
-
-		foreach ($TAllFact as &$objecttmp) {
-			$result = $objecttmp->validate($user);
-			if ($result <= 0) {
-				$error++;
-				setEventMessages($objecttmp->error, $objecttmp->errors, 'errors');
-				break;
-			}
-
-			$id = $objecttmp->id; // For builddoc action
-
-			// Builddoc
-			$donotredirect = 1;
-			$upload_dir = $conf->facture->dir_output;
-			$permissiontoadd = $user->rights->facture->creer;
-
-			// Call action to build doc
-			$savobject = $object;
-			$object = $objecttmp;
-			include DOL_DOCUMENT_ROOT.'/core/actions_builddoc.inc.php';
-			$object = $savobject;
-		}
-
-		$massaction = $action = 'confirm_createbills';
-	}
-
-	if (!$error) {
-		$db->commit();
-
-		if ($nb_bills_created == 1) {
-			$texttoshow = $langs->trans('BillXCreated', '{s1}');
-			$texttoshow = str_replace('{s1}', '<a href="'.DOL_URL_ROOT.'/compta/facture/card.php?id='.urlencode($lastid).'">'.$lastref.'</a>', $texttoshow);
-			setEventMessages($texttoshow, null, 'mesgs');
-		} else {
-			setEventMessages($langs->trans('BillCreated', $nb_bills_created), null, 'mesgs');
-		}
-
-		// Make a redirect to avoid to bill twice if we make a refresh or back
-		$param = '';
-		if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) {
-			$param .= '&contextpage='.urlencode($contextpage);
-		}
-		if ($limit > 0 && $limit != $conf->liste_limit) {
-			$param .= '&limit='.urlencode($limit);
-		}
-		if ($sall) {
-			$param .= '&sall='.urlencode($sall);
-		}
-		if ($socid > 0) {
-			$param .= '&socid='.urlencode($socid);
-		}
-		if ($search_status != '') {
-			$param .= '&search_status='.urlencode($search_status);
-		}
-		if ($search_orderday) {
-			$param .= '&search_orderday='.urlencode($search_orderday);
-		}
-		if ($search_ordermonth) {
-			$param .= '&search_ordermonth='.urlencode($search_ordermonth);
-		}
-		if ($search_orderyear) {
-			$param .= '&search_orderyear='.urlencode($search_orderyear);
-		}
-		if ($search_deliveryday) {
-			$param .= '&search_deliveryday='.urlencode($search_deliveryday);
-		}
-		if ($search_deliverymonth) {
-			$param .= '&search_deliverymonth='.urlencode($search_deliverymonth);
-		}
-		if ($search_deliveryyear) {
-			$param .= '&search_deliveryyear='.urlencode($search_deliveryyear);
-		}
-		if ($search_ref) {
-			$param .= '&search_ref='.urlencode($search_ref);
-		}
-		if ($search_company) {
-			$param .= '&search_company='.urlencode($search_company);
-		}
-		if ($search_ref_customer) {
-			$param .= '&search_ref_customer='.urlencode($search_ref_customer);
-		}
-		if ($search_user > 0) {
-			$param .= '&search_user='.urlencode($search_user);
-		}
-		if ($search_sale > 0) {
-			$param .= '&search_sale='.urlencode($search_sale);
-		}
-		if ($search_total_ht != '') {
-			$param .= '&search_total_ht='.urlencode($search_total_ht);
-		}
-		if ($search_total_vat != '') {
-			$param .= '&search_total_vat='.urlencode($search_total_vat);
-		}
-		if ($search_total_ttc != '') {
-			$param .= '&search_total_ttc='.urlencode($search_total_ttc);
-		}
-		if ($search_project_ref >= 0) {
-			$param .= "&search_project_ref=".urlencode($search_project_ref);
-		}
-		if ($show_files) {
-			$param .= '&show_files='.urlencode($show_files);
-		}
-		if ($optioncss != '') {
-			$param .= '&optioncss='.urlencode($optioncss);
-		}
-		if ($billed != '') {
-			$param .= '&billed='.urlencode($billed);
-		}
-
-		header("Location: ".$_SERVER['PHP_SELF'].'?'.$param);
-		exit;
-	} else {
-		$db->rollback();
-
-		$action = 'create';
-		$_GET["origin"] = $_POST["origin"];
-		$_GET["originid"] = $_POST["originid"];
-		setEventMessages("Error", null, 'errors');
-		$error++;
-	}
-}
-
 if (!$error && $massaction == 'cancelorders') {
 	$db->begin();
 

+ 6 - 0
htdocs/core/class/commondocgenerator.class.php

@@ -60,6 +60,12 @@ abstract class CommonDocGenerator
 	 */
 	public $update_main_doc_field;
 
+	/**
+	 * @var string	The name of constant to use to scan ODT files (Exemple: 'COMMANDE_ADDON_PDF_ODT_PATH')
+	 */
+	public $scandir;
+
+
 	/**
 	 *	Constructor
 	 *

+ 16 - 10
htdocs/core/class/commonobject.class.php

@@ -3391,9 +3391,9 @@ abstract class CommonObject
 	 *  Must be called at end of methods addline or updateline.
 	 *
 	 *	@param	int		$exclspec          	>0 = Exclude special product (product_type=9)
-	 *  @param  string	$roundingadjust    	'none'=Do nothing, 'auto'=Use default method (MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND if defined, or '0'), '0'=Force mode total of rounding, '1'=Force mode rounding of total
+	 *  @param  string	$roundingadjust    	'none'=Do nothing, 'auto'=Use default method (MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND if defined, or '0'), '0'=Force mode Total of rounding, '1'=Force mode Rounding of total
 	 *  @param	int		$nodatabaseupdate	1=Do not update database. Update only properties of object.
-	 *  @param	Societe	$seller				If roundingadjust is '0' or '1' or maybe 'auto', it means we recalculate total for lines before calculating total for object and for this, we need seller object.
+	 *  @param	Societe	$seller				If roundingadjust is '0' or '1' or maybe 'auto', it means we recalculate total for lines before calculating total for object and for this, we need seller object (used to analyze lines to check corrupted data).
 	 *	@return	int    			           	<0 if KO, >0 if OK
 	 */
 	public function update_price($exclspec = 0, $roundingadjust = 'none', $nodatabaseupdate = 0, $seller = null)
@@ -6078,7 +6078,7 @@ abstract class CommonObject
 							$this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
 							return -1;
 						} elseif ($value == '') {
-							$new_array_options[$key] = null;
+							$value = null;
 						}
 						//dol_syslog("double value"." sur ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
 						$new_array_options[$key] = $value;
@@ -6444,7 +6444,7 @@ abstract class CommonObject
 						$this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
 						return -1;
 					} elseif ($value === '') {
-						$this->array_options["options_".$key] = null;
+						$value = null;
 					}
 					//dol_syslog("double value"." sur ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
 					$this->array_options["options_".$key] = $value;
@@ -7923,16 +7923,22 @@ abstract class CommonObject
 
 						// Convert date into timestamp format (value in memory must be a timestamp)
 						if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date'))) {
-							$datenotinstring = $this->array_options['options_'.$key];
-							if (!is_numeric($this->array_options['options_'.$key])) {	// For backward compatibility
-								$datenotinstring = $this->db->jdate($datenotinstring);
+							$datenotinstring = null;
+							if (array_key_exists('options_'.$key, $this->array_options)) {
+								$datenotinstring = $this->array_options['options_'.$key];
+								if (!is_numeric($this->array_options['options_'.$key])) {	// For backward compatibility
+									$datenotinstring = $this->db->jdate($datenotinstring);
+								}
 							}
 							$value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) ? dol_mktime(12, 0, 0, GETPOST($keyprefix.'options_'.$key.$keysuffix."month", 'int', 3), GETPOST($keyprefix.'options_'.$key.$keysuffix."day", 'int', 3), GETPOST($keyprefix.'options_'.$key.$keysuffix."year", 'int', 3)) : $datenotinstring;
 						}
 						if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('datetime'))) {
-							$datenotinstring = $this->array_options['options_'.$key];
-							if (!is_numeric($this->array_options['options_'.$key])) {	// For backward compatibility
-								$datenotinstring = $this->db->jdate($datenotinstring);
+							$datenotinstring = null;
+							if (array_key_exists('options_'.$key, $this->array_options)) {
+								$datenotinstring = $this->array_options['options_'.$key];
+								if (!is_numeric($this->array_options['options_'.$key])) {	// For backward compatibility
+									$datenotinstring = $this->db->jdate($datenotinstring);
+								}
 							}
 							$value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) ? dol_mktime(GETPOST($keyprefix.'options_'.$key.$keysuffix."hour", 'int', 3), GETPOST($keyprefix.'options_'.$key.$keysuffix."min", 'int', 3), GETPOST($keyprefix.'options_'.$key.$keysuffix."sec", 'int', 3), GETPOST($keyprefix.'options_'.$key.$keysuffix."month", 'int', 3), GETPOST($keyprefix.'options_'.$key.$keysuffix."day", 'int', 3), GETPOST($keyprefix.'options_'.$key.$keysuffix."year", 'int', 3), 'tzuserrel') : $datenotinstring;
 						}

+ 117 - 18
htdocs/core/class/html.form.class.php

@@ -1587,6 +1587,8 @@ class Form
 				}
 			}
 			print '</select>';
+			print ajax_combobox('select_'.$htmlname);
+
 			return $qualifiedlines;
 		} else {
 			dol_print_error($this->db);
@@ -5633,6 +5635,7 @@ class Form
 				if ($filter) {
 					$newfilter .= ' AND ('.$filter.')';
 				}
+				// output the combo of discounts
 				$nbqualifiedlines = $this->select_remises($selected, $htmlname, $newfilter, $socid, $maxvalue);
 				if ($nbqualifiedlines > 0) {
 					print ' &nbsp; <input type="submit" class="button smallpaddingimp" value="'.dol_escape_htmltag($langs->trans("UseLine")).'"';
@@ -5872,6 +5875,7 @@ class Form
 		}
 
 		$out .= '</select>';
+
 		// Make select dynamic
 		include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
 		$out .= ajax_combobox($htmlname);
@@ -7416,7 +7420,7 @@ class Form
 		// Bom:bom/class/bom.class.php:0:t.status=1:ref
 		// Bom:bom/class/bom.class.php:0:(t.status:=:1):ref
 		$InfoFieldList = explode(":", $objectdesc, 4);
-		$vartmp = $InfoFieldList[3];
+		$vartmp = (empty($InfoFieldList[3]) ? '' : $InfoFieldList[3]);
 		$reg = array();
 		if (preg_match('/^.*:(\w*)$/', $vartmp, $reg)) {
 			$InfoFieldList[4] = $reg[1];	// take the sort field
@@ -7753,13 +7757,6 @@ class Form
 			}
 		}
 
-		// Add code for jquery to use multiselect
-		if ($addjscombo && $jsbeautify) {
-			// Enhance with select2
-			include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
-			$out .= ajax_combobox($htmlname, array(), 0, 0, 'resolve', $show_empty < 0 ? (string) $show_empty : '-1');
-		}
-
 		$out .= '<select id="'.preg_replace('/^\./', '', $htmlname).'" '.($disabled ? 'disabled="disabled" ' : '').'class="flat '.(preg_replace('/^\./', '', $htmlname)).($morecss ? ' '.$morecss : '').'"';
 		$out .= ' name="'.preg_replace('/^\./', '', $htmlname).'" '.($moreparam ? $moreparam : '');
 		$out .= '>';
@@ -7798,7 +7795,7 @@ class Form
 				if (is_array($tmpvalue)) {
 					$value = $tmpvalue['label'];
 					$disabled = empty($tmpvalue['disabled']) ? '' : ' disabled';
-					$style = empty($tmpvalue['css']) ? ' class="'.$tmpvalue['css'].'"' : '';
+					$style = empty($tmpvalue['css']) ? '' : ' class="'.$tmpvalue['css'].'"';
 				} else {
 					$value = $tmpvalue;
 					$disabled = '';
@@ -7859,6 +7856,14 @@ class Form
 		}
 
 		$out .= "</select>";
+
+		// Add code for jquery to use multiselect
+		if ($addjscombo && $jsbeautify) {
+			// Enhance with select2
+			include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
+			$out .= ajax_combobox($htmlname, array(), 0, 0, 'resolve', $show_empty < 0 ? (string) $show_empty : '-1');
+		}
+
 		return $out;
 	}
 
@@ -8530,7 +8535,7 @@ class Form
 	public function showLinkToObjectBlock($object, $restrictlinksto = array(), $excludelinksto = array())
 	{
 		global $conf, $langs, $hookmanager;
-		global $bc, $action;
+		global $action;
 
 		$linktoelem = '';
 		$linktoelemlist = '';
@@ -8577,11 +8582,10 @@ class Form
 			);
 		}
 
-		// Can complete the possiblelink array
-		$hookmanager->initHooks(array('commonobject'));
-		$parameters = array('listofidcompanytoscan' => $listofidcompanytoscan);
-
 		if (!empty($listofidcompanytoscan)) {  // If empty, we don't have criteria to scan the object we can link to
+			// Can complete the possiblelink array
+			$hookmanager->initHooks(array('commonobject'));
+			$parameters = array('listofidcompanytoscan' => $listofidcompanytoscan, 'possiblelinks' => $possiblelinks);
 			$reshook = $hookmanager->executeHooks('showLinkToObjectBlock', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
 		}
 
@@ -9228,12 +9232,12 @@ class Form
 				if (!empty($conf->gravatar->enabled) && $email && empty($noexternsourceoverwrite)) {
 					// see https://gravatar.com/site/implement/images/php/
 					$ret .= '<!-- Put link to gravatar -->';
-					$ret .= '<img class="photo'.$modulepart.($cssclass ? ' '.$cssclass : '').'" alt="Gravatar avatar" title="'.$email.' Gravatar avatar" '.($width ? ' width="'.$width.'"' : '').($height ? ' height="'.$height.'"' : '').' src="https://www.gravatar.com/avatar/'.md5(strtolower(trim($email))).'?s='.$width.'&d='.$defaultimg.'">'; // gravatar need md5 hash
+					$ret .= '<img class="photo'.$modulepart.($cssclass ? ' '.$cssclass : '').'" alt="" title="'.$email.' Gravatar avatar" '.($width ? ' width="'.$width.'"' : '').($height ? ' height="'.$height.'"' : '').' src="https://www.gravatar.com/avatar/'.md5(strtolower(trim($email))).'?s='.$width.'&d='.$defaultimg.'">'; // gravatar need md5 hash
 				} else {
 					if ($nophoto == 'company') {
-						$ret .= '<div class="photo'.$modulepart.($cssclass ? ' '.$cssclass : '').'" alt="No photo" '.($width ? ' width="'.$width.'"' : '').($height ? ' height="'.$height.'"' : '').'">'.img_picto('', 'company').'</div>';
+						$ret .= '<div class="photo'.$modulepart.($cssclass ? ' '.$cssclass : '').'" alt="" '.($width ? ' width="'.$width.'"' : '').($height ? ' height="'.$height.'"' : '').'">'.img_picto('', 'company').'</div>';
 					} else {
-						$ret .= '<img class="photo'.$modulepart.($cssclass ? ' '.$cssclass : '').'" alt="No photo" '.($width ? ' width="'.$width.'"' : '').($height ? ' height="'.$height.'"' : '').' src="'.DOL_URL_ROOT.$nophoto.'">';
+						$ret .= '<img class="photo'.$modulepart.($cssclass ? ' '.$cssclass : '').'" alt="" '.($width ? ' width="'.$width.'"' : '').($height ? ' height="'.$height.'"' : '').' src="'.DOL_URL_ROOT.$nophoto.'">';
 					}
 				}
 			}
@@ -9344,7 +9348,7 @@ class Form
 					if ($disableline) {
 						$out .= ' disabled';
 					}
-					if ((is_object($selected[0]) && $selected[0]->id == $obj->rowid) || (!is_object($selected[0]) && in_array($obj->rowid, $selected))) {
+					if ((isset($selected[0]) && is_object($selected[0]) && $selected[0]->id == $obj->rowid) || ((!isset($selected[0]) || !is_object($selected[0])) && !empty($selected) && in_array($obj->rowid, $selected))) {
 						$out .= ' selected';
 					}
 					$out .= '>';
@@ -9774,6 +9778,101 @@ class Form
 		}
 	}
 
+	/**
+	 *  Output a combo list with invoices qualified for a third party
+	 *
+	 * @param int $selected Id invoice preselected
+	 * @param string $htmlname Name of HTML select
+	 * @param int $maxlength Maximum length of label
+	 * @param int $option_only Return only html options lines without the select tag
+	 * @param string $show_empty Add an empty line ('1' or string to show for empty line)
+	 * @param int $forcefocus Force focus on field (works with javascript only)
+	 * @param int $disabled Disabled
+	 * @param string $morecss More css added to the select component
+	 * @return int                    Nbr of project if OK, <0 if KO
+	 */
+	public function selectInvoiceRec($selected = '', $htmlname = 'facrecid', $maxlength = 24, $option_only = 0, $show_empty = '1', $forcefocus = 0, $disabled = 0, $morecss = 'maxwidth500')
+	{
+		global $user, $conf, $langs;
+
+		$out = '';
+
+		dol_syslog('FactureRec::fetch', LOG_DEBUG);
+
+		$sql = 'SELECT f.rowid, f.entity, f.titre as title, f.suspended, f.fk_soc';
+		//$sql.= ', el.fk_source';
+		$sql .= ' FROM ' . MAIN_DB_PREFIX . 'facture_rec as f';
+		$sql .= " WHERE f.entity IN (" . getEntity('invoice') . ")";
+		$sql .= " ORDER BY f.titre ASC";
+
+		$resql = $this->db->query($sql);
+		if ($resql) {
+			// Use select2 selector
+			if (!empty($conf->use_javascript_ajax)) {
+				include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
+				$comboenhancement = ajax_combobox($htmlname, '', 0, $forcefocus);
+				$out .= $comboenhancement;
+				$morecss = 'minwidth200imp maxwidth500';
+			}
+
+			if (empty($option_only)) {
+				$out .= '<select class="valignmiddle flat' . ($morecss ? ' ' . $morecss : '') . '"' . ($disabled ? ' disabled="disabled"' : '') . ' id="' . $htmlname . '" name="' . $htmlname . '">';
+			}
+			if (!empty($show_empty)) {
+				$out .= '<option value="0" class="optiongrey">';
+				if (!is_numeric($show_empty)) {
+					$out .= $show_empty;
+				} else {
+					$out .= '&nbsp;';
+				}
+				$out .= '</option>';
+			}
+			$num = $this->db->num_rows($resql);
+			if ($num) {
+				while ($obj = $this->db->fetch_object($resql)) {
+					$labeltoshow = dol_trunc($obj->title, 18); // Invoice ref
+
+					$disabled = 0;
+					if (!empty($obj->suspended)) {
+						$disabled = 1;
+						$labeltoshow .= ' - ' . $langs->trans("Closed");
+					}
+
+
+					if (!empty($selected) && $selected == $obj->rowid) {
+						$out .= '<option value="' . $obj->rowid . '" selected';
+						//if ($disabled) $out.=' disabled';						// with select2, field can't be preselected if disabled
+						$out .= '>' . $labeltoshow . '</option>';
+					} else {
+						if ($hideunselectables && $disabled && ($selected != $obj->rowid)) {
+							$resultat = '';
+						} else {
+							$resultat = '<option value="' . $obj->rowid . '"';
+							if ($disabled) {
+								$resultat .= ' disabled';
+							}
+							$resultat .= '>';
+							$resultat .= $labeltoshow;
+							$resultat .= '</option>';
+						}
+						$out .= $resultat;
+					}
+				}
+			}
+			if (empty($option_only)) {
+				$out .= '</select>';
+			}
+
+			print $out;
+
+			$this->db->free($resql);
+			return $num;
+		} else {
+			$this->errors[]=$this->db->lasterror;
+			return -1;
+		}
+	}
+
 	/**
 	 * Output the component to make advanced search criteries
 	 *

+ 6 - 4
htdocs/core/class/html.formfile.class.php

@@ -7,7 +7,7 @@
  * Copyright (C) 2014		Marcos García		<marcosgdf@gmail.com>
  * Copyright (C) 2015		Bahfir Abbes		<bafbes@gmail.com>
  * Copyright (C) 2016-2017	Ferran Marcet		<fmarcet@2byte.es>
- * Copyright (C) 2019-2021  Frédéric France     <frederic.france@netlogic.fr>
+ * Copyright (C) 2019-2022  Frédéric France     <frederic.france@netlogic.fr>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -246,10 +246,10 @@ class FormFile
 					$out .= '<td>'.$options.'</td>';
 				}
 				$out .= '<td valign="middle" class="nowrap">';
-				$out .= '<input type="checkbox" '.$rename.' class="savingdocmask" name="savingdocmask" value="'.dol_escape_js($savingdocmask).'"> ';
-				$out .= '<span class="opacitymedium">';
+				$out .= '<input type="checkbox" '.$rename.' class="savingdocmask" name="savingdocmask" id="savingdocmask" value="'.dol_escape_js($savingdocmask).'"> ';
+				$out .= '<label class="opacitymedium small" for="savingdocmask">';
 				$out .= $langs->trans("SaveUploadedFileWithMask", preg_replace('/__file__/', $langs->transnoentitiesnoconv("OriginFileName"), $savingdocmask), $langs->transnoentitiesnoconv("OriginFileName"));
-				$out .= '</span>';
+				$out .= '</label>';
 				$out .= '</td>';
 				$out .= '</tr>';
 			}
@@ -986,6 +986,8 @@ class FormFile
 					$out .= '<td class="right">';
 					$out .= dol_print_date($file->datea, 'dayhour');
 					$out .= '</td>';
+					// for share link of files
+					$out .= '<td></td>';
 					if ($delallowed || $printer || $morepicto) {
 						$out .= '<td></td>';
 					}

+ 10 - 3
htdocs/core/class/html.formmail.class.php

@@ -781,7 +781,11 @@ class FormMail extends Form
 					} elseif ($this->withmaindocfile == -1) {
 						$out .= '<input type="checkbox" id="addmaindocfile" name="addmaindocfile" value="1" checked="checked" />';
 					}
-					$out .= ' <label for="addmaindocfile">'.$langs->trans("JoinMainDoc").'.</label><br>';
+					if (!empty($conf->global->MAIL_MASS_ACTION_ADD_LAST_IF_MAIN_DOC_NOT_FOUND)) {
+						$out .= ' <label for="addmaindocfile">'.$langs->trans("JoinMainDocOrLastGenerated").'.</label><br>';
+					} else {
+						$out .= ' <label for="addmaindocfile">'.$langs->trans("JoinMainDoc").'.</label><br>';
+					}
 				}
 
 				if (is_numeric($this->withfile)) {
@@ -1202,6 +1206,9 @@ class FormMail extends Form
 			if (!empty($conf->global->MAIL_FORCE_DELIVERY_RECEIPT_INVOICE) && !empty($this->param['models']) && $this->param['models'] == 'facture_send') {
 				$defaultvaluefordeliveryreceipt = 1;
 			}
+			if (!empty($conf->global->MAIL_FORCE_DELIVERY_RECEIPT_SUPPLIER_ORDER) && !empty($this->param['models']) && $this->param['models'] == 'order_supplier_send') {
+				$defaultvaluefordeliveryreceipt = 1;
+			}
 			$out .= $form->selectyesno('deliveryreceipt', (GETPOSTISSET("deliveryreceipt") ? GETPOST("deliveryreceipt") : $defaultvaluefordeliveryreceipt), 1);
 		}
 		$out .= "</td></tr>\n";
@@ -1254,9 +1261,9 @@ class FormMail extends Form
 	 *  @param	string		$type_template	Get message for model/type=$type_template, type='all' also included.
 	 *  @param	User		$user			Get template public or limited to this user
 	 *  @param	Translate	$outputlangs	Output lang object
-	 *  @param	int			$id				Id of template to find, or -1 for first found with position 0, or 0 for first found whatever is position (priority order depends on lang provided or not) or -2 for exact match with label (no answer if not found)
+	 *  @param	int			$id				Id of template to get, or -1 for first found with position 0, or 0 for first found whatever is position (priority order depends on lang provided or not) or -2 for exact match with label (no answer if not found)
 	 *  @param  int         $active         1=Only active template, 0=Only disabled, -1=All
-	 *  @param	string		$label			Label of template
+	 *  @param	string		$label			Label of template to get
 	 *  @return ModelMail|integer			One instance of ModelMail or -1 if error
 	 */
 	public function getEMailTemplate($dbs, $type_template, $user, $outputlangs, $id = 0, $active = 1, $label = '')

+ 8 - 3
htdocs/core/class/html.formother.class.php

@@ -1000,7 +1000,7 @@ class FormOther
 			6=>$langs->trans("Day6")
 		);
 
-		$select_week = '<select class="flat" name="'.$htmlname.'">';
+		$select_week = '<select class="flat" name="'.$htmlname.'" id="'.$htmlname.'">';
 		if ($useempty) {
 			$select_week .= '<option value="-1">&nbsp;</option>';
 		}
@@ -1014,6 +1014,9 @@ class FormOther
 			$select_week .= '</option>';
 		}
 		$select_week .= '</select>';
+
+		$select_week .= ajax_combobox($htmlname);
+
 		return $select_week;
 	}
 
@@ -1080,12 +1083,14 @@ class FormOther
 	 *  @param	int			$invert			Invert
 	 *  @param	string		$option			Option
 	 *  @param	string		$morecss		More CSS
+	 *  @param  bool		$addjscombo		Add js combo
 	 *  @return	string
+	 *  @deprecated
 	 */
-	public function select_year($selected = '', $htmlname = 'yearid', $useempty = 0, $min_year = 10, $max_year = 5, $offset = 0, $invert = 0, $option = '', $morecss = 'valignmiddle maxwidth75imp')
+	public function select_year($selected = '', $htmlname = 'yearid', $useempty = 0, $min_year = 10, $max_year = 5, $offset = 0, $invert = 0, $option = '', $morecss = 'valignmiddle maxwidth75imp', $addjscombo = false)
 	{
 		// phpcs:enable
-		print $this->selectyear($selected, $htmlname, $useempty, $min_year, $max_year, $offset, $invert, $option, $morecss);
+		print $this->selectyear($selected, $htmlname, $useempty, $min_year, $max_year, $offset, $invert, $option, $morecss, $addjscombo);
 	}
 
 	/**

+ 27 - 23
htdocs/core/class/html.formprojet.class.php

@@ -56,28 +56,34 @@ class FormProjets
 	/**
 	 *	Output a combo list with projects qualified for a third party / user
 	 *
-	 *	@param	int		$socid      	Id third party (-1=all, 0=only projects not linked to a third party, id=projects not linked or linked to third party id)
-	 *	@param  string	$selected   	Id project preselected ('' or id of project)
-	 *	@param  string	$htmlname   	Name of HTML field
-	 *	@param	int		$maxlength		Maximum length of label
-	 *	@param	int		$option_only	Return only html options lines without the select tag
-	 *	@param	int		$show_empty		Add an empty line
-	 *  @param	int		$discard_closed Discard closed projects (0=Keep, 1=hide completely, 2=Disable). Use a negative value to not show the "discarded" tooltip.
-	 *  @param	int		$forcefocus		Force focus on field (works with javascript only)
-	 *  @param	int		$disabled		Disabled
-	 *  @param  int     $mode           0 for HTML mode and 1 for JSON mode
-	 *  @param  string  $filterkey      Key to filter
-	 *  @param  int     $nooutput       No print output. Return it only.
-	 *  @param  int     $forceaddid     Force to add project id in list, event if not qualified
-	 *  @param  string  $morecss        More css
-	 *	@param  int     $htmlid         Html id to use instead of htmlname
-	 *	@return string           		Return html content
+	 *	@param	int				$socid      			Id third party (-1=all, 0=only projects not linked to a third party, id=projects not linked or linked to third party id)
+	 *	@param  string|Project	$selected   			Id of preselected project or Project (or ''). Note: If you know the ref, you can also provide it into $selected_input_value to save one request in some cases.
+	 *	@param  string			$htmlname   			Name of HTML field
+	 *	@param	int				$maxlength				Maximum length of label
+	 *	@param	int				$option_only			Return only html options lines without the select tag
+	 *	@param	int				$show_empty				Add an empty line
+	 *  @param	int				$discard_closed 		Discard closed projects (0=Keep, 1=hide completely, 2=Disable). Use a negative value to not show the "discarded" tooltip.
+	 *  @param	int				$forcefocus				Force focus on field (works with javascript only)
+	 *  @param	int				$disabled				Disabled
+	 *  @param  int     		$mode           		0 for HTML mode and 1 for JSON mode
+	 *  @param  string  		$filterkey      		Key to filter
+	 *  @param  int     		$nooutput       		No print output. Return it only.
+	 *  @param  int     		$forceaddid     		Force to add project id in list, event if not qualified
+	 *  @param  string  		$morecss        		More css
+	 *	@param  int     		$htmlid         		Html id to use instead of htmlname
+	 *	@return string           						Return html content
 	 */
 	public function select_projects($socid = -1, $selected = '', $htmlname = 'projectid', $maxlength = 16, $option_only = 0, $show_empty = 1, $discard_closed = 0, $forcefocus = 0, $disabled = 0, $mode = 0, $filterkey = '', $nooutput = 0, $forceaddid = 0, $morecss = '', $htmlid = '')
 	{
 		// phpcs:enable
 		global $langs, $conf, $form;
 
+		$selected_input_value = '';
+		if (is_object($selected)) {
+			$selected_input_value = $selected->ref;
+			$selected = $selected->id;
+		}
+
 		$out = '';
 
 		if (!empty($conf->use_javascript_ajax) && !empty($conf->global->PROJECT_USE_SEARCH_TO_SELECT)) {
@@ -89,22 +95,20 @@ class FormProjets
 				$project->fetch($selected);
 				$selected_input_value = $project->ref;
 			}
-			$urloption = 'socid='.$socid.'&htmlname='.$htmlname.'&discardclosed='.$discard_closed;
+			$urloption = 'socid='.((int) $socid).'&htmlname='.urlencode($htmlname).'&discardclosed='.((int) $discard_closed);
+
+			$out .= '<input type="text" class="minwidth200'.($morecss ? ' '.$morecss : '').'" name="search_'.$htmlname.'" id="search_'.$htmlname.'" value="'.$selected_input_value.'"'.$placeholder.' />';
+
 			$out .= ajax_autocompleter($selected, $htmlname, DOL_URL_ROOT.'/projet/ajax/projects.php', $urloption, $conf->global->PROJECT_USE_SEARCH_TO_SELECT, 0, array(
 				//  'update' => array(
 				//      'projectid' => 'id'
 				//  )
 			));
-
-			$out .= '<input type="text" class="minwidth200'.($morecss ? ' '.$morecss : '').'" name="search_'.$htmlname.'" id="search_'.$htmlname.'" value="'.$selected_input_value.'"'.$placeholder.' />';
 		} else {
 			$out .= $this->select_projects_list($socid, $selected, $htmlname, $maxlength, $option_only, $show_empty, abs($discard_closed), $forcefocus, $disabled, 0, $filterkey, 1, $forceaddid, $htmlid, $morecss);
 		}
 		if ($discard_closed > 0) {
-			if (class_exists('Form')) {
-				if (!is_object($form)) {
-					$form = new Form($this->db);
-				}
+			if (!empty($form)) {
 				$out .= $form->textwithpicto('', $langs->trans("ClosedProjectsAreHidden"));
 			}
 		}

+ 10 - 3
htdocs/core/class/rssparser.class.php

@@ -227,11 +227,16 @@ class RssParser
 		} else {
 			try {
 				$result = getURLContent($this->_urlRSS, 'GET', '', 1, array(), array('http', 'https'), 0);
+
 				if (!empty($result['content'])) {
 					$str = $result['content'];
+				} elseif (!empty($result['curl_error_msg'])) {
+					$this->error = 'Error retrieving URL '.$this->_urlRSS.' - '.$result['curl_error_msg'];
+					return -1;
 				}
 			} catch (Exception $e) {
-				print 'Error retrieving URL '.$this->_urlRSS.' - '.$e->getMessage();
+				$this->error = 'Error retrieving URL '.$this->_urlRSS.' - '.$e->getMessage();
+				return -2;
 			}
 		}
 
@@ -248,7 +253,8 @@ class RssParser
 				}
 
 				$xmlparser = xml_parser_create('');
-				if (!is_resource($xmlparser)) {
+
+				if (!is_resource($xmlparser) && !is_object($xmlparser)) {
 					$this->error = "ErrorFailedToCreateParser";
 					return -1;
 				}
@@ -256,10 +262,11 @@ class RssParser
 				xml_set_object($xmlparser, $this);
 				xml_set_element_handler($xmlparser, 'feed_start_element', 'feed_end_element');
 				xml_set_character_data_handler($xmlparser, 'feed_cdata');
+
 				$status = xml_parse($xmlparser, $str);
 				xml_parser_free($xmlparser);
 				$rss = $this;
-				//var_dump($rss->_format);exit;
+				//var_dump($status.' '.$rss->_format);exit;
 			}
 		}
 

+ 1 - 1
htdocs/core/extrafieldsinimport.inc.php

@@ -11,7 +11,7 @@ if (empty($keyforselect) || empty($keyforelement) || empty($keyforaliasextra)) {
 }
 
 // Add extra fields
-$sql = "SELECT name, label, type, param, fieldcomputed, fielddefault FROM ".MAIN_DB_PREFIX."extrafields";
+$sql = "SELECT name, label, type, param, fieldcomputed, fielddefault, fieldrequired FROM ".MAIN_DB_PREFIX."extrafields";
 $sql .= " WHERE elementtype = '".$this->db->escape($keyforselect)."' AND type <> 'separate' AND entity IN (0, ".((int) $conf->entity).') ORDER BY pos ASC';
 //print $sql;
 $resql = $this->db->query($sql);

+ 1 - 1
htdocs/core/filemanagerdol/connectors/php/config.php

@@ -50,7 +50,7 @@ $Config['Enabled'] = true;
 $extEntity = (empty($entity) ? 1 : $entity); // For multicompany with external access
 
 $Config['UserFilesPath'] = DOL_URL_ROOT.'/viewimage.php?modulepart=medias'.(empty($website) ? '' : '_'.$website).'&entity='.$extEntity.'&file=';
-$Config['UserFilesAbsolutePathRelative'] = (empty($website) ? ((!empty($entity) ? '/'.$entity : '').'/medias/') : ('/website/'.$website));
+$Config['UserFilesAbsolutePathRelative'] = (!empty($entity) ? '/'.$entity : '').(empty($website) ? '/medias/' : ('/website/'.$website));
 
 
 // Fill the following value it you prefer to specify the absolute path for the

+ 2 - 0
htdocs/core/js/lib_head.js.php

@@ -990,6 +990,7 @@ function document_preview(file, type, title)
 		img.src = file;
 
 	}
+
 	function show_preview(mode) {
 		/* console.log("mode="+mode+" file="+file+" type="+type+" width="+width+" height="+height); */
 		var newElem = '<object name="objectpreview" data="'+file+'" type="'+type+'" width="'+object_width+'" height="'+object_height+'" param="noparam"></object>';
@@ -1004,6 +1005,7 @@ function document_preview(file, type, title)
 		}
 
 		$("#dialogforpopup").html(newElem);
+
 		$("#dialogforpopup").dialog({
 			closeOnEscape: true,
 			resizable: true,

+ 12 - 9
htdocs/core/lib/functions.lib.php

@@ -2769,7 +2769,7 @@ function dol_print_url($url, $target = '_blank', $max = 32, $withpicto = 0)
  * @param 	int			$addlink		0=no link, 1=email has a html email link (+ link to create action if constant AGENDA_ADDACTIONFOREMAIL is on)
  * @param	int			$max			Max number of characters to show
  * @param	int			$showinvalid	1=Show warning if syntax email is wrong
- * @param	int			$withpicto		Show picto
+ * @param	int|string	$withpicto		Show picto
  * @return	string						HTML Link
  */
 function dol_print_email($email, $cid = 0, $socid = 0, $addlink = 0, $max = 64, $showinvalid = 1, $withpicto = 0)
@@ -2818,7 +2818,7 @@ function dol_print_email($email, $cid = 0, $socid = 0, $addlink = 0, $max = 64,
 	}
 
 	//$rep = '<div class="nospan" style="margin-right: 10px">';
-	$rep = ($withpicto ? img_picto($langs->trans("EMail").' : '.$email, 'object_email.png').' ' : '').$newemail;
+	$rep = ($withpicto ? img_picto($langs->trans("EMail").' : '.$email, (is_numeric($withpicto) ? 'email' : $withpicto)).' ' : '').$newemail;
 	//$rep .= '</div>';
 	if ($hookmanager) {
 		$parameters = array('cid' => $cid, 'socid' => $socid, 'addlink' => $addlink, 'picto' => $withpicto);
@@ -3730,9 +3730,9 @@ function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $
 				'bank_account', 'barcode', 'bank', 'bell', 'bill', 'billa', 'billr', 'billd', 'bookmark', 'bom', 'briefcase-medical', 'bug', 'building',
 				'card', 'calendar', 'calendarmonth', 'calendarweek', 'calendarday', 'calendarperuser', 'calendarpertype',
 				'cash-register', 'category', 'chart', 'check', 'clock', 'close_title', 'cog', 'collab', 'company', 'contact', 'country', 'contract', 'conversation', 'cron', 'cubes',
-				'multicurrency',
+				'currency', 'multicurrency',
 				'delete', 'dolly', 'dollyrevert', 'donation', 'download', 'dynamicprice',
-				'edit', 'ellipsis-h', 'email', 'entity', 'eraser', 'establishment', 'expensereport', 'external-link-alt', 'external-link-square-alt',
+				'edit', 'ellipsis-h', 'email', 'entity', 'envelope', 'eraser', 'establishment', 'expensereport', 'external-link-alt', 'external-link-square-alt',
 				'filter', 'file-code', 'file-export', 'file-import', 'file-upload', 'autofill', 'folder', 'folder-open', 'folder-plus',
 				'generate', 'globe', 'globe-americas', 'graph', 'grip', 'grip_title', 'group',
 				'help', 'holiday',
@@ -3789,7 +3789,7 @@ function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $
 				'switch_off'=>'toggle-off', 'switch_on'=>'toggle-on', 'switch_on_red'=>'toggle-on', 'check'=>'check', 'bookmark'=>'star',
 				'bank'=>'university', 'close_title'=>'times', 'delete'=>'trash', 'filter'=>'filter',
 				'list-alt'=>'list-alt', 'calendar'=>'calendar-alt', 'calendarmonth'=>'calendar-alt', 'calendarweek'=>'calendar-week', 'calendarday'=>'calendar-day', 'calendarperuser'=>'table',
-				'intervention'=>'ambulance', 'invoice'=>'file-invoice-dollar', 'multicurrency'=>'dollar-sign', 'order'=>'file-invoice',
+				'intervention'=>'ambulance', 'invoice'=>'file-invoice-dollar', 'currency'=>'dollar-sign', 'multicurrency'=>'dollar-sign', 'order'=>'file-invoice',
 				'error'=>'exclamation-triangle', 'warning'=>'exclamation-triangle',
 				'other'=>'square',
 				'playdisabled'=>'play', 'pdf'=>'file-pdf',  'poll'=>'check-double', 'pos'=>'cash-register', 'preview'=>'binoculars', 'project'=>'project-diagram', 'projectpub'=>'project-diagram', 'projecttask'=>'tasks', 'propal'=>'file-signature',
@@ -3866,7 +3866,7 @@ function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $
 				'ecm'=>'infobox-action', 'eventorganization'=>'infobox-project',
 				'hrm'=>'infobox-adherent', 'group'=>'infobox-adherent', 'intervention'=>'infobox-contrat',
 				'incoterm'=>'infobox-supplier_proposal',
-				'multicurrency'=>'infobox-bank_account',
+				'currency'=>'infobox-bank_account', 'multicurrency'=>'infobox-bank_account',
 				'members'=>'infobox-adherent', 'member'=>'infobox-adherent', 'money-bill-alt'=>'infobox-bank_account',
 				'order'=>'infobox-commande',
 				'user'=>'infobox-adherent', 'users'=>'infobox-adherent',
@@ -4576,19 +4576,19 @@ function img_searchclear($titlealt = 'default', $other = '')
  *  @param	string	$textfordropdown	Show a text to click to dropdown the info box.
  *	@return	string						String with info text
  */
-function info_admin($text, $infoonimgalt = 0, $nodiv = 0, $admin = '1', $morecss = '', $textfordropdown = '')
+function info_admin($text, $infoonimgalt = 0, $nodiv = 0, $admin = '1', $morecss = 'hideonsmartphone', $textfordropdown = '')
 {
 	global $conf, $langs;
 
 	if ($infoonimgalt) {
-		$result = img_picto($text, 'info', 'class="hideonsmartphone'.($morecss ? ' '.$morecss : '').'"');
+		$result = img_picto($text, 'info', 'class="'.($morecss ? ' '.$morecss : '').'"');
 	} else {
 		if (empty($conf->use_javascript_ajax)) {
 			$textfordropdown = '';
 		}
 
 		$class = (empty($admin) ? 'undefined' : ($admin == '1' ? 'info' : $admin));
-		$result = ($nodiv ? '' : '<div class="'.$class.' hideonsmartphone'.($morecss ? ' '.$morecss : '').($textfordropdown ? ' hidden' : '').'">').'<span class="fa fa-info-circle" title="'.dol_escape_htmltag($admin ? $langs->trans('InfoAdmin') : $langs->trans('Note')).'"></span> '.$text.($nodiv ? '' : '</div>');
+		$result = ($nodiv ? '' : '<div class="'.$class.($morecss ? ' '.$morecss : '').($textfordropdown ? ' hidden' : '').'">').'<span class="fa fa-info-circle" title="'.dol_escape_htmltag($admin ? $langs->trans('InfoAdmin') : $langs->trans('Note')).'"></span> '.$text.($nodiv ? '' : '</div>');
 
 		if ($textfordropdown) {
 			$tmpresult = '<span class="'.$class.'text opacitymedium cursorpointer">'.$langs->trans($textfordropdown).' '.img_picto($langs->trans($textfordropdown), '1downarrow').'</span>';
@@ -8990,6 +8990,9 @@ function printCommonFooter($zone = 'private')
 								print 'jQuery("select[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
 								print 'jQuery("select[name=\''.$paramkey.'\'] option[value=\'-1\']").prop(\'value\', \'\');'."\n";
 								print 'jQuery("select[name=\''.$paramkey.'\'] option[value=\'0\']").prop(\'value\', \'\');'."\n";
+
+								// Add 'field required' class on closest td for all input elements : input, textarea and select
+								print 'jQuery(":input[name=\'' . $paramkey . '\']").closest("tr").find("td:first").addClass("fieldrequired");' . "\n";
 							}
 						}
 					}

+ 3 - 0
htdocs/core/lib/modulebuilder.lib.php

@@ -121,6 +121,9 @@ function rebuildObjectClass($destdir, $module, $objectname, $newmask, $readdir =
 				$i++;
 				$texttoinsert .= "\t\t'".$key."' => array('type'=>'".$val['type']."',";
 				$texttoinsert .= " 'label'=>'".$val['label']."',";
+				if ($val['picto']) {
+					$texttoinsert .= " 'picto'=>'".$val['picto']."',";
+				}
 				$texttoinsert .= " 'enabled'=>'".($val['enabled'] !== '' ? $val['enabled'] : 1)."',";
 				$texttoinsert .= " 'position'=>".($val['position'] !== '' ? $val['position'] : 50).",";
 				$texttoinsert .= " 'notnull'=>".(empty($val['notnull']) ? 0 : $val['notnull']).",";

+ 5 - 2
htdocs/core/lib/usergroups.lib.php

@@ -367,6 +367,7 @@ function showSkins($fuser, $edit = 0, $foruserprofile = false)
 		$url = 'https://www.dolistore.com/9-skins';
 		print '<a href="'.$url.'" target="_blank" rel="noopener noreferrer external">';
 		print $langs->trans('DownloadMoreSkins');
+		print img_picto('', 'globe', 'class="paddingleft"');
 		print '</a>';
 		print '</th></tr>';
 	}
@@ -435,7 +436,9 @@ function showSkins($fuser, $edit = 0, $foruserprofile = false)
 	$colorbacktitle1 = '';
 	$colortexttitle = '';
 	$colorbacklineimpair1 = '';
+	$colorbacklineimpair2 = '';
 	$colorbacklinepair1 = '';
+	$colorbacklinepair2 = '';
 	$colortextlink = '';
 	$colorbacklinepairhover = '';
 	$colorbacklinepairhover = '';
@@ -746,7 +749,7 @@ function showSkins($fuser, $edit = 0, $foruserprofile = false)
 		print '<td>'.$langs->trans("BackgroundTableLineOddColor").'</td>';
 		print '<td colspan="'.($colspan - 1).'">';
 		if ($edit) {
-			print $formother->selectColor(colorArrayToHex(colorStringToArray((!empty($conf->global->THEME_ELDY_LINEIMPAIR1) ? $conf->global->THEME_ELDY_LINEIMPAIR1 : ''), array()), ''), 'THEME_ELDY_LINEIMPAIR1', '', 1, '', '', 'colorbacklinepair2').' ';
+			print $formother->selectColor(colorArrayToHex(colorStringToArray((!empty($conf->global->THEME_ELDY_LINEIMPAIR1) ? $conf->global->THEME_ELDY_LINEIMPAIR1 : ''), array()), ''), 'THEME_ELDY_LINEIMPAIR1', '', 1, '', '', 'colorbacklineimpair2').' ';
 		} else {
 			$color = colorArrayToHex(colorStringToArray($conf->global->THEME_ELDY_LINEIMPAIR1, array()), '');
 			if ($color) {
@@ -770,7 +773,7 @@ function showSkins($fuser, $edit = 0, $foruserprofile = false)
 		print '<td>'.$langs->trans("BackgroundTableLineEvenColor").'</td>';
 		print '<td colspan="'.($colspan - 1).'">';
 		if ($edit) {
-			print $formother->selectColor(colorArrayToHex(colorStringToArray((!empty($conf->global->THEME_ELDY_LINEPAIR1) ? $conf->global->THEME_ELDY_LINEPAIR1 : ''), array()), ''), 'THEME_ELDY_LINEPAIR1', '', 1, '', '', 'colorbacklineimpair2').' ';
+			print $formother->selectColor(colorArrayToHex(colorStringToArray((!empty($conf->global->THEME_ELDY_LINEPAIR1) ? $conf->global->THEME_ELDY_LINEPAIR1 : ''), array()), ''), 'THEME_ELDY_LINEPAIR1', '', 1, '', '', 'colorbacklinepair2').' ';
 		} else {
 			$color = colorArrayToHex(colorStringToArray($conf->global->THEME_ELDY_LINEPAIR1, array()), '');
 			if ($color) {

+ 2 - 2
htdocs/core/lib/website.lib.php

@@ -499,7 +499,7 @@ function includeContainer($containerref)
 		$containerref .= '.php';
 	}
 
-	$fullpathfile = DOL_DATA_ROOT.'/website/'.$websitekey.'/'.$containerref;
+	$fullpathfile = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/website/'.$websitekey.'/'.$containerref;
 
 	if (empty($includehtmlcontentopened)) {
 		$includehtmlcontentopened = 0;
@@ -988,7 +988,7 @@ function getPagesFromSearchCriterias($type, $algo, $searchstring, $max = 25, $so
 	if (!$error && (empty($max) || ($found < $max)) && (preg_match('/sitefiles/', $algo))) {
 		global $dolibarr_main_data_root;
 
-		$pathofwebsite = $dolibarr_main_data_root.'/website/'.$website->ref;
+		$pathofwebsite = $dolibarr_main_data_root.($conf->entity > 1 ? '/'.$conf->entity : '').'/website/'.$website->ref;
 		$filehtmlheader = $pathofwebsite.'/htmlheader.html';
 		$filecss = $pathofwebsite.'/styles.css.php';
 		$filejs = $pathofwebsite.'/javascript.js.php';

+ 24 - 15
htdocs/core/menus/standard/auguria.lib.php

@@ -64,10 +64,10 @@ function print_auguria_menu($db, $atarget, $type_user, &$tabMenu, &$menu, $noout
 	// Show/Hide vertical menu. The hamburger icon for .menuhider action.
 	if ($mode != 'jmobile' && $mode != 'topnb' && $usemenuhider && empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
 		$showmode = 1;
-		$classname = 'class="tmenu menuhider"';
+		$classname = 'class="tmenu menuhider nohover"';
 		$idsel = 'menu';
 
-		$menu->add('#', (!empty($conf->global->THEME_TOPMENU_DISABLE_IMAGE) ? '<span class="fa fa-bars"></span>' : ''), 0, $showmode, $atarget, "xxx", '', 0, $id, $idsel, $classname);
+		$menu->add('#', (getDolGlobalInt('THEME_TOPMENU_DISABLE_IMAGE') == 1 ? '<span class="fa fa-bars"></span>' : ''), 0, $showmode, $atarget, "xxx", '', 0, $id, $idsel, $classname);
 	}
 
 	$num = count($newTabMenu);
@@ -231,22 +231,31 @@ function print_text_menu_entry_auguria($text, $showmode, $url, $id, $idsel, $cla
 {
 	global $langs;
 
+	$classnameimg = str_replace('class="', 'class="tmenuimage ', $classname);
+	$classnametxt = str_replace('class="', 'class="tmenulabel ', $classname);
+
 	if ($showmode == 1) {
-		print '<a class="tmenuimage" tabindex="-1" href="'.$url.'"'.($atarget ? ' target="'.$atarget.'"' : '').' title="'.dol_escape_htmltag($text).'">';
-		print '<div class="'.$id.' '.$idsel.' topmenuimage"><span class="'.$id.' tmenuimage" id="mainmenuspan_'.$idsel.'"></span></div>';
-		print '</a>';
-		print '<a '.$classname.' id="mainmenua_'.$idsel.'" href="'.$url.'"'.($atarget ? ' target="'.$atarget.'"' : '').' title="'.dol_escape_htmltag($text).'">';
-		print '<span class="mainmenuaspan">';
-		print $text;
-		print '</span>';
+		print '<a '.$classnametxt.' tabindex="-1" href="'.$url.'"'.($atarget ? ' target="'.$atarget.'"' : '').' title="'.dol_escape_htmltag($text).'">';
+		print '<div class="'.$id.' '.$idsel.' topmenuimage"><span class="'.$id.' tmenuimageforpng" id="mainmenuspan_'.$idsel.'"></span></div>';
 		print '</a>';
+		if (empty($conf->global->THEME_TOPMENU_DISABLE_TEXT)) {
+			print '<a '.$classnameimg.' id="mainmenua_'.$idsel.'" href="'.$url.'"'.($atarget ? ' target="'.$atarget.'"' : '').' title="'.dol_escape_htmltag($text).'">';
+			print '<span class="mainmenuaspan">';
+			print $text;
+			print '</span>';
+			print '</a>';
+		}
 	} elseif ($showmode == 2) {
-		print '<div class="'.$id.' '.$idsel.' topmenuimage tmenudisabled"><span class="'.$id.'" id="mainmenuspan_'.$idsel.'"></span></div>';
-		print '<a class="tmenudisabled" id="mainmenua_'.$idsel.'" href="#" title="'.dol_escape_htmltag($langs->trans("NotAllowed")).'">';
-		print '<span class="mainmenuaspan">';
-		print $text;
-		print '</span>';
-		print '</a>';
+		print '<div '.$classnameimg.' title="'.dol_escape_htmltag($text.' - '.$langs->trans("NotAllowed")).'">';
+		print '<div class="'.$id.' '.$idsel.' topmenuimage tmenudisabled"><span class="'.$id.' tmenuimageforpng tmenudisabled" id="mainmenuspan_'.$idsel.'"></span></div>';
+		print '</div>';
+		if (empty($conf->global->THEME_TOPMENU_DISABLE_TEXT)) {
+			print '<span '.$classnametxt.' id="mainmenua_'.$idsel.'" href="#" title="'.dol_escape_htmltag($text.' - '.$langs->trans("NotAllowed")).'">';
+			print '<span class="mainmenuaspan">';
+			print $text;
+			print '</span>';
+			print '</span>';
+		}
 	}
 }
 

+ 16 - 10
htdocs/core/menus/standard/eldy.lib.php

@@ -64,10 +64,10 @@ function print_eldy_menu($db, $atarget, $type_user, &$tabMenu, &$menu, $noout =
 	// Show/Hide vertical menu. The hamburger icon for .menuhider action.
 	if ($mode != 'jmobile' && $mode != 'topnb' && $usemenuhider && empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
 		$showmode = 1;
-		$classname = 'class="tmenu menuhider"';
+		$classname = 'class="tmenu menuhider nohover"';
 		$idsel = 'menu';
 
-		$menu->add('#', (!empty($conf->global->THEME_TOPMENU_DISABLE_IMAGE) ? '<span class="fa fa-bars"></span>' : ''), 0, $showmode, $atarget, "xxx", '', 0, $id, $idsel, $classname);
+		$menu->add('#', (getDolGlobalInt('THEME_TOPMENU_DISABLE_IMAGE') == 1 ? '<span class="fa fa-bars"></span>' : ''), 0, $showmode, $atarget, "xxx", '', 0, $id, $idsel, $classname);
 	}
 
 	$menu_arr = array();
@@ -626,26 +626,31 @@ function print_text_menu_entry($text, $showmode, $url, $id, $idsel, $classname,
 {
 	global $conf, $langs;
 
+	$classnameimg = str_replace('class="', 'class="tmenuimage ', $classname);
+	$classnametxt = str_replace('class="', 'class="tmenulabel ', $classname);
+
 	//$conf->global->THEME_TOPMENU_DISABLE_TEXT=1;
 	if ($showmode == 1) {
-		print '<a class="tmenuimage" tabindex="-1" href="'.$url.'"'.($atarget ? ' target="'.$atarget.'"' : '').' title="'.dol_escape_htmltag($text).'">';
-		print '<div class="'.$id.' '.$idsel.' topmenuimage"><span class="'.$id.' tmenuimage" id="mainmenuspan_'.$idsel.'"></span></div>';
+		print '<a '.$classnameimg.' tabindex="-1" href="'.$url.'"'.($atarget ? ' target="'.$atarget.'"' : '').' title="'.dol_escape_htmltag($text).'">';
+		print '<div class="'.$id.' '.$idsel.' topmenuimage"><span class="'.$id.' tmenuimageforpng" id="mainmenuspan_'.$idsel.'"></span></div>';
 		print '</a>';
 		if (empty($conf->global->THEME_TOPMENU_DISABLE_TEXT)) {
-			print '<a '.$classname.' id="mainmenua_'.$idsel.'" href="'.$url.'"'.($atarget ? ' target="'.$atarget.'"' : '').' title="'.dol_escape_htmltag($text).'">';
+			print '<a '.$classnametxt.' id="mainmenua_'.$idsel.'" href="'.$url.'"'.($atarget ? ' target="'.$atarget.'"' : '').' title="'.dol_escape_htmltag($text).'">';
 			print '<span class="mainmenuaspan">';
 			print $text;
 			print '</span>';
 			print '</a>';
 		}
 	} elseif ($showmode == 2) {
-		print '<div class="'.$id.' '.$idsel.' topmenuimage tmenudisabled"><span class="'.$id.'" id="mainmenuspan_'.$idsel.'"></span></div>';
+		print '<div '.$classnameimg.' title="'.dol_escape_htmltag($text.' - '.$langs->trans("NotAllowed")).'">';
+		print '<div class="'.$id.' '.$idsel.' topmenuimage tmenudisabled"><span class="'.$id.' tmenuimageforpng tmenudisabled" id="mainmenuspan_'.$idsel.'"></span></div>';
+		print '</div>';
 		if (empty($conf->global->THEME_TOPMENU_DISABLE_TEXT)) {
-			print '<a class="tmenudisabled" id="mainmenua_'.$idsel.'" href="#" title="'.dol_escape_htmltag($langs->trans("NotAllowed")).'">';
-			print '<span class="mainmenuaspan">';
+			print '<span '.$classnametxt.' id="mainmenua_'.$idsel.'" href="#" title="'.dol_escape_htmltag($text.' - '.$langs->trans("NotAllowed")).'">';
+			print '<span class="mainmenuaspan tmenudisabled">';
 			print $text;
 			print '</span>';
-			print '</a>';
+			print '</span>';
 		}
 	}
 }
@@ -2283,7 +2288,7 @@ function get_left_menu_hrm($mainmenu, &$newmenu, $usemenuhider = 1, $leftmenu =
 
 		// Expense report
 		if (!empty($conf->expensereport->enabled)) {
-			$langs->load("trips");
+			$langs->loadLangs(array("trips", "bills"));
 			$newmenu->add("/expensereport/index.php?leftmenu=expensereport&amp;mainmenu=hrm", $langs->trans("TripsAndExpenses"), 0, $user->rights->expensereport->lire, '', $mainmenu, 'expensereport', 0, '', '', '', img_picto('', 'trip', 'class="pictofixedwidth"'));
 			$newmenu->add("/expensereport/card.php?action=create&amp;leftmenu=expensereport&amp;mainmenu=hrm", $langs->trans("New"), 1, $user->rights->expensereport->creer);
 			$newmenu->add("/expensereport/list.php?leftmenu=expensereport&amp;mainmenu=hrm", $langs->trans("List"), 1, $user->rights->expensereport->lire);
@@ -2295,6 +2300,7 @@ function get_left_menu_hrm($mainmenu, &$newmenu, $usemenuhider = 1, $leftmenu =
 				$newmenu->add("/expensereport/list.php?search_status=4&amp;leftmenu=expensereport&amp;mainmenu=hrm", $langs->trans("Canceled"), 2, $user->rights->expensereport->lire);
 				$newmenu->add("/expensereport/list.php?search_status=99&amp;leftmenu=expensereport&amp;mainmenu=hrm", $langs->trans("Refused"), 2, $user->rights->expensereport->lire);
 			}
+			$newmenu->add("/expensereport/payment/list.php?leftmenu=expensereport_payments&amp;mainmenu=hrm", $langs->trans("Payments"), 1, $user->rights->expensereport->lire);
 			$newmenu->add("/expensereport/stats/index.php?leftmenu=expensereport&amp;mainmenu=hrm", $langs->trans("Statistics"), 1, $user->rights->expensereport->lire);
 		}
 

+ 23 - 10
htdocs/core/menus/standard/empty.php

@@ -102,7 +102,7 @@ class MenuManager
 			// Show/Hide vertical menu
 			if ($mode != 'jmobile' && $mode != 'topnb' && $usemenuhider && empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
 				$showmode = 1;
-				$classname = 'class="tmenu menuhider"';
+				$classname = 'class="tmenu menuhider nohover"';
 				$idsel = 'menu';
 
 				$this->menu->add('#', '', 0, $showmode, $atarget, "xxx", '', 0, $id, $idsel, $classname);
@@ -513,19 +513,32 @@ function print_text_menu_entry_empty($text, $showmode, $url, $id, $idsel, $class
 {
 	global $conf, $langs;
 
+	$classnameimg = str_replace('class="', 'class="tmenuimage ', $classname);
+	$classnametxt = str_replace('class="', 'class="tmenulabel ', $classname);
+
 	if ($showmode == 1) {
-		print '<a class="tmenuimage" tabindex="-1" href="'.$url.'"'.($atarget ? ' target="'.$atarget.'"' : '').'>';
-		print '<div class="'.$id.' '.$idsel.'"><span class="'.$id.' tmenuimage" id="mainmenuspan_'.$idsel.'"></span></div>';
-		print '</a>';
-		print '<a '.$classname.' id="mainmenua_'.$idsel.'" href="'.$url.'"'.($atarget ? ' target="'.$atarget.'"' : '').'>';
-		print '<span class="mainmenuaspan">';
-		print $text;
-		print '</span>';
+		print '<a '.$classnameimg.' tabindex="-1" href="'.$url.'"'.($atarget ? ' target="'.$atarget.'"' : '').' title="'.dol_escape_htmltag($text).'">';
+		print '<div class="'.$id.' '.$idsel.' topmenuimage"><span class="'.$id.' tmenuimageforpng" id="mainmenuspan_'.$idsel.'"></span></div>';
 		print '</a>';
+		if (empty($conf->global->THEME_TOPMENU_DISABLE_TEXT)) {
+			print '<a '.$classnametxt.' id="mainmenua_'.$idsel.'" href="'.$url.'"'.($atarget ? ' target="'.$atarget.'"' : '').'>';
+			print '<span class="mainmenuaspan">';
+			print $text;
+			print '</span>';
+			print '</a>';
+		}
 	}
 	if ($showmode == 2) {
-		print '<div class="'.$id.' '.$idsel.' tmenudisabled"><span class="'.$id.'" id="mainmenuspan_'.$idsel.'"></span></div>';
-		print '<a class="tmenudisabled" id="mainmenua_'.$idsel.'" href="#" title="'.dol_escape_htmltag($langs->trans("NotAllowed")).'">';
+		print '<div '.$classnameimg.' title="'.dol_escape_htmltag($text.' - '.$langs->trans("NotAllowed")).'">';
+		print '<div class="'.$id.' '.$idsel.' topmenuimage tmenudisabled"><span class="'.$id.' tmenuimageforpng tmenudisabled" id="mainmenuspan_'.$idsel.'"></span></div>';
+		print '</div>';
+		if (empty($conf->global->THEME_TOPMENU_DISABLE_TEXT)) {
+			print '<span '.$classnametxt.' id="mainmenua_'.$idsel.'" href="#" title="'.dol_escape_htmltag($text.' - '.$langs->trans("NotAllowed")).'">';
+			print '<span class="mainmenuaspan">';
+			print $text;
+			print '</span>';
+			print '</span>';
+		}
 	}
 }
 

+ 3 - 1
htdocs/core/modules/DolibarrModules.class.php

@@ -826,7 +826,9 @@ class DolibarrModules // Can not be abstract, because we need to instantiate it
 	}
 
 	/**
-	 * Tells if module is core or external
+	 * Tells if module is core or external.
+	 * 'dolibarr' and 'dolibarr_deprecated' is core
+	 * 'experimental' and 'development' is core
 	 *
 	 * @return string  'core', 'external' or 'unknown'
 	 */

+ 1 - 1
htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php

@@ -1528,7 +1528,7 @@ class pdf_eratosthene extends ModelePDFCommandes
 			$posy += 4;
 			$pdf->SetXY($posx, $posy);
 			$pdf->SetTextColor(0, 0, 60);
-			$pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefCustomer")." : ".$outputlangs->convToOutputCharset($object->ref_client), '', 'R');
+			$pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefCustomer")." : ".dol_trunc($outputlangs->convToOutputCharset($object->ref_client), 65), '', 'R');
 		}
 
 		if (!empty($conf->global->PDF_SHOW_PROJECT_TITLE)) {

+ 6 - 2
htdocs/core/modules/commande/mod_commande_saphir.php

@@ -104,7 +104,7 @@ class mod_commande_saphir extends ModeleNumRefCommandes
 		$old_code_type = $mysoc->typent_code;
 		$mysoc->code_client = 'CCCCCCCCCC';
 		$mysoc->typent_code = 'TTTTTTTTTT';
-		$numExample = $this->getNextValue($mysoc, '');
+		$numExample = $this->getNextValue($mysoc, null);
 		$mysoc->code_client = $old_code_client;
 		$mysoc->typent_code = $old_code_type;
 
@@ -138,7 +138,11 @@ class mod_commande_saphir extends ModeleNumRefCommandes
 		// Get entities
 		$entity = getEntity('ordernumber', 1, $object);
 
-		$date = ($object->date_commande ? $object->date_commande : $object->date);
+		if (is_object($object)) {
+			$date = ($object->date_commande ? $object->date_commande : $object->date);
+		} else {
+			$date = dol_now();
+		}
 
 		$numFinal = get_next_value($db, $mask, 'commande', 'ref', '', $objsoc, $date, 'next', false, null, $entity);
 

+ 1 - 1
htdocs/core/modules/expedition/doc/pdf_rouget.modules.php

@@ -212,7 +212,7 @@ class pdf_rouget extends ModelePdfExpedition
 		}
 
 		// Load traductions files required by page
-		$outputlangs->loadLangs(array("main", "bills", "products", "dict", "companies", "propal", "deliveries", "sendings", "productbatch"));
+		$outputlangs->loadLangs(array("main", "bills", "products", "dict", "companies", "propal", "deliveries", "sendings", "productbatch", "other"));
 
 		$nblines = count($object->lines);
 

+ 1 - 1
htdocs/core/modules/facture/doc/pdf_sponge.modules.php

@@ -2050,7 +2050,7 @@ class pdf_sponge extends ModelePDFFactures
 			$posy += 4;
 			$pdf->SetXY($posx, $posy);
 			$pdf->SetTextColor(0, 0, 60);
-			$pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefCustomer")." : ".$outputlangs->convToOutputCharset($object->ref_client), '', 'R');
+			$pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefCustomer")." : ".dol_trunc($outputlangs->convToOutputCharset($object->ref_client), 65), '', 'R');
 		}
 
 		if (!empty($conf->global->PDF_SHOW_PROJECT_TITLE)) {

+ 17 - 1
htdocs/core/modules/mailings/contacts1.modules.php

@@ -302,7 +302,15 @@ class mailing_contacts1 extends MailingTargets
 			dol_print_error($this->db);
 		}
 		$s .= '</select>';
+
 		$s .= ajax_combobox("filter_category_supplier_contact");
+
+		// Choose language
+		require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php';
+		$formadmin = new FormAdmin($this->db);
+		$s .= $langs->trans("DefaultLang").': ';
+		$s .= $formadmin->select_language($langs->getDefaultLang(1), 'filter_lang', 0, 0, 1, 0, 0, '', 0, 0, 0, null, 1);
+
 		return $s;
 	}
 
@@ -336,6 +344,7 @@ class mailing_contacts1 extends MailingTargets
 		$filter_category = GETPOST('filter_category', 'alpha');
 		$filter_category_customer = GETPOST('filter_category_customer', 'alpha');
 		$filter_category_supplier = GETPOST('filter_category_supplier', 'alpha');
+		$filter_lang = GETPOST('filter_lang', 'alpha');
 
 		$cibles = array();
 
@@ -381,6 +390,7 @@ class mailing_contacts1 extends MailingTargets
 		// Exclude unsubscribed email adresses
 		$sql .= " AND sp.statut = 1";
 		$sql .= " AND sp.email NOT IN (SELECT email FROM ".MAIN_DB_PREFIX."mailing_cibles WHERE fk_mailing=".((int) $mailing_id).")";
+
 		// Filter on category
 		if ($filter_category != 'all' && $filter_category != '-1') {
 			$sql .= " AND cs.fk_categorie = c.rowid AND cs.fk_socpeople = sp.rowid";
@@ -394,6 +404,12 @@ class mailing_contacts1 extends MailingTargets
 			$sql .= " AND c3s.fk_categorie = c3.rowid AND c3s.fk_soc = sp.fk_soc";
 			$sql .= " AND c3.label = '".$this->db->escape($filter_category_supplier)."'";
 		}
+
+		// Filter on language
+		if ($filter_lang != '') {
+			$sql .= " AND sp.default_lang = '".$this->db->escape($filter_lang)."'";
+		}
+
 		// Filter on nature
 		$key = $filter;
 
@@ -420,7 +436,7 @@ class mailing_contacts1 extends MailingTargets
 		}
 
 		$sql .= " ORDER BY sp.email";
-		//print "wwwwwwx".$sql;
+		// print "wwwwwwx".$sql;
 
 		// Stocke destinataires dans cibles
 		$result = $this->db->query($sql);

+ 15 - 0
htdocs/core/modules/mailings/thirdparties.modules.php

@@ -78,6 +78,9 @@ class mailing_thirdparties extends MailingTargets
 			$sql .= " WHERE s.email <> ''";
 			$sql .= " AND s.entity IN (".getEntity('societe').")";
 			$sql .= " AND s.email NOT IN (SELECT email FROM ".MAIN_DB_PREFIX."mailing_cibles WHERE fk_mailing=".((int) $mailing_id).")";
+			if (GETPOST('default_lang', 'alpha')) {
+				$sql .= " AND s.default_lang LIKE '".$this->db->escape(GETPOST('default_lang', 'alpha'))."%'";
+			}
 		} else {
 			$addFilter = "";
 			if (GETPOSTISSET("filter_client") && GETPOST("filter_client") <> '-1') {
@@ -108,6 +111,11 @@ class mailing_thirdparties extends MailingTargets
 					$addDescription .= $langs->trans("Disabled");
 				}
 			}
+			if (GETPOST('default_lang', 'alpha')) {
+				$addFilter .= " AND s.default_lang LIKE '".$this->db->escape(GETPOST('default_lang', 'alpha'))."%'";
+				$addDescription = $langs->trans('DefaultLang')."=";
+			}
+
 			$sql = "SELECT s.rowid as id, s.email as email, s.nom as name, null as fk_contact, null as firstname, c.label as label";
 			$sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."categorie_societe as cs, ".MAIN_DB_PREFIX."categorie as c";
 			$sql .= " WHERE s.email <> ''";
@@ -302,6 +310,13 @@ class mailing_thirdparties extends MailingTargets
 		$s .= '<option value="0">'.$langs->trans("Disabled").'</option>';
 		$s .= '</select>';
 		$s .= ajax_combobox("filter_status_thirdparties");
+
+		// Choose language
+		require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php';
+		$formadmin = new FormAdmin($this->db);
+		$s .= $langs->trans("DefaultLang").': ';
+		$s .= $formadmin->select_language($langs->getDefaultLang(1), 'filter_lang', 0, 0, 1, 0, 0, '', 0, 0, 0, null, 1);
+
 		return $s;
 	}
 

+ 21 - 24
htdocs/core/modules/modEmailCollector.class.php

@@ -263,7 +263,8 @@ class modEmailCollector extends DolibarrModules
 	 */
 	public function init($options = '')
 	{
-		global $conf, $user;
+		global $conf, $user, $langs;
+		$langs->load("admin");
 
 		$sql = array();
 
@@ -271,21 +272,20 @@ class modEmailCollector extends DolibarrModules
 		$tmpresql = $this->db->query($tmpsql);
 		if ($tmpresql) {
 			if ($this->db->num_rows($tmpresql) == 0) {
-				$descriptionA1 = 'This collector will scan your mailbox to find emails that match some rules and create automatically a ticket (Module Ticket must be enabled) with the email informations. You can use this collector if you provide some support by email, so your ticket request will be automatically generated.';
-				$descriptionA1 .= ' If the collector Collect_Responses is also enabled, when you send an email from the ticket, you may also see answers of your customers or partners directly on the ticket view.';
-
+				$descriptionA1 = $langs->trans('EmailCollectorExampleToCollectTicketRequestsDesc');
+				$label = $langs->trans('EmailCollectorExampleToCollectTicketRequests');
 				$sqlforexampleA1 = "INSERT INTO ".MAIN_DB_PREFIX."emailcollector_emailcollector (entity, ref, label, description, source_directory, date_creation, fk_user_creat, status)";
-				$sqlforexampleA1 .= " VALUES (".$conf->entity.", 'Collect_Ticket_Requets', 'Example to collect ticket requests', '".$this->db->escape($descriptionA1)."', 'INBOX', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 0)";
+				$sqlforexampleA1 .= " VALUES (".$conf->entity.", 'Collect_Ticket_Requests', '".$this->db->escape($label)."', '".$this->db->escape($descriptionA1)."', 'INBOX', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 0)";
 
 				$sqlforexampleFilterA1 = "INSERT INTO ".MAIN_DB_PREFIX."emailcollector_emailcollectorfilter (fk_emailcollector, type, date_creation, fk_user_creat, status)";
-				$sqlforexampleFilterA1 .= " VALUES ((SELECT rowid FROM ".MAIN_DB_PREFIX."emailcollector_emailcollector WHERE ref = 'Collect_Ticket_Requets' and entity = ".$conf->entity."), 'isnotanswer', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 1)";
+				$sqlforexampleFilterA1 .= " VALUES ((SELECT rowid FROM ".MAIN_DB_PREFIX."emailcollector_emailcollector WHERE ref = 'Collect_Ticket_Requests' and entity = ".$conf->entity."), 'isnotanswer', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 1)";
 				$sqlforexampleFilterA2 = "INSERT INTO ".MAIN_DB_PREFIX."emailcollector_emailcollectorfilter (fk_emailcollector, type, date_creation, fk_user_creat, status)";
-				$sqlforexampleFilterA2 .= " VALUES ((SELECT rowid FROM ".MAIN_DB_PREFIX."emailcollector_emailcollector WHERE ref = 'Collect_Ticket_Requets' and entity = ".$conf->entity."), 'withouttrackingid', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 1)";
+				$sqlforexampleFilterA2 .= " VALUES ((SELECT rowid FROM ".MAIN_DB_PREFIX."emailcollector_emailcollector WHERE ref = 'Collect_Ticket_Requests' and entity = ".$conf->entity."), 'withouttrackingid', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 1)";
 				$sqlforexampleFilterA3 = "INSERT INTO ".MAIN_DB_PREFIX."emailcollector_emailcollectorfilter (fk_emailcollector, type, rulevalue, date_creation, fk_user_creat, status)";
-				$sqlforexampleFilterA3 .= " VALUES ((SELECT rowid FROM ".MAIN_DB_PREFIX."emailcollector_emailcollector WHERE ref = 'Collect_Ticket_Requets' and entity = ".$conf->entity."), 'to', 'support@example.com', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 1)";
+				$sqlforexampleFilterA3 .= " VALUES ((SELECT rowid FROM ".MAIN_DB_PREFIX."emailcollector_emailcollector WHERE ref = 'Collect_Ticket_Requests' and entity = ".$conf->entity."), 'to', 'support@example.com', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 1)";
 
 				$sqlforexampleA4 = "INSERT INTO ".MAIN_DB_PREFIX."emailcollector_emailcollectoraction (fk_emailcollector, type, date_creation, fk_user_creat, status)";
-				$sqlforexampleA4 .= " VALUES ((SELECT rowid FROM ".MAIN_DB_PREFIX."emailcollector_emailcollector WHERE ref = 'Collect_Ticket_Requets' and entity = ".$conf->entity."), 'ticket', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 1)";
+				$sqlforexampleA4 .= " VALUES ((SELECT rowid FROM ".MAIN_DB_PREFIX."emailcollector_emailcollector WHERE ref = 'Collect_Ticket_Requests' and entity = ".$conf->entity."), 'ticket', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 1)";
 
 				$sql[] = $sqlforexampleA1;
 				$sql[] = $sqlforexampleFilterA1;
@@ -301,10 +301,10 @@ class modEmailCollector extends DolibarrModules
 		$tmpresql = $this->db->query($tmpsql);
 		if ($tmpresql) {
 			if ($this->db->num_rows($tmpresql) == 0) {
-				$descriptionA1 = 'This collector will scan your mailbox "Sent" directory to find emails that was sent as an answer of another email directly from your email software and not from Dolibarr. If such an email is found, the event of answer is recorded into Dolibarr.';
-
+				$descriptionA1 = $langs->trans('EmailCollectorExampleToCollectAnswersFromExternalEmailSoftware');
+				$label = $langs->trans('EmailCollectorExampleToCollectAnswersFromExternalEmailSoftware');
 				$sqlforexampleA1 = "INSERT INTO ".MAIN_DB_PREFIX."emailcollector_emailcollector (entity, ref, label, description, source_directory, date_creation, fk_user_creat, status)";
-				$sqlforexampleA1 .= " VALUES (".$conf->entity.", 'Collect_Responses_Out', 'Example to collect answers to emails done from your external email software', '".$this->db->escape($descriptionA1)."', 'Sent', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 0)";
+				$sqlforexampleA1 .= " VALUES (".$conf->entity.", 'Collect_Responses_Out', '".$this->db->escape($label)."', '".$this->db->escape($descriptionA1)."', 'Sent', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 0)";
 
 				$sqlforexampleFilterA1 = "INSERT INTO ".MAIN_DB_PREFIX."emailcollector_emailcollectorfilter (fk_emailcollector, type, date_creation, fk_user_creat, status)";
 				$sqlforexampleFilterA1 .= " VALUES ((SELECT rowid FROM ".MAIN_DB_PREFIX."emailcollector_emailcollector WHERE ref = 'Collect_Responses_Out' and entity = ".((int) $conf->entity)."), 'isanswer', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 1)";
@@ -324,10 +324,10 @@ class modEmailCollector extends DolibarrModules
 		$tmpresql = $this->db->query($tmpsql);
 		if ($tmpresql) {
 			if ($this->db->num_rows($tmpresql) == 0) {
-				$descriptionB1 = 'This collector will scan your mailbox to find all emails that are an answer of an email sent from your application. An event (Module Agenda must be enabled) with the email response will be recorded at the good place. For example, if your send a commercial proposal, order, invoice or message for a ticket by email from the application, and your customer answers your email, the system will automatically catch the answer and add it into your ERP.';
-
+				$descriptionB1 = $langs->trans('EmailCollectorExampleToCollectDolibarrAnswersDesc');
+				$label = $langs->trans('EmailCollectorExampleToCollectDolibarrAnswers');
 				$sqlforexampleB1 = "INSERT INTO ".MAIN_DB_PREFIX."emailcollector_emailcollector (entity, ref, label, description, source_directory, date_creation, fk_user_creat, status)";
-				$sqlforexampleB1 .= " VALUES (".$conf->entity.", 'Collect_Responses_In', 'Example to collect any received email that is a response of an email sent from Dolibarr', '".$this->db->escape($descriptionB1)."', 'INBOX', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 0)";
+				$sqlforexampleB1 .= " VALUES (".$conf->entity.", 'Collect_Responses_In', '".$this->db->escape($label)."', '".$this->db->escape($descriptionB1)."', 'INBOX', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 0)";
 				$sqlforexampleB2 = "INSERT INTO ".MAIN_DB_PREFIX."emailcollector_emailcollectorfilter (fk_emailcollector, type, date_creation, fk_user_creat, status)";
 				$sqlforexampleB2 .= " VALUES ((SELECT rowid FROM ".MAIN_DB_PREFIX."emailcollector_emailcollector WHERE ref = 'Collect_Responses_In' and entity = ".((int) $conf->entity)."), 'isanswer', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 1)";
 				$sqlforexampleB3 = "INSERT INTO ".MAIN_DB_PREFIX."emailcollector_emailcollectoraction (fk_emailcollector, type, date_creation, fk_user_creat, status)";
@@ -345,12 +345,10 @@ class modEmailCollector extends DolibarrModules
 		$tmpresql = $this->db->query($tmpsql);
 		if ($tmpresql) {
 			if ($this->db->num_rows($tmpresql) == 0) {
-				$descriptionC1 = "This collector will scan your mailbox to find emails that match some rules and create automatically a lead (Module Project must be enabled) with the email informations. You can use this collector if you want to follow your lead using the module Project (1 lead = 1 project), so your leads will be automatically generated.";
-				$descriptionC1 .= " If the collector Collect_Responses is also enabled, when you send an email from your leads, proposals or any other object, you may also see answers of your customers or partners directly on the application.<br>";
-				$descriptionC1 .= "Note: With this initial example, the title of the lead is generated including the email. If the thirdparty can't be found in database (new customer), the lead will be attached to the thirdparty with ID 1.";
-
+				$descriptionC1 = $langs->trans("EmailCollectorExampleToCollectLeadsDesc");
+				$label = $langs->trans('EmailCollectorExampleToCollectLeads');
 				$sqlforexampleC1 = "INSERT INTO ".MAIN_DB_PREFIX."emailcollector_emailcollector (entity, ref, label, description, source_directory, date_creation, fk_user_creat, status)";
-				$sqlforexampleC1 .= " VALUES (".$conf->entity.", 'Collect_Leads', 'Example to collect leads', '".$this->db->escape($descriptionC1)."', 'INBOX', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 0)";
+				$sqlforexampleC1 .= " VALUES (".$conf->entity.", 'Collect_Leads', '".$this->db->escape($label)."', '".$this->db->escape($descriptionC1)."', 'INBOX', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 0)";
 
 				$sqlforexampleFilterC1 = "INSERT INTO ".MAIN_DB_PREFIX."emailcollector_emailcollectorfilter (fk_emailcollector, type, date_creation, fk_user_creat, status)";
 				$sqlforexampleFilterC1 .= " VALUES ((SELECT rowid FROM ".MAIN_DB_PREFIX."emailcollector_emailcollector WHERE ref = 'Collect_Leads' and entity = ".((int) $conf->entity)."), 'isnotanswer', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 1)";
@@ -376,11 +374,10 @@ class modEmailCollector extends DolibarrModules
 		$tmpresql = $this->db->query($tmpsql);
 		if ($tmpresql) {
 			if ($this->db->num_rows($tmpresql) == 0) {
-				$descriptionC1 = "This collector will scan your mailbox to find emails send for a recruitment (Module Recruitment must be enabled). You can complete this collector if you want to automaticallycreate a candidature for a job request.";
-				$descriptionC1 .= "Note: With this initial example, the title of the candidature is generated including the email.";
-
+				$descriptionC1 = $langs->trans("EmailCollectorExampleToCollectJobCandidaturesDesc");
+				$label = $langs->trans('EmailCollectorExampleToCollectJobCandidatures');
 				$sqlforexampleC1 = "INSERT INTO ".MAIN_DB_PREFIX."emailcollector_emailcollector (entity, ref, label, description, source_directory, date_creation, fk_user_creat, status)";
-				$sqlforexampleC1 .= " VALUES (".$conf->entity.", 'Collect_Candidatures', 'Example to collect email for job candidatures', '".$this->db->escape($descriptionC1)."', 'INBOX', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 0)";
+				$sqlforexampleC1 .= " VALUES (".$conf->entity.", 'Collect_Candidatures', '".$this->db->escape($label)."', '".$this->db->escape($descriptionC1)."', 'INBOX', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 0)";
 
 				$sqlforexampleFilterC1 = "INSERT INTO ".MAIN_DB_PREFIX."emailcollector_emailcollectorfilter (fk_emailcollector, type, date_creation, fk_user_creat, status)";
 				$sqlforexampleFilterC1 .= " VALUES ((SELECT rowid FROM ".MAIN_DB_PREFIX."emailcollector_emailcollector WHERE ref = 'Collect_Candidatures' and entity = ".((int) $conf->entity)."), 'isnotanswer', '".$this->db->idate(dol_now())."', ".((int) $user->id).", 1)";

+ 1 - 1
htdocs/core/modules/modIntracommreport.class.php

@@ -50,7 +50,7 @@ class modIntracommreport extends DolibarrModules
 		$this->description = "Intracomm report management (Support for French DEB/DES format)";
 
 		// Possible values for version are: 'development', 'experimental', 'dolibarr' or 'dolibarr_deprecated' or version
-		$this->version = 'experimental';
+		$this->version = 'development';
 
 		$this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);
 		$this->picto = 'intracommreport';

+ 10 - 8
htdocs/core/modules/modPartnership.class.php

@@ -219,19 +219,21 @@ class modPartnership extends DolibarrModules
 			// Label of tables
 			'tablib'=>array("PartnershipType"),
 			// Request to select fields
-			'tabsql'=>array('SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.MAIN_DB_PREFIX.'c_partnership_type as f WHERE f.entity = '.$conf->entity),
+			'tabsql'=>array('SELECT f.rowid as rowid, f.code, f.label, f.keyword, f.active FROM '.MAIN_DB_PREFIX.'c_partnership_type as f WHERE f.entity = '.((int) $conf->entity)),
 			// Sort order
 			'tabsqlsort'=>array("label ASC"),
 			// List of fields (result of select to show dictionary)
-			'tabfield'=>array("code,label"),
+			'tabfield'=>array("code,label,keyword"),
 			// List of fields (list of fields to edit a record)
-			'tabfieldvalue'=>array("code,label"),
+			'tabfieldvalue'=>array("code,label,keyword"),
 			// List of fields (list of fields for insert)
-			'tabfieldinsert'=>array("code,label"),
+			'tabfieldinsert'=>array("code,label,keyword"),
 			// Name of columns with primary key (try to always name it 'rowid')
 			'tabrowid'=>array("rowid"),
 			// Condition to show each dictionary
-			'tabcond'=>array($conf->partnership->enabled)
+			'tabcond'=>array($conf->partnership->enabled),
+			// Help tooltip for each fields of the dictionary
+			'tabhelp'=>array(array('keyword'=>$langs->trans('KeywordToCheckInWebsite')))
 		);
 
 		// Boxes/Widgets
@@ -428,7 +430,7 @@ class modPartnership extends DolibarrModules
 		$sql = array();
 
 		// Document templates
-		$moduledir = 'partnership';
+		$moduledir = dol_sanitizeFileName('partnership');
 		$myTmpObjects = array();
 		$myTmpObjects['Partnership'] = array('includerefgeneration'=>0, 'includedocgeneration'=>0);
 
@@ -437,8 +439,8 @@ class modPartnership extends DolibarrModules
 				continue;
 			}
 			if ($myTmpObjectArray['includerefgeneration']) {
-				$src = DOL_DOCUMENT_ROOT.'/install/doctemplates/partnership/template_partnerships.odt';
-				$dirodt = DOL_DATA_ROOT.'/doctemplates/partnership';
+				$src = DOL_DOCUMENT_ROOT.'/install/doctemplates/'.$moduledir.'/template_partnerships.odt';
+				$dirodt = DOL_DATA_ROOT.'/doctemplates/'.$moduledir;
 				$dest = $dirodt.'/template_partnerships.odt';
 
 				if (file_exists($src) && !file_exists($dest)) {

+ 2 - 2
htdocs/core/modules/modProjet.class.php

@@ -222,7 +222,7 @@ class modProjet extends DolibarrModules
 			's.phone'=>'Text', 's.email'=>'Text', 's.siren'=>'Text', 's.siret'=>'Text', 's.ape'=>'Text', 's.idprof4'=>'Text', 's.code_compta'=>'Text', 's.code_compta_fournisseur'=>'Text',
 			'p.rowid'=>"List:projet:ref::project", 'p.ref'=>"Text", 'p.title'=>"Text",
 			'p.usage_opportunity'=>'Boolean', 'p.usage_task'=>'Boolean', 'p.usage_bill_time'=>'Boolean',
-			'p.datec'=>"Date", 'p.dateo'=>"Date", 'p.datee'=>"Date", 'p.fk_statut'=>'Status', 'cls.code'=>"Text", 'p.opp_percent'=>'Numeric', 'p.opp_amount'=>'Numeric', 'p.description'=>"Text", 'p.entity'=>'Numeric',
+			'p.datec'=>"Date", 'p.dateo'=>"Date", 'p.datee'=>"Date", 'p.fk_statut'=>'Status', 'cls.code'=>"Text", 'p.opp_percent'=>'Numeric', 'p.opp_amount'=>'Numeric', 'p.description'=>"Text", 'p.entity'=>'Numeric', 'p.budget_amount'=>'Numeric',
 			'pt.rowid'=>'Numeric', 'pt.ref'=>'Text', 'pt.label'=>'Text', 'pt.dateo'=>"Date", 'pt.datee'=>"Date", 'pt.duration_effective'=>"Duree", 'pt.planned_workload'=>"Numeric", 'pt.progress'=>"Numeric", 'pt.description'=>"Text",
 			'ptt.rowid'=>'Numeric', 'ptt.task_date'=>'Date', 'ptt.task_duration'=>"Duree", 'ptt.fk_user'=>"List:user:CONCAT(lastname,' ',firstname)", 'ptt.note'=>"Text"
 		);
@@ -235,7 +235,7 @@ class modProjet extends DolibarrModules
 			's.phone'=>'Phone', 's.email'=>'Email', 's.siren'=>'ProfId1', 's.siret'=>'ProfId2', 's.ape'=>'ProfId3', 's.idprof4'=>'ProfId4', 's.code_compta'=>'CustomerAccountancyCode', 's.code_compta_fournisseur'=>'SupplierAccountancyCode',
 			'p.rowid'=>"ProjectId", 'p.ref'=>"RefProject", 'p.title'=>'ProjectLabel',
 			'p.usage_opportunity'=>'ProjectFollowOpportunity', 'p.usage_task'=>'ProjectFollowTasks', 'p.usage_bill_time'=>'BillTime',
-			'p.datec'=>"DateCreation", 'p.dateo'=>"DateStart", 'p.datee'=>"DateEnd", 'p.fk_statut'=>'ProjectStatus', 'cls.code'=>'OpportunityStatus', 'p.opp_percent'=>'OpportunityProbability', 'p.opp_amount'=>'OpportunityAmount', 'p.description'=>"Description"
+			'p.datec'=>"DateCreation", 'p.dateo'=>"DateStart", 'p.datee'=>"DateEnd", 'p.fk_statut'=>'ProjectStatus', 'cls.code'=>'OpportunityStatus', 'p.opp_percent'=>'OpportunityProbability', 'p.opp_amount'=>'OpportunityAmount', 'p.budget_amount'=>'Budget', 'p.description'=>"Description"
 		);
 		// Add multicompany field
 		if (!empty($conf->global->MULTICOMPANY_ENTITY_IN_EXPORT_IF_SHARED)) {

+ 7 - 1
htdocs/core/modules/modTicket.class.php

@@ -40,6 +40,7 @@ class modTicket extends DolibarrModules
 	public function __construct($db)
 	{
 		global $langs, $conf;
+		$langs->load("ticket");
 
 		$this->db = $db;
 
@@ -103,6 +104,7 @@ class modTicket extends DolibarrModules
 		// List of particular constants to add when module is enabled
 		// (key, 'chaine', value, desc, visible, 'current' or 'allentities', deleteonunactive)
 		// Example:
+		$default_signature = $langs->trans('TicketMessageMailSignatureText', getDolGlobalString('MAIN_INFO_SOCIETE_NOM'));
 		$this->const = array(
 			1 => array('TICKET_ENABLE_PUBLIC_INTERFACE', 'chaine', '0', 'Enable ticket public interface', 0),
 			2 => array('TICKET_ADDON', 'chaine', 'mod_ticket_simple', 'Ticket ref module', 0),
@@ -111,7 +113,11 @@ class modTicket extends DolibarrModules
 			5 => array('TICKET_DELAY_BEFORE_FIRST_RESPONSE', 'chaine', '0', 'Maximum wanted elapsed time before a first answer to a ticket (in hours). Display a warning in tickets list if not respected.', 0),
 			6 => array('TICKET_DELAY_SINCE_LAST_RESPONSE', 'chaine', '0', 'Maximum wanted elapsed time between two answers on the same ticket (in hours). Display a warning in tickets list if not respected.', 0),
 			7 => array('TICKET_NOTIFY_AT_CLOSING', 'chaine', '0', 'Default notify contacts when closing a module', 0),
-			8 => array('TICKET_PRODUCT_CATEGORY', 'chaine', 0, 'The category of product that is being used for ticket accounting', 0)
+			8 => array('TICKET_PRODUCT_CATEGORY', 'chaine', 0, 'The category of product that is being used for ticket accounting', 0),
+			9 => array('TICKET_NOTIFICATION_EMAIL_FROM', 'chaine', getDolGlobalString('MAIN_MAIL_EMAIL_FROM'), 'Email to use by default as sender for messages sent from Dolibarr', 0),
+			10 => array('TICKET_MESSAGE_MAIL_INTRO', 'chaine', $langs->trans('TicketMessageMailIntroText'), 'Introduction text of ticket replies sent from Dolibarr', 0),
+			11 => array('TICKET_MESSAGE_MAIL_SIGNATURE', 'chaine', $default_signature, 'Signature to use by default for messages sent from Dolibarr', 0),
+			12 => array('MAIN_EMAILCOLLECTOR_MAIL_WITHOUT_HEADER', 'chaine', "1", 'Disable the rendering of headers in tickets', 0)
 		);
 
 

+ 3 - 3
htdocs/core/modules/project/mod_project_simple.php

@@ -125,14 +125,14 @@ class mod_project_simple extends ModeleNumRefProjects
 	 */
 	public function getNextValue($objsoc, $project)
 	{
-		global $db, $conf;
+		global $db;
 
 		// First, we get the max value
 		$posindice = strlen($this->prefix) + 6;
 		$sql = "SELECT MAX(CAST(SUBSTRING(ref FROM ".$posindice.") AS SIGNED)) as max";
 		$sql .= " FROM ".MAIN_DB_PREFIX."projet";
 		$sql .= " WHERE ref LIKE '".$db->escape($this->prefix)."____-%'";
-		$sql .= " AND entity = ".$conf->entity;
+		$sql .= " AND entity IN (".getEntity('projectnumber', 1, $project).")";
 
 		$resql = $db->query($sql);
 		if ($resql) {
@@ -147,7 +147,7 @@ class mod_project_simple extends ModeleNumRefProjects
 			return -1;
 		}
 
-		$date = empty($project->date_c) ?dol_now() : $project->date_c;
+		$date = (empty($project->date_c) ? dol_now() : $project->date_c);
 
 		//$yymm = strftime("%y%m",time());
 		$yymm = strftime("%y%m", $date);

+ 5 - 2
htdocs/core/modules/project/mod_project_universal.php

@@ -136,8 +136,11 @@ class mod_project_universal extends ModeleNumRefProjects
 			return 0;
 		}
 
-		$date = empty($project->date_c) ?dol_now() : $project->date_c;
-		$numFinal = get_next_value($db, $mask, 'projet', 'ref', '', (is_object($objsoc) ? $objsoc->code_client : ''), $date);
+		// Get entities
+		$entity = getEntity('projectnumber', 1, $project);
+
+		$date = (empty($project->date_c) ? dol_now() : $project->date_c);
+		$numFinal = get_next_value($db, $mask, 'projet', 'ref', '', (is_object($objsoc) ? $objsoc : ''), $date, 'next', false, null, $entity);
 
 		return  $numFinal;
 	}

+ 1 - 1
htdocs/core/modules/propale/doc/pdf_cyan.modules.php

@@ -1618,7 +1618,7 @@ class pdf_cyan extends ModelePDFPropales
 			$posy += 4;
 			$pdf->SetXY($posx, $posy);
 			$pdf->SetTextColor(0, 0, 60);
-			$pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefCustomer")." : ".$outputlangs->convToOutputCharset($object->ref_client), '', 'R');
+			$pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefCustomer")." : ".dol_trunc($outputlangs->convToOutputCharset($object->ref_client), 65), '', 'R');
 		}
 
 		if (!empty($conf->global->PDF_SHOW_PROJECT_TITLE)) {

+ 71 - 59
htdocs/core/modules/stock/doc/pdf_standard.modules.php

@@ -1,6 +1,7 @@
 <?php
 /* Copyright (C) 2017 	Laurent Destailleur <eldy@stocks.sourceforge.net>
  * Copyright (C) 2022 	Ferran Marcet <fmarcet@2byte.es>
+ * Copyright (C) 2022 	Nicolas Silobre <nsilobre@ns-info90.fr>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -255,6 +256,9 @@ class pdf_standard extends ModelePDFStock
 				$heightforinfotot = 40; // Height reserved to output the info and total part
 				$heightforfreetext = (isset($conf->global->MAIN_PDF_FREETEXT_HEIGHT) ? $conf->global->MAIN_PDF_FREETEXT_HEIGHT : 5); // Height reserved to output the free text on last page
 				$heightforfooter = $this->marge_basse + 8; // Height reserved to output the footer (value include bottom margin)
+				if (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_SHOW_FOOT_DETAILS)) {
+					$heightforfooter += 6;
+				}
 
 				if (class_exists('TCPDF')) {
 					$pdf->setPrintHeader(false);
@@ -520,7 +524,7 @@ class pdf_standard extends ModelePDFStock
 						$pdf->SetLineStyle(array('dash'=>0));
 
 						$pdf->SetFont('', 'B', $default_font_size - 1);
-						$pdf->SetTextColor(0, 0, 120);
+						$pdf->SetTextColor(0, 0, 0);
 
 						// Ref.
 						$pdf->SetXY($this->posxdesc, $curY);
@@ -677,58 +681,60 @@ class pdf_standard extends ModelePDFStock
 		}
 
 		$pdf->SetDrawColor(128, 128, 128);
+
 		$pdf->SetFont('', 'B', $default_font_size - 3);
 
 		// Output Rect
-		//$this->printRect($pdf,$this->marge_gauche, $tab_top, $this->page_largeur-$this->marge_gauche-$this->marge_droite, $tab_height, $hidetop, $hidebottom);	// Rect takes a length in 3rd parameter and 4th parameter
+		$this->printRect($pdf, $this->marge_gauche, $tab_top, $this->page_largeur-$this->marge_gauche-$this->marge_droite, $tab_height, $hidetop, $hidebottom);	// Rect takes a length in 3rd parameter and 4th parameter
 
 		$pdf->SetLineStyle(array('dash'=>'0', 'color'=>array(200, 200, 200)));
 		$pdf->SetDrawColor(200, 200, 200);
 		$pdf->line($this->marge_gauche, $tab_top, $this->page_largeur - $this->marge_droite, $tab_top);
 		$pdf->SetLineStyle(array('dash'=>0));
 		$pdf->SetDrawColor(128, 128, 128);
-		$pdf->SetTextColor(0, 0, 120);
+		$pdf->SetTextColor(0, 0, 0);
+
 
 		if (empty($hidetop)) {
-			//$pdf->line($this->marge_gauche, $tab_top+5, $this->page_largeur-$this->marge_droite, $tab_top+5);	// line takes a position y in 2nd parameter and 4th parameter
+			$pdf->line($this->marge_gauche, $tab_top+11, $this->page_largeur-$this->marge_droite, $tab_top+11);	// line takes a position y in 2nd parameter and 4th parameter
 			$pdf->SetXY($this->posxdesc - 1, $tab_top + 1);
 			$pdf->MultiCell($this->wref, 3, $outputlangs->transnoentities("Ref"), '', 'L');
 		}
 
-		//$pdf->line($this->posxlabel-1, $tab_top, $this->posxlabel-1, $tab_top + $tab_height);
+		$pdf->line($this->posxlabel-1, $tab_top, $this->posxlabel-1, $tab_top + $tab_height);
 		if (empty($hidetop)) {
 			$pdf->SetXY($this->posxlabel - 1, $tab_top + 1);
-			$pdf->MultiCell($this->posxqty - $this->posxlabel - 1, 2, $outputlangs->transnoentities("Label"), '', 'L');
+			$pdf->MultiCell($this->posxqty - $this->posxlabel - 1, 2, $outputlangs->transnoentities("Label"), '', 'C');
 		}
 
-		//$pdf->line($this->posxqty-1, $tab_top, $this->posxqty-1, $tab_top + $tab_height);
+		$pdf->line($this->posxqty-1, $tab_top, $this->posxqty-1, $tab_top + $tab_height);
 		if (empty($hidetop)) {
 			$pdf->SetXY($this->posxqty - 1, $tab_top + 1);
-			$pdf->MultiCell($this->posxup - $this->posxqty - 1, 2, $outputlangs->transnoentities("Units"), '', 'R');
+			$pdf->MultiCell($this->posxup - $this->posxqty - 1, 2, $outputlangs->transnoentities("Units"), '', 'C');
 		}
 
-		//$pdf->line($this->posxup-1, $tab_top, $this->posxup-1, $tab_top + $tab_height);
+		$pdf->line($this->posxup-1, $tab_top, $this->posxup-1, $tab_top + $tab_height);
 		if (empty($hidetop)) {
 			$pdf->SetXY($this->posxup - 1, $tab_top + 1);
-			$pdf->MultiCell($this->posxunit - $this->posxup - 1, 2, $outputlangs->transnoentities("AverageUnitPricePMPShort"), '', 'R');
+			$pdf->MultiCell($this->posxunit - $this->posxup - 1, 2, $outputlangs->transnoentities("AverageUnitPricePMPShort"), '', 'C');
 		}
 
-		//$pdf->line($this->posxunit - 1, $tab_top, $this->posxunit - 1, $tab_top + $tab_height);
+		$pdf->line($this->posxunit - 1, $tab_top, $this->posxunit - 1, $tab_top + $tab_height);
 		if (empty($hidetop)) {
 			$pdf->SetXY($this->posxunit - 1, $tab_top + 1);
-			$pdf->MultiCell($this->posxdiscount - $this->posxunit - 1, 2, $outputlangs->transnoentities("EstimatedStockValueShort"), '', 'R');
+			$pdf->MultiCell($this->posxdiscount - $this->posxunit - 1, 2, $outputlangs->transnoentities("EstimatedStockValueShort"), '', 'C');
 		}
 
-		//$pdf->line($this->posxdiscount-1, $tab_top, $this->posxdiscount-1, $tab_top + $tab_height);
+		$pdf->line($this->posxdiscount-1, $tab_top, $this->posxdiscount-1, $tab_top + $tab_height);
 		if (empty($hidetop)) {
 			$pdf->SetXY($this->posxdiscount - 1, $tab_top + 1);
-			$pdf->MultiCell($this->postotalht - $this->posxdiscount + 1, 2, $outputlangs->transnoentities("SellPriceMin"), '', 'R');
+			$pdf->MultiCell($this->postotalht - $this->posxdiscount + 1, 2, $outputlangs->transnoentities("SellPriceMin"), '', 'C');
 		}
 
-		//$pdf->line($this->postotalht, $tab_top, $this->postotalht, $tab_top + $tab_height);
+		$pdf->line($this->postotalht, $tab_top, $this->postotalht, $tab_top + $tab_height);
 		if (empty($hidetop)) {
 			$pdf->SetXY($this->postotalht - 1, $tab_top + 1);
-			$pdf->MultiCell($this->page_largeur - $this->marge_droite - $this->postotalht, 2, $outputlangs->transnoentities("EstimatedStockValueSellShort"), '', 'R');
+			$pdf->MultiCell($this->page_largeur - $this->marge_droite - $this->postotalht, 2, $outputlangs->transnoentities("EstimatedStockValueSellShort"), '', 'C');
 		}
 
 		if (empty($hidetop)) {
@@ -810,7 +816,10 @@ class pdf_standard extends ModelePDFStock
 		$pdf->SetFont('', '', $default_font_size - 1);
 		$pdf->SetXY($posx, $posy);
 		$pdf->SetTextColor(0, 0, 60);
-		$pdf->MultiCell(100, 3, $outputlangs->transnoentities("Label").' : '.$object->lieu, '', 'R');
+		if (!empty($object->lieu)) {
+			$pdf->MultiCell(100, 3, $outputlangs->transnoentities("Label").' : '.$object->lieu, '', 'R');
+		}
+
 
 		$posy += 4;
 		$pdf->SetXY($posx, $posy);
@@ -831,51 +840,54 @@ class pdf_standard extends ModelePDFStock
 		$yafterright = $pdf->GetY();
 
 		// Description
-		$nexY = max($yafterleft, $yafterright);
-		$nexY += 5;
-		$pdf->SetXY($posx, $posy);
-		$pdf->writeHTMLCell(190, 2, $this->marge_gauche, $nexY, '<b>'.$outputlangs->transnoentities("Description").' : </b>'.nl2br($object->description), 0, 1);
-		$nexY = $pdf->GetY();
-
-		$calcproductsunique = $object->nb_different_products();
-		$calcproducts = $object->nb_products();
-
-		// Total nb of different products
-		$pdf->writeHTMLCell(190, 2, $this->marge_gauche, $nexY, '<b>'.$outputlangs->transnoentities("NumberOfDifferentProducts").' : </b>'.(empty($calcproductsunique['nb']) ? '0' : $calcproductsunique['nb']), 0, 1);
-		$nexY = $pdf->GetY();
-
-		// Nb of products
-		$valtoshow = price2num($calcproducts['nb'], 'MS');
-		$pdf->writeHTMLCell(190, 2, $this->marge_gauche, $nexY, '<b>'.$outputlangs->transnoentities("NumberOfProducts").' : </b>'.(empty($valtoshow) ? '0' : $valtoshow), 0, 1);
-		$nexY = $pdf->GetY();
-
-		// Value
-		$pdf->writeHTMLCell(190, 2, $this->marge_gauche, $nexY, '<b>'.$outputlangs->transnoentities("EstimatedStockValueShort").' : </b>'.price((empty($calcproducts['value']) ? '0' : price2num($calcproducts['value'], 'MT')), 0, $langs, 0, -1, -1, $conf->currency), 0, 1);
-		$nexY = $pdf->GetY();
-
-		// Value
-		$pdf->writeHTMLCell(190, 2, $this->marge_gauche, $nexY, '<b>'.$outputlangs->transnoentities("Date").' : </b>'.dol_print_date(dol_now(), 'dayhour'), 0, 1);
-		$nexY = $pdf->GetY();
+		$nbpage = $pdf->getPage();
+		if ($nbpage == 1) {
+			$nexY = max($yafterleft, $yafterright);
+			$nexY += 5;
+			$pdf->SetXY($posx, $posy);
+			$pdf->writeHTMLCell(190, 2, $this->marge_gauche, $nexY, '<b>'.$outputlangs->transnoentities("Description").' : </b>'.nl2br($object->description), 0, 1);
+			$nexY = $pdf->GetY();
+
+			$calcproductsunique = $object->nb_different_products();
+			$calcproducts = $object->nb_products();
+
+			// Total nb of different products
+			$pdf->writeHTMLCell(190, 2, $this->marge_gauche, $nexY, '<b>'.$outputlangs->transnoentities("NumberOfDifferentProducts").' : </b>'.(empty($calcproductsunique['nb']) ? '0' : $calcproductsunique['nb']), 0, 1);
+			$nexY = $pdf->GetY();
+
+			// Nb of products
+			$valtoshow = price2num($calcproducts['nb'], 'MS');
+			$pdf->writeHTMLCell(190, 2, $this->marge_gauche, $nexY, '<b>'.$outputlangs->transnoentities("NumberOfProducts").' : </b>'.(empty($valtoshow) ? '0' : $valtoshow), 0, 1);
+			$nexY = $pdf->GetY();
+
+			// Value
+			$pdf->writeHTMLCell(190, 2, $this->marge_gauche, $nexY, '<b>'.$outputlangs->transnoentities("EstimatedStockValueShort").' : </b>'.price((empty($calcproducts['value']) ? '0' : price2num($calcproducts['value'], 'MT')), 0, $langs, 0, -1, -1, $conf->currency), 0, 1);
+			$nexY = $pdf->GetY();
+
+			// Value
+			$pdf->writeHTMLCell(190, 2, $this->marge_gauche, $nexY, '<b>'.$outputlangs->transnoentities("Date").' : </b>'.dol_print_date(dol_now(), 'dayhour'), 0, 1);
+			$nexY = $pdf->GetY();
+
+			// Last movement
+			$sql = "SELECT max(m.datem) as datem";
+			$sql .= " FROM ".MAIN_DB_PREFIX."stock_mouvement as m";
+			$sql .= " WHERE m.fk_entrepot = ".((int) $object->id);
+			$resqlbis = $this->db->query($sql);
+			if ($resqlbis) {
+				$obj = $this->db->fetch_object($resqlbis);
+				$lastmovementdate = $this->db->jdate($obj->datem);
+			} else {
+				dol_print_error($this->db);
+			}
 
-		// Last movement
-		$sql = "SELECT max(m.datem) as datem";
-		$sql .= " FROM ".MAIN_DB_PREFIX."stock_mouvement as m";
-		$sql .= " WHERE m.fk_entrepot = ".((int) $object->id);
-		$resqlbis = $this->db->query($sql);
-		if ($resqlbis) {
-			$obj = $this->db->fetch_object($resqlbis);
-			$lastmovementdate = $this->db->jdate($obj->datem);
-		} else {
-			dol_print_error($this->db);
-		}
+			if ($lastmovementdate) {
+				$toWrite = dol_print_date($lastmovementdate, 'dayhour').' ';
+			} else {
+				$toWrite = $outputlangs->transnoentities("None");
+			}
 
-		if ($lastmovementdate) {
-			$toWrite = dol_print_date($lastmovementdate, 'dayhour').' ';
-		} else {
-			$toWrite = $outputlangs->transnoentities("None");
+			$pdf->writeHTMLCell(190, 2, $this->marge_gauche, $nexY, '<b>'.$outputlangs->transnoentities("LastMovement").' : </b>'.$toWrite, 0, 1);
 		}
-
-		$pdf->writeHTMLCell(190, 2, $this->marge_gauche, $nexY, '<b>'.$outputlangs->transnoentities("LastMovement").' : </b>'.$toWrite, 0, 1);
 		$nexY = $pdf->GetY();
 
 		$posy += 2;

+ 1 - 1
htdocs/core/modules/supplier_proposal/doc/pdf_aurore.modules.php

@@ -1344,7 +1344,7 @@ class pdf_aurore extends ModelePDFSupplierProposal
 			$posy += 4;
 			$pdf->SetXY($posx, $posy);
 			$pdf->SetTextColor(0, 0, 60);
-			$pdf->MultiCell(100, 3, $outputlangs->transnoentities("RefCustomer")." : ".$outputlangs->convToOutputCharset($object->ref_client), '', 'R');
+			$pdf->MultiCell(100, 3, $outputlangs->transnoentities("RefCustomer")." : ".dol_trunc($outputlangs->convToOutputCharset($object->ref_client), 65), '', 'R');
 		}
 		/* PHFAVRE
 		$posy+=4;

+ 1 - 1
htdocs/core/modules/ticket/mod_ticket_simple.php

@@ -129,7 +129,7 @@ class mod_ticket_simple extends ModeleNumRefTicket
 		$sql .= " FROM ".MAIN_DB_PREFIX."ticket";
 		$search = $this->prefix."____-%";
 		$sql .= " WHERE ref LIKE '".$db->escape($search)."'";
-		$sql .= " AND entity = ".$conf->entity;
+		$sql .= " AND entity IN (".getEntity('ticketnumber', 1, $ticket).")";
 
 		$resql = $db->query($sql);
 		if ($resql) {

+ 4 - 1
htdocs/core/modules/ticket/mod_ticket_universal.php

@@ -134,8 +134,11 @@ class mod_ticket_universal extends ModeleNumRefTicket
 			return 0;
 		}
 
+		// Get entities
+		$entity = getEntity('ticketnumber', 1, $ticket);
+
 		$date = empty($ticket->datec) ? dol_now() : $ticket->datec;
-		$numFinal = get_next_value($db, $mask, 'ticket', 'ref', '', $objsoc->code_client, $date);
+		$numFinal = get_next_value($db, $mask, 'ticket', 'ref', '', $objsoc->code_client, $date, 'next', false, null, $entity);
 
 		return $numFinal;
 	}

+ 1 - 1
htdocs/core/tpl/admin_extrafields_add.tpl.php

@@ -145,7 +145,7 @@ $listofexamplesforlink = 'Societe:societe/class/societe.class.php<br>Contact:con
 
 <table summary="listofattributes" class="border centpercent">
 <!-- Label -->
-<tr><td class="titlefieldcreate fieldrequired"><?php echo $langs->trans("LabelOrTranslationKey"); ?></td><td class="valeur"><input type="text" name="label" size="40" value="<?php echo GETPOST('label', 'alpha'); ?>"></td></tr>
+<tr><td class="titlefieldcreate fieldrequired"><?php echo $langs->trans("LabelOrTranslationKey"); ?></td><td class="valeur"><input type="text" name="label" class="width200" value="<?php echo GETPOST('label', 'alpha'); ?>" autofocus></td></tr>
 <!-- Code -->
 <tr><td class="fieldrequired"><?php echo $langs->trans("AttributeCode"); ?></td><td class="valeur"><input type="text" name="attrname" id="attrname"  size="10" value="<?php echo GETPOST('attrname', 'alpha'); ?>"> <span class="opacitymedium">(<?php echo $langs->trans("AlphaNumOnlyLowerCharsAndNoSpace"); ?>)</span></td></tr>
 <!-- Type -->

Some files were not shown because too many files changed in this diff